mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 10:54:59 +00:00
added /sql endpoint; implemented SHOW TABLES (#1935)
* squashed 45 commits into one :) * tlt/sql experiment (#2035) * Move PlanOperator to sql3/planner/types package includes: type PlanOperatorColumn struct type PlanOperator interface * Remove planner dependencies from pilosa package The goal after this is to prevent the planner package (which doesn't exist yet) from being imported by the pilosa package; we just want it injected into the server in server/server.go. This is because the planner package uses pilosa types, so we need to avoid circular dependencies. Added ExecutionPlannerFn Make public: pilosa.ExecOptions Added a pilosa.Executor interface Added a planner.types.CompilePlanner interface Isolated the planner calls to: - Executor.Execute() - *API.[method]() * Move executionplanner files into the sql3/planner package. This required a bit of gymnastics, and there are some things around FieldOptions which need to be addressed soon. * Remove the hacky FieldOptions stuff I added earlier This implementation just uses the pilosa.FieldOption functional options provided by the API (as opposed to trying to build a FieldOptions object. It also changes field types to constants. These are private for now, but if we need to make them public, we should put them in the planner/types package. * Implement the "scale" value from Decimal(scale) Also, precision and scale were currently reversed in the parser. This fixes that. * Modify the parser to handle CACHETYPE <type> SIZE <size> It's a little odd to me that the cache type values are Tokens, but I guess it's ok. One thing to keep in mind is that FeatureBase expects lowercase values, so this commit changes the parser to set the value to the lowercase version of the type. * Fix the /sql2 tests This entailed a combination of commenting out or t.Skip()-ing tests which covered code in the parser that has been commented out or removed as not currently supported in sql3. It also adds some coverage for the sql.Contraint stringers. * Prevent JSON sql results from containing closing commas This commit just re-works the existing output code to avoid inserting closing commas (which results in invalid JSON). * Enhance the CREATE TABLE test coverage. In particular, ensure that the fields which get created in FeatureBase are what we expect based on the fields defined in the CREATE TABLE statement. This also ensures that the TIMEQUANTUM and CACHETYPE contraints are not provided for the same field (since those constraints are not supported together). * Adjust the EBNF file to indicate SIZE contraint is optional A CACHETYPE can be provided without a SIZE. This change indicates that SIZE is optional. * Remove `executionplanner_` from file names (#2040) * implementation of ALTER TABLE (sans column RENAME) * refactored expression analysis; added more robust type checking; all unary and bin ops function on ints * added type support for expressions; full bin/unary op support; added cast; more literal support * cast int to all other types * all literals (except idset, stringset & timestamp) make it thru; cast to all types with int as source now works * implemented LIKE/NOT LIKE * Implemented IS [NOT] NULL * Move sql2 files into sql3/parser package (#2045) * Move sql2 files into sql3/parser package This also removes the sql2 package. * Fix tests which were typing _id fields as INT intead of ID * implemented BETWEEN, NOT BETWEEN * Add featurebase/error package (#2046) * Add featurebase/error package I copied the `dax/errors` package which I am starting to use in the DAX prototype into `featurebase/errors` in order to start using it with the sql3 package. It's basically a wrapper around `github.com/pkg/errors`, but it uses a customer coded error. The sql package can define its own errors based on the `featurebase/errors` types. Then do things like `Wrap()` and `Is()`. * Address the linter complaints: shadowed variables, unreachable code * implemented IN & NOT IN with expression lists * first cut of CASE * Fixed some errors from rebase * updated bnf; removed unused code; tightened up error handling * first crack at basic CLI for SQL3 Use: `featurebase cli` Still lots to do here, but for example: > select count(*) from tremor +--------------+ | COUNT | +--------------+ | 1.158321e+06 | +--------------+ * Iterate on the CLI (#2057) Handle the errors. Add an "exit" command. Add some general formatting and white space. Add termination character: ";" (semicolon) This commit allows a user to provide multiple or partial SQL statements. Example of multiple statements: ``` show tables; select * from foo; ``` Example of partial (multi-line) statements: ``` select * from foo; ``` Don't uppercase the header values * error refactoring; first cut of TOP; remove unused code; use log.Printf instead of fmt.Printf * fixed a bug with QualifiedRef from refactoring; added bones of INSERT; removal of unused code; tightened up errors more; fixed failing tests * single value list for INSERT * Update bnf per discussion with Travis; INSERT now doing the requisite stuff * Pat's eyes went square - nothing wrong with TOP, Pat needed to learn arrays again. * improved some errors; fixed tests to suit * send warnings back in the api; update CLI to display warnings * start warning on stuff not implemented so we don't get bugged about it * Tlt/sql experiment (#2063) * Expresssion -> Expression * Add SQL planner test - adds a test to which it is easier to add tables and SQL statments - un-exports all of the expression types - removes the planner pointer from the expression types (it can be added back later if need be) * Fix where clause on a string field Prior to this commit, the binary expression for a where clause on a string field was building the call by providing a range operator which is typically used for BSI fields. This changes it to use the call.Args for string values. * Update planner tests to handle multiple sql for the same results * Reorganize SQL tests Introduce a test/helpers package and move shared MustQueryRows into that package. * Add a compatibility map for field types. (#2064) This is primarily to address the fact that ID fields were previously incompatible with INT literals. We should probably consider introducing a custom type for FieldType which can be used to define compatibilities. * significantly refactored type checking * Handle nil (NULL) values in the sql CLI. (#2067) go-pretty panics if the interface{} field value is nil. This replaces nil values with a "NULL" string. * Squash some commits fixed a still failing test added line, col to all error messages refactored source handling to enable table aliases fixed some copypasta per review warnings for order by & topn; implemented select as a source starting to handle in (select...); added stub for optimizer JSON-encode the sql error and warning strings (#2069) Error strings with unencoded characters (like double quotes) were resulting in invalid json. got insert working; added symbol table; added concrete optimizer; added nascent NestedLoopsOperator; rewrite "where foo in (select..." as inner join * all about the sets (#2085) * implemented setcontains() * implemented set literal; insert set column values; setcontains/all/any both in expr eval and pql filters * Convert test to use latest framework. (#2086) * fixed some comments * removed refactored tests Co-authored-by: Travis Turner <travis@pilosa.com> * Add support for Decimal fields to the sql test. (#2090) * dates (#2094) * return dates as strings in output; tightened up decimal type checking * return dates as strings in output; tightened up decimal type checking * fixed failing tests after decimal changes * can now insert decimal values * implemented insert for timestamp data type; implemented current_date, current_timestamp constants * fixed some failing tests * handle date literals from strings in insert statements * changes from feedback * Fix pointer method error * sql3 API interface (#2110) * Introduce API-related interfaces: SchemaAPI, ComputeAPI The sql3 code was relying on the pointer: *pilosa.API in order to call API methods directly on the local node. If we want to import and use the sql3 package in another service (the DAX queryer, for example), we need to be able to use an implementation of an interface for those API method calls. This commit introduces two interfaces, both automatically implemented by pilosa.API: - SchemaAPI - ComputeAPI * Convert sql3 code to use IndexInfo instead of Index The sql3 code was relying on a *pilosa.Index and its methods to get general information like index and field name, type, etc. This commit converts everything to use a *pilosa.IndexInfo instead. This allows us to modify the SchemaAPI interface to also return IndexInfo instead of Index, which will be a lot easier to implement in a non-pilosa package (like DAX); creating a *pilosa.Index requires providing things like data directory paths and holders, which are not necessary for these use cases. * Unary and Binary Ops R US plus CAST (#2111) * implemented string literal for timestamp epoch * fixed failing test * fixed the failing test again * refactored tests; implemented unary op tests for all datatypes; implemented binop tests for int/int, int/id, int/decimal & ID/int * implemented all binary ops for INT & all other types, ID & all other types * implemented binary ops for DECIMAL types & all other types * added STRING & BOOL to various tests; implemented all remaining binOp tests * fix up some stuff after rebasing * refactored test defs into multiple files; implemented CAST for every datatype * added tests for like/not like * addressed review feedback * addressed type review feedback * tightened up IS [NOT] NULL behavior plus tests (#2118) * tightened up IS [NOT] NULL behavior plus tests * BETWEEN/NOT BETWEEN with all data types * addressed review feedback * Handle negative integers in column min/max constraints (#2120) This commit parses the min/max contraint as an expression, as opposed to an int literal, so that negative values are treated as Unary expressions. There currently isn't support for min/max constraints on `decimal` fiels, so for now this change only expects +/- integer values. * Implement the CREATE TABLE keypartitions logic (#2123) * Execution time, IN/NOT IN & multiple aggregates (#2124) * added display of execution time * IN/NOT IN tests for all data types * fixed date parsing * removed duplicative tests * refactoring aggregates * suport multiple aggregates * Address review feedback * final round of feedback * Add method SchemaAPI.CreateIndexAndFields() (#2127) In order to support a CREATE TABLE statement as a single command, this commit alters the SchemaAPI interface to contain a single method which handles both the index and its fields. It also updates the sql3 code to use this interface instead of CreateIndex() and CreateField() indepedently. * Symbol Handling (Again) (#2129) * Refactored symbol handling in the planner; re-instated the select as source tests * removed commented out code * addressing review feedback * Move hard-coded _id field out of planner and into interface implementation (#2130) This commit moves the hard-coded addition of the `_id` field from the planner to the SchemaAPI.IndexInfo() implementation method. NOTE: If anything was expecting SchemaAPI.Schema() to also return the `_id` field as part of its field list in each table, then it would not be there because the `_id` field is only added in the IndexInfo() method for now. Currently that's not a problem because nothing is expecting the `_id` field for `Schema()`. * Multiple aggregates, all aggregates stand alone and in GROUP BY (#2132) * handle multiple aggregates in group by queries * added handling for avg() aggregate both stand alone and in group by * tightened up sum & avg outside of group by * added min, max & percentile * added warnings * Make MaterializedRowSet implement the PlanOperator interface. (#2133) This commit refactors the PQLMultiGroupByOperator to have a PlanOperator as its output. Then, when it initializes, it sets up a MaterializedRowSet and populates that with the values from the multiple group by operations. * added explicit min/max pql operators * saved a file I forgot to save * per review * Un-indent some if/else nesting (#2136) Co-authored-by: Travis Turner <travis@pilosa.com> * Add optional `name` argument to test structs. This commit adds the `name` argument to `tableTest` and `sqlTest` so that a test can be optionally named. This allows a developer to more easily run/identify a particular test by name. * Inbuilt functions (redux) (#2141) * set functions type parameter type checking * implemented datepart * include SQL3 type in SHOW COLUMNS output * fixed select as source; failing SHOW COLUMNS test * select in select list * dump output columns; handle optimization for select list subqueries * make it an error to return multiple rows for a select list subquery * added description * contants and test coverage for datepart function * SQL3 Refactor-palooza (#2182) * removed unneeded IsAggregate() * first cut of working nested loops operator aka INNER JOIN * remove selectListItemPlanExpression * added some warnings * all the tests are passing again! * addressed some linter complaints * added basic order by * bug fixes; added 'or replace'/'replace' to insert * for insert references should return appropriately * added back ability to use subquery singleton expressions * removed dead code; fixed test * json-able plan, Schema() plus refactoring * fixed dumb code * add some tests for time quantum behavior * Code cleanup during review. Also fixed INSERT to keyed table bug. This commit contains a lot of minor adjustments made during code review. It also contains a bug fix that was preventing INSERT into a keyed table (i.e. _id type STRING) from working. Co-authored-by: Travis Turner <travis@molecula.com> * Fix expected min/max on timestamp column test (decimal field) I don't know why this changed, but presumably something to do with decimal related work that happened on master. * Fix compile problem after rebase * review feedback Co-authored-by: Matthew Jaffee <jaffee@pilosa.com> Co-authored-by: Travis Turner <travis@pilosa.com> Co-authored-by: Travis Turner <travis@molecula.com> Co-authored-by: Fletcher Haynes <fletcher@capitalprawn.com>
This commit is contained in:
parent
650b7eaa45
commit
e4a4a06af0
114 changed files with 31292 additions and 10711 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -66,3 +66,7 @@ pilosa-sec-data-idk
|
|||
tags.dot
|
||||
*.log
|
||||
*.swp
|
||||
*__debug_bin
|
||||
|
||||
# SQL3
|
||||
/sql3/sql3.html
|
||||
|
|
|
|||
3
Makefile
3
Makefile
|
|
@ -379,3 +379,6 @@ install-gometalinter:
|
|||
|
||||
test-external-lookup:
|
||||
$(GO) test . -tags='$(BUILD_TAGS) $(TEST_TAGS)' $(TESTFLAGS) -run ^TestExternalLookup$$ -externalLookupDSN $(EXTERNAL_LOOKUP_DSN)
|
||||
|
||||
bnf:
|
||||
ebnf2railroad --no-overview-diagram --no-optimizations ./sql3/sql3.ebnf
|
||||
|
|
|
|||
126
api.go
126
api.go
|
|
@ -29,6 +29,7 @@ import (
|
|||
//"github.com/molecula/featurebase/v3/pg"
|
||||
"github.com/molecula/featurebase/v3/pql"
|
||||
"github.com/molecula/featurebase/v3/roaring"
|
||||
planner_types "github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
"github.com/molecula/featurebase/v3/stats"
|
||||
"github.com/molecula/featurebase/v3/tracing"
|
||||
"github.com/pkg/errors"
|
||||
|
|
@ -198,7 +199,7 @@ func (api *API) query(ctx context.Context, req *QueryRequest) (QueryResponse, er
|
|||
}
|
||||
|
||||
// TODO can we get rid of exec options and pass the QueryRequest directly to executor?
|
||||
execOpts := &execOptions{
|
||||
execOpts := &ExecOptions{
|
||||
Remote: req.Remote,
|
||||
Profile: req.Profile,
|
||||
PreTranslated: req.PreTranslated,
|
||||
|
|
@ -1070,6 +1071,23 @@ func (api *API) Schema(ctx context.Context, withViews bool) ([]*IndexInfo, error
|
|||
return api.holder.limitedSchema()
|
||||
}
|
||||
|
||||
// IndexInfo returns the same information as Schema(), but only for a single
|
||||
// index.
|
||||
func (api *API) IndexInfo(ctx context.Context, name string) (*IndexInfo, error) {
|
||||
schema, err := api.Schema(ctx, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, idx := range schema {
|
||||
if idx.Name == name {
|
||||
return idx, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, ErrIndexNotFound
|
||||
}
|
||||
|
||||
// ApplySchema takes the given schema and applies it across the
|
||||
// cluster (if remote is false), or just to this node (if remote is
|
||||
// true). This is designed for the use case of replicating a schema
|
||||
|
|
@ -3171,8 +3189,15 @@ processing:
|
|||
}
|
||||
return result, ctx.Err()
|
||||
}
|
||||
func (api *API) Plan(ctx context.Context, q string) (*Stmt, error) {
|
||||
return api.server.PlanSQL(ctx, q)
|
||||
|
||||
// CompilePlan takes a sql string and returns a PlanOperator. Note that this is
|
||||
// different from the internal CompilePlan() method on the CompilePlanner
|
||||
// interface, which takes a parser statement and returns a PlanOperator. In
|
||||
// other words, this CompilePlan() both parses and plans the provided sql
|
||||
// string; it's the equivalent of the CompileExecutionPlan() method on Server.
|
||||
// TODO: consider renaming this to something with less conflict.
|
||||
func (api *API) CompilePlan(ctx context.Context, q string) (planner_types.PlanOperator, error) {
|
||||
return api.server.CompileExecutionPlan(ctx, q)
|
||||
}
|
||||
|
||||
func (api *API) RBFDebugInfo() map[string]*rbf.DebugInfo {
|
||||
|
|
@ -3312,3 +3337,98 @@ var methodsNormal = map[apiMethod]struct{}{
|
|||
apiIngestNodeOperations: {},
|
||||
apiMutexCheck: {},
|
||||
}
|
||||
|
||||
// SchemaAPI is a subset of the API methods which have to do with schema. This
|
||||
// interface was introduced in order to remove, from the sql3 package, the
|
||||
// pointer to API, and instead use this interface. In the current FeatureBase,
|
||||
// this interface can be implemented directly with API. But in an implementation
|
||||
// for DAX, for example, we might want something else servicing the
|
||||
// schema-related calls to the SchemaAPI.
|
||||
type SchemaAPI interface {
|
||||
CreateIndexAndFields(ctx context.Context, indexName string, options IndexOptions, fields []CreateFieldObj) error
|
||||
CreateField(ctx context.Context, indexName string, fieldName string, opts ...FieldOption) (*Field, error)
|
||||
DeleteField(ctx context.Context, indexName string, fieldName string) error
|
||||
DeleteIndex(ctx context.Context, indexName string) error
|
||||
IndexInfo(ctx context.Context, indexName string) (*IndexInfo, error)
|
||||
Schema(ctx context.Context, withViews bool) ([]*IndexInfo, error)
|
||||
}
|
||||
|
||||
// CreateFieldObj is used to encapsulate the information required for creating a
|
||||
// field in the SchemaAPI.CreateIndexAndFields interface method.
|
||||
type CreateFieldObj struct {
|
||||
Name string
|
||||
Options []FieldOption
|
||||
}
|
||||
|
||||
// ComputeAPI is a subset of the API methods which have to do with compute
|
||||
// operations such as import.
|
||||
type ComputeAPI interface {
|
||||
Import(ctx context.Context, qcx *Qcx, req *ImportRequest, opts ...ImportOption) error
|
||||
ImportValue(ctx context.Context, qcx *Qcx, req *ImportValueRequest, opts ...ImportOption) error
|
||||
Txf() *TxFactory
|
||||
}
|
||||
|
||||
// FeatureBaseSchemaAPI is a wrapper around pilosa.API. It implements the
|
||||
// SchemaAPI interface with methods which are not a part of pilosa.API.
|
||||
type FeatureBaseSchemaAPI struct {
|
||||
*API
|
||||
}
|
||||
|
||||
func (fapi *FeatureBaseSchemaAPI) CreateIndexAndFields(ctx context.Context, indexName string, options IndexOptions, fields []CreateFieldObj) error {
|
||||
// Add the index.
|
||||
if _, err := fapi.CreateIndex(ctx, indexName, options); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Now add fields.
|
||||
for _, f := range fields {
|
||||
if _, err := fapi.CreateField(ctx, indexName, f.Name, f.Options...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IndexInfo wraps the API.IndexInfo method and prepends an _id field to its
|
||||
// list of fields.
|
||||
func (fapi *FeatureBaseSchemaAPI) IndexInfo(ctx context.Context, indexName string) (*IndexInfo, error) {
|
||||
idx, err := fapi.API.IndexInfo(ctx, indexName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// sortedFields will contain the sorted list of fields from IndexInfo, along
|
||||
// with the primary key field (which will always be at the beginning of the
|
||||
// list).
|
||||
sortedFields := make([]*FieldInfo, 0, len(idx.Fields)+1)
|
||||
|
||||
// Add the primary key field to the beginning of the list.
|
||||
idKeys := idx.Options.Keys
|
||||
idType := "id"
|
||||
if idKeys {
|
||||
idType = "string"
|
||||
}
|
||||
|
||||
idFld := &FieldInfo{
|
||||
Name: "_id",
|
||||
CreatedAt: idx.CreatedAt,
|
||||
Options: FieldOptions{
|
||||
Type: idType,
|
||||
Keys: idKeys,
|
||||
},
|
||||
}
|
||||
sortedFields = append(sortedFields, idFld)
|
||||
|
||||
// Sort idx.Fields by CreatedAt before adding them to sortedFields.
|
||||
sort.Slice(idx.Fields, func(i, j int) bool {
|
||||
return idx.Fields[i].CreatedAt < idx.Fields[j].CreatedAt
|
||||
})
|
||||
|
||||
// Add the sorted fields to sortedFields.
|
||||
sortedFields = append(sortedFields, idx.Fields...)
|
||||
|
||||
idx.Fields = sortedFields
|
||||
|
||||
return idx, nil
|
||||
}
|
||||
|
|
|
|||
31
cmd/cli.go
Normal file
31
cmd/cli.go
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/molecula/featurebase/v3/ctl"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var cli *ctl.CLICommand
|
||||
|
||||
// newCLICommand runs the FeatureBase CLI subcommand for ingesting bulk data.
|
||||
func newCLICommand(stdin io.Reader, stdout, stderr io.Writer) *cobra.Command {
|
||||
cli = ctl.NewCLICommand(stdin, stdout, stderr)
|
||||
cliCmd := &cobra.Command{
|
||||
Use: "cli",
|
||||
Short: "Query FB with SQL3 from the command line",
|
||||
Long: ``,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return cli.Run(context.Background())
|
||||
},
|
||||
}
|
||||
|
||||
flags := cliCmd.Flags()
|
||||
flags.StringVarP(&cli.Host, "host", "", cli.Host, "hostname of FeatureBase.")
|
||||
flags.StringVarP(&cli.Port, "port", "", cli.Port, "port of FeatureBase.")
|
||||
|
||||
return cliCmd
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ import (
|
|||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/molecula/featurebase/v3/sql2"
|
||||
"github.com/molecula/featurebase/v3/sql3/parser"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
|
@ -32,7 +32,7 @@ func run(ctx context.Context, args []string) error {
|
|||
return fmt.Errorf("query required")
|
||||
}
|
||||
|
||||
stmt, err := sql2.NewParser(strings.NewReader(q)).ParseStatement()
|
||||
stmt, err := parser.NewParser(strings.NewReader(q)).ParseStatement()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ at https://docs.molecula.cloud/.
|
|||
rc.AddCommand(newHolderCmd(stdin, stdout, stderr))
|
||||
rc.AddCommand(newHolderCmd(stdin, stdout, stderr))
|
||||
rc.AddCommand(newKeygenCommand(stdin, stdout, stderr))
|
||||
rc.AddCommand(newCLICommand(stdin, stdout, stderr))
|
||||
|
||||
rc.SetOutput(stderr)
|
||||
return rc
|
||||
|
|
|
|||
246
ctl/cli.go
Normal file
246
ctl/cli.go
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
package ctl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/chzyer/readline"
|
||||
"github.com/jedib0t/go-pretty/table"
|
||||
"github.com/jedib0t/go-pretty/text"
|
||||
featurebase "github.com/molecula/featurebase/v3"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
promptBegin string = "fbsql> "
|
||||
promptMid string = " -> "
|
||||
terminationChar string = ";"
|
||||
exitCommand string = "exit"
|
||||
nullValue string = "NULL"
|
||||
)
|
||||
|
||||
var (
|
||||
splash string = fmt.Sprintf(`FeatureBase CLI (%s)
|
||||
Type "exit" to quit.
|
||||
`, featurebase.Version)
|
||||
)
|
||||
|
||||
type CLICommand struct {
|
||||
Host string `json:"host"`
|
||||
Port string `json:"port"`
|
||||
|
||||
// commands holds the list of sql commands to be executed.
|
||||
commands []string
|
||||
}
|
||||
|
||||
func NewCLICommand(stdin io.Reader, stdout, stderr io.Writer) *CLICommand {
|
||||
return &CLICommand{
|
||||
Host: "localhost",
|
||||
Port: "10101",
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *CLICommand) Run(ctx context.Context) error {
|
||||
// Print the splash message.
|
||||
fmt.Print(splash)
|
||||
|
||||
rl, err := readline.New(promptBegin)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "getting readline")
|
||||
}
|
||||
defer rl.Close()
|
||||
|
||||
if !strings.HasPrefix(cmd.Host, "http") {
|
||||
cmd.Host = "http://" + cmd.Host
|
||||
}
|
||||
|
||||
// partialCommand holds all input prior to receiving a termination
|
||||
// character.
|
||||
var partialCommand string
|
||||
|
||||
// inMidCommand indicates whether a partial command has been received and
|
||||
// we're still waiting for a termination character.
|
||||
var inMidCommand bool
|
||||
|
||||
for {
|
||||
if inMidCommand {
|
||||
rl.SetPrompt(promptMid)
|
||||
} else {
|
||||
rl.SetPrompt(promptBegin)
|
||||
// Add some white space before each new prompt.
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// Read user provided input.
|
||||
line, err := rl.Readline()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "reading line")
|
||||
}
|
||||
|
||||
if !inMidCommand {
|
||||
// Handle the exit command.
|
||||
if line == exitCommand || line == exitCommand+terminationChar {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Look for a termination character;
|
||||
parts := strings.Split(line, terminationChar)
|
||||
|
||||
// Length of 1 means a termination character was not received.
|
||||
if len(parts) == 1 {
|
||||
if parts[0] != "" {
|
||||
partialCommand = appendCommand(partialCommand, parts[0])
|
||||
inMidCommand = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
for i, part := range parts {
|
||||
partIsFinal := i == len(parts)-1
|
||||
partIsBlank := part == ""
|
||||
|
||||
if partIsBlank && partIsFinal {
|
||||
continue
|
||||
}
|
||||
|
||||
if partIsBlank && !partIsFinal {
|
||||
if inMidCommand {
|
||||
cmd.commands = append(cmd.commands, strings.TrimSpace(partialCommand))
|
||||
partialCommand = ""
|
||||
inMidCommand = false
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if !partIsBlank && partIsFinal {
|
||||
partialCommand = part
|
||||
inMidCommand = true
|
||||
continue
|
||||
}
|
||||
|
||||
if !partIsBlank && !partIsFinal {
|
||||
partialCommand = appendCommand(partialCommand, part)
|
||||
cmd.commands = append(cmd.commands, strings.TrimSpace(partialCommand))
|
||||
partialCommand = ""
|
||||
inMidCommand = false
|
||||
}
|
||||
}
|
||||
|
||||
if err := cmd.executeCommands(ctx); err != nil {
|
||||
return errors.Wrap(err, "executing commands")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func appendCommand(orig string, part string) string {
|
||||
if orig == "" {
|
||||
return part
|
||||
} else {
|
||||
return orig + " " + part
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *CLICommand) executeCommands(ctx context.Context) error {
|
||||
// Clear out the buffered commands on any exit from this method.
|
||||
defer func() {
|
||||
cmd.commands = nil
|
||||
}()
|
||||
|
||||
for _, sql := range cmd.commands {
|
||||
resp, err := http.Post(fmt.Sprintf("%s:%s/sql", cmd.Host, cmd.Port), "application/sql", strings.NewReader(sql))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "posting query")
|
||||
}
|
||||
|
||||
var sqlResponse response
|
||||
dec := json.NewDecoder(resp.Body)
|
||||
err = dec.Decode(&sqlResponse)
|
||||
if err != nil {
|
||||
fmt.Printf("couldn't decode response: %v\n", err)
|
||||
}
|
||||
|
||||
err = sqlResponse.WriteOut(os.Stdout)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "writing out response")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type response struct {
|
||||
Schema featurebase.SQLSchema `json:"schema"`
|
||||
Data [][]interface{} `json:"data"`
|
||||
Error string `json:"error"`
|
||||
Warnings []string `json:"warnings"`
|
||||
ExecutionTime int64 `json:"exec_time"`
|
||||
}
|
||||
|
||||
func (r *response) WriteOut(w io.Writer) error {
|
||||
if r.Error != "" {
|
||||
if _, err := w.Write([]byte("Error: " + r.Error + "\n")); err != nil {
|
||||
return errors.Wrapf(err, "writing error: %s", r.Error)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
t := table.NewWriter()
|
||||
t.SetOutputMirror(w)
|
||||
|
||||
// Don't uppercase the header values.
|
||||
t.Style().Format.Header = text.FormatDefault
|
||||
|
||||
t.AppendHeader(schemaToRow(r.Schema))
|
||||
for _, row := range r.Data {
|
||||
// If the value is nil, replace it with a null string; go-pretty doesn't
|
||||
// expect nil pointers in the data values.
|
||||
for i := range row {
|
||||
if row[i] == nil {
|
||||
row[i] = nullValue
|
||||
}
|
||||
}
|
||||
t.AppendRow(table.Row(row))
|
||||
}
|
||||
t.Render()
|
||||
|
||||
if len(r.Warnings) > 0 {
|
||||
if _, err := w.Write([]byte("\n")); err != nil {
|
||||
return errors.Wrapf(err, "writing warning: %s", r.Error)
|
||||
}
|
||||
for _, warning := range r.Warnings {
|
||||
if _, err := w.Write([]byte("Warning: " + warning + "\n")); err != nil {
|
||||
return errors.Wrapf(err, "writing warning: %s", r.Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
lifeAffirmingMessage := ""
|
||||
if r.ExecutionTime < 1000000 {
|
||||
lifeAffirmingMessage = " (You're welcome! 🚀)"
|
||||
}
|
||||
|
||||
if r.ExecutionTime > 5000000 {
|
||||
lifeAffirmingMessage = " (Sorry! That took longer than expected 😭)"
|
||||
}
|
||||
|
||||
if _, err := w.Write([]byte(fmt.Sprintf("\nExecution time: %dμs%s\n", r.ExecutionTime, lifeAffirmingMessage))); err != nil {
|
||||
return errors.Wrapf(err, "writing execution time: %s", r.Error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func schemaToRow(schema featurebase.SQLSchema) []interface{} {
|
||||
ret := make([]interface{}, len(schema.Fields))
|
||||
for i, field := range schema.Fields {
|
||||
ret[i] = field.Name
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
|
@ -87,15 +87,7 @@ func BuildServerFlags(cmd *cobra.Command, srv *server.Command) {
|
|||
// RBF specific flags. See pilosa/rbf/cfg/cfg.go for definitions.
|
||||
srv.Config.RBFConfig.DefineFlags(flags)
|
||||
|
||||
// Postgres endpoint
|
||||
flags.StringVar(&srv.Config.Postgres.Bind, "postgres.bind", srv.Config.Postgres.Bind, "Address to which to bind a postgres endpoint (leave blank to disable)")
|
||||
SetTLSConfig(flags, "postgres.", &srv.Config.Postgres.TLS.CertificatePath, &srv.Config.Postgres.TLS.CertificateKeyPath, &srv.Config.Postgres.TLS.CACertPath, &srv.Config.Postgres.TLS.SkipVerify, &srv.Config.Postgres.TLS.EnableClientVerification)
|
||||
flags.DurationVar((*time.Duration)(&srv.Config.Postgres.StartupTimeout), "postgres.startup-timeout", time.Duration(srv.Config.Postgres.StartupTimeout), "Timeout for postgres connection startup. (set 0 to disable)")
|
||||
flags.DurationVar((*time.Duration)(&srv.Config.Postgres.ReadTimeout), "postgres.read-timeout", time.Duration(srv.Config.Postgres.ReadTimeout), "Timeout for reads on a postgres connection. (set 0 to disable; does not include connection idling)")
|
||||
flags.DurationVar((*time.Duration)(&srv.Config.Postgres.WriteTimeout), "postgres.write-timeout", time.Duration(srv.Config.Postgres.WriteTimeout), "Timeout for writes on a postgres connection. (set 0 to disable)")
|
||||
flags.Uint32Var(&srv.Config.Postgres.MaxStartupSize, "postgres.max-startup-size", srv.Config.Postgres.MaxStartupSize, "Maximum acceptable size of a postgres startup packet, in bytes. (set 0 to disable)")
|
||||
flags.Uint16Var(&srv.Config.Postgres.ConnectionLimit, "postgres.connection-limit", srv.Config.Postgres.ConnectionLimit, "Maximum number of simultaneous postgres connections to allow. (set 0 to disable)")
|
||||
flags.Uint16Var(&srv.Config.Postgres.SqlVersion, "postgres.sql-version", srv.Config.Postgres.SqlVersion, "Molecula Sql Handling Version (default 1)")
|
||||
flags.BoolVar(&srv.Config.SQL.EndpointEnabled, "sql.endpoint-enabled", srv.Config.SQL.EndpointEnabled, "Enable FeatureBase SQL /sql endpoint (default false)")
|
||||
|
||||
// Future flags.
|
||||
flags.BoolVar(&srv.Config.Future.Rename, "future.rename", false, "Present application name as FeatureBase. Defaults to false, will default to true in an upcoming release.")
|
||||
|
|
|
|||
85
errors/errors.go
Normal file
85
errors/errors.go
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
// Package errors wraps pkg/errors and includes some custom featurs such as
|
||||
// error codes.
|
||||
package errors
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Code is an error code which can be used to check against a given error. For
|
||||
// example, see the Is() method.
|
||||
type Code string
|
||||
|
||||
func New(code Code, message string) error {
|
||||
return errors.WithStack(codedError{
|
||||
code: code,
|
||||
message: message,
|
||||
})
|
||||
}
|
||||
|
||||
func As(err error, target interface{}) bool {
|
||||
return errors.As(err, target)
|
||||
}
|
||||
|
||||
func Cause(err error) error {
|
||||
return errors.Cause(err)
|
||||
}
|
||||
|
||||
func Errorf(format string, args ...interface{}) error {
|
||||
return errors.Errorf(format, args...)
|
||||
}
|
||||
|
||||
// Is is a fork of the Is() method from `pkg/errors` which takes as its target
|
||||
// an error Code instead of an error.
|
||||
func Is(err error, target Code) bool {
|
||||
match := codedError{
|
||||
code: target,
|
||||
}
|
||||
return errors.Is(err, match)
|
||||
}
|
||||
|
||||
func Unwrap(err error) error {
|
||||
return errors.Unwrap(err)
|
||||
}
|
||||
|
||||
func WithMessage(err error, message string) error {
|
||||
return errors.WithMessage(err, message)
|
||||
}
|
||||
|
||||
func WithMessagef(err error, format string, args ...interface{}) error {
|
||||
return errors.WithMessagef(err, format, args...)
|
||||
}
|
||||
|
||||
func WithStack(err error) error {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
func Wrap(err error, message string) error {
|
||||
return errors.Wrap(err, message)
|
||||
}
|
||||
|
||||
func Wrapf(err error, fmt string, args ...interface{}) error {
|
||||
return errors.Wrapf(err, fmt, args...)
|
||||
}
|
||||
|
||||
// codedError is the fundamental type used by this package to provide coded
|
||||
// errors.
|
||||
type codedError struct {
|
||||
code Code
|
||||
message string
|
||||
}
|
||||
|
||||
func (ce codedError) Error() string {
|
||||
return ce.message
|
||||
}
|
||||
|
||||
// func (ce codedError) As(target interface{}) bool {
|
||||
// return false
|
||||
// }
|
||||
|
||||
func (ce codedError) Is(err error) bool {
|
||||
if e, ok := err.(codedError); ok && ce.code == e.code {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
81
errors/errors_test.go
Normal file
81
errors/errors_test.go
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
package errors_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/molecula/featurebase/v3/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestErrors(t *testing.T) {
|
||||
|
||||
var errUncoded errors.Code = "TestErrUncoded"
|
||||
var errFieldNotFound errors.Code = "TestErrFieldNotFound"
|
||||
var errTableNotFound errors.Code = "TestErrTableNotFound"
|
||||
|
||||
newErrFieldNotFound := func(fld string) error {
|
||||
return errors.New(
|
||||
errFieldNotFound,
|
||||
fmt.Sprintf("field not found '%s'", fld),
|
||||
)
|
||||
}
|
||||
|
||||
newErrTableNotFound := func(tbl string) error {
|
||||
return errors.New(
|
||||
errTableNotFound,
|
||||
fmt.Sprintf("table not found '%s'", tbl),
|
||||
)
|
||||
}
|
||||
|
||||
t.Run("Is", func(t *testing.T) {
|
||||
uncoded := errors.New(errUncoded, "uncoded error")
|
||||
fnf := newErrFieldNotFound("fld")
|
||||
tnf := newErrTableNotFound("tbl")
|
||||
fnfCustom := errors.New(errFieldNotFound, "custom field message")
|
||||
|
||||
tests := []struct {
|
||||
err error
|
||||
target errors.Code
|
||||
exp bool
|
||||
}{
|
||||
{
|
||||
err: uncoded,
|
||||
target: errUncoded,
|
||||
exp: true,
|
||||
},
|
||||
{
|
||||
err: uncoded,
|
||||
target: errFieldNotFound,
|
||||
exp: false,
|
||||
},
|
||||
{
|
||||
err: fnf,
|
||||
target: errFieldNotFound,
|
||||
exp: true,
|
||||
},
|
||||
{
|
||||
err: fnf,
|
||||
target: errTableNotFound,
|
||||
exp: false,
|
||||
},
|
||||
{
|
||||
err: errors.Wrap(tnf, "with message"),
|
||||
target: errTableNotFound,
|
||||
exp: true,
|
||||
},
|
||||
{
|
||||
err: fnfCustom,
|
||||
target: errFieldNotFound,
|
||||
exp: true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) {
|
||||
got := errors.Is(test.err, test.target)
|
||||
assert.Equal(t, test.exp, got)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
198
executor.go
198
executor.go
|
|
@ -43,6 +43,10 @@ const (
|
|||
errConnectionRefused = "connect: connection refused"
|
||||
)
|
||||
|
||||
type Executor interface {
|
||||
Execute(context.Context, string, *pql.Query, []uint64, *ExecOptions) (QueryResponse, error)
|
||||
}
|
||||
|
||||
// executor recursively executes calls in a PQL query across all shards.
|
||||
type executor struct {
|
||||
Holder *Holder
|
||||
|
|
@ -70,7 +74,7 @@ type executor struct {
|
|||
maxMemory int64
|
||||
}
|
||||
|
||||
// executorOption is a functional option type for pilosa.Executor
|
||||
// executorOption is a functional option type for pilosa.executor
|
||||
type executorOption func(e *executor) error
|
||||
|
||||
func optExecutorInternalQueryClient(c *InternalClient) executorOption {
|
||||
|
|
@ -112,7 +116,7 @@ func emptyResult(c *pql.Call) interface{} {
|
|||
return nil
|
||||
}
|
||||
|
||||
// newExecutor returns a new instance of Executor.
|
||||
// newExecutor returns a new instance of executor.
|
||||
func newExecutor(opts ...executorOption) *executor {
|
||||
e := &executor{
|
||||
workerPoolSize: 2,
|
||||
|
|
@ -168,8 +172,8 @@ func (e *executor) InitStats() {
|
|||
}
|
||||
|
||||
// Execute executes a PQL query.
|
||||
func (e *executor) Execute(ctx context.Context, index string, q *pql.Query, shards []uint64, opt *execOptions) (QueryResponse, error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.Execute")
|
||||
func (e *executor) Execute(ctx context.Context, index string, q *pql.Query, shards []uint64, opt *ExecOptions) (QueryResponse, error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.Execute")
|
||||
span.LogKV("pql", q.String())
|
||||
defer span.Finish()
|
||||
|
||||
|
|
@ -203,7 +207,7 @@ func (e *executor) Execute(ctx context.Context, index string, q *pql.Query, shar
|
|||
|
||||
// Default options.
|
||||
if opt == nil {
|
||||
opt = &execOptions{}
|
||||
opt = &ExecOptions{}
|
||||
}
|
||||
// Default maximum memory, if not passed in.
|
||||
if opt.MaxMemory == 0 && q.HasCall("Extract") {
|
||||
|
|
@ -335,7 +339,7 @@ func safeCopy(resp QueryResponse) (out QueryResponse) {
|
|||
|
||||
// handlePreCalls traverses the call tree looking for calls that need
|
||||
// precomputed values (e.g. Distinct, UnionRows, ConstRow...).
|
||||
func (e *executor) handlePreCalls(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) error {
|
||||
func (e *executor) handlePreCalls(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *ExecOptions) error {
|
||||
if c.Name == "Precomputed" {
|
||||
idx := c.Args["valueidx"].(int64)
|
||||
if idx >= 0 && idx < int64(len(opt.EmbeddedData)) {
|
||||
|
|
@ -434,7 +438,7 @@ func (e *executor) dumpPrecomputedCalls(ctx context.Context, c *pql.Call) {
|
|||
}
|
||||
|
||||
// handlePreCallChildren handles any pre-calls in the children of a given call.
|
||||
func (e *executor) handlePreCallChildren(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) error {
|
||||
func (e *executor) handlePreCallChildren(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *ExecOptions) error {
|
||||
for i := range c.Children {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
|
|
@ -461,8 +465,8 @@ func (e *executor) handlePreCallChildren(ctx context.Context, qcx *Qcx, index st
|
|||
return nil
|
||||
}
|
||||
|
||||
func (e *executor) execute(ctx context.Context, qcx *Qcx, index string, q *pql.Query, shards []uint64, opt *execOptions) ([]interface{}, error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.execute")
|
||||
func (e *executor) execute(ctx context.Context, qcx *Qcx, index string, q *pql.Query, shards []uint64, opt *ExecOptions) ([]interface{}, error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.execute")
|
||||
defer span.Finish()
|
||||
|
||||
// Apply translations if necessary.
|
||||
|
|
@ -603,7 +607,7 @@ func (vc *ValCount) cleanup() {
|
|||
}
|
||||
|
||||
// preprocessQuery expands any calls that need preprocessing.
|
||||
func (e *executor) preprocessQuery(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (*pql.Call, error) {
|
||||
func (e *executor) preprocessQuery(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *ExecOptions) (*pql.Call, error) {
|
||||
switch c.Name {
|
||||
case "All":
|
||||
_, hasLimit, err := c.UintArg("limit")
|
||||
|
|
@ -650,8 +654,8 @@ func (e *executor) preprocessQuery(ctx context.Context, qcx *Qcx, index string,
|
|||
}
|
||||
|
||||
// executeCall executes a call.
|
||||
func (e *executor) executeCall(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (interface{}, error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeCall")
|
||||
func (e *executor) executeCall(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *ExecOptions) (interface{}, error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeCall")
|
||||
defer span.Finish()
|
||||
|
||||
if err := validateQueryContext(ctx); err != nil {
|
||||
|
|
@ -842,11 +846,11 @@ func (e *executor) validateTimeCallArgs(c *pql.Call, indexName string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (e *executor) executeOptionsCall(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (interface{}, error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeOptionsCall")
|
||||
func (e *executor) executeOptionsCall(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *ExecOptions) (interface{}, error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeOptionsCall")
|
||||
defer span.Finish()
|
||||
|
||||
optCopy := &execOptions{}
|
||||
optCopy := &ExecOptions{}
|
||||
*optCopy = *opt
|
||||
if arg, ok := c.Args["shards"]; ok {
|
||||
if optShards, ok := arg.([]interface{}); ok {
|
||||
|
|
@ -866,7 +870,7 @@ func (e *executor) executeOptionsCall(ctx context.Context, qcx *Qcx, index strin
|
|||
}
|
||||
|
||||
// executeIncludesColumnCall executes an IncludesColumn() call.
|
||||
func (e *executor) executeIncludesColumnCall(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (bool, error) {
|
||||
func (e *executor) executeIncludesColumnCall(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *ExecOptions) (bool, error) {
|
||||
// Get the shard containing the column, since that's the only
|
||||
// shard that needs to execute this query.
|
||||
var shard uint64
|
||||
|
|
@ -902,7 +906,7 @@ func (e *executor) executeIncludesColumnCall(ctx context.Context, qcx *Qcx, inde
|
|||
}
|
||||
|
||||
// executeFieldValueCall executes a FieldValue() call.
|
||||
func (e *executor) executeFieldValueCall(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (_ ValCount, err error) {
|
||||
func (e *executor) executeFieldValueCall(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *ExecOptions) (_ ValCount, err error) {
|
||||
fieldName, ok := c.Args["field"].(string)
|
||||
if !ok || fieldName == "" {
|
||||
return ValCount{}, ErrFieldRequired
|
||||
|
|
@ -993,7 +997,7 @@ func (e *executor) executeFieldValueCallShard(ctx context.Context, qcx *Qcx, fie
|
|||
}
|
||||
|
||||
// executeLimitCall executes a Limit() call.
|
||||
func (e *executor) executeLimitCall(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (*Row, error) {
|
||||
func (e *executor) executeLimitCall(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *ExecOptions) (*Row, error) {
|
||||
bitmapCall := c.Children[0]
|
||||
|
||||
limit, hasLimit, err := c.UintArg("limit")
|
||||
|
|
@ -1070,7 +1074,7 @@ func (e *executor) executeLimitCall(ctx context.Context, qcx *Qcx, index string,
|
|||
|
||||
// executeIncludesColumnCallShard
|
||||
func (e *executor) executeIncludesColumnCallShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64, column uint64) (_ bool, err error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeIncludesColumnCallShard")
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeIncludesColumnCallShard")
|
||||
defer span.Finish()
|
||||
|
||||
if len(c.Children) == 1 {
|
||||
|
|
@ -1085,8 +1089,8 @@ func (e *executor) executeIncludesColumnCallShard(ctx context.Context, qcx *Qcx,
|
|||
}
|
||||
|
||||
// executeSum executes a Sum() call.
|
||||
func (e *executor) executeSum(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (_ ValCount, err error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeSum")
|
||||
func (e *executor) executeSum(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *ExecOptions) (_ ValCount, err error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeSum")
|
||||
defer span.Finish()
|
||||
|
||||
fieldName, err := c.FirstStringArg("field", "_field")
|
||||
|
|
@ -1139,8 +1143,8 @@ func (e *executor) executeSum(ctx context.Context, qcx *Qcx, index string, c *pq
|
|||
|
||||
// executeDistinct executes a Distinct call on a field. It returns a
|
||||
// SignedRow for int fields and a *Row for set/mutex/time fields.
|
||||
func (e *executor) executeDistinct(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (interface{}, error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeDistinct")
|
||||
func (e *executor) executeDistinct(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *ExecOptions) (interface{}, error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeDistinct")
|
||||
defer span.Finish()
|
||||
|
||||
field, hasField, err := c.StringArg("field")
|
||||
|
|
@ -1191,8 +1195,8 @@ func (e *executor) executeDistinct(ctx context.Context, qcx *Qcx, index string,
|
|||
}
|
||||
|
||||
// executeMin executes a Min() call.
|
||||
func (e *executor) executeMin(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (_ ValCount, err error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeMin")
|
||||
func (e *executor) executeMin(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *ExecOptions) (_ ValCount, err error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeMin")
|
||||
defer span.Finish()
|
||||
|
||||
if _, err := c.FirstStringArg("field", "_field"); err != nil {
|
||||
|
|
@ -1227,8 +1231,8 @@ func (e *executor) executeMin(ctx context.Context, qcx *Qcx, index string, c *pq
|
|||
}
|
||||
|
||||
// executeMax executes a Max() call.
|
||||
func (e *executor) executeMax(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (_ ValCount, err error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeMax")
|
||||
func (e *executor) executeMax(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *ExecOptions) (_ ValCount, err error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeMax")
|
||||
defer span.Finish()
|
||||
|
||||
if _, err := c.FirstStringArg("field", "_field"); err != nil {
|
||||
|
|
@ -1263,8 +1267,8 @@ func (e *executor) executeMax(ctx context.Context, qcx *Qcx, index string, c *pq
|
|||
}
|
||||
|
||||
// executePercentile executes a Percentile() call.
|
||||
func (e *executor) executePercentile(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (_ ValCount, err error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executePercentile")
|
||||
func (e *executor) executePercentile(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *ExecOptions) (_ ValCount, err error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executePercentile")
|
||||
defer span.Finish()
|
||||
|
||||
// get nth
|
||||
|
|
@ -1389,8 +1393,8 @@ func (e *executor) executePercentile(ctx context.Context, qcx *Qcx, index string
|
|||
}
|
||||
|
||||
// executeMinRow executes a MinRow() call.
|
||||
func (e *executor) executeMinRow(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (_ interface{}, err error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeMinRow")
|
||||
func (e *executor) executeMinRow(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *ExecOptions) (_ interface{}, err error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeMinRow")
|
||||
defer span.Finish()
|
||||
|
||||
if field := c.Args["field"]; field == "" {
|
||||
|
|
@ -1428,8 +1432,8 @@ func (e *executor) executeMinRow(ctx context.Context, qcx *Qcx, index string, c
|
|||
}
|
||||
|
||||
// executeMaxRow executes a MaxRow() call.
|
||||
func (e *executor) executeMaxRow(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (_ interface{}, err error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeMaxRow")
|
||||
func (e *executor) executeMaxRow(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *ExecOptions) (_ interface{}, err error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeMaxRow")
|
||||
defer span.Finish()
|
||||
|
||||
if field := c.Args["field"]; field == "" {
|
||||
|
|
@ -1467,8 +1471,8 @@ func (e *executor) executeMaxRow(ctx context.Context, qcx *Qcx, index string, c
|
|||
}
|
||||
|
||||
// executePrecomputedCall pretends to execute a call that we have a precomputed value for.
|
||||
func (e *executor) executePrecomputedCall(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (_ *Row, err error) {
|
||||
span, _ := tracing.StartSpanFromContext(ctx, "Executor.executePrecomputedCall")
|
||||
func (e *executor) executePrecomputedCall(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *ExecOptions) (_ *Row, err error) {
|
||||
span, _ := tracing.StartSpanFromContext(ctx, "executor.executePrecomputedCall")
|
||||
defer span.Finish()
|
||||
result := NewRow()
|
||||
|
||||
|
|
@ -1479,8 +1483,8 @@ func (e *executor) executePrecomputedCall(ctx context.Context, qcx *Qcx, index s
|
|||
}
|
||||
|
||||
// executeBitmapCall executes a call that returns a bitmap.
|
||||
func (e *executor) executeBitmapCall(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (_ *Row, err error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeBitmapCall")
|
||||
func (e *executor) executeBitmapCall(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *ExecOptions) (_ *Row, err error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeBitmapCall")
|
||||
span.LogKV("pqlCallName", c.Name)
|
||||
defer span.Finish()
|
||||
|
||||
|
|
@ -1528,7 +1532,7 @@ func (e *executor) executeBitmapCallShard(ctx context.Context, qcx *Qcx, index s
|
|||
return nil, err
|
||||
}
|
||||
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeBitmapCallShard")
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeBitmapCallShard")
|
||||
defer span.Finish()
|
||||
|
||||
switch c.Name {
|
||||
|
|
@ -1562,7 +1566,7 @@ func (e *executor) executeBitmapCallShard(ctx context.Context, qcx *Qcx, index s
|
|||
// executeDistinctShard executes a Distinct call on a single shard, yielding
|
||||
// a SignedRow of the values found.
|
||||
func (e *executor) executeDistinctShard(ctx context.Context, qcx *Qcx, index string, fieldName string, c *pql.Call, shard uint64) (result interface{}, err error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeDistinctShard")
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeDistinctShard")
|
||||
defer span.Finish()
|
||||
|
||||
idx := e.Holder.Index(index)
|
||||
|
|
@ -1898,7 +1902,7 @@ func executeDistinctShardBSI(ctx context.Context, qcx *Qcx, idx *Index, fieldNam
|
|||
|
||||
// executeSumCountShard calculates the sum and count for bsiGroups on a shard.
|
||||
func (e *executor) executeSumCountShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, filter *Row, shard uint64) (_ ValCount, err0 error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeSumCountShard")
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeSumCountShard")
|
||||
defer span.Finish()
|
||||
|
||||
// use tx to keep consistency between
|
||||
|
|
@ -1941,7 +1945,7 @@ func (e *executor) executeSumCountShard(ctx context.Context, qcx *Qcx, index str
|
|||
}
|
||||
defer finisher(&err0)
|
||||
|
||||
sumspan, _ := tracing.StartSpanFromContext(ctx, "Executor.executeSumCountShard_fragment.sum")
|
||||
sumspan, _ := tracing.StartSpanFromContext(ctx, "executor.executeSumCountShard_fragment.sum")
|
||||
defer sumspan.Finish()
|
||||
vsum, vcount, err := fragment.sum(tx, filter, bsig.BitDepth)
|
||||
if err != nil {
|
||||
|
|
@ -1961,7 +1965,7 @@ func (e *executor) executeSumCountShard(ctx context.Context, qcx *Qcx, index str
|
|||
|
||||
// executeMinShard calculates the min for bsiGroups on a shard.
|
||||
func (e *executor) executeMinShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64) (_ ValCount, err0 error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeMinShard")
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeMinShard")
|
||||
defer span.Finish()
|
||||
|
||||
idx := e.Holder.Index(index)
|
||||
|
|
@ -2113,8 +2117,8 @@ func (e *executor) executeMaxRowShard(ctx context.Context, qcx *Qcx, index strin
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (e *executor) executeTopK(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (interface{}, error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeTopK")
|
||||
func (e *executor) executeTopK(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *ExecOptions) (interface{}, error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeTopK")
|
||||
defer span.Finish()
|
||||
|
||||
mapFn := func(ctx context.Context, shard uint64, mopt *mapOptions) (_ interface{}, err error) {
|
||||
|
|
@ -2172,7 +2176,7 @@ func (e *executor) executeTopK(ctx context.Context, qcx *Qcx, index string, c *p
|
|||
|
||||
// executeTopKShard builds a perpendicular BSI bitmap of a shard for TopK.
|
||||
func (e *executor) executeTopKShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64) (_ []*Row, err0 error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeTopKShard")
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeTopKShard")
|
||||
defer span.Finish()
|
||||
|
||||
// Look up the index.
|
||||
|
|
@ -2245,7 +2249,7 @@ func (e *executor) executeTopKShard(ctx context.Context, qcx *Qcx, index string,
|
|||
|
||||
// executeTopKShardSet builds a perpendicular BSI bitmap of a set field within a shard.
|
||||
func (e *executor) executeTopKShardSet(ctx context.Context, tx Tx, filter *Row, index, field string, shard uint64) ([]*Row, error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeTopKShardSet")
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeTopKShardSet")
|
||||
defer span.Finish()
|
||||
|
||||
f := e.Holder.fragment(index, field, viewStandard, shard)
|
||||
|
|
@ -2535,8 +2539,8 @@ func (f *topKFilter) fillIt(it roaring.ContainerIterator) {
|
|||
// executeTopN executes a TopN() call.
|
||||
// This first performs the TopN() to determine the top results and then
|
||||
// requeries to retrieve the full counts for each of the top results.
|
||||
func (e *executor) executeTopN(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (*PairsField, error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeTopN")
|
||||
func (e *executor) executeTopN(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *ExecOptions) (*PairsField, error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeTopN")
|
||||
defer span.Finish()
|
||||
|
||||
idsArg, _, err := c.UintSliceArg("ids")
|
||||
|
|
@ -2587,8 +2591,8 @@ func (e *executor) executeTopN(ctx context.Context, qcx *Qcx, index string, c *p
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (e *executor) executeTopNShards(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (*PairsField, error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeTopNShards")
|
||||
func (e *executor) executeTopNShards(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *ExecOptions) (*PairsField, error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeTopNShards")
|
||||
defer span.Finish()
|
||||
|
||||
// Execute calls in bulk on each remote node and merge.
|
||||
|
|
@ -2626,7 +2630,7 @@ func (e *executor) executeTopNShards(ctx context.Context, qcx *Qcx, index string
|
|||
|
||||
// executeTopNShard executes a TopN call for a single shard.
|
||||
func (e *executor) executeTopNShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64) (_ *PairsField, err0 error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeTopNShard")
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeTopNShard")
|
||||
defer span.Finish()
|
||||
|
||||
fieldName, _ := c.Args["_field"].(string)
|
||||
|
|
@ -2707,7 +2711,7 @@ func (e *executor) executeTopNShard(ctx context.Context, qcx *Qcx, index string,
|
|||
|
||||
// executeDifferenceShard executes a difference() call for a local shard.
|
||||
func (e *executor) executeDifferenceShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64) (_ *Row, err error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeDifferenceShard")
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeDifferenceShard")
|
||||
defer span.Finish()
|
||||
|
||||
var other *Row
|
||||
|
|
@ -2937,8 +2941,8 @@ func findGroupCounts(v interface{}) []GroupCount {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (e *executor) executeGroupBy(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (*GroupCounts, error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeGroupBy")
|
||||
func (e *executor) executeGroupBy(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *ExecOptions) (*GroupCounts, error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeGroupBy")
|
||||
defer span.Finish()
|
||||
// validate call
|
||||
if len(c.Children) == 0 {
|
||||
|
|
@ -3665,7 +3669,7 @@ func applyConditionToGroupCounts(gcs []GroupCount, subj string, cond *pql.Condit
|
|||
}
|
||||
|
||||
func (e *executor) executeGroupByShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, filter *pql.Call, shard uint64, childRows []RowIDs, bases map[int]int64, ignoreLimit bool) (_ []GroupCount, err error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeGroupByShard")
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeGroupByShard")
|
||||
defer span.Finish()
|
||||
|
||||
var filterRow *Row
|
||||
|
|
@ -3680,7 +3684,7 @@ func (e *executor) executeGroupByShard(ctx context.Context, qcx *Qcx, index stri
|
|||
return nil, err
|
||||
}
|
||||
|
||||
newspan, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeGroupByShard_newGroupByIterator")
|
||||
newspan, ctx := tracing.StartSpanFromContext(ctx, "executor.executeGroupByShard_newGroupByIterator")
|
||||
iter, err := newGroupByIterator(e, qcx, childRows, c.Children, aggregate, filterRow, index, shard, e.Holder)
|
||||
newspan.Finish()
|
||||
|
||||
|
|
@ -3733,7 +3737,7 @@ func (e *executor) executeGroupByShard(ctx context.Context, qcx *Qcx, index stri
|
|||
return results, nil
|
||||
}
|
||||
|
||||
func (e *executor) executeRows(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (RowIDs, error) {
|
||||
func (e *executor) executeRows(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *ExecOptions) (RowIDs, error) {
|
||||
// Fetch field name from argument.
|
||||
// Check "field" first for backwards compatibility.
|
||||
// TODO: remove at Pilosa 2.0
|
||||
|
|
@ -4103,7 +4107,7 @@ var (
|
|||
typeSQLNullInt64 = reflect.TypeOf(sql.NullInt64{})
|
||||
)
|
||||
|
||||
func (e *executor) executeExternalLookup(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (res ExtractedTable, err error) {
|
||||
func (e *executor) executeExternalLookup(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *ExecOptions) (res ExtractedTable, err error) {
|
||||
if e.Holder.lookupDB == nil {
|
||||
return ExtractedTable{}, errors.New("external DB connection is not configured")
|
||||
}
|
||||
|
|
@ -4351,7 +4355,7 @@ type TimeArgs struct {
|
|||
To time.Time
|
||||
}
|
||||
|
||||
func (e *executor) executeExtract(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (ExtractedIDMatrix, error) {
|
||||
func (e *executor) executeExtract(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *ExecOptions) (ExtractedIDMatrix, error) {
|
||||
// Extract the column filter call.
|
||||
if len(c.Children) < 1 {
|
||||
return ExtractedIDMatrix{}, errors.New("missing column filter in Extract")
|
||||
|
|
@ -4757,7 +4761,7 @@ func (e *executor) executeExtractShard(ctx context.Context, qcx *Qcx, index stri
|
|||
}
|
||||
|
||||
func (e *executor) executeRowShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64) (_ *Row, err0 error) {
|
||||
span, _ := tracing.StartSpanFromContext(ctx, "Executor.executeRowShard")
|
||||
span, _ := tracing.StartSpanFromContext(ctx, "executor.executeRowShard")
|
||||
defer span.Finish()
|
||||
|
||||
// Handle bsiGroup ranges differently.
|
||||
|
|
@ -4870,7 +4874,7 @@ func (e *executor) executeRowShard(ctx context.Context, qcx *Qcx, index string,
|
|||
|
||||
// executeRowBSIGroupShard executes a range(bsiGroup) call for a local shard.
|
||||
func (e *executor) executeRowBSIGroupShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64) (cloneable *Row, err0 error) {
|
||||
span, _ := tracing.StartSpanFromContext(ctx, "Executor.executeRowBSIGroupShard")
|
||||
span, _ := tracing.StartSpanFromContext(ctx, "executor.executeRowBSIGroupShard")
|
||||
defer span.Finish()
|
||||
|
||||
// Only one conditional should be present.
|
||||
|
|
@ -5037,7 +5041,7 @@ func (e *executor) executeRowBSIGroupShard(ctx context.Context, qcx *Qcx, index
|
|||
|
||||
// executeIntersectShard executes a intersect() call for a local shard.
|
||||
func (e *executor) executeIntersectShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64) (_ *Row, err error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeIntersectShard")
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeIntersectShard")
|
||||
defer span.Finish()
|
||||
|
||||
var other *Row
|
||||
|
|
@ -5062,7 +5066,7 @@ func (e *executor) executeIntersectShard(ctx context.Context, qcx *Qcx, index st
|
|||
|
||||
// executeUnionShard executes a union() call for a local shard.
|
||||
func (e *executor) executeUnionShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64) (out *Row, err error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeUnionShard")
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeUnionShard")
|
||||
defer span.Finish()
|
||||
|
||||
if len(c.Children) == 0 {
|
||||
|
|
@ -5193,7 +5197,7 @@ func (e *executor) executeInnerUnionRowsShard(ctx context.Context, qcx *Qcx, ind
|
|||
|
||||
// executeXorShard executes a xor() call for a local shard.
|
||||
func (e *executor) executeXorShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64) (_ *Row, err error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeXorShard")
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeXorShard")
|
||||
defer span.Finish()
|
||||
|
||||
other := NewRow()
|
||||
|
|
@ -5234,7 +5238,7 @@ func (e *executor) executePrecomputedCallShard(ctx context.Context, qcx *Qcx, in
|
|||
|
||||
// executeNotShard executes a Not() call for a local shard.
|
||||
func (e *executor) executeNotShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64) (_ *Row, err0 error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeNotShard")
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeNotShard")
|
||||
defer span.Finish()
|
||||
|
||||
if len(c.Children) == 0 {
|
||||
|
|
@ -5293,7 +5297,7 @@ func (e *executor) executeConstRow(ctx context.Context, index string, c *pql.Cal
|
|||
return NewRow(ids...), nil
|
||||
}
|
||||
|
||||
func (e *executor) executeUnionRows(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (*Row, error) {
|
||||
func (e *executor) executeUnionRows(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *ExecOptions) (*Row, error) {
|
||||
// Turn UnionRows(Rows(...)) into Union(Row(...), ...).
|
||||
var rows []*pql.Call
|
||||
for _, child := range c.Children {
|
||||
|
|
@ -5379,7 +5383,7 @@ func (e *executor) executeUnionRows(ctx context.Context, qcx *Qcx, index string,
|
|||
|
||||
// executeAllCallShard executes an All() call for a local shard.
|
||||
func (e *executor) executeAllCallShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64) (res *Row, err0 error) {
|
||||
span, _ := tracing.StartSpanFromContext(ctx, "Executor.executeAllCallShard")
|
||||
span, _ := tracing.StartSpanFromContext(ctx, "executor.executeAllCallShard")
|
||||
defer span.Finish()
|
||||
|
||||
if len(c.Children) > 0 {
|
||||
|
|
@ -5436,8 +5440,8 @@ func (e *executor) executeShiftShard(ctx context.Context, qcx *Qcx, index string
|
|||
}
|
||||
|
||||
// executeCount executes a count() call.
|
||||
func (e *executor) executeCount(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (uint64, error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeCount")
|
||||
func (e *executor) executeCount(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *ExecOptions) (uint64, error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeCount")
|
||||
defer span.Finish()
|
||||
|
||||
if len(c.Children) == 0 {
|
||||
|
|
@ -5492,8 +5496,8 @@ func (e *executor) executeCount(ctx context.Context, qcx *Qcx, index string, c *
|
|||
}
|
||||
|
||||
// executeClearBit executes a Clear() call.
|
||||
func (e *executor) executeClearBit(ctx context.Context, qcx *Qcx, index string, c *pql.Call, opt *execOptions) (bool, error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeClearBit")
|
||||
func (e *executor) executeClearBit(ctx context.Context, qcx *Qcx, index string, c *pql.Call, opt *ExecOptions) (bool, error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeClearBit")
|
||||
defer span.Finish()
|
||||
|
||||
// Read colID
|
||||
|
|
@ -5536,8 +5540,8 @@ func (e *executor) executeClearBit(ctx context.Context, qcx *Qcx, index string,
|
|||
}
|
||||
|
||||
// executeClearBitField executes a Clear() call for a field.
|
||||
func (e *executor) executeClearBitField(ctx context.Context, qcx *Qcx, index string, c *pql.Call, f *Field, colID, rowID uint64, opt *execOptions) (_ bool, err0 error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeClearBitField")
|
||||
func (e *executor) executeClearBitField(ctx context.Context, qcx *Qcx, index string, c *pql.Call, f *Field, colID, rowID uint64, opt *ExecOptions) (_ bool, err0 error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeClearBitField")
|
||||
defer span.Finish()
|
||||
|
||||
shard := colID / ShardWidth
|
||||
|
|
@ -5582,8 +5586,8 @@ func (e *executor) executeClearBitField(ctx context.Context, qcx *Qcx, index str
|
|||
}
|
||||
|
||||
// executeClearRow executes a ClearRow() call.
|
||||
func (e *executor) executeClearRow(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (_ bool, err error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeClearRow")
|
||||
func (e *executor) executeClearRow(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *ExecOptions) (_ bool, err error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeClearRow")
|
||||
defer span.Finish()
|
||||
|
||||
// Ensure the field type supports ClearRow().
|
||||
|
|
@ -5634,7 +5638,7 @@ func (e *executor) executeClearRow(ctx context.Context, qcx *Qcx, index string,
|
|||
|
||||
// executeClearRowShard executes a ClearRow() call for a single shard.
|
||||
func (e *executor) executeClearRowShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64) (_ bool, err0 error) {
|
||||
span, _ := tracing.StartSpanFromContext(ctx, "Executor.executeClearRowShard")
|
||||
span, _ := tracing.StartSpanFromContext(ctx, "executor.executeClearRowShard")
|
||||
defer span.Finish()
|
||||
|
||||
fieldName, err := c.FieldArg()
|
||||
|
|
@ -5683,7 +5687,7 @@ func (e *executor) executeClearRowShard(ctx context.Context, qcx *Qcx, index str
|
|||
|
||||
// executeSetRow executes a Store() call.
|
||||
|
||||
func (e *executor) executeSetRow(ctx context.Context, qcx *Qcx, indexName string, c *pql.Call, shards []uint64, opt *execOptions) (bool, error) {
|
||||
func (e *executor) executeSetRow(ctx context.Context, qcx *Qcx, indexName string, c *pql.Call, shards []uint64, opt *ExecOptions) (bool, error) {
|
||||
// Parse arguments.
|
||||
fieldName, err := c.FieldArg()
|
||||
if err != nil {
|
||||
|
|
@ -5800,8 +5804,8 @@ func (e *executor) executeSetRowShard(ctx context.Context, qcx *Qcx, index strin
|
|||
}
|
||||
|
||||
// executeSet executes a Set() call.
|
||||
func (e *executor) executeSet(ctx context.Context, qcx *Qcx, index string, c *pql.Call, opt *execOptions) (_ bool, err0 error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeSet")
|
||||
func (e *executor) executeSet(ctx context.Context, qcx *Qcx, index string, c *pql.Call, opt *ExecOptions) (_ bool, err0 error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeSet")
|
||||
defer span.Finish()
|
||||
|
||||
// Read colID.
|
||||
|
|
@ -5895,8 +5899,8 @@ func (e *executor) executeSet(ctx context.Context, qcx *Qcx, index string, c *pq
|
|||
}
|
||||
|
||||
// executeSetBitField executes a Set() call for a specific field.
|
||||
func (e *executor) executeSetBitField(ctx context.Context, qcx *Qcx, index string, c *pql.Call, f *Field, colID, rowID uint64, timestamp *time.Time, opt *execOptions) (_ bool, err0 error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeSetBitField")
|
||||
func (e *executor) executeSetBitField(ctx context.Context, qcx *Qcx, index string, c *pql.Call, f *Field, colID, rowID uint64, timestamp *time.Time, opt *ExecOptions) (_ bool, err0 error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeSetBitField")
|
||||
defer span.Finish()
|
||||
|
||||
shard := colID / ShardWidth
|
||||
|
|
@ -5941,8 +5945,8 @@ func (e *executor) executeSetBitField(ctx context.Context, qcx *Qcx, index strin
|
|||
}
|
||||
|
||||
// executeSetValueField executes a Set() call for a specific int field.
|
||||
func (e *executor) executeSetValueField(ctx context.Context, qcx *Qcx, index string, c *pql.Call, f *Field, colID uint64, value int64, opt *execOptions) (_ bool, err0 error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeSetValueField")
|
||||
func (e *executor) executeSetValueField(ctx context.Context, qcx *Qcx, index string, c *pql.Call, f *Field, colID uint64, value int64, opt *ExecOptions) (_ bool, err0 error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeSetValueField")
|
||||
defer span.Finish()
|
||||
|
||||
shard := colID / ShardWidth
|
||||
|
|
@ -5988,8 +5992,8 @@ func (e *executor) executeSetValueField(ctx context.Context, qcx *Qcx, index str
|
|||
}
|
||||
|
||||
// executeClearValueField removes value for colID if present
|
||||
func (e *executor) executeClearValueField(ctx context.Context, qcx *Qcx, index string, c *pql.Call, f *Field, colID uint64, opt *execOptions) (_ bool, err0 error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeClearValueField")
|
||||
func (e *executor) executeClearValueField(ctx context.Context, qcx *Qcx, index string, c *pql.Call, f *Field, colID uint64, opt *ExecOptions) (_ bool, err0 error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeClearValueField")
|
||||
defer span.Finish()
|
||||
|
||||
shard := colID / ShardWidth
|
||||
|
|
@ -6034,7 +6038,7 @@ func (e *executor) executeClearValueField(ctx context.Context, qcx *Qcx, index s
|
|||
|
||||
// remoteExec executes a PQL query remotely for a set of shards on a node.
|
||||
func (e *executor) remoteExec(ctx context.Context, node *disco.Node, index string, q *pql.Query, shards []uint64, embed []*Row, maxMemory int64) (results []interface{}, err error) { // nolint: interfacer
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeExec")
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeExec")
|
||||
defer span.Finish()
|
||||
|
||||
// Encode request object.
|
||||
|
|
@ -6089,8 +6093,8 @@ loop:
|
|||
// mapReduce has to ensure that it never returns before any work it spawned has
|
||||
// terminated. It's not enough to cancel the jobs; we have to wait for them to be
|
||||
// done, or we can unmap resources they're still using.
|
||||
func (e *executor) mapReduce(ctx context.Context, index string, shards []uint64, c *pql.Call, opt *execOptions, mapFn mapFunc, reduceFn reduceFunc) (result interface{}, err error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.mapReduce")
|
||||
func (e *executor) mapReduce(ctx context.Context, index string, shards []uint64, c *pql.Call, opt *ExecOptions, mapFn mapFunc, reduceFn reduceFunc) (result interface{}, err error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.mapReduce")
|
||||
defer span.Finish()
|
||||
|
||||
ch := make(chan mapResponse)
|
||||
|
|
@ -6219,8 +6223,8 @@ func makeEmbeddedDataForShards(allRows []*Row, shards []uint64) []*Row {
|
|||
return newRows
|
||||
}
|
||||
|
||||
func (e *executor) mapper(ctx context.Context, eg *errgroup.Group, ch chan mapResponse, nodes []*disco.Node, index string, shards []uint64, c *pql.Call, opt *execOptions, lastAttempt bool, mapFn mapFunc, reduceFn reduceFunc) (reterr error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.mapper")
|
||||
func (e *executor) mapper(ctx context.Context, eg *errgroup.Group, ch chan mapResponse, nodes []*disco.Node, index string, shards []uint64, c *pql.Call, opt *ExecOptions, lastAttempt bool, mapFn mapFunc, reduceFn reduceFunc) (reterr error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.mapper")
|
||||
defer span.Finish()
|
||||
|
||||
// Group shards together by nodes.
|
||||
|
|
@ -6402,7 +6406,7 @@ var errShutdown = errors.New("executor has shut down")
|
|||
|
||||
// mapperLocal performs map & reduce entirely on the local node.
|
||||
func (e *executor) mapperLocal(ctx context.Context, shards []uint64, mapFn mapFunc, reduceFn reduceFunc, memoryAvailable int64) (_ interface{}, err error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.mapperLocal")
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.mapperLocal")
|
||||
defer span.Finish()
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
|
@ -7176,7 +7180,7 @@ func (e *executor) callZero(c *pql.Call) *pql.Call {
|
|||
}
|
||||
|
||||
func (e *executor) translateResults(ctx context.Context, index string, idx *Index, calls []*pql.Call, results []interface{}, memoryAvailable int64) (err error) {
|
||||
span, _ := tracing.StartSpanFromContext(ctx, "Executor.translateResults")
|
||||
span, _ := tracing.StartSpanFromContext(ctx, "executor.translateResults")
|
||||
defer span.Finish()
|
||||
|
||||
idMap := make(map[uint64]string)
|
||||
|
|
@ -7858,8 +7862,8 @@ type mapResponse struct {
|
|||
err error
|
||||
}
|
||||
|
||||
// execOptions represents an execution context for a single Execute() call.
|
||||
type execOptions struct {
|
||||
// ExecOptions represents an execution context for a single Execute() call.
|
||||
type ExecOptions struct {
|
||||
Remote bool
|
||||
Profile bool
|
||||
PreTranslated bool
|
||||
|
|
@ -8712,8 +8716,8 @@ func decimalToInt64(dec pql.Decimal, opt FieldOptions) int64 {
|
|||
}
|
||||
|
||||
// executeDeleteRecords executes a delete() call.
|
||||
func (e *executor) executeDeleteRecords(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (bool, error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeDelete")
|
||||
func (e *executor) executeDeleteRecords(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *ExecOptions) (bool, error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "executor.executeDelete")
|
||||
defer span.Finish()
|
||||
|
||||
if len(c.Children) == 0 {
|
||||
|
|
@ -8977,7 +8981,7 @@ func deleteKeyTranslation(ctx context.Context, idx *Index, shard uint64, records
|
|||
return idx.TranslateStore(paritionID).Delete(records)
|
||||
}
|
||||
|
||||
func (e *executor) executeSort(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *execOptions) (*SortedRow, error) {
|
||||
func (e *executor) executeSort(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *ExecOptions) (*SortedRow, error) {
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeSort")
|
||||
defer span.Finish()
|
||||
|
||||
|
|
|
|||
15
go.mod
15
go.mod
|
|
@ -14,6 +14,7 @@ require (
|
|||
github.com/benbjohnson/immutable v0.3.0
|
||||
github.com/buger/jsonparser v1.1.1
|
||||
github.com/cespare/xxhash v1.1.0
|
||||
github.com/chzyer/readline v1.5.0
|
||||
github.com/confluentinc/confluent-kafka-go v1.9.1
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/denisenkom/go-mssqldb v0.11.0
|
||||
|
|
@ -21,6 +22,7 @@ require (
|
|||
github.com/getsentry/sentry-go v0.13.0
|
||||
github.com/glycerine/vprint v0.0.0-20200730000117-76cea49a68ea
|
||||
github.com/go-avro/avro v0.0.0-20171219232920-444163702c11
|
||||
github.com/go-openapi/strfmt v0.21.2 // indirect
|
||||
github.com/go-sql-driver/mysql v1.6.0
|
||||
github.com/go-test/deep v1.0.7
|
||||
github.com/gogo/protobuf v1.3.2
|
||||
|
|
@ -32,6 +34,9 @@ require (
|
|||
github.com/gorilla/securecookie v1.1.1
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1
|
||||
github.com/improbable-eng/grpc-web v0.15.0
|
||||
github.com/jedib0t/go-pretty v4.3.0+incompatible
|
||||
github.com/jonboulle/clockwork v0.3.0 // indirect
|
||||
github.com/klauspost/compress v1.15.1 // indirect
|
||||
github.com/lib/pq v1.10.5
|
||||
github.com/molecula/apophenia v0.0.0-20190827192002-68b7a14a478b
|
||||
github.com/opentracing/opentracing-go v1.2.0
|
||||
|
|
@ -83,6 +88,7 @@ require (
|
|||
github.com/DataDog/datadog-go/v5 v5.1.0 // indirect
|
||||
github.com/DataDog/gostackparse v0.5.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.5.2 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
|
|
@ -93,6 +99,8 @@ require (
|
|||
github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-openapi/errors v0.19.8 // indirect
|
||||
github.com/go-stack/stack v1.8.0 // indirect
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
|
|
@ -107,16 +115,16 @@ require (
|
|||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/jonboulle/clockwork v0.3.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.15.1 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/magiconair/properties v1.8.5 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.2 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
|
|
@ -138,6 +146,7 @@ require (
|
|||
go.etcd.io/etcd/client/v2 v2.305.4 // indirect
|
||||
go.etcd.io/etcd/pkg/v3 v3.5.4 // indirect
|
||||
go.etcd.io/etcd/raft/v3 v3.5.4 // indirect
|
||||
go.mongodb.org/mongo-driver v1.7.5 // indirect
|
||||
go.opentelemetry.io/contrib v0.20.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0 // indirect
|
||||
go.opentelemetry.io/otel v0.20.0 // indirect
|
||||
|
|
@ -153,7 +162,7 @@ require (
|
|||
go.uber.org/zap v1.17.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 // indirect
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220503193339-ba3ae3f07e29 // indirect
|
||||
|
|
|
|||
26
go.sum
26
go.sum
|
|
@ -109,6 +109,8 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV
|
|||
github.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
|
||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef h1:46PFijGLmAjMPwCCCo7Jf0W6f9slllCkkv7vyc1yOSg=
|
||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
|
||||
github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
|
|
@ -160,8 +162,14 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
|
|||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/logex v1.2.0 h1:+eqR0HfOetur4tgnC8ftU5imRnhi4te+BadWS95c5AM=
|
||||
github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/readline v1.5.0 h1:lSwwFrbNviGePhkewF1az4oLmcwqCZijQ2/Wi3BGHAI=
|
||||
github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/chzyer/test v0.0.0-20210722231415-061457976a23 h1:dZ0/VyGgQdVGAss6Ju0dt5P0QltE0SFY5Woh6hbIfiQ=
|
||||
github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
|
||||
|
|
@ -296,9 +304,13 @@ github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AE
|
|||
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-openapi/errors v0.19.8 h1:doM+tQdZbUm9gydV9yR+iQNmztbjj7I3sW4sIcAwIzc=
|
||||
github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
|
||||
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
|
||||
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
|
||||
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
|
||||
github.com/go-openapi/strfmt v0.21.2 h1:5NDNgadiX1Vhemth/TH4gCGopWSTdDjxl60H3B7f+os=
|
||||
github.com/go-openapi/strfmt v0.21.2/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k=
|
||||
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
|
||||
github.com/go-pg/pg/v10 v10.0.0/go.mod h1:XHU1AkQW534GFuUdSiQ46+Xw6Ah+9+b8DlT4YwhiXL8=
|
||||
github.com/go-pg/zerochecker v0.2.0/go.mod h1:NJZ4wKL0NmTtz0GKCoJ8kym6Xn/EQzXRl2OnAe7MmDo=
|
||||
|
|
@ -317,6 +329,7 @@ github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG
|
|||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
|
|
@ -629,6 +642,8 @@ github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dv
|
|||
github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jaffee/commandeer v0.5.0 h1:241M9N+gHQmPyjIG+yy8GGcZPfzFuIyOmJHzm5ka92g=
|
||||
github.com/jaffee/commandeer v0.5.0/go.mod h1:kCwfuSvZ2T0NVEr3LDSo6fDUgi0xSBnAVDdkOKTtpLQ=
|
||||
github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo=
|
||||
github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag=
|
||||
github.com/jhump/gopoet v0.0.0-20190322174617-17282ff210b3/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI=
|
||||
github.com/jhump/gopoet v0.1.0/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI=
|
||||
github.com/jhump/goprotoc v0.5.0/go.mod h1:VrbvcYrQOrTi3i0Vf+m+oqQWk9l72mjkJCYo7UvLHRQ=
|
||||
|
|
@ -688,6 +703,7 @@ github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs
|
|||
github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||
github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.14.2/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.15.1 h1:y9FcTHGyrebwfP0ZZqFiaxTaiDnUrGkJkI+f583BL1A=
|
||||
github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
|
|
@ -763,6 +779,7 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA
|
|||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-runewidth v0.0.2 h1:UnlwIPBGaTZfPQ6T1IGzPI0EkYAQmT9fAEJ/poFC63o=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
|
|
@ -785,6 +802,7 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu
|
|||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
|
|
@ -822,6 +840,7 @@ github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI
|
|||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
|
||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
|
|
@ -1048,6 +1067,7 @@ github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT
|
|||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/rtred v0.1.2/go.mod h1:hd69WNXQ5RP9vHd7dqekAz+RIdtfBogmglkZSRxCHFQ=
|
||||
github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw=
|
||||
|
|
@ -1149,6 +1169,8 @@ go.etcd.io/etcd/raft/v3 v3.5.4/go.mod h1:SCuunjYvZFC0fBX0vxMSPjuZmpcSk+XaAcMrD6D
|
|||
go.etcd.io/etcd/server/v3 v3.5.4 h1:CMAZd0g8Bn5NRhynW6pKhc4FRg41/0QYy3d7aNm9874=
|
||||
go.etcd.io/etcd/server/v3 v3.5.4/go.mod h1:S5/YTU15KxymM5l3T6b09sNOHPXqGYIZStpuuGbb65c=
|
||||
go.mongodb.org/mongo-driver v1.5.1/go.mod h1:gRXCHX4Jo7J0IJ1oDQyUxF7jfy19UfxniMS4xxMmUqw=
|
||||
go.mongodb.org/mongo-driver v1.7.5 h1:ny3p0reEpgsR2cfA5cjgwFZg3Cv/ofFh/8jbhGtz9VI=
|
||||
go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng=
|
||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
|
|
@ -1464,8 +1486,9 @@ golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
|
|
@ -1717,6 +1740,7 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/mysql v1.0.1/go.mod h1:KtqSthtg55lFp3S5kUXqlGaelnWpKitn4k1xZTnoiPw=
|
||||
|
|
|
|||
175
http_handler.go
175
http_handler.go
|
|
@ -11,6 +11,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"math/big"
|
||||
"mime"
|
||||
"net"
|
||||
"net/http"
|
||||
|
|
@ -37,6 +38,7 @@ import (
|
|||
"github.com/molecula/featurebase/v3/monitor"
|
||||
"github.com/molecula/featurebase/v3/pql"
|
||||
"github.com/molecula/featurebase/v3/rbf"
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
"github.com/molecula/featurebase/v3/storage"
|
||||
"github.com/molecula/featurebase/v3/tracing"
|
||||
"github.com/pkg/errors"
|
||||
|
|
@ -79,6 +81,10 @@ type Handler struct {
|
|||
auth *authn.Auth
|
||||
|
||||
permissions *authz.GroupPermissions
|
||||
|
||||
// sqlEnabled is serving as a feature flag for turning on/off the /sql
|
||||
// endpoint.
|
||||
sqlEnabled bool
|
||||
}
|
||||
|
||||
// externalPrefixFlag denotes endpoints that are intended to be exposed to clients.
|
||||
|
|
@ -211,6 +217,14 @@ func OptHandlerCloseTimeout(d time.Duration) handlerOption {
|
|||
}
|
||||
}
|
||||
|
||||
// OptHandlerSQLEnabled enables the /sql endpoint.
|
||||
func OptHandlerSQLEnabled(v bool) handlerOption {
|
||||
return func(h *Handler) error {
|
||||
h.sqlEnabled = v
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var makeImportOk sync.Once
|
||||
var importOk []byte
|
||||
|
||||
|
|
@ -519,6 +533,12 @@ func newRouter(handler *Handler) http.Handler {
|
|||
router.HandleFunc("/transaction/{id}/finish", handler.chkAuthZ(handler.handlePostFinishTransaction, authz.Read)).Methods("POST").Name("PostFinishTransaction")
|
||||
router.HandleFunc("/transactions", handler.chkAuthZ(handler.handleGetTransactions, authz.Read)).Methods("GET").Name("GetTransactions")
|
||||
router.HandleFunc("/queries", handler.chkAuthZ(handler.handleGetActiveQueries, authz.Admin)).Methods("GET").Name("GetActiveQueries")
|
||||
|
||||
// enable this endpoint based on config
|
||||
if handler.sqlEnabled {
|
||||
router.HandleFunc("/sql", handler.chkAuthZ(handler.handlePostSQL, authz.Admin)).Methods("POST").Name("PostSQL")
|
||||
}
|
||||
|
||||
router.HandleFunc("/query-history", handler.chkAuthZ(handler.handleGetPastQueries, authz.Admin)).Methods("GET").Name("GetPastQueries")
|
||||
router.HandleFunc("/version", handler.handleGetVersion).Methods("GET").Name("GetVersion")
|
||||
|
||||
|
|
@ -1329,6 +1349,159 @@ func (h *Handler) handlePostQuery(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
func (h *Handler) writeBadRequest(w http.ResponseWriter, r *http.Request, err error) {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
e := h.writeQueryResponse(w, r, &QueryResponse{Err: err})
|
||||
if e != nil {
|
||||
h.logger.Errorf("write query response error: %v (while trying to write another error: %v)", e, err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// handlePostSQL handles /sql requests.
|
||||
func (h *Handler) handlePostSQL(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
b, err := io.ReadAll(r.Body)
|
||||
|
||||
if err != nil {
|
||||
h.writeBadRequest(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
|
||||
rootOperator, err := h.api.CompilePlan(r.Context(), string(b))
|
||||
if err != nil {
|
||||
h.writeBadRequest(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Write response back to client.
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
// the pandas data frame format in json per https://molecula.atlassian.net/wiki/spaces/MOLECULA/pages/540999700/Queries
|
||||
|
||||
// Opening bracket.
|
||||
w.Write([]byte("{"))
|
||||
|
||||
// Write the closing bracket on any exit from this method.
|
||||
defer func() {
|
||||
duration := time.Since(start)
|
||||
value, err := json.Marshal(duration.Microseconds())
|
||||
if err != nil {
|
||||
value = big.NewInt(-1).Bytes()
|
||||
}
|
||||
w.Write([]byte(`,"exec_time":`))
|
||||
w.Write(value)
|
||||
w.Write([]byte("}"))
|
||||
}()
|
||||
|
||||
// writeError is a helper function that can be called anywhere during the
|
||||
// output handling to insert an error into the json output.
|
||||
writeError := func(err error) {
|
||||
if err != nil {
|
||||
errMsg, err := json.Marshal(err.Error())
|
||||
if err != nil {
|
||||
errMsg = []byte(`"PROBLEM ENCODING ERROR MESSAGE"`)
|
||||
}
|
||||
w.Write([]byte(`,"error":`))
|
||||
w.Write(errMsg)
|
||||
}
|
||||
}
|
||||
|
||||
// writeWarnings is a helper function that can be called anywhere during the
|
||||
// output handling to insert warnings into the json output.
|
||||
writeWarnings := func(warnings []string) {
|
||||
if len(warnings) > 0 {
|
||||
w.Write([]byte(`,"warnings": [`))
|
||||
for i, warn := range warnings {
|
||||
warnMsg, err := json.Marshal(warn)
|
||||
if err != nil {
|
||||
warnMsg = []byte(`"PROBLEM ENCODING WARNING"`)
|
||||
}
|
||||
w.Write(warnMsg)
|
||||
if i < len(warnings)-1 {
|
||||
w.Write([]byte(`,`))
|
||||
}
|
||||
}
|
||||
w.Write([]byte(`]`))
|
||||
}
|
||||
}
|
||||
|
||||
// Get a query iterator.
|
||||
iter, err := rootOperator.Iterator(r.Context(), nil)
|
||||
if err != nil {
|
||||
writeError(err)
|
||||
writeWarnings(rootOperator.Warnings())
|
||||
return
|
||||
}
|
||||
|
||||
// Read schema & write to response.
|
||||
columns := rootOperator.Schema()
|
||||
schema := SQLSchema{
|
||||
Fields: make([]*SQLField, len(columns)),
|
||||
}
|
||||
for i, col := range columns {
|
||||
schema.Fields[i] = &SQLField{
|
||||
Name: col.Name,
|
||||
Type: col.Type.TypeName(),
|
||||
}
|
||||
}
|
||||
w.Write([]byte(`"schema":`))
|
||||
jsonSchema, err := json.Marshal(schema)
|
||||
if err != nil {
|
||||
h.logger.Errorf("write schema response error: %s", err)
|
||||
// Provide an empty list as the schema value to maintain valid json.
|
||||
w.Write([]byte("[]"))
|
||||
writeError(err)
|
||||
writeWarnings(rootOperator.Warnings())
|
||||
return
|
||||
}
|
||||
w.Write(jsonSchema)
|
||||
|
||||
// Write the data (rows).
|
||||
w.Write([]byte(`,"data":[`))
|
||||
|
||||
var rowErr error
|
||||
var currentRow types.Row
|
||||
var nextErr error
|
||||
|
||||
rowCounter := 1
|
||||
for currentRow, nextErr = iter.Next(r.Context()); nextErr == nil; currentRow, nextErr = iter.Next(r.Context()) {
|
||||
jsonRow, err := json.Marshal(currentRow)
|
||||
if err != nil {
|
||||
h.logger.Errorf("json encoding error: %s", err)
|
||||
rowErr = err
|
||||
break
|
||||
}
|
||||
|
||||
if rowCounter > 1 {
|
||||
// Include a comma between data rows.
|
||||
w.Write([]byte(","))
|
||||
}
|
||||
w.Write(jsonRow)
|
||||
|
||||
rowCounter++
|
||||
}
|
||||
if nextErr != nil && nextErr != types.ErrNoMoreRows {
|
||||
rowErr = nextErr
|
||||
}
|
||||
|
||||
w.Write([]byte("]"))
|
||||
|
||||
writeError(rowErr)
|
||||
writeWarnings(rootOperator.Warnings())
|
||||
}
|
||||
|
||||
type SQLField struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type SQLSchema struct {
|
||||
Fields []*SQLField `json:"fields"`
|
||||
}
|
||||
|
||||
func (h *Handler) handleCPUProfileStart(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if h.pprofCPUProfileBuffer == nil {
|
||||
|
|
@ -1511,7 +1684,7 @@ type postIndexRequest struct {
|
|||
Options IndexOptions `json:"options"`
|
||||
}
|
||||
|
||||
//_postIndexRequest is necessary to avoid recursion while decoding.
|
||||
// _postIndexRequest is necessary to avoid recursion while decoding.
|
||||
type _postIndexRequest postIndexRequest
|
||||
|
||||
// Custom Unmarshal JSON to validate request body when creating a new index.
|
||||
|
|
|
|||
12
index.go
12
index.go
|
|
@ -937,6 +937,17 @@ type IndexInfo struct {
|
|||
ShardWidth uint64 `json:"shardWidth"`
|
||||
}
|
||||
|
||||
// Field returns the FieldInfo the provided field name. If the field does not
|
||||
// exist, it returns nil
|
||||
func (ii *IndexInfo) Field(name string) *FieldInfo {
|
||||
for _, fld := range ii.Fields {
|
||||
if fld.Name == name {
|
||||
return fld
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type indexInfoSlice []*IndexInfo
|
||||
|
||||
func (p indexInfoSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
|
|
@ -947,6 +958,7 @@ func (p indexInfoSlice) Less(i, j int) bool { return p[i].Name < p[j].Name }
|
|||
type IndexOptions struct {
|
||||
Keys bool `json:"keys"`
|
||||
TrackExistence bool `json:"trackExistence"`
|
||||
PartitionN int `json:"partitionN"`
|
||||
}
|
||||
|
||||
type importData struct {
|
||||
|
|
|
|||
124
pg/cancel.go
124
pg/cancel.go
|
|
@ -1,124 +0,0 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
package pg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ErrCancelledMissingConnection is an error triggered by cancelling a connection that does not exist.
|
||||
var ErrCancelledMissingConnection = errors.New("cancelled connection does not exist")
|
||||
|
||||
// CancellationToken is a value used to identify a backend for cancellation.
|
||||
type CancellationToken struct {
|
||||
PID, Key int32
|
||||
}
|
||||
|
||||
// CancellationManager manages postgres connection cancellation.
|
||||
type CancellationManager interface {
|
||||
// Token acquires a new cancellation token.
|
||||
// The returned channel is sent to every time the connection is cancelled.
|
||||
// The connection may be cancelled an unlimited number of times.
|
||||
Token() (<-chan struct{}, context.CancelFunc, CancellationToken, error)
|
||||
|
||||
// Cancel sends a cancellation notification to the connection with the associated token.
|
||||
// If the token is not associated with a connection, this returns ErrCancelledMissingConnection.
|
||||
Cancel(CancellationToken) error
|
||||
}
|
||||
|
||||
// NewLocalCancellationManager creates an in-memory CancellationManager using randomly generated tokens.
|
||||
// The provided reader is expected to be secure (e.g. crypto/rand.Reader).
|
||||
func NewLocalCancellationManager(rand io.Reader) CancellationManager {
|
||||
return &localCancellationManager{
|
||||
rand: rand,
|
||||
connections: make(map[CancellationToken]chan<- struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
type localCancellationManager struct {
|
||||
mu sync.RWMutex
|
||||
rand io.Reader
|
||||
connections map[CancellationToken]chan<- struct{}
|
||||
}
|
||||
|
||||
func (c *localCancellationManager) Token() (<-chan struct{}, context.CancelFunc, CancellationToken, error) {
|
||||
notify := make(chan struct{}, 1)
|
||||
|
||||
gen:
|
||||
token, err := c.generateToken()
|
||||
if err != nil {
|
||||
return nil, nil, CancellationToken{}, err
|
||||
}
|
||||
cancel := c.registerToken(token, notify)
|
||||
if cancel == nil {
|
||||
goto gen
|
||||
}
|
||||
|
||||
return notify, cancel, token, nil
|
||||
}
|
||||
|
||||
func (c *localCancellationManager) generateToken() (CancellationToken, error) {
|
||||
var data [8]byte
|
||||
for {
|
||||
var n int
|
||||
for n < 8 {
|
||||
nn, err := c.rand.Read(data[n:])
|
||||
if err != nil {
|
||||
return CancellationToken{}, errors.Wrap(err, "generating a cancellation token")
|
||||
}
|
||||
n += nn
|
||||
}
|
||||
|
||||
pid := int32(binary.LittleEndian.Uint32(data[:4]))
|
||||
if pid < 0 {
|
||||
continue
|
||||
}
|
||||
key := int32(binary.LittleEndian.Uint32(data[4:]))
|
||||
if key < 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
return CancellationToken{PID: pid, Key: key}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *localCancellationManager) registerToken(token CancellationToken, notify chan<- struct{}) context.CancelFunc {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if _, collision := c.connections[token]; collision {
|
||||
return nil
|
||||
}
|
||||
|
||||
c.connections[token] = notify
|
||||
|
||||
return func() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
delete(c.connections, token)
|
||||
|
||||
close(notify)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *localCancellationManager) Cancel(token CancellationToken) error {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
ch := c.connections[token]
|
||||
if ch == nil {
|
||||
return ErrCancelledMissingConnection
|
||||
}
|
||||
|
||||
select {
|
||||
case ch <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
package pg
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCancel(t *testing.T) {
|
||||
mgr := NewLocalCancellationManager(rand.Reader)
|
||||
notify, cancel, token, err := mgr.Token()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-notify:
|
||||
t.Fatal("unexpected cancellation")
|
||||
default:
|
||||
}
|
||||
|
||||
err = mgr.Cancel(token)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-notify:
|
||||
default:
|
||||
t.Fatal("cancellation not propogated")
|
||||
}
|
||||
|
||||
select {
|
||||
case <-notify:
|
||||
t.Fatal("unexpected cancellation")
|
||||
default:
|
||||
}
|
||||
|
||||
err = mgr.Cancel(CancellationToken{PID: -1, Key: -1})
|
||||
if err == nil {
|
||||
t.Fatal("invalid cancellation completed")
|
||||
}
|
||||
|
||||
select {
|
||||
case <-notify:
|
||||
t.Fatal("unexpected cancellation")
|
||||
default:
|
||||
}
|
||||
|
||||
cancel()
|
||||
|
||||
select {
|
||||
case _, ok := <-notify:
|
||||
if ok {
|
||||
t.Fatal("unexpected cancellation")
|
||||
}
|
||||
default:
|
||||
t.Fatal("expected cancellation channel to be closed")
|
||||
}
|
||||
|
||||
err = mgr.Cancel(token)
|
||||
if err == nil {
|
||||
t.Fatal("invalid cancellation completed")
|
||||
}
|
||||
}
|
||||
236
pg/io.go
236
pg/io.go
|
|
@ -1,236 +0,0 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
package pg
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// timeoutWriter wraps a connection and implements io.Writer with a timeout for each write.
|
||||
type timeoutWriter struct {
|
||||
conn net.Conn
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func (w *timeoutWriter) Write(data []byte) (int, error) {
|
||||
err := w.conn.SetWriteDeadline(time.Now().Add(w.timeout))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return w.conn.Write(data)
|
||||
}
|
||||
|
||||
// errPreempted is an error used to indicate preemption of an idle connection.
|
||||
var errPreempted = errors.New("preempted during idle")
|
||||
|
||||
// idleState is an atomic state value used to track a preemptible connection.
|
||||
type idleState uint32
|
||||
|
||||
const (
|
||||
idleStateActive idleState = iota
|
||||
idleStateIdle
|
||||
idleStatePreempted
|
||||
idleStatePendingPreemption
|
||||
)
|
||||
|
||||
func (s *idleState) load() idleState {
|
||||
return idleState(atomic.LoadUint32((*uint32)(unsafe.Pointer(s))))
|
||||
}
|
||||
|
||||
func (s *idleState) cas(old, new idleState) bool {
|
||||
return atomic.CompareAndSwapUint32((*uint32)(unsafe.Pointer(s)), uint32(old), uint32(new))
|
||||
}
|
||||
|
||||
// idleReader is an io.Reader implementation on a preemptible network connection.
|
||||
// The connection has 2 modes: "idle" and "active".
|
||||
// While in idle mode, the connection has no timeout but can be preempted.
|
||||
// While in active mode, the connection may have a read timeout but cannot be immediately preempted.
|
||||
// When a read completes in idle mode, the connection returns to active mode.
|
||||
// If the connection is preempted in active mode, the preemption will be deferred until the connection returns to idle mode.
|
||||
// This also provides a read timeout.
|
||||
type idleReader struct {
|
||||
conn net.Conn
|
||||
timeout time.Duration
|
||||
state idleState
|
||||
preemptMu sync.Mutex
|
||||
}
|
||||
|
||||
// setIdle pushes the reader into idle mode.
|
||||
// If a preemption is pending, it will be delivered on the next call to Read.
|
||||
func (r *idleReader) setIdle() error {
|
||||
// Clear the read deadline.
|
||||
err := r.conn.SetReadDeadline(time.Time{})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to clear deadline")
|
||||
}
|
||||
|
||||
for {
|
||||
// Transition to idle mode.
|
||||
state := r.state.load()
|
||||
var target idleState
|
||||
switch state {
|
||||
case idleStateActive:
|
||||
// active -> idle
|
||||
target = idleStateIdle
|
||||
case idleStatePendingPreemption:
|
||||
// pending preemption -> preempted
|
||||
// Switching to idle mode activates the preemption.
|
||||
target = idleStatePreempted
|
||||
default:
|
||||
panic("inconsistent state")
|
||||
}
|
||||
|
||||
if r.state.cas(state, target) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// preempt the reader.
|
||||
// If the reader is not currently idle, the preemption will be delivered next time the connection enters idle mode.
|
||||
// This does not wait until the preemption error is delivered.
|
||||
func (r *idleReader) preempt() error {
|
||||
r.preemptMu.Lock()
|
||||
defer r.preemptMu.Unlock()
|
||||
for {
|
||||
state := r.state.load()
|
||||
var target idleState
|
||||
switch state {
|
||||
case idleStateActive:
|
||||
// active -> pending preemption
|
||||
target = idleStatePendingPreemption
|
||||
case idleStateIdle:
|
||||
// idle -> preempted
|
||||
target = idleStatePreempted
|
||||
case idleStatePendingPreemption, idleStatePreempted:
|
||||
// A preemption has already been delivered.
|
||||
return nil
|
||||
default:
|
||||
panic("inconsistent state")
|
||||
}
|
||||
|
||||
ok := r.state.cas(state, target)
|
||||
if ok && target == idleStatePreempted {
|
||||
// We have entered preemption mode.
|
||||
// Preempt the current read on the connection.
|
||||
return r.conn.SetReadDeadline(time.Now())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read from the connection.
|
||||
func (r *idleReader) Read(data []byte) (int, error) {
|
||||
var needsDeadlineReset bool
|
||||
state := r.state.load()
|
||||
switch state {
|
||||
case idleStateActive, idleStatePendingPreemption:
|
||||
// Connection is active.
|
||||
// There is no need to worry about preemption.
|
||||
if r.timeout != 0 {
|
||||
// Apply a read timeout.
|
||||
err := r.conn.SetReadDeadline(time.Now().Add(r.timeout))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return r.conn.Read(data)
|
||||
|
||||
case idleStateIdle:
|
||||
// Read, and handle preemption.
|
||||
n, err := r.conn.Read(data)
|
||||
if err != nil {
|
||||
// Check if the error was caused by preemption.
|
||||
state = r.state.load()
|
||||
switch {
|
||||
case state == idleStatePreempted && n != 0:
|
||||
// Some data was read before the preemption was delivered.
|
||||
// Re-activate and discard the error.
|
||||
|
||||
// Synchronize against the preempter.
|
||||
// This is necessary to ensure that the cancellation deadline is cleared.
|
||||
r.preemptMu.Lock()
|
||||
defer r.preemptMu.Unlock()
|
||||
|
||||
// Re-activate the connection.
|
||||
// No CAS loop is necessary since we are synchronized against preempters.
|
||||
r.state = idleStatePendingPreemption
|
||||
|
||||
// The deadline may need to reset since the preempter may have changed it.
|
||||
needsDeadlineReset = true
|
||||
|
||||
case state == idleStatePreempted:
|
||||
// The read was preempted.
|
||||
return 0, errPreempted
|
||||
|
||||
case state != idleStateIdle:
|
||||
// No other states make sense here.
|
||||
panic("inconsistent state")
|
||||
|
||||
default:
|
||||
// No preemption was involved.
|
||||
// It is just a regular network error.
|
||||
return n, err
|
||||
}
|
||||
} else {
|
||||
// The read went through.
|
||||
|
||||
// Exit from idle mode.
|
||||
// Ideally, transition to active mode.
|
||||
// However, a preemption may trigger while this is running.
|
||||
for state == idleStateIdle {
|
||||
if r.state.cas(idleStateIdle, idleStateActive) {
|
||||
state = idleStateActive
|
||||
break
|
||||
}
|
||||
|
||||
state = r.state.load()
|
||||
}
|
||||
|
||||
switch state {
|
||||
case idleStateActive:
|
||||
// The connection was reactivated normally.
|
||||
|
||||
case idleStatePreempted:
|
||||
// The connection was preempted after the read completed.
|
||||
// Defer the preemption and complete successfully.
|
||||
|
||||
// Synchronize against the preempter.
|
||||
// This is necessary to ensure that the cancellation deadline is cleared.
|
||||
r.preemptMu.Lock()
|
||||
defer r.preemptMu.Unlock()
|
||||
|
||||
// Re-activate the connection.
|
||||
// No CAS loop is necessary since we are synchronized against preempters.
|
||||
r.state = idleStatePendingPreemption
|
||||
|
||||
// The deadline may need to reset since the preempter may have changed it.
|
||||
needsDeadlineReset = true
|
||||
|
||||
default:
|
||||
panic("inconsistent state")
|
||||
}
|
||||
}
|
||||
|
||||
if needsDeadlineReset && r.timeout == 0 {
|
||||
// Clear the deadline.
|
||||
err := r.conn.SetReadDeadline(time.Time{})
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
|
||||
return n, nil
|
||||
|
||||
case idleStatePreempted:
|
||||
// The connection is preempted.
|
||||
return 0, errPreempted
|
||||
|
||||
default:
|
||||
panic("inconsistent state")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,652 +0,0 @@
|
|||
```mermaid
|
||||
sequenceDiagram
|
||||
|
||||
participant 213070643358480 as c0
|
||||
participant 213070643358482 as c1
|
||||
participant 213070643358484 as c2
|
||||
participant 213070643358486 as c3
|
||||
participant 213070643358488 as c4
|
||||
participant 213070643358490 as c5
|
||||
participant 213070643358492 as c6
|
||||
participant 213070643358494 as c7
|
||||
participant 213070643358496 as c8
|
||||
participant 213070643358498 as c9
|
||||
participant 213070643358500 as c10
|
||||
participant 213070643358502 as c11
|
||||
213070643358480->>server:+SSL REQUEST
|
||||
server-->>213070643358480:-SSL BACKEND ANSWER: N
|
||||
213070643358480->>server:+STARTUP MESSAGE version: 3 database=trait_store extra_float_digits=2 TimeZone=GMT client_encoding=UTF8 user=docker DateStyle=ISO
|
||||
server-->>213070643358480:-AUTHENTIFICATION REQUEST code=0 (SUCCESS)
|
||||
server-->>213070643358480:-PARAMETER STATUS name='application_name', value=''
|
||||
server-->>213070643358480:-PARAMETER STATUS name='client_encoding', value='UTF8'
|
||||
server-->>213070643358480:-PARAMETER STATUS name='DateStyle', value='ISO, MDY'
|
||||
server-->>213070643358480:-PARAMETER STATUS name='integer_datetimes', value='on'
|
||||
server-->>213070643358480:-PARAMETER STATUS name='IntervalStyle', value='postgres'
|
||||
server-->>213070643358480:-PARAMETER STATUS name='is_superuser', value='on'
|
||||
server-->>213070643358480:-PARAMETER STATUS name='server_encoding', value='UTF8'
|
||||
server-->>213070643358480:-PARAMETER STATUS name='server_version', value='13.0.0'
|
||||
server-->>213070643358480:-PARAMETER STATUS name='session_authorization', value='docker'
|
||||
server-->>213070643358480:-PARAMETER STATUS name='standard_conforming_strings', value='on'
|
||||
server-->>213070643358480:-PARAMETER STATUS name='TimeZone', value='GMT'
|
||||
server-->>213070643358480:-BACKEND KEY DATA pid=1459324827, key=1506254533
|
||||
server-->>213070643358480:-READY FOR QUERY type=<IDLE>
|
||||
213070643358480->>server:+PARSE name='', num_params=0, params_type=, query=SET extra_float_digits = 3
|
||||
213070643358480->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358480->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358480->>server:+SYNC
|
||||
server-->>213070643358480:-PARSE COMPLETE
|
||||
server-->>213070643358480:-BIND COMPLETE
|
||||
server-->>213070643358480:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643358480:-READY FOR QUERY type=<IDLE>
|
||||
213070643358480->>server:+PARSE name='', num_params=0, params_type=, query=SET application_name = 'PostgreSQL JDBC Driver'
|
||||
213070643358480->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358480->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358480->>server:+SYNC
|
||||
server-->>213070643358480:-PARSE COMPLETE
|
||||
server-->>213070643358480:-BIND COMPLETE
|
||||
server-->>213070643358480:-PARAMETER STATUS name='application_name', value='PostgreSQL JDBC Driver'
|
||||
server-->>213070643358480:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643358480:-READY FOR QUERY type=<IDLE>
|
||||
213070643358480->>server:+PARSE name='', num_params=0, params_type=, query=
|
||||
213070643358480->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358480->>server:+DESCRIBE kind='P', name=''
|
||||
213070643358480->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358480->>server:+SYNC
|
||||
server-->>213070643358480:-PARSE COMPLETE
|
||||
server-->>213070643358480:-BIND COMPLETE
|
||||
server-->>213070643358480:-NO DATA
|
||||
server-->>213070643358480:-EMPTY QUERY RESPONSE
|
||||
server-->>213070643358480:-READY FOR QUERY type=<IDLE>
|
||||
213070643358480->>server:+DISCONNECT
|
||||
213070643358482->>server:+SSL REQUEST
|
||||
server-->>213070643358482:-SSL BACKEND ANSWER: N
|
||||
213070643358482->>server:+STARTUP MESSAGE version: 3 database=trait_store extra_float_digits=2 TimeZone=GMT client_encoding=UTF8 user=docker DateStyle=ISO
|
||||
server-->>213070643358482:-AUTHENTIFICATION REQUEST code=0 (SUCCESS)
|
||||
server-->>213070643358482:-PARAMETER STATUS name='application_name', value=''
|
||||
server-->>213070643358482:-PARAMETER STATUS name='client_encoding', value='UTF8'
|
||||
server-->>213070643358482:-PARAMETER STATUS name='DateStyle', value='ISO, MDY'
|
||||
server-->>213070643358482:-PARAMETER STATUS name='integer_datetimes', value='on'
|
||||
server-->>213070643358482:-PARAMETER STATUS name='IntervalStyle', value='postgres'
|
||||
server-->>213070643358482:-PARAMETER STATUS name='is_superuser', value='on'
|
||||
server-->>213070643358482:-PARAMETER STATUS name='server_encoding', value='UTF8'
|
||||
server-->>213070643358482:-PARAMETER STATUS name='server_version', value='13.0.0'
|
||||
server-->>213070643358482:-PARAMETER STATUS name='session_authorization', value='docker'
|
||||
server-->>213070643358482:-PARAMETER STATUS name='standard_conforming_strings', value='on'
|
||||
server-->>213070643358482:-PARAMETER STATUS name='TimeZone', value='GMT'
|
||||
server-->>213070643358482:-BACKEND KEY DATA pid=1742342691, key=1299317425
|
||||
server-->>213070643358482:-READY FOR QUERY type=<IDLE>
|
||||
213070643358482->>server:+PARSE name='', num_params=0, params_type=, query=SET extra_float_digits = 3
|
||||
213070643358482->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358482->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358482->>server:+SYNC
|
||||
server-->>213070643358482:-PARSE COMPLETE
|
||||
server-->>213070643358482:-BIND COMPLETE
|
||||
server-->>213070643358482:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643358482:-READY FOR QUERY type=<IDLE>
|
||||
213070643358482->>server:+PARSE name='', num_params=0, params_type=, query=SET application_name = 'PostgreSQL JDBC Driver'
|
||||
213070643358482->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358482->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358482->>server:+SYNC
|
||||
server-->>213070643358482:-PARSE COMPLETE
|
||||
server-->>213070643358482:-BIND COMPLETE
|
||||
server-->>213070643358482:-PARAMETER STATUS name='application_name', value='PostgreSQL JDBC Driver'
|
||||
server-->>213070643358482:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643358482:-READY FOR QUERY type=<IDLE>
|
||||
213070643358482->>server:+PARSE name='', num_params=0, params_type=, query=
|
||||
213070643358482->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358482->>server:+DESCRIBE kind='P', name=''
|
||||
213070643358482->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358482->>server:+SYNC
|
||||
server-->>213070643358482:-PARSE COMPLETE
|
||||
server-->>213070643358482:-BIND COMPLETE
|
||||
server-->>213070643358482:-NO DATA
|
||||
server-->>213070643358482:-EMPTY QUERY RESPONSE
|
||||
server-->>213070643358482:-READY FOR QUERY type=<IDLE>
|
||||
213070643358482->>server:+DISCONNECT
|
||||
213070643358484->>server:+SSL REQUEST
|
||||
server-->>213070643358484:-SSL BACKEND ANSWER: N
|
||||
213070643358484->>server:+STARTUP MESSAGE version: 3 database=trait_store extra_float_digits=2 TimeZone=GMT client_encoding=UTF8 user=docker DateStyle=ISO
|
||||
server-->>213070643358484:-AUTHENTIFICATION REQUEST code=0 (SUCCESS)
|
||||
server-->>213070643358484:-PARAMETER STATUS name='application_name', value=''
|
||||
server-->>213070643358484:-PARAMETER STATUS name='client_encoding', value='UTF8'
|
||||
server-->>213070643358484:-PARAMETER STATUS name='DateStyle', value='ISO, MDY'
|
||||
server-->>213070643358484:-PARAMETER STATUS name='integer_datetimes', value='on'
|
||||
server-->>213070643358484:-PARAMETER STATUS name='IntervalStyle', value='postgres'
|
||||
server-->>213070643358484:-PARAMETER STATUS name='is_superuser', value='on'
|
||||
server-->>213070643358484:-PARAMETER STATUS name='server_encoding', value='UTF8'
|
||||
server-->>213070643358484:-PARAMETER STATUS name='server_version', value='13.0.0'
|
||||
server-->>213070643358484:-PARAMETER STATUS name='session_authorization', value='docker'
|
||||
server-->>213070643358484:-PARAMETER STATUS name='standard_conforming_strings', value='on'
|
||||
server-->>213070643358484:-PARAMETER STATUS name='TimeZone', value='GMT'
|
||||
server-->>213070643358484:-BACKEND KEY DATA pid=700852804, key=1377869267
|
||||
server-->>213070643358484:-READY FOR QUERY type=<IDLE>
|
||||
213070643358484->>server:+PARSE name='', num_params=0, params_type=, query=SET extra_float_digits = 3
|
||||
213070643358484->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358484->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358484->>server:+SYNC
|
||||
server-->>213070643358484:-PARSE COMPLETE
|
||||
server-->>213070643358484:-BIND COMPLETE
|
||||
server-->>213070643358484:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643358484:-READY FOR QUERY type=<IDLE>
|
||||
213070643358484->>server:+PARSE name='', num_params=0, params_type=, query=SET application_name = 'PostgreSQL JDBC Driver'
|
||||
213070643358484->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358484->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358484->>server:+SYNC
|
||||
server-->>213070643358484:-PARSE COMPLETE
|
||||
server-->>213070643358484:-BIND COMPLETE
|
||||
server-->>213070643358484:-PARAMETER STATUS name='application_name', value='PostgreSQL JDBC Driver'
|
||||
server-->>213070643358484:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643358484:-READY FOR QUERY type=<IDLE>
|
||||
213070643358484->>server:+PARSE name='', num_params=0, params_type=, query=
|
||||
213070643358484->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358484->>server:+DESCRIBE kind='P', name=''
|
||||
213070643358484->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358484->>server:+SYNC
|
||||
server-->>213070643358484:-PARSE COMPLETE
|
||||
server-->>213070643358484:-BIND COMPLETE
|
||||
server-->>213070643358484:-NO DATA
|
||||
server-->>213070643358484:-EMPTY QUERY RESPONSE
|
||||
server-->>213070643358484:-READY FOR QUERY type=<IDLE>
|
||||
213070643358484->>server:+DISCONNECT
|
||||
213070643358486->>server:+SSL REQUEST
|
||||
server-->>213070643358486:-SSL BACKEND ANSWER: N
|
||||
213070643358486->>server:+STARTUP MESSAGE version: 3 database=trait_store extra_float_digits=2 TimeZone=GMT client_encoding=UTF8 user=docker DateStyle=ISO
|
||||
server-->>213070643358486:-AUTHENTIFICATION REQUEST code=0 (SUCCESS)
|
||||
server-->>213070643358486:-PARAMETER STATUS name='application_name', value=''
|
||||
server-->>213070643358486:-PARAMETER STATUS name='client_encoding', value='UTF8'
|
||||
server-->>213070643358486:-PARAMETER STATUS name='DateStyle', value='ISO, MDY'
|
||||
server-->>213070643358486:-PARAMETER STATUS name='integer_datetimes', value='on'
|
||||
server-->>213070643358486:-PARAMETER STATUS name='IntervalStyle', value='postgres'
|
||||
server-->>213070643358486:-PARAMETER STATUS name='is_superuser', value='on'
|
||||
server-->>213070643358486:-PARAMETER STATUS name='server_encoding', value='UTF8'
|
||||
server-->>213070643358486:-PARAMETER STATUS name='server_version', value='13.0.0'
|
||||
server-->>213070643358486:-PARAMETER STATUS name='session_authorization', value='docker'
|
||||
server-->>213070643358486:-PARAMETER STATUS name='standard_conforming_strings', value='on'
|
||||
server-->>213070643358486:-PARAMETER STATUS name='TimeZone', value='GMT'
|
||||
server-->>213070643358486:-BACKEND KEY DATA pid=413235241, key=1302652759
|
||||
server-->>213070643358486:-READY FOR QUERY type=<IDLE>
|
||||
213070643358486->>server:+PARSE name='', num_params=0, params_type=, query=SET extra_float_digits = 3
|
||||
213070643358486->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358486->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358486->>server:+SYNC
|
||||
server-->>213070643358486:-PARSE COMPLETE
|
||||
server-->>213070643358486:-BIND COMPLETE
|
||||
server-->>213070643358486:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643358486:-READY FOR QUERY type=<IDLE>
|
||||
213070643358486->>server:+PARSE name='', num_params=0, params_type=, query=SET application_name = 'PostgreSQL JDBC Driver'
|
||||
213070643358486->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358486->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358486->>server:+SYNC
|
||||
server-->>213070643358486:-PARSE COMPLETE
|
||||
server-->>213070643358486:-BIND COMPLETE
|
||||
server-->>213070643358486:-PARAMETER STATUS name='application_name', value='PostgreSQL JDBC Driver'
|
||||
server-->>213070643358486:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643358486:-READY FOR QUERY type=<IDLE>
|
||||
213070643358486->>server:+PARSE name='', num_params=0, params_type=, query=
|
||||
213070643358486->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358486->>server:+DESCRIBE kind='P', name=''
|
||||
213070643358486->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358486->>server:+SYNC
|
||||
server-->>213070643358486:-PARSE COMPLETE
|
||||
server-->>213070643358486:-BIND COMPLETE
|
||||
server-->>213070643358486:-NO DATA
|
||||
server-->>213070643358486:-EMPTY QUERY RESPONSE
|
||||
server-->>213070643358486:-READY FOR QUERY type=<IDLE>
|
||||
213070643358486->>server:+PARSE name='', num_params=0, params_type=, query=SELECT pg_backend_pid()
|
||||
213070643358486->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358486->>server:+DESCRIBE kind='P', name=''
|
||||
213070643358486->>server:+EXECUTE name='', nb_rows=0
|
||||
213070643358486->>server:+SYNC
|
||||
server-->>213070643358486:-PARSE COMPLETE
|
||||
server-->>213070643358486:-BIND COMPLETE
|
||||
server-->>213070643358486:-ROW DESCRIPTION: num_fields=1 ---[Field 01]--- name='pg_backend_pid' type=23 type_len=4 type_mod=4294967295 relid=0 attnum=0 format=0
|
||||
server-->>213070643358486:-DATA ROW num_values=1 ---[Value 0001]--- length=9 value='413235241'
|
||||
server-->>213070643358486:-COMMAND COMPLETE command='SELECT'
|
||||
server-->>213070643358486:-READY FOR QUERY type=<IDLE>
|
||||
213070643358486->>server:+PARSE name='', num_params=0, params_type=, query=SELECT VERSION() AS version
|
||||
213070643358486->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358486->>server:+DESCRIBE kind='P', name=''
|
||||
213070643358486->>server:+EXECUTE name='', nb_rows=0
|
||||
213070643358486->>server:+SYNC
|
||||
server-->>213070643358486:-PARSE COMPLETE
|
||||
server-->>213070643358486:-BIND COMPLETE
|
||||
server-->>213070643358486:-ROW DESCRIPTION: num_fields=1 ---[Field 01]--- name='version' type=25 type_len=65535 type_mod=4294967295 relid=0 attnum=0 format=0
|
||||
server-->>213070643358486:-DATA ROW num_values=1 ---[Value 0001]--- length=27 value='PostgresSQL 13.0 (molecula)'
|
||||
server-->>213070643358486:-COMMAND COMPLETE command='SELECT'
|
||||
server-->>213070643358486:-READY FOR QUERY type=<IDLE>
|
||||
213070643358486->>server:+PARSE name='', num_params=0, params_type=, query= SELECT COUNT(*) FROM pg_type AS t0, pg_aggregate AS t1, pg_settings AS t2, pg_settings AS t3
|
||||
213070643358486->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358486->>server:+DESCRIBE kind='P', name=''
|
||||
213070643358486->>server:+EXECUTE name='', nb_rows=0
|
||||
213070643358486->>server:+SYNC
|
||||
213070643358488->>server:+SSL REQUEST
|
||||
server-->>213070643358488:-SSL BACKEND ANSWER: N
|
||||
213070643358488->>server:+STARTUP MESSAGE version: 3 database=trait_store extra_float_digits=2 TimeZone=GMT client_encoding=UTF8 user=docker DateStyle=ISO
|
||||
server-->>213070643358488:-AUTHENTIFICATION REQUEST code=0 (SUCCESS)
|
||||
server-->>213070643358488:-PARAMETER STATUS name='application_name', value=''
|
||||
server-->>213070643358488:-PARAMETER STATUS name='client_encoding', value='UTF8'
|
||||
server-->>213070643358488:-PARAMETER STATUS name='DateStyle', value='ISO, MDY'
|
||||
server-->>213070643358488:-PARAMETER STATUS name='integer_datetimes', value='on'
|
||||
server-->>213070643358488:-PARAMETER STATUS name='IntervalStyle', value='postgres'
|
||||
server-->>213070643358488:-PARAMETER STATUS name='is_superuser', value='on'
|
||||
server-->>213070643358488:-PARAMETER STATUS name='server_encoding', value='UTF8'
|
||||
server-->>213070643358488:-PARAMETER STATUS name='server_version', value='13.0.0'
|
||||
server-->>213070643358488:-PARAMETER STATUS name='session_authorization', value='docker'
|
||||
server-->>213070643358488:-PARAMETER STATUS name='standard_conforming_strings', value='on'
|
||||
server-->>213070643358488:-PARAMETER STATUS name='TimeZone', value='GMT'
|
||||
server-->>213070643358488:-BACKEND KEY DATA pid=180554708, key=1504602717
|
||||
server-->>213070643358488:-READY FOR QUERY type=<IDLE>
|
||||
213070643358488->>server:+PARSE name='', num_params=0, params_type=, query=SET extra_float_digits = 3
|
||||
213070643358488->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358488->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358488->>server:+SYNC
|
||||
server-->>213070643358488:-PARSE COMPLETE
|
||||
server-->>213070643358488:-BIND COMPLETE
|
||||
server-->>213070643358488:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643358488:-READY FOR QUERY type=<IDLE>
|
||||
213070643358488->>server:+PARSE name='', num_params=0, params_type=, query=SET application_name = 'PostgreSQL JDBC Driver'
|
||||
213070643358488->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358488->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358488->>server:+SYNC
|
||||
server-->>213070643358488:-PARSE COMPLETE
|
||||
server-->>213070643358488:-BIND COMPLETE
|
||||
server-->>213070643358488:-PARAMETER STATUS name='application_name', value='PostgreSQL JDBC Driver'
|
||||
server-->>213070643358488:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643358488:-READY FOR QUERY type=<IDLE>
|
||||
213070643358488->>server:+PARSE name='', num_params=0, params_type=, query=
|
||||
213070643358488->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358488->>server:+DESCRIBE kind='P', name=''
|
||||
213070643358488->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358488->>server:+SYNC
|
||||
server-->>213070643358488:-PARSE COMPLETE
|
||||
server-->>213070643358488:-BIND COMPLETE
|
||||
server-->>213070643358488:-NO DATA
|
||||
server-->>213070643358488:-EMPTY QUERY RESPONSE
|
||||
server-->>213070643358488:-READY FOR QUERY type=<IDLE>
|
||||
213070643358488->>server:+DISCONNECT
|
||||
213070643358490->>server:+SSL REQUEST
|
||||
server-->>213070643358490:-SSL BACKEND ANSWER: N
|
||||
213070643358490->>server:+STARTUP MESSAGE version: 3 database=trait_store extra_float_digits=2 TimeZone=GMT client_encoding=UTF8 user=docker DateStyle=ISO
|
||||
server-->>213070643358490:-AUTHENTIFICATION REQUEST code=0 (SUCCESS)
|
||||
server-->>213070643358490:-PARAMETER STATUS name='application_name', value=''
|
||||
server-->>213070643358490:-PARAMETER STATUS name='client_encoding', value='UTF8'
|
||||
server-->>213070643358490:-PARAMETER STATUS name='DateStyle', value='ISO, MDY'
|
||||
server-->>213070643358490:-PARAMETER STATUS name='integer_datetimes', value='on'
|
||||
server-->>213070643358490:-PARAMETER STATUS name='IntervalStyle', value='postgres'
|
||||
server-->>213070643358490:-PARAMETER STATUS name='is_superuser', value='on'
|
||||
server-->>213070643358490:-PARAMETER STATUS name='server_encoding', value='UTF8'
|
||||
server-->>213070643358490:-PARAMETER STATUS name='server_version', value='13.0.0'
|
||||
server-->>213070643358490:-PARAMETER STATUS name='session_authorization', value='docker'
|
||||
server-->>213070643358490:-PARAMETER STATUS name='standard_conforming_strings', value='on'
|
||||
server-->>213070643358490:-PARAMETER STATUS name='TimeZone', value='GMT'
|
||||
server-->>213070643358490:-BACKEND KEY DATA pid=1713101217, key=29850369
|
||||
server-->>213070643358490:-READY FOR QUERY type=<IDLE>
|
||||
213070643358490->>server:+PARSE name='', num_params=0, params_type=, query=SET extra_float_digits = 3
|
||||
213070643358490->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358490->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358490->>server:+SYNC
|
||||
server-->>213070643358490:-PARSE COMPLETE
|
||||
server-->>213070643358490:-BIND COMPLETE
|
||||
server-->>213070643358490:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643358490:-READY FOR QUERY type=<IDLE>
|
||||
213070643358490->>server:+PARSE name='', num_params=0, params_type=, query=SET application_name = 'PostgreSQL JDBC Driver'
|
||||
213070643358490->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358490->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358490->>server:+SYNC
|
||||
server-->>213070643358490:-PARSE COMPLETE
|
||||
server-->>213070643358490:-BIND COMPLETE
|
||||
server-->>213070643358490:-PARAMETER STATUS name='application_name', value='PostgreSQL JDBC Driver'
|
||||
server-->>213070643358490:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643358490:-READY FOR QUERY type=<IDLE>
|
||||
213070643358490->>server:+PARSE name='', num_params=0, params_type=, query=
|
||||
213070643358490->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358490->>server:+DESCRIBE kind='P', name=''
|
||||
213070643358490->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358490->>server:+SYNC
|
||||
server-->>213070643358490:-PARSE COMPLETE
|
||||
server-->>213070643358490:-BIND COMPLETE
|
||||
server-->>213070643358490:-NO DATA
|
||||
server-->>213070643358490:-EMPTY QUERY RESPONSE
|
||||
server-->>213070643358490:-READY FOR QUERY type=<IDLE>
|
||||
213070643358490->>server:+PARSE name='', num_params=0, params_type=, query=SELECT VERSION() AS version
|
||||
213070643358490->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358490->>server:+DESCRIBE kind='P', name=''
|
||||
213070643358490->>server:+EXECUTE name='', nb_rows=0
|
||||
213070643358490->>server:+SYNC
|
||||
server-->>213070643358490:-PARSE COMPLETE
|
||||
server-->>213070643358490:-BIND COMPLETE
|
||||
server-->>213070643358490:-ROW DESCRIPTION: num_fields=1 ---[Field 01]--- name='version' type=25 type_len=65535 type_mod=4294967295 relid=0 attnum=0 format=0
|
||||
server-->>213070643358490:-DATA ROW num_values=1 ---[Value 0001]--- length=27 value='PostgresSQL 13.0 (molecula)'
|
||||
server-->>213070643358490:-COMMAND COMPLETE command='SELECT'
|
||||
server-->>213070643358490:-READY FOR QUERY type=<IDLE>
|
||||
213070643358490->>server:+PARSE name='', num_params=0, params_type=, query=
|
||||
213070643358490->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358490->>server:+DESCRIBE kind='P', name=''
|
||||
213070643358490->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358490->>server:+SYNC
|
||||
server-->>213070643358490:-PARSE COMPLETE
|
||||
server-->>213070643358490:-BIND COMPLETE
|
||||
server-->>213070643358490:-NO DATA
|
||||
server-->>213070643358490:-EMPTY QUERY RESPONSE
|
||||
server-->>213070643358490:-READY FOR QUERY type=<IDLE>
|
||||
213070643358490->>server:+PARSE name='', num_params=0, params_type=, query= SELECT pid as id, query as stmt, EXTRACT(seconds from query_start - NOW()) as elapsed_time FROM pg_stat_activity WHERE usename='docker'
|
||||
213070643358490->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358490->>server:+DESCRIBE kind='P', name=''
|
||||
213070643358490->>server:+EXECUTE name='', nb_rows=0
|
||||
213070643358490->>server:+SYNC
|
||||
server-->>213070643358490:-PARSE COMPLETE
|
||||
server-->>213070643358490:-BIND COMPLETE
|
||||
server-->>213070643358490:-ROW DESCRIPTION: num_fields=3 ---[Field 01]--- name='id' type=23 type_len=4 type_mod=4294967295 relid=0 attnum=0 format=0 ---[Field 02]--- name='stmt' type=25 type_len=65535 type_mod=4294967295 relid=0 attnum=0 format=0 ---[Field 03]--- name='elapsed_time' type=701 type_len=8 type_mod=4294967295 relid=0 attnum=0 format=0
|
||||
server-->>213070643358490:-DATA ROW num_values=3 ---[Value 0001]--- length=9 value='413235241' ---[Value 0002]--- length=148 value=' SELECT COUNT(*). FROM pg_type AS t0,. pg_aggregate AS t1,. pg_settings AS t2,. pg_settings AS t3.' ---[Value 0003]--- length=11 value='1.311371547'
|
||||
server-->>213070643358490:-DATA ROW num_values=3 ---[Value 0001]--- length=10 value='1713101217' ---[Value 0002]--- length=190 value=' SELECT pid as id,. query as stmt,. EXTRACT(seconds from query_start - NOW()) as elapsed_time. FROM pg_stat_activity. WHERE usename='docker'.' ---[Value 0003]--- length=11 value='0.000169969'
|
||||
server-->>213070643358490:-COMMAND COMPLETE command='SELECT'
|
||||
server-->>213070643358490:-READY FOR QUERY type=<IDLE>
|
||||
213070643358492->>server:+SSL REQUEST
|
||||
server-->>213070643358492:-SSL BACKEND ANSWER: N
|
||||
213070643358492->>server:+STARTUP MESSAGE version: 3 database=trait_store extra_float_digits=2 TimeZone=GMT client_encoding=UTF8 user=docker DateStyle=ISO
|
||||
server-->>213070643358492:-AUTHENTIFICATION REQUEST code=0 (SUCCESS)
|
||||
server-->>213070643358492:-PARAMETER STATUS name='application_name', value=''
|
||||
server-->>213070643358492:-PARAMETER STATUS name='client_encoding', value='UTF8'
|
||||
server-->>213070643358492:-PARAMETER STATUS name='DateStyle', value='ISO, MDY'
|
||||
server-->>213070643358492:-PARAMETER STATUS name='integer_datetimes', value='on'
|
||||
server-->>213070643358492:-PARAMETER STATUS name='IntervalStyle', value='postgres'
|
||||
server-->>213070643358492:-PARAMETER STATUS name='is_superuser', value='on'
|
||||
server-->>213070643358492:-PARAMETER STATUS name='server_encoding', value='UTF8'
|
||||
server-->>213070643358492:-PARAMETER STATUS name='server_version', value='13.0.0'
|
||||
server-->>213070643358492:-PARAMETER STATUS name='session_authorization', value='docker'
|
||||
server-->>213070643358492:-PARAMETER STATUS name='standard_conforming_strings', value='on'
|
||||
server-->>213070643358492:-PARAMETER STATUS name='TimeZone', value='GMT'
|
||||
server-->>213070643358492:-BACKEND KEY DATA pid=2034361563, key=1419995666
|
||||
server-->>213070643358492:-READY FOR QUERY type=<IDLE>
|
||||
213070643358492->>server:+PARSE name='', num_params=0, params_type=, query=SET extra_float_digits = 3
|
||||
213070643358492->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358492->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358492->>server:+SYNC
|
||||
server-->>213070643358492:-PARSE COMPLETE
|
||||
server-->>213070643358492:-BIND COMPLETE
|
||||
server-->>213070643358492:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643358492:-READY FOR QUERY type=<IDLE>
|
||||
213070643358492->>server:+PARSE name='', num_params=0, params_type=, query=SET application_name = 'PostgreSQL JDBC Driver'
|
||||
213070643358492->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358492->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358492->>server:+SYNC
|
||||
server-->>213070643358492:-PARSE COMPLETE
|
||||
server-->>213070643358492:-BIND COMPLETE
|
||||
server-->>213070643358492:-PARAMETER STATUS name='application_name', value='PostgreSQL JDBC Driver'
|
||||
server-->>213070643358492:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643358492:-READY FOR QUERY type=<IDLE>
|
||||
213070643358492->>server:+PARSE name='', num_params=0, params_type=, query=
|
||||
213070643358492->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358492->>server:+DESCRIBE kind='P', name=''
|
||||
213070643358492->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358492->>server:+SYNC
|
||||
server-->>213070643358492:-PARSE COMPLETE
|
||||
server-->>213070643358492:-BIND COMPLETE
|
||||
server-->>213070643358492:-NO DATA
|
||||
server-->>213070643358492:-EMPTY QUERY RESPONSE
|
||||
server-->>213070643358492:-READY FOR QUERY type=<IDLE>
|
||||
213070643358492->>server:+DISCONNECT
|
||||
213070643358494->>server:+SSL REQUEST
|
||||
server-->>213070643358494:-SSL BACKEND ANSWER: N
|
||||
213070643358494->>server:+STARTUP MESSAGE version: 3 database=trait_store extra_float_digits=2 TimeZone=GMT client_encoding=UTF8 user=docker DateStyle=ISO
|
||||
server-->>213070643358494:-AUTHENTIFICATION REQUEST code=0 (SUCCESS)
|
||||
server-->>213070643358494:-PARAMETER STATUS name='application_name', value=''
|
||||
server-->>213070643358494:-PARAMETER STATUS name='client_encoding', value='UTF8'
|
||||
server-->>213070643358494:-PARAMETER STATUS name='DateStyle', value='ISO, MDY'
|
||||
server-->>213070643358494:-PARAMETER STATUS name='integer_datetimes', value='on'
|
||||
server-->>213070643358494:-PARAMETER STATUS name='IntervalStyle', value='postgres'
|
||||
server-->>213070643358494:-PARAMETER STATUS name='is_superuser', value='on'
|
||||
server-->>213070643358494:-PARAMETER STATUS name='server_encoding', value='UTF8'
|
||||
server-->>213070643358494:-PARAMETER STATUS name='server_version', value='13.0.0'
|
||||
server-->>213070643358494:-PARAMETER STATUS name='session_authorization', value='docker'
|
||||
server-->>213070643358494:-PARAMETER STATUS name='standard_conforming_strings', value='on'
|
||||
server-->>213070643358494:-PARAMETER STATUS name='TimeZone', value='GMT'
|
||||
server-->>213070643358494:-BACKEND KEY DATA pid=964429462, key=1673405115
|
||||
server-->>213070643358494:-READY FOR QUERY type=<IDLE>
|
||||
213070643358494->>server:+PARSE name='', num_params=0, params_type=, query=SET extra_float_digits = 3
|
||||
213070643358494->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358494->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358494->>server:+SYNC
|
||||
server-->>213070643358494:-PARSE COMPLETE
|
||||
server-->>213070643358494:-BIND COMPLETE
|
||||
server-->>213070643358494:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643358494:-READY FOR QUERY type=<IDLE>
|
||||
213070643358494->>server:+PARSE name='', num_params=0, params_type=, query=SET application_name = 'PostgreSQL JDBC Driver'
|
||||
213070643358494->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358494->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358494->>server:+SYNC
|
||||
server-->>213070643358494:-PARSE COMPLETE
|
||||
server-->>213070643358494:-BIND COMPLETE
|
||||
server-->>213070643358494:-PARAMETER STATUS name='application_name', value='PostgreSQL JDBC Driver'
|
||||
server-->>213070643358494:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643358494:-READY FOR QUERY type=<IDLE>
|
||||
213070643358494->>server:+PARSE name='', num_params=0, params_type=, query=
|
||||
213070643358494->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358494->>server:+DESCRIBE kind='P', name=''
|
||||
213070643358494->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358494->>server:+SYNC
|
||||
server-->>213070643358494:-PARSE COMPLETE
|
||||
server-->>213070643358494:-BIND COMPLETE
|
||||
server-->>213070643358494:-NO DATA
|
||||
server-->>213070643358494:-EMPTY QUERY RESPONSE
|
||||
server-->>213070643358494:-READY FOR QUERY type=<IDLE>
|
||||
213070643358494->>server:+PARSE name='', num_params=0, params_type=, query=select pg_terminate_backend(413235241)
|
||||
213070643358494->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358494->>server:+DESCRIBE kind='P', name=''
|
||||
213070643358494->>server:+EXECUTE name='', nb_rows=0
|
||||
213070643358494->>server:+SYNC
|
||||
server-->>213070643358494:-PARSE COMPLETE
|
||||
server-->>213070643358494:-BIND COMPLETE
|
||||
server-->>213070643358494:-ROW DESCRIPTION: num_fields=1 ---[Field 01]--- name='pg_terminate_backend' type=16 type_len=1 type_mod=4294967295 relid=0 attnum=0 format=0
|
||||
server-->>213070643358494:-DATA ROW num_values=1 ---[Value 0001]--- length=1 value='t'
|
||||
server-->>213070643358494:-COMMAND COMPLETE command='SELECT 1'
|
||||
server-->>213070643358486:-PARSE COMPLETE
|
||||
server-->>213070643358486:-BIND COMPLETE
|
||||
server-->>213070643358486:-ROW DESCRIPTION: num_fields=1 ---[Field 01]--- name='count' type=20 type_len=8 type_mod=4294967295 relid=0 attnum=0 format=0
|
||||
server-->>213070643358486:-ERROR RESPONSE Severity: 'FATAL' Message: 'terminating connection due to administrator command' Code: '57P01'
|
||||
server-->>213070643358494:-READY FOR QUERY type=<IDLE>
|
||||
213070643358490->>server:+DISCONNECT
|
||||
213070643358494->>server:+DISCONNECT
|
||||
213070643358496->>server:+SSL REQUEST
|
||||
server-->>213070643358496:-SSL BACKEND ANSWER: N
|
||||
213070643358496->>server:+STARTUP MESSAGE version: 3 database=trait_store extra_float_digits=2 TimeZone=GMT client_encoding=UTF8 user=docker DateStyle=ISO
|
||||
server-->>213070643358496:-AUTHENTIFICATION REQUEST code=0 (SUCCESS)
|
||||
server-->>213070643358496:-PARAMETER STATUS name='application_name', value=''
|
||||
server-->>213070643358496:-PARAMETER STATUS name='client_encoding', value='UTF8'
|
||||
server-->>213070643358496:-PARAMETER STATUS name='DateStyle', value='ISO, MDY'
|
||||
server-->>213070643358496:-PARAMETER STATUS name='integer_datetimes', value='on'
|
||||
server-->>213070643358496:-PARAMETER STATUS name='IntervalStyle', value='postgres'
|
||||
server-->>213070643358496:-PARAMETER STATUS name='is_superuser', value='on'
|
||||
server-->>213070643358496:-PARAMETER STATUS name='server_encoding', value='UTF8'
|
||||
server-->>213070643358496:-PARAMETER STATUS name='server_version', value='13.0.0'
|
||||
server-->>213070643358496:-PARAMETER STATUS name='session_authorization', value='docker'
|
||||
server-->>213070643358496:-PARAMETER STATUS name='standard_conforming_strings', value='on'
|
||||
server-->>213070643358496:-PARAMETER STATUS name='TimeZone', value='GMT'
|
||||
server-->>213070643358496:-BACKEND KEY DATA pid=841257432, key=992978867
|
||||
server-->>213070643358496:-READY FOR QUERY type=<IDLE>
|
||||
213070643358496->>server:+PARSE name='', num_params=0, params_type=, query=SET extra_float_digits = 3
|
||||
213070643358496->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358496->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358496->>server:+SYNC
|
||||
server-->>213070643358496:-PARSE COMPLETE
|
||||
server-->>213070643358496:-BIND COMPLETE
|
||||
server-->>213070643358496:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643358496:-READY FOR QUERY type=<IDLE>
|
||||
213070643358496->>server:+PARSE name='', num_params=0, params_type=, query=SET application_name = 'PostgreSQL JDBC Driver'
|
||||
213070643358496->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358496->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358496->>server:+SYNC
|
||||
server-->>213070643358496:-PARSE COMPLETE
|
||||
server-->>213070643358496:-BIND COMPLETE
|
||||
server-->>213070643358496:-PARAMETER STATUS name='application_name', value='PostgreSQL JDBC Driver'
|
||||
server-->>213070643358496:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643358496:-READY FOR QUERY type=<IDLE>
|
||||
213070643358496->>server:+PARSE name='', num_params=0, params_type=, query=
|
||||
213070643358496->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358496->>server:+DESCRIBE kind='P', name=''
|
||||
213070643358496->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358496->>server:+SYNC
|
||||
server-->>213070643358496:-PARSE COMPLETE
|
||||
server-->>213070643358496:-BIND COMPLETE
|
||||
server-->>213070643358496:-NO DATA
|
||||
server-->>213070643358496:-EMPTY QUERY RESPONSE
|
||||
server-->>213070643358496:-READY FOR QUERY type=<IDLE>
|
||||
213070643358496->>server:+DISCONNECT
|
||||
213070643358498->>server:+SSL REQUEST
|
||||
server-->>213070643358498:-SSL BACKEND ANSWER: N
|
||||
213070643358498->>server:+STARTUP MESSAGE version: 3 database=trait_store extra_float_digits=2 TimeZone=GMT client_encoding=UTF8 user=docker DateStyle=ISO
|
||||
server-->>213070643358498:-AUTHENTIFICATION REQUEST code=0 (SUCCESS)
|
||||
server-->>213070643358498:-PARAMETER STATUS name='application_name', value=''
|
||||
server-->>213070643358498:-PARAMETER STATUS name='client_encoding', value='UTF8'
|
||||
server-->>213070643358498:-PARAMETER STATUS name='DateStyle', value='ISO, MDY'
|
||||
server-->>213070643358498:-PARAMETER STATUS name='integer_datetimes', value='on'
|
||||
server-->>213070643358498:-PARAMETER STATUS name='IntervalStyle', value='postgres'
|
||||
server-->>213070643358498:-PARAMETER STATUS name='is_superuser', value='on'
|
||||
server-->>213070643358498:-PARAMETER STATUS name='server_encoding', value='UTF8'
|
||||
server-->>213070643358498:-PARAMETER STATUS name='server_version', value='13.0.0'
|
||||
server-->>213070643358498:-PARAMETER STATUS name='session_authorization', value='docker'
|
||||
server-->>213070643358498:-PARAMETER STATUS name='standard_conforming_strings', value='on'
|
||||
server-->>213070643358498:-PARAMETER STATUS name='TimeZone', value='GMT'
|
||||
server-->>213070643358498:-BACKEND KEY DATA pid=645931978, key=579078180
|
||||
server-->>213070643358498:-READY FOR QUERY type=<IDLE>
|
||||
213070643358498->>server:+PARSE name='', num_params=0, params_type=, query=SET extra_float_digits = 3
|
||||
213070643358498->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358498->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358498->>server:+SYNC
|
||||
server-->>213070643358498:-PARSE COMPLETE
|
||||
server-->>213070643358498:-BIND COMPLETE
|
||||
server-->>213070643358498:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643358498:-READY FOR QUERY type=<IDLE>
|
||||
213070643358498->>server:+PARSE name='', num_params=0, params_type=, query=SET application_name = 'PostgreSQL JDBC Driver'
|
||||
213070643358498->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358498->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358498->>server:+SYNC
|
||||
server-->>213070643358498:-PARSE COMPLETE
|
||||
server-->>213070643358498:-BIND COMPLETE
|
||||
server-->>213070643358498:-PARAMETER STATUS name='application_name', value='PostgreSQL JDBC Driver'
|
||||
server-->>213070643358498:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643358498:-READY FOR QUERY type=<IDLE>
|
||||
213070643358498->>server:+PARSE name='', num_params=0, params_type=, query=
|
||||
213070643358498->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358498->>server:+DESCRIBE kind='P', name=''
|
||||
213070643358498->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358498->>server:+SYNC
|
||||
server-->>213070643358498:-PARSE COMPLETE
|
||||
server-->>213070643358498:-BIND COMPLETE
|
||||
server-->>213070643358498:-NO DATA
|
||||
server-->>213070643358498:-EMPTY QUERY RESPONSE
|
||||
server-->>213070643358498:-READY FOR QUERY type=<IDLE>
|
||||
213070643358498->>server:+PARSE name='', num_params=0, params_type=, query=SELECT 1
|
||||
213070643358498->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358498->>server:+DESCRIBE kind='P', name=''
|
||||
213070643358498->>server:+EXECUTE name='', nb_rows=0
|
||||
213070643358498->>server:+SYNC
|
||||
server-->>213070643358498:-PARSE COMPLETE
|
||||
server-->>213070643358498:-BIND COMPLETE
|
||||
server-->>213070643358498:-ROW DESCRIPTION: num_fields=1 ---[Field 01]--- name='?column?' type=23 type_len=4 type_mod=4294967295 relid=0 attnum=0 format=0
|
||||
server-->>213070643358498:-DATA ROW num_values=1 ---[Value 0001]--- length=1 value='1'
|
||||
server-->>213070643358498:-COMMAND COMPLETE command='SELECT'
|
||||
server-->>213070643358498:-READY FOR QUERY type=<IDLE>
|
||||
213070643358498->>server:+DISCONNECT
|
||||
213070643358500->>server:+SSL REQUEST
|
||||
server-->>213070643358500:-SSL BACKEND ANSWER: N
|
||||
213070643358500->>server:+STARTUP MESSAGE version: 3 database=trait_store extra_float_digits=2 TimeZone=GMT client_encoding=UTF8 user=docker DateStyle=ISO
|
||||
server-->>213070643358500:-AUTHENTIFICATION REQUEST code=0 (SUCCESS)
|
||||
server-->>213070643358500:-PARAMETER STATUS name='application_name', value=''
|
||||
server-->>213070643358500:-PARAMETER STATUS name='client_encoding', value='UTF8'
|
||||
server-->>213070643358500:-PARAMETER STATUS name='DateStyle', value='ISO, MDY'
|
||||
server-->>213070643358500:-PARAMETER STATUS name='integer_datetimes', value='on'
|
||||
server-->>213070643358500:-PARAMETER STATUS name='IntervalStyle', value='postgres'
|
||||
server-->>213070643358500:-PARAMETER STATUS name='is_superuser', value='on'
|
||||
server-->>213070643358500:-PARAMETER STATUS name='server_encoding', value='UTF8'
|
||||
server-->>213070643358500:-PARAMETER STATUS name='server_version', value='13.0.0'
|
||||
server-->>213070643358500:-PARAMETER STATUS name='session_authorization', value='docker'
|
||||
server-->>213070643358500:-PARAMETER STATUS name='standard_conforming_strings', value='on'
|
||||
server-->>213070643358500:-PARAMETER STATUS name='TimeZone', value='GMT'
|
||||
server-->>213070643358500:-BACKEND KEY DATA pid=1632401438, key=1341645778
|
||||
server-->>213070643358500:-READY FOR QUERY type=<IDLE>
|
||||
213070643358500->>server:+PARSE name='', num_params=0, params_type=, query=SET extra_float_digits = 3
|
||||
213070643358500->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358500->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358500->>server:+SYNC
|
||||
server-->>213070643358500:-PARSE COMPLETE
|
||||
server-->>213070643358500:-BIND COMPLETE
|
||||
server-->>213070643358500:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643358500:-READY FOR QUERY type=<IDLE>
|
||||
213070643358500->>server:+PARSE name='', num_params=0, params_type=, query=SET application_name = 'PostgreSQL JDBC Driver'
|
||||
213070643358500->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358500->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358500->>server:+SYNC
|
||||
server-->>213070643358500:-PARSE COMPLETE
|
||||
server-->>213070643358500:-BIND COMPLETE
|
||||
server-->>213070643358500:-PARAMETER STATUS name='application_name', value='PostgreSQL JDBC Driver'
|
||||
server-->>213070643358500:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643358500:-READY FOR QUERY type=<IDLE>
|
||||
213070643358500->>server:+PARSE name='', num_params=0, params_type=, query=
|
||||
213070643358500->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358500->>server:+DESCRIBE kind='P', name=''
|
||||
213070643358500->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358500->>server:+SYNC
|
||||
server-->>213070643358500:-PARSE COMPLETE
|
||||
server-->>213070643358500:-BIND COMPLETE
|
||||
server-->>213070643358500:-NO DATA
|
||||
server-->>213070643358500:-EMPTY QUERY RESPONSE
|
||||
server-->>213070643358500:-READY FOR QUERY type=<IDLE>
|
||||
213070643358500->>server:+DISCONNECT
|
||||
213070643358502->>server:+SSL REQUEST
|
||||
server-->>213070643358502:-SSL BACKEND ANSWER: N
|
||||
213070643358502->>server:+STARTUP MESSAGE version: 3 database=trait_store extra_float_digits=2 TimeZone=GMT client_encoding=UTF8 user=docker DateStyle=ISO
|
||||
server-->>213070643358502:-AUTHENTIFICATION REQUEST code=0 (SUCCESS)
|
||||
server-->>213070643358502:-PARAMETER STATUS name='application_name', value=''
|
||||
server-->>213070643358502:-PARAMETER STATUS name='client_encoding', value='UTF8'
|
||||
server-->>213070643358502:-PARAMETER STATUS name='DateStyle', value='ISO, MDY'
|
||||
server-->>213070643358502:-PARAMETER STATUS name='integer_datetimes', value='on'
|
||||
server-->>213070643358502:-PARAMETER STATUS name='IntervalStyle', value='postgres'
|
||||
server-->>213070643358502:-PARAMETER STATUS name='is_superuser', value='on'
|
||||
server-->>213070643358502:-PARAMETER STATUS name='server_encoding', value='UTF8'
|
||||
server-->>213070643358502:-PARAMETER STATUS name='server_version', value='13.0.0'
|
||||
server-->>213070643358502:-PARAMETER STATUS name='session_authorization', value='docker'
|
||||
server-->>213070643358502:-PARAMETER STATUS name='standard_conforming_strings', value='on'
|
||||
server-->>213070643358502:-PARAMETER STATUS name='TimeZone', value='GMT'
|
||||
server-->>213070643358502:-BACKEND KEY DATA pid=232586748, key=226557416
|
||||
server-->>213070643358502:-READY FOR QUERY type=<IDLE>
|
||||
213070643358502->>server:+PARSE name='', num_params=0, params_type=, query=SET extra_float_digits = 3
|
||||
213070643358502->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358502->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358502->>server:+SYNC
|
||||
server-->>213070643358502:-PARSE COMPLETE
|
||||
server-->>213070643358502:-BIND COMPLETE
|
||||
server-->>213070643358502:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643358502:-READY FOR QUERY type=<IDLE>
|
||||
213070643358502->>server:+PARSE name='', num_params=0, params_type=, query=SET application_name = 'PostgreSQL JDBC Driver'
|
||||
213070643358502->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358502->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358502->>server:+SYNC
|
||||
server-->>213070643358502:-PARSE COMPLETE
|
||||
server-->>213070643358502:-BIND COMPLETE
|
||||
server-->>213070643358502:-PARAMETER STATUS name='application_name', value='PostgreSQL JDBC Driver'
|
||||
server-->>213070643358502:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643358502:-READY FOR QUERY type=<IDLE>
|
||||
213070643358502->>server:+PARSE name='', num_params=0, params_type=, query=
|
||||
213070643358502->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358502->>server:+DESCRIBE kind='P', name=''
|
||||
213070643358502->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643358502->>server:+SYNC
|
||||
server-->>213070643358502:-PARSE COMPLETE
|
||||
server-->>213070643358502:-BIND COMPLETE
|
||||
server-->>213070643358502:-NO DATA
|
||||
server-->>213070643358502:-EMPTY QUERY RESPONSE
|
||||
server-->>213070643358502:-READY FOR QUERY type=<IDLE>
|
||||
213070643358502->>server:+PARSE name='', num_params=0, params_type=, query=SELECT VERSION() AS version
|
||||
213070643358502->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643358502->>server:+DESCRIBE kind='P', name=''
|
||||
213070643358502->>server:+EXECUTE name='', nb_rows=0
|
||||
213070643358502->>server:+SYNC
|
||||
server-->>213070643358502:-PARSE COMPLETE
|
||||
server-->>213070643358502:-BIND COMPLETE
|
||||
server-->>213070643358502:-ROW DESCRIPTION: num_fields=1 ---[Field 01]--- name='version' type=25 type_len=65535 type_mod=4294967295 relid=0 attnum=0 format=0
|
||||
server-->>213070643358502:-DATA ROW num_values=1 ---[Value 0001]--- length=27 value='PostgresSQL 13.0 (molecula)'
|
||||
server-->>213070643358502:-COMMAND COMPLETE command='SELECT'
|
||||
server-->>213070643358502:-READY FOR QUERY type=<IDLE>
|
||||
213070643358502->>server:+DISCONNECT
|
||||
```
|
||||
|
|
@ -1,677 +0,0 @@
|
|||
```mermaid
|
||||
sequenceDiagram
|
||||
|
||||
participant 213070643360888 as c0
|
||||
participant 213070643360892 as c1
|
||||
participant 213070643360896 as c2
|
||||
participant 213070643360900 as c3
|
||||
participant 213070643360904 as c4
|
||||
participant 213070643360908 as c5
|
||||
participant 213070643360912 as c6
|
||||
participant 213070643360916 as c7
|
||||
participant 213070643360920 as c8
|
||||
participant 213070643360924 as c9
|
||||
participant 213070643360928 as c10
|
||||
participant 213070643360932 as c11
|
||||
213070643360888->>server:+SSL REQUEST
|
||||
server-->>213070643360888:-SSL BACKEND ANSWER: N
|
||||
213070643360888->>server:+STARTUP MESSAGE version: 3 database=trait_store extra_float_digits=2 TimeZone=GMT client_encoding=UTF8 user=docker DateStyle=ISO
|
||||
server-->>213070643360888:-AUTHENTIFICATION REQUEST code=5 (MD5 salt='affbc4bf')
|
||||
213070643360888->>server:+PASSWORD MESSAGE password=md5de1ae46649b137ee14e14d8fd5fc6cb6
|
||||
server-->>213070643360888:-AUTHENTIFICATION REQUEST code=0 (SUCCESS)
|
||||
server-->>213070643360888:-PARAMETER STATUS name='application_name', value=''
|
||||
server-->>213070643360888:-PARAMETER STATUS name='client_encoding', value='UTF8'
|
||||
server-->>213070643360888:-PARAMETER STATUS name='DateStyle', value='ISO, MDY'
|
||||
server-->>213070643360888:-PARAMETER STATUS name='integer_datetimes', value='on'
|
||||
server-->>213070643360888:-PARAMETER STATUS name='IntervalStyle', value='postgres'
|
||||
server-->>213070643360888:-PARAMETER STATUS name='is_superuser', value='on'
|
||||
server-->>213070643360888:-PARAMETER STATUS name='server_encoding', value='UTF8'
|
||||
server-->>213070643360888:-PARAMETER STATUS name='server_version', value='13.3 (Debian 13.3-1.pgdg100+1)'
|
||||
server-->>213070643360888:-PARAMETER STATUS name='session_authorization', value='docker'
|
||||
server-->>213070643360888:-PARAMETER STATUS name='standard_conforming_strings', value='on'
|
||||
server-->>213070643360888:-PARAMETER STATUS name='TimeZone', value='GMT'
|
||||
server-->>213070643360888:-BACKEND KEY DATA pid=97, key=2120775944
|
||||
server-->>213070643360888:-READY FOR QUERY type=<IDLE>
|
||||
213070643360888->>server:+PARSE name='', num_params=0, params_type=, query=SET extra_float_digits = 3
|
||||
213070643360888->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360888->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360888->>server:+SYNC
|
||||
server-->>213070643360888:-PARSE COMPLETE
|
||||
server-->>213070643360888:-BIND COMPLETE
|
||||
server-->>213070643360888:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643360888:-READY FOR QUERY type=<IDLE>
|
||||
213070643360888->>server:+PARSE name='', num_params=0, params_type=, query=SET application_name = 'PostgreSQL JDBC Driver'
|
||||
213070643360888->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360888->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360888->>server:+SYNC
|
||||
server-->>213070643360888:-PARSE COMPLETE
|
||||
server-->>213070643360888:-BIND COMPLETE
|
||||
server-->>213070643360888:-PARAMETER STATUS name='application_name', value='PostgreSQL JDBC Driver'
|
||||
server-->>213070643360888:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643360888:-READY FOR QUERY type=<IDLE>
|
||||
213070643360888->>server:+PARSE name='', num_params=0, params_type=, query=
|
||||
213070643360888->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360888->>server:+DESCRIBE kind='P', name=''
|
||||
213070643360888->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360888->>server:+SYNC
|
||||
server-->>213070643360888:-PARSE COMPLETE
|
||||
server-->>213070643360888:-BIND COMPLETE
|
||||
server-->>213070643360888:-NO DATA
|
||||
server-->>213070643360888:-EMPTY QUERY RESPONSE
|
||||
server-->>213070643360888:-READY FOR QUERY type=<IDLE>
|
||||
213070643360888->>server:+DISCONNECT
|
||||
213070643360892->>server:+SSL REQUEST
|
||||
server-->>213070643360892:-SSL BACKEND ANSWER: N
|
||||
213070643360892->>server:+STARTUP MESSAGE version: 3 database=trait_store extra_float_digits=2 TimeZone=GMT client_encoding=UTF8 user=docker DateStyle=ISO
|
||||
server-->>213070643360892:-AUTHENTIFICATION REQUEST code=5 (MD5 salt='0b76ce86')
|
||||
213070643360892->>server:+PASSWORD MESSAGE password=md5b82e4b6283fe5694c0199ca058378bb8
|
||||
server-->>213070643360892:-AUTHENTIFICATION REQUEST code=0 (SUCCESS)
|
||||
server-->>213070643360892:-PARAMETER STATUS name='application_name', value=''
|
||||
server-->>213070643360892:-PARAMETER STATUS name='client_encoding', value='UTF8'
|
||||
server-->>213070643360892:-PARAMETER STATUS name='DateStyle', value='ISO, MDY'
|
||||
server-->>213070643360892:-PARAMETER STATUS name='integer_datetimes', value='on'
|
||||
server-->>213070643360892:-PARAMETER STATUS name='IntervalStyle', value='postgres'
|
||||
server-->>213070643360892:-PARAMETER STATUS name='is_superuser', value='on'
|
||||
server-->>213070643360892:-PARAMETER STATUS name='server_encoding', value='UTF8'
|
||||
server-->>213070643360892:-PARAMETER STATUS name='server_version', value='13.3 (Debian 13.3-1.pgdg100+1)'
|
||||
server-->>213070643360892:-PARAMETER STATUS name='session_authorization', value='docker'
|
||||
server-->>213070643360892:-PARAMETER STATUS name='standard_conforming_strings', value='on'
|
||||
server-->>213070643360892:-PARAMETER STATUS name='TimeZone', value='GMT'
|
||||
server-->>213070643360892:-BACKEND KEY DATA pid=98, key=1329847468
|
||||
server-->>213070643360892:-READY FOR QUERY type=<IDLE>
|
||||
213070643360892->>server:+PARSE name='', num_params=0, params_type=, query=SET extra_float_digits = 3
|
||||
213070643360892->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360892->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360892->>server:+SYNC
|
||||
server-->>213070643360892:-PARSE COMPLETE
|
||||
server-->>213070643360892:-BIND COMPLETE
|
||||
server-->>213070643360892:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643360892:-READY FOR QUERY type=<IDLE>
|
||||
213070643360892->>server:+PARSE name='', num_params=0, params_type=, query=SET application_name = 'PostgreSQL JDBC Driver'
|
||||
213070643360892->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360892->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360892->>server:+SYNC
|
||||
server-->>213070643360892:-PARSE COMPLETE
|
||||
server-->>213070643360892:-BIND COMPLETE
|
||||
server-->>213070643360892:-PARAMETER STATUS name='application_name', value='PostgreSQL JDBC Driver'
|
||||
server-->>213070643360892:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643360892:-READY FOR QUERY type=<IDLE>
|
||||
213070643360892->>server:+PARSE name='', num_params=0, params_type=, query=
|
||||
213070643360892->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360892->>server:+DESCRIBE kind='P', name=''
|
||||
213070643360892->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360892->>server:+SYNC
|
||||
server-->>213070643360892:-PARSE COMPLETE
|
||||
server-->>213070643360892:-BIND COMPLETE
|
||||
server-->>213070643360892:-NO DATA
|
||||
server-->>213070643360892:-EMPTY QUERY RESPONSE
|
||||
server-->>213070643360892:-READY FOR QUERY type=<IDLE>
|
||||
213070643360892->>server:+DISCONNECT
|
||||
213070643360896->>server:+SSL REQUEST
|
||||
server-->>213070643360896:-SSL BACKEND ANSWER: N
|
||||
213070643360896->>server:+STARTUP MESSAGE version: 3 database=trait_store extra_float_digits=2 TimeZone=GMT client_encoding=UTF8 user=docker DateStyle=ISO
|
||||
server-->>213070643360896:-AUTHENTIFICATION REQUEST code=5 (MD5 salt='80e88231')
|
||||
213070643360896->>server:+PASSWORD MESSAGE password=md59023db05ad8c94976359641cf0ada45a
|
||||
server-->>213070643360896:-AUTHENTIFICATION REQUEST code=0 (SUCCESS)
|
||||
server-->>213070643360896:-PARAMETER STATUS name='application_name', value=''
|
||||
server-->>213070643360896:-PARAMETER STATUS name='client_encoding', value='UTF8'
|
||||
server-->>213070643360896:-PARAMETER STATUS name='DateStyle', value='ISO, MDY'
|
||||
server-->>213070643360896:-PARAMETER STATUS name='integer_datetimes', value='on'
|
||||
server-->>213070643360896:-PARAMETER STATUS name='IntervalStyle', value='postgres'
|
||||
server-->>213070643360896:-PARAMETER STATUS name='is_superuser', value='on'
|
||||
server-->>213070643360896:-PARAMETER STATUS name='server_encoding', value='UTF8'
|
||||
server-->>213070643360896:-PARAMETER STATUS name='server_version', value='13.3 (Debian 13.3-1.pgdg100+1)'
|
||||
server-->>213070643360896:-PARAMETER STATUS name='session_authorization', value='docker'
|
||||
server-->>213070643360896:-PARAMETER STATUS name='standard_conforming_strings', value='on'
|
||||
server-->>213070643360896:-PARAMETER STATUS name='TimeZone', value='GMT'
|
||||
server-->>213070643360896:-BACKEND KEY DATA pid=99, key=1084480878
|
||||
server-->>213070643360896:-READY FOR QUERY type=<IDLE>
|
||||
213070643360896->>server:+PARSE name='', num_params=0, params_type=, query=SET extra_float_digits = 3
|
||||
213070643360896->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360896->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360896->>server:+SYNC
|
||||
server-->>213070643360896:-PARSE COMPLETE
|
||||
server-->>213070643360896:-BIND COMPLETE
|
||||
server-->>213070643360896:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643360896:-READY FOR QUERY type=<IDLE>
|
||||
213070643360896->>server:+PARSE name='', num_params=0, params_type=, query=SET application_name = 'PostgreSQL JDBC Driver'
|
||||
213070643360896->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360896->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360896->>server:+SYNC
|
||||
server-->>213070643360896:-PARSE COMPLETE
|
||||
server-->>213070643360896:-BIND COMPLETE
|
||||
server-->>213070643360896:-PARAMETER STATUS name='application_name', value='PostgreSQL JDBC Driver'
|
||||
server-->>213070643360896:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643360896:-READY FOR QUERY type=<IDLE>
|
||||
213070643360896->>server:+PARSE name='', num_params=0, params_type=, query=
|
||||
213070643360896->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360896->>server:+DESCRIBE kind='P', name=''
|
||||
213070643360896->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360896->>server:+SYNC
|
||||
server-->>213070643360896:-PARSE COMPLETE
|
||||
server-->>213070643360896:-BIND COMPLETE
|
||||
server-->>213070643360896:-NO DATA
|
||||
server-->>213070643360896:-EMPTY QUERY RESPONSE
|
||||
server-->>213070643360896:-READY FOR QUERY type=<IDLE>
|
||||
213070643360896->>server:+DISCONNECT
|
||||
213070643360900->>server:+SSL REQUEST
|
||||
server-->>213070643360900:-SSL BACKEND ANSWER: N
|
||||
213070643360900->>server:+STARTUP MESSAGE version: 3 database=trait_store extra_float_digits=2 TimeZone=GMT client_encoding=UTF8 user=docker DateStyle=ISO
|
||||
server-->>213070643360900:-AUTHENTIFICATION REQUEST code=5 (MD5 salt='68a07783')
|
||||
213070643360900->>server:+PASSWORD MESSAGE password=md5136c8b8ec47347f93827ec5d7023199c
|
||||
server-->>213070643360900:-AUTHENTIFICATION REQUEST code=0 (SUCCESS)
|
||||
server-->>213070643360900:-PARAMETER STATUS name='application_name', value=''
|
||||
server-->>213070643360900:-PARAMETER STATUS name='client_encoding', value='UTF8'
|
||||
server-->>213070643360900:-PARAMETER STATUS name='DateStyle', value='ISO, MDY'
|
||||
server-->>213070643360900:-PARAMETER STATUS name='integer_datetimes', value='on'
|
||||
server-->>213070643360900:-PARAMETER STATUS name='IntervalStyle', value='postgres'
|
||||
server-->>213070643360900:-PARAMETER STATUS name='is_superuser', value='on'
|
||||
server-->>213070643360900:-PARAMETER STATUS name='server_encoding', value='UTF8'
|
||||
server-->>213070643360900:-PARAMETER STATUS name='server_version', value='13.3 (Debian 13.3-1.pgdg100+1)'
|
||||
server-->>213070643360900:-PARAMETER STATUS name='session_authorization', value='docker'
|
||||
server-->>213070643360900:-PARAMETER STATUS name='standard_conforming_strings', value='on'
|
||||
server-->>213070643360900:-PARAMETER STATUS name='TimeZone', value='GMT'
|
||||
server-->>213070643360900:-BACKEND KEY DATA pid=100, key=594556468
|
||||
server-->>213070643360900:-READY FOR QUERY type=<IDLE>
|
||||
213070643360900->>server:+PARSE name='', num_params=0, params_type=, query=SET extra_float_digits = 3
|
||||
213070643360900->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360900->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360900->>server:+SYNC
|
||||
server-->>213070643360900:-PARSE COMPLETE
|
||||
server-->>213070643360900:-BIND COMPLETE
|
||||
server-->>213070643360900:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643360900:-READY FOR QUERY type=<IDLE>
|
||||
213070643360900->>server:+PARSE name='', num_params=0, params_type=, query=SET application_name = 'PostgreSQL JDBC Driver'
|
||||
213070643360900->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360900->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360900->>server:+SYNC
|
||||
server-->>213070643360900:-PARSE COMPLETE
|
||||
server-->>213070643360900:-BIND COMPLETE
|
||||
server-->>213070643360900:-PARAMETER STATUS name='application_name', value='PostgreSQL JDBC Driver'
|
||||
server-->>213070643360900:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643360900:-READY FOR QUERY type=<IDLE>
|
||||
213070643360900->>server:+PARSE name='', num_params=0, params_type=, query=
|
||||
213070643360900->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360900->>server:+DESCRIBE kind='P', name=''
|
||||
213070643360900->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360900->>server:+SYNC
|
||||
server-->>213070643360900:-PARSE COMPLETE
|
||||
server-->>213070643360900:-BIND COMPLETE
|
||||
server-->>213070643360900:-NO DATA
|
||||
server-->>213070643360900:-EMPTY QUERY RESPONSE
|
||||
server-->>213070643360900:-READY FOR QUERY type=<IDLE>
|
||||
213070643360900->>server:+PARSE name='', num_params=0, params_type=, query=SELECT pg_backend_pid()
|
||||
213070643360900->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360900->>server:+DESCRIBE kind='P', name=''
|
||||
213070643360900->>server:+EXECUTE name='', nb_rows=0
|
||||
213070643360900->>server:+SYNC
|
||||
server-->>213070643360900:-PARSE COMPLETE
|
||||
server-->>213070643360900:-BIND COMPLETE
|
||||
server-->>213070643360900:-ROW DESCRIPTION: num_fields=1 ---[Field 01]--- name='pg_backend_pid' type=23 type_len=4 type_mod=4294967295 relid=0 attnum=0 format=0
|
||||
server-->>213070643360900:-DATA ROW num_values=1 ---[Value 0001]--- length=3 value='100'
|
||||
server-->>213070643360900:-COMMAND COMPLETE command='SELECT 1'
|
||||
server-->>213070643360900:-READY FOR QUERY type=<IDLE>
|
||||
213070643360900->>server:+PARSE name='', num_params=0, params_type=, query=SELECT VERSION() AS version
|
||||
213070643360900->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360900->>server:+DESCRIBE kind='P', name=''
|
||||
213070643360900->>server:+EXECUTE name='', nb_rows=0
|
||||
213070643360900->>server:+SYNC
|
||||
server-->>213070643360900:-PARSE COMPLETE
|
||||
server-->>213070643360900:-BIND COMPLETE
|
||||
server-->>213070643360900:-ROW DESCRIPTION: num_fields=1 ---[Field 01]--- name='version' type=25 type_len=65535 type_mod=4294967295 relid=0 attnum=0 format=0
|
||||
server-->>213070643360900:-DATA ROW num_values=1 ---[Value 0001]--- length=112 value='PostgreSQL 13.3 (Debian 13.3-1.pgdg100+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit'
|
||||
server-->>213070643360900:-COMMAND COMPLETE command='SELECT 1'
|
||||
server-->>213070643360900:-READY FOR QUERY type=<IDLE>
|
||||
213070643360900->>server:+PARSE name='', num_params=0, params_type=, query= SELECT COUNT(*) FROM pg_type AS t0, pg_aggregate AS t1, pg_settings AS t2, pg_settings AS t3
|
||||
213070643360900->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360900->>server:+DESCRIBE kind='P', name=''
|
||||
213070643360900->>server:+EXECUTE name='', nb_rows=0
|
||||
213070643360900->>server:+SYNC
|
||||
213070643360904->>server:+SSL REQUEST
|
||||
server-->>213070643360904:-SSL BACKEND ANSWER: N
|
||||
213070643360904->>server:+STARTUP MESSAGE version: 3 database=trait_store extra_float_digits=2 TimeZone=GMT client_encoding=UTF8 user=docker DateStyle=ISO
|
||||
server-->>213070643360904:-AUTHENTIFICATION REQUEST code=5 (MD5 salt='a03ee692')
|
||||
213070643360904->>server:+PASSWORD MESSAGE password=md5cfedb52ae0cafa87a4f1066df3ba6802
|
||||
server-->>213070643360904:-AUTHENTIFICATION REQUEST code=0 (SUCCESS)
|
||||
server-->>213070643360904:-PARAMETER STATUS name='application_name', value=''
|
||||
server-->>213070643360904:-PARAMETER STATUS name='client_encoding', value='UTF8'
|
||||
server-->>213070643360904:-PARAMETER STATUS name='DateStyle', value='ISO, MDY'
|
||||
server-->>213070643360904:-PARAMETER STATUS name='integer_datetimes', value='on'
|
||||
server-->>213070643360904:-PARAMETER STATUS name='IntervalStyle', value='postgres'
|
||||
server-->>213070643360904:-PARAMETER STATUS name='is_superuser', value='on'
|
||||
server-->>213070643360904:-PARAMETER STATUS name='server_encoding', value='UTF8'
|
||||
server-->>213070643360904:-PARAMETER STATUS name='server_version', value='13.3 (Debian 13.3-1.pgdg100+1)'
|
||||
server-->>213070643360904:-PARAMETER STATUS name='session_authorization', value='docker'
|
||||
server-->>213070643360904:-PARAMETER STATUS name='standard_conforming_strings', value='on'
|
||||
server-->>213070643360904:-PARAMETER STATUS name='TimeZone', value='GMT'
|
||||
server-->>213070643360904:-BACKEND KEY DATA pid=101, key=2854304053
|
||||
server-->>213070643360904:-READY FOR QUERY type=<IDLE>
|
||||
213070643360904->>server:+PARSE name='', num_params=0, params_type=, query=SET extra_float_digits = 3
|
||||
213070643360904->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360904->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360904->>server:+SYNC
|
||||
server-->>213070643360904:-PARSE COMPLETE
|
||||
server-->>213070643360904:-BIND COMPLETE
|
||||
server-->>213070643360904:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643360904:-READY FOR QUERY type=<IDLE>
|
||||
213070643360904->>server:+PARSE name='', num_params=0, params_type=, query=SET application_name = 'PostgreSQL JDBC Driver'
|
||||
213070643360904->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360904->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360904->>server:+SYNC
|
||||
server-->>213070643360904:-PARSE COMPLETE
|
||||
server-->>213070643360904:-BIND COMPLETE
|
||||
server-->>213070643360904:-PARAMETER STATUS name='application_name', value='PostgreSQL JDBC Driver'
|
||||
server-->>213070643360904:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643360904:-READY FOR QUERY type=<IDLE>
|
||||
213070643360904->>server:+PARSE name='', num_params=0, params_type=, query=
|
||||
213070643360904->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360904->>server:+DESCRIBE kind='P', name=''
|
||||
213070643360904->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360904->>server:+SYNC
|
||||
server-->>213070643360904:-PARSE COMPLETE
|
||||
server-->>213070643360904:-BIND COMPLETE
|
||||
server-->>213070643360904:-NO DATA
|
||||
server-->>213070643360904:-EMPTY QUERY RESPONSE
|
||||
server-->>213070643360904:-READY FOR QUERY type=<IDLE>
|
||||
213070643360904->>server:+DISCONNECT
|
||||
213070643360908->>server:+SSL REQUEST
|
||||
server-->>213070643360908:-SSL BACKEND ANSWER: N
|
||||
213070643360908->>server:+STARTUP MESSAGE version: 3 database=trait_store extra_float_digits=2 TimeZone=GMT client_encoding=UTF8 user=docker DateStyle=ISO
|
||||
server-->>213070643360908:-AUTHENTIFICATION REQUEST code=5 (MD5 salt='be3afb9d')
|
||||
213070643360908->>server:+PASSWORD MESSAGE password=md57b5cfa30f89fc7733c814addcd548c03
|
||||
server-->>213070643360908:-AUTHENTIFICATION REQUEST code=0 (SUCCESS)
|
||||
server-->>213070643360908:-PARAMETER STATUS name='application_name', value=''
|
||||
server-->>213070643360908:-PARAMETER STATUS name='client_encoding', value='UTF8'
|
||||
server-->>213070643360908:-PARAMETER STATUS name='DateStyle', value='ISO, MDY'
|
||||
server-->>213070643360908:-PARAMETER STATUS name='integer_datetimes', value='on'
|
||||
server-->>213070643360908:-PARAMETER STATUS name='IntervalStyle', value='postgres'
|
||||
server-->>213070643360908:-PARAMETER STATUS name='is_superuser', value='on'
|
||||
server-->>213070643360908:-PARAMETER STATUS name='server_encoding', value='UTF8'
|
||||
server-->>213070643360908:-PARAMETER STATUS name='server_version', value='13.3 (Debian 13.3-1.pgdg100+1)'
|
||||
server-->>213070643360908:-PARAMETER STATUS name='session_authorization', value='docker'
|
||||
server-->>213070643360908:-PARAMETER STATUS name='standard_conforming_strings', value='on'
|
||||
server-->>213070643360908:-PARAMETER STATUS name='TimeZone', value='GMT'
|
||||
server-->>213070643360908:-BACKEND KEY DATA pid=102, key=3089396074
|
||||
server-->>213070643360908:-READY FOR QUERY type=<IDLE>
|
||||
213070643360908->>server:+PARSE name='', num_params=0, params_type=, query=SET extra_float_digits = 3
|
||||
213070643360908->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360908->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360908->>server:+SYNC
|
||||
server-->>213070643360908:-PARSE COMPLETE
|
||||
server-->>213070643360908:-BIND COMPLETE
|
||||
server-->>213070643360908:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643360908:-READY FOR QUERY type=<IDLE>
|
||||
213070643360908->>server:+PARSE name='', num_params=0, params_type=, query=SET application_name = 'PostgreSQL JDBC Driver'
|
||||
213070643360908->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360908->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360908->>server:+SYNC
|
||||
server-->>213070643360908:-PARSE COMPLETE
|
||||
server-->>213070643360908:-BIND COMPLETE
|
||||
server-->>213070643360908:-PARAMETER STATUS name='application_name', value='PostgreSQL JDBC Driver'
|
||||
server-->>213070643360908:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643360908:-READY FOR QUERY type=<IDLE>
|
||||
213070643360908->>server:+PARSE name='', num_params=0, params_type=, query=
|
||||
213070643360908->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360908->>server:+DESCRIBE kind='P', name=''
|
||||
213070643360908->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360908->>server:+SYNC
|
||||
server-->>213070643360908:-PARSE COMPLETE
|
||||
server-->>213070643360908:-BIND COMPLETE
|
||||
server-->>213070643360908:-NO DATA
|
||||
server-->>213070643360908:-EMPTY QUERY RESPONSE
|
||||
server-->>213070643360908:-READY FOR QUERY type=<IDLE>
|
||||
213070643360908->>server:+PARSE name='', num_params=0, params_type=, query=SELECT VERSION() AS version
|
||||
213070643360908->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360908->>server:+DESCRIBE kind='P', name=''
|
||||
213070643360908->>server:+EXECUTE name='', nb_rows=0
|
||||
213070643360908->>server:+SYNC
|
||||
server-->>213070643360908:-PARSE COMPLETE
|
||||
server-->>213070643360908:-BIND COMPLETE
|
||||
server-->>213070643360908:-ROW DESCRIPTION: num_fields=1 ---[Field 01]--- name='version' type=25 type_len=65535 type_mod=4294967295 relid=0 attnum=0 format=0
|
||||
server-->>213070643360908:-DATA ROW num_values=1 ---[Value 0001]--- length=112 value='PostgreSQL 13.3 (Debian 13.3-1.pgdg100+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit'
|
||||
server-->>213070643360908:-COMMAND COMPLETE command='SELECT 1'
|
||||
server-->>213070643360908:-READY FOR QUERY type=<IDLE>
|
||||
213070643360908->>server:+PARSE name='', num_params=0, params_type=, query=
|
||||
213070643360908->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360908->>server:+DESCRIBE kind='P', name=''
|
||||
213070643360908->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360908->>server:+SYNC
|
||||
server-->>213070643360908:-PARSE COMPLETE
|
||||
server-->>213070643360908:-BIND COMPLETE
|
||||
server-->>213070643360908:-NO DATA
|
||||
server-->>213070643360908:-EMPTY QUERY RESPONSE
|
||||
server-->>213070643360908:-READY FOR QUERY type=<IDLE>
|
||||
213070643360908->>server:+PARSE name='', num_params=0, params_type=, query= SELECT pid as id, query as stmt, EXTRACT(seconds from query_start - NOW()) as elapsed_time FROM pg_stat_activity WHERE usename='docker'
|
||||
213070643360908->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360908->>server:+DESCRIBE kind='P', name=''
|
||||
213070643360908->>server:+EXECUTE name='', nb_rows=0
|
||||
213070643360908->>server:+SYNC
|
||||
server-->>213070643360908:-PARSE COMPLETE
|
||||
server-->>213070643360908:-BIND COMPLETE
|
||||
server-->>213070643360908:-ROW DESCRIPTION: num_fields=3 ---[Field 01]--- name='id' type=23 type_len=4 type_mod=4294967295 relid=12250 attnum=3 format=0 ---[Field 02]--- name='stmt' type=25 type_len=65535 type_mod=4294967295 relid=12250 attnum=20 format=0 ---[Field 03]--- name='elapsed_time' type=701 type_len=8 type_mod=4294967295 relid=0 attnum=0 format=0
|
||||
server-->>213070643360908:-DATA ROW num_values=3 ---[Value 0001]--- length=2 value='81' ---[Value 0002]--- length=0 value='' ---[Value 0003]--- length=-1 value=NULL
|
||||
server-->>213070643360908:-DATA ROW num_values=3 ---[Value 0001]--- length=3 value='100' ---[Value 0002]--- length=148 value=' SELECT COUNT(*). FROM pg_type AS t0,. pg_aggregate AS t1,. pg_settings AS t2,. pg_settings AS t3.' ---[Value 0003]--- length=9 value='-1.155449'
|
||||
server-->>213070643360908:-DATA ROW num_values=3 ---[Value 0001]--- length=3 value='102' ---[Value 0002]--- length=190 value=' SELECT pid as id,. query as stmt,. EXTRACT(seconds from query_start - NOW()) as elapsed_time. FROM pg_stat_activity. WHERE usename='docker'.' ---[Value 0003]--- length=8 value='0.002903'
|
||||
server-->>213070643360908:-COMMAND COMPLETE command='SELECT 3'
|
||||
server-->>213070643360908:-READY FOR QUERY type=<IDLE>
|
||||
213070643360912->>server:+SSL REQUEST
|
||||
server-->>213070643360912:-SSL BACKEND ANSWER: N
|
||||
213070643360912->>server:+STARTUP MESSAGE version: 3 database=trait_store extra_float_digits=2 TimeZone=GMT client_encoding=UTF8 user=docker DateStyle=ISO
|
||||
server-->>213070643360912:-AUTHENTIFICATION REQUEST code=5 (MD5 salt='14444412')
|
||||
213070643360912->>server:+PASSWORD MESSAGE password=md59cef18c88b7988d7ac2f215fc1569c62
|
||||
server-->>213070643360912:-AUTHENTIFICATION REQUEST code=0 (SUCCESS)
|
||||
server-->>213070643360912:-PARAMETER STATUS name='application_name', value=''
|
||||
server-->>213070643360912:-PARAMETER STATUS name='client_encoding', value='UTF8'
|
||||
server-->>213070643360912:-PARAMETER STATUS name='DateStyle', value='ISO, MDY'
|
||||
server-->>213070643360912:-PARAMETER STATUS name='integer_datetimes', value='on'
|
||||
server-->>213070643360912:-PARAMETER STATUS name='IntervalStyle', value='postgres'
|
||||
server-->>213070643360912:-PARAMETER STATUS name='is_superuser', value='on'
|
||||
server-->>213070643360912:-PARAMETER STATUS name='server_encoding', value='UTF8'
|
||||
server-->>213070643360912:-PARAMETER STATUS name='server_version', value='13.3 (Debian 13.3-1.pgdg100+1)'
|
||||
server-->>213070643360912:-PARAMETER STATUS name='session_authorization', value='docker'
|
||||
server-->>213070643360912:-PARAMETER STATUS name='standard_conforming_strings', value='on'
|
||||
server-->>213070643360912:-PARAMETER STATUS name='TimeZone', value='GMT'
|
||||
server-->>213070643360912:-BACKEND KEY DATA pid=103, key=1911102642
|
||||
server-->>213070643360912:-READY FOR QUERY type=<IDLE>
|
||||
213070643360912->>server:+PARSE name='', num_params=0, params_type=, query=SET extra_float_digits = 3
|
||||
213070643360912->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360912->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360912->>server:+SYNC
|
||||
server-->>213070643360912:-PARSE COMPLETE
|
||||
server-->>213070643360912:-BIND COMPLETE
|
||||
server-->>213070643360912:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643360912:-READY FOR QUERY type=<IDLE>
|
||||
213070643360912->>server:+PARSE name='', num_params=0, params_type=, query=SET application_name = 'PostgreSQL JDBC Driver'
|
||||
213070643360912->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360912->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360912->>server:+SYNC
|
||||
server-->>213070643360912:-PARSE COMPLETE
|
||||
server-->>213070643360912:-BIND COMPLETE
|
||||
server-->>213070643360912:-PARAMETER STATUS name='application_name', value='PostgreSQL JDBC Driver'
|
||||
server-->>213070643360912:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643360912:-READY FOR QUERY type=<IDLE>
|
||||
213070643360912->>server:+PARSE name='', num_params=0, params_type=, query=
|
||||
213070643360912->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360912->>server:+DESCRIBE kind='P', name=''
|
||||
213070643360912->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360912->>server:+SYNC
|
||||
server-->>213070643360912:-PARSE COMPLETE
|
||||
server-->>213070643360912:-BIND COMPLETE
|
||||
server-->>213070643360912:-NO DATA
|
||||
server-->>213070643360912:-EMPTY QUERY RESPONSE
|
||||
server-->>213070643360912:-READY FOR QUERY type=<IDLE>
|
||||
213070643360912->>server:+DISCONNECT
|
||||
213070643360916->>server:+SSL REQUEST
|
||||
server-->>213070643360916:-SSL BACKEND ANSWER: N
|
||||
213070643360916->>server:+STARTUP MESSAGE version: 3 database=trait_store extra_float_digits=2 TimeZone=GMT client_encoding=UTF8 user=docker DateStyle=ISO
|
||||
server-->>213070643360916:-AUTHENTIFICATION REQUEST code=5 (MD5 salt='9eb7546f')
|
||||
213070643360916->>server:+PASSWORD MESSAGE password=md5c7655226306ab14fa17e44dba876a348
|
||||
server-->>213070643360916:-AUTHENTIFICATION REQUEST code=0 (SUCCESS)
|
||||
server-->>213070643360916:-PARAMETER STATUS name='application_name', value=''
|
||||
server-->>213070643360916:-PARAMETER STATUS name='client_encoding', value='UTF8'
|
||||
server-->>213070643360916:-PARAMETER STATUS name='DateStyle', value='ISO, MDY'
|
||||
server-->>213070643360916:-PARAMETER STATUS name='integer_datetimes', value='on'
|
||||
server-->>213070643360916:-PARAMETER STATUS name='IntervalStyle', value='postgres'
|
||||
server-->>213070643360916:-PARAMETER STATUS name='is_superuser', value='on'
|
||||
server-->>213070643360916:-PARAMETER STATUS name='server_encoding', value='UTF8'
|
||||
server-->>213070643360916:-PARAMETER STATUS name='server_version', value='13.3 (Debian 13.3-1.pgdg100+1)'
|
||||
server-->>213070643360916:-PARAMETER STATUS name='session_authorization', value='docker'
|
||||
server-->>213070643360916:-PARAMETER STATUS name='standard_conforming_strings', value='on'
|
||||
server-->>213070643360916:-PARAMETER STATUS name='TimeZone', value='GMT'
|
||||
server-->>213070643360916:-BACKEND KEY DATA pid=104, key=927987783
|
||||
server-->>213070643360916:-READY FOR QUERY type=<IDLE>
|
||||
213070643360916->>server:+PARSE name='', num_params=0, params_type=, query=SET extra_float_digits = 3
|
||||
213070643360916->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360916->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360916->>server:+SYNC
|
||||
server-->>213070643360916:-PARSE COMPLETE
|
||||
server-->>213070643360916:-BIND COMPLETE
|
||||
server-->>213070643360916:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643360916:-READY FOR QUERY type=<IDLE>
|
||||
213070643360916->>server:+PARSE name='', num_params=0, params_type=, query=SET application_name = 'PostgreSQL JDBC Driver'
|
||||
213070643360916->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360916->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360916->>server:+SYNC
|
||||
server-->>213070643360916:-PARSE COMPLETE
|
||||
server-->>213070643360916:-BIND COMPLETE
|
||||
server-->>213070643360916:-PARAMETER STATUS name='application_name', value='PostgreSQL JDBC Driver'
|
||||
server-->>213070643360916:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643360916:-READY FOR QUERY type=<IDLE>
|
||||
213070643360916->>server:+PARSE name='', num_params=0, params_type=, query=
|
||||
213070643360916->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360916->>server:+DESCRIBE kind='P', name=''
|
||||
213070643360916->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360916->>server:+SYNC
|
||||
server-->>213070643360916:-PARSE COMPLETE
|
||||
server-->>213070643360916:-BIND COMPLETE
|
||||
server-->>213070643360916:-NO DATA
|
||||
server-->>213070643360916:-EMPTY QUERY RESPONSE
|
||||
server-->>213070643360916:-READY FOR QUERY type=<IDLE>
|
||||
213070643360916->>server:+PARSE name='', num_params=0, params_type=, query=select pg_terminate_backend(100)
|
||||
213070643360916->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360916->>server:+DESCRIBE kind='P', name=''
|
||||
213070643360916->>server:+EXECUTE name='', nb_rows=0
|
||||
213070643360916->>server:+SYNC
|
||||
server-->>213070643360916:-PARSE COMPLETE
|
||||
server-->>213070643360916:-BIND COMPLETE
|
||||
server-->>213070643360916:-ROW DESCRIPTION: num_fields=1 ---[Field 01]--- name='pg_terminate_backend' type=16 type_len=1 type_mod=4294967295 relid=0 attnum=0 format=0
|
||||
server-->>213070643360916:-DATA ROW num_values=1 ---[Value 0001]--- length=1 value='t'
|
||||
server-->>213070643360916:-COMMAND COMPLETE command='SELECT 1'
|
||||
server-->>213070643360916:-READY FOR QUERY type=<IDLE>
|
||||
server-->>213070643360900:-PARSE COMPLETE
|
||||
server-->>213070643360900:-BIND COMPLETE
|
||||
server-->>213070643360900:-ROW DESCRIPTION: num_fields=1 ---[Field 01]--- name='count' type=20 type_len=8 type_mod=4294967295 relid=0 attnum=0 format=0
|
||||
server-->>213070643360900:-ERROR RESPONSE File: 'postgres.c' Severity: 'FATAL' Message: 'terminating connection due to administrator command' Code: '57P01' Routine: 'ProcessInterrupts' Line: '3090'
|
||||
213070643360908->>server:+DISCONNECT
|
||||
213070643360916->>server:+DISCONNECT
|
||||
213070643360920->>server:+SSL REQUEST
|
||||
server-->>213070643360920:-SSL BACKEND ANSWER: N
|
||||
213070643360920->>server:+STARTUP MESSAGE version: 3 database=trait_store extra_float_digits=2 TimeZone=GMT client_encoding=UTF8 user=docker DateStyle=ISO
|
||||
server-->>213070643360920:-AUTHENTIFICATION REQUEST code=5 (MD5 salt='1ad4dcf7')
|
||||
213070643360920->>server:+PASSWORD MESSAGE password=md554de511c5219f67a8bbe92da445fa5a2
|
||||
server-->>213070643360920:-AUTHENTIFICATION REQUEST code=0 (SUCCESS)
|
||||
server-->>213070643360920:-PARAMETER STATUS name='application_name', value=''
|
||||
server-->>213070643360920:-PARAMETER STATUS name='client_encoding', value='UTF8'
|
||||
server-->>213070643360920:-PARAMETER STATUS name='DateStyle', value='ISO, MDY'
|
||||
server-->>213070643360920:-PARAMETER STATUS name='integer_datetimes', value='on'
|
||||
server-->>213070643360920:-PARAMETER STATUS name='IntervalStyle', value='postgres'
|
||||
server-->>213070643360920:-PARAMETER STATUS name='is_superuser', value='on'
|
||||
server-->>213070643360920:-PARAMETER STATUS name='server_encoding', value='UTF8'
|
||||
server-->>213070643360920:-PARAMETER STATUS name='server_version', value='13.3 (Debian 13.3-1.pgdg100+1)'
|
||||
server-->>213070643360920:-PARAMETER STATUS name='session_authorization', value='docker'
|
||||
server-->>213070643360920:-PARAMETER STATUS name='standard_conforming_strings', value='on'
|
||||
server-->>213070643360920:-PARAMETER STATUS name='TimeZone', value='GMT'
|
||||
server-->>213070643360920:-BACKEND KEY DATA pid=105, key=3630332440
|
||||
server-->>213070643360920:-READY FOR QUERY type=<IDLE>
|
||||
213070643360920->>server:+PARSE name='', num_params=0, params_type=, query=SET extra_float_digits = 3
|
||||
213070643360920->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360920->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360920->>server:+SYNC
|
||||
server-->>213070643360920:-PARSE COMPLETE
|
||||
server-->>213070643360920:-BIND COMPLETE
|
||||
server-->>213070643360920:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643360920:-READY FOR QUERY type=<IDLE>
|
||||
213070643360920->>server:+PARSE name='', num_params=0, params_type=, query=SET application_name = 'PostgreSQL JDBC Driver'
|
||||
213070643360920->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360920->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360920->>server:+SYNC
|
||||
server-->>213070643360920:-PARSE COMPLETE
|
||||
server-->>213070643360920:-BIND COMPLETE
|
||||
server-->>213070643360920:-PARAMETER STATUS name='application_name', value='PostgreSQL JDBC Driver'
|
||||
server-->>213070643360920:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643360920:-READY FOR QUERY type=<IDLE>
|
||||
213070643360920->>server:+PARSE name='', num_params=0, params_type=, query=
|
||||
213070643360920->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360920->>server:+DESCRIBE kind='P', name=''
|
||||
213070643360920->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360920->>server:+SYNC
|
||||
server-->>213070643360920:-PARSE COMPLETE
|
||||
server-->>213070643360920:-BIND COMPLETE
|
||||
server-->>213070643360920:-NO DATA
|
||||
server-->>213070643360920:-EMPTY QUERY RESPONSE
|
||||
server-->>213070643360920:-READY FOR QUERY type=<IDLE>
|
||||
213070643360920->>server:+DISCONNECT
|
||||
213070643360924->>server:+SSL REQUEST
|
||||
server-->>213070643360924:-SSL BACKEND ANSWER: N
|
||||
213070643360924->>server:+STARTUP MESSAGE version: 3 database=trait_store extra_float_digits=2 TimeZone=GMT client_encoding=UTF8 user=docker DateStyle=ISO
|
||||
server-->>213070643360924:-AUTHENTIFICATION REQUEST code=5 (MD5 salt='494c352f')
|
||||
213070643360924->>server:+PASSWORD MESSAGE password=md5919fc9ed056904fa0d8e1dd00625a556
|
||||
server-->>213070643360924:-AUTHENTIFICATION REQUEST code=0 (SUCCESS)
|
||||
server-->>213070643360924:-PARAMETER STATUS name='application_name', value=''
|
||||
server-->>213070643360924:-PARAMETER STATUS name='client_encoding', value='UTF8'
|
||||
server-->>213070643360924:-PARAMETER STATUS name='DateStyle', value='ISO, MDY'
|
||||
server-->>213070643360924:-PARAMETER STATUS name='integer_datetimes', value='on'
|
||||
server-->>213070643360924:-PARAMETER STATUS name='IntervalStyle', value='postgres'
|
||||
server-->>213070643360924:-PARAMETER STATUS name='is_superuser', value='on'
|
||||
server-->>213070643360924:-PARAMETER STATUS name='server_encoding', value='UTF8'
|
||||
server-->>213070643360924:-PARAMETER STATUS name='server_version', value='13.3 (Debian 13.3-1.pgdg100+1)'
|
||||
server-->>213070643360924:-PARAMETER STATUS name='session_authorization', value='docker'
|
||||
server-->>213070643360924:-PARAMETER STATUS name='standard_conforming_strings', value='on'
|
||||
server-->>213070643360924:-PARAMETER STATUS name='TimeZone', value='GMT'
|
||||
server-->>213070643360924:-BACKEND KEY DATA pid=106, key=3364308023
|
||||
server-->>213070643360924:-READY FOR QUERY type=<IDLE>
|
||||
213070643360924->>server:+PARSE name='', num_params=0, params_type=, query=SET extra_float_digits = 3
|
||||
213070643360924->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360924->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360924->>server:+SYNC
|
||||
server-->>213070643360924:-PARSE COMPLETE
|
||||
server-->>213070643360924:-BIND COMPLETE
|
||||
server-->>213070643360924:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643360924:-READY FOR QUERY type=<IDLE>
|
||||
213070643360924->>server:+PARSE name='', num_params=0, params_type=, query=SET application_name = 'PostgreSQL JDBC Driver'
|
||||
213070643360924->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360924->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360924->>server:+SYNC
|
||||
server-->>213070643360924:-PARSE COMPLETE
|
||||
server-->>213070643360924:-BIND COMPLETE
|
||||
server-->>213070643360924:-PARAMETER STATUS name='application_name', value='PostgreSQL JDBC Driver'
|
||||
server-->>213070643360924:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643360924:-READY FOR QUERY type=<IDLE>
|
||||
213070643360924->>server:+PARSE name='', num_params=0, params_type=, query=
|
||||
213070643360924->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360924->>server:+DESCRIBE kind='P', name=''
|
||||
213070643360924->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360924->>server:+SYNC
|
||||
server-->>213070643360924:-PARSE COMPLETE
|
||||
server-->>213070643360924:-BIND COMPLETE
|
||||
server-->>213070643360924:-NO DATA
|
||||
server-->>213070643360924:-EMPTY QUERY RESPONSE
|
||||
server-->>213070643360924:-READY FOR QUERY type=<IDLE>
|
||||
213070643360924->>server:+PARSE name='', num_params=0, params_type=, query=SELECT 1
|
||||
213070643360924->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360924->>server:+DESCRIBE kind='P', name=''
|
||||
213070643360924->>server:+EXECUTE name='', nb_rows=0
|
||||
213070643360924->>server:+SYNC
|
||||
server-->>213070643360924:-PARSE COMPLETE
|
||||
server-->>213070643360924:-BIND COMPLETE
|
||||
server-->>213070643360924:-ROW DESCRIPTION: num_fields=1 ---[Field 01]--- name='?column?' type=23 type_len=4 type_mod=4294967295 relid=0 attnum=0 format=0
|
||||
server-->>213070643360924:-DATA ROW num_values=1 ---[Value 0001]--- length=1 value='1'
|
||||
server-->>213070643360924:-COMMAND COMPLETE command='SELECT 1'
|
||||
server-->>213070643360924:-READY FOR QUERY type=<IDLE>
|
||||
213070643360924->>server:+DISCONNECT
|
||||
213070643360928->>server:+SSL REQUEST
|
||||
server-->>213070643360928:-SSL BACKEND ANSWER: N
|
||||
213070643360928->>server:+STARTUP MESSAGE version: 3 database=trait_store extra_float_digits=2 TimeZone=GMT client_encoding=UTF8 user=docker DateStyle=ISO
|
||||
server-->>213070643360928:-AUTHENTIFICATION REQUEST code=5 (MD5 salt='4b2173db')
|
||||
213070643360928->>server:+PASSWORD MESSAGE password=md53977c39c5c7cdef7f80e74b256a8ce25
|
||||
server-->>213070643360928:-AUTHENTIFICATION REQUEST code=0 (SUCCESS)
|
||||
server-->>213070643360928:-PARAMETER STATUS name='application_name', value=''
|
||||
server-->>213070643360928:-PARAMETER STATUS name='client_encoding', value='UTF8'
|
||||
server-->>213070643360928:-PARAMETER STATUS name='DateStyle', value='ISO, MDY'
|
||||
server-->>213070643360928:-PARAMETER STATUS name='integer_datetimes', value='on'
|
||||
server-->>213070643360928:-PARAMETER STATUS name='IntervalStyle', value='postgres'
|
||||
server-->>213070643360928:-PARAMETER STATUS name='is_superuser', value='on'
|
||||
server-->>213070643360928:-PARAMETER STATUS name='server_encoding', value='UTF8'
|
||||
server-->>213070643360928:-PARAMETER STATUS name='server_version', value='13.3 (Debian 13.3-1.pgdg100+1)'
|
||||
server-->>213070643360928:-PARAMETER STATUS name='session_authorization', value='docker'
|
||||
server-->>213070643360928:-PARAMETER STATUS name='standard_conforming_strings', value='on'
|
||||
server-->>213070643360928:-PARAMETER STATUS name='TimeZone', value='GMT'
|
||||
server-->>213070643360928:-BACKEND KEY DATA pid=107, key=1988416582
|
||||
server-->>213070643360928:-READY FOR QUERY type=<IDLE>
|
||||
213070643360928->>server:+PARSE name='', num_params=0, params_type=, query=SET extra_float_digits = 3
|
||||
213070643360928->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360928->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360928->>server:+SYNC
|
||||
server-->>213070643360928:-PARSE COMPLETE
|
||||
server-->>213070643360928:-BIND COMPLETE
|
||||
server-->>213070643360928:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643360928:-READY FOR QUERY type=<IDLE>
|
||||
213070643360928->>server:+PARSE name='', num_params=0, params_type=, query=SET application_name = 'PostgreSQL JDBC Driver'
|
||||
213070643360928->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360928->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360928->>server:+SYNC
|
||||
server-->>213070643360928:-PARSE COMPLETE
|
||||
server-->>213070643360928:-BIND COMPLETE
|
||||
server-->>213070643360928:-PARAMETER STATUS name='application_name', value='PostgreSQL JDBC Driver'
|
||||
server-->>213070643360928:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643360928:-READY FOR QUERY type=<IDLE>
|
||||
213070643360928->>server:+PARSE name='', num_params=0, params_type=, query=
|
||||
213070643360928->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360928->>server:+DESCRIBE kind='P', name=''
|
||||
213070643360928->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360928->>server:+SYNC
|
||||
server-->>213070643360928:-PARSE COMPLETE
|
||||
server-->>213070643360928:-BIND COMPLETE
|
||||
server-->>213070643360928:-NO DATA
|
||||
server-->>213070643360928:-EMPTY QUERY RESPONSE
|
||||
server-->>213070643360928:-READY FOR QUERY type=<IDLE>
|
||||
213070643360928->>server:+DISCONNECT
|
||||
213070643360932->>server:+SSL REQUEST
|
||||
server-->>213070643360932:-SSL BACKEND ANSWER: N
|
||||
213070643360932->>server:+STARTUP MESSAGE version: 3 database=trait_store extra_float_digits=2 TimeZone=GMT client_encoding=UTF8 user=docker DateStyle=ISO
|
||||
server-->>213070643360932:-AUTHENTIFICATION REQUEST code=5 (MD5 salt='f6d0f51d')
|
||||
213070643360932->>server:+PASSWORD MESSAGE password=md5b5b322fe9bcc7c242c6b44cc8a7898e8
|
||||
server-->>213070643360932:-AUTHENTIFICATION REQUEST code=0 (SUCCESS)
|
||||
server-->>213070643360932:-PARAMETER STATUS name='application_name', value=''
|
||||
server-->>213070643360932:-PARAMETER STATUS name='client_encoding', value='UTF8'
|
||||
server-->>213070643360932:-PARAMETER STATUS name='DateStyle', value='ISO, MDY'
|
||||
server-->>213070643360932:-PARAMETER STATUS name='integer_datetimes', value='on'
|
||||
server-->>213070643360932:-PARAMETER STATUS name='IntervalStyle', value='postgres'
|
||||
server-->>213070643360932:-PARAMETER STATUS name='is_superuser', value='on'
|
||||
server-->>213070643360932:-PARAMETER STATUS name='server_encoding', value='UTF8'
|
||||
server-->>213070643360932:-PARAMETER STATUS name='server_version', value='13.3 (Debian 13.3-1.pgdg100+1)'
|
||||
server-->>213070643360932:-PARAMETER STATUS name='session_authorization', value='docker'
|
||||
server-->>213070643360932:-PARAMETER STATUS name='standard_conforming_strings', value='on'
|
||||
server-->>213070643360932:-PARAMETER STATUS name='TimeZone', value='GMT'
|
||||
server-->>213070643360932:-BACKEND KEY DATA pid=108, key=997991029
|
||||
server-->>213070643360932:-READY FOR QUERY type=<IDLE>
|
||||
213070643360932->>server:+PARSE name='', num_params=0, params_type=, query=SET extra_float_digits = 3
|
||||
213070643360932->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360932->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360932->>server:+SYNC
|
||||
server-->>213070643360932:-PARSE COMPLETE
|
||||
server-->>213070643360932:-BIND COMPLETE
|
||||
server-->>213070643360932:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643360932:-READY FOR QUERY type=<IDLE>
|
||||
213070643360932->>server:+PARSE name='', num_params=0, params_type=, query=SET application_name = 'PostgreSQL JDBC Driver'
|
||||
213070643360932->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360932->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360932->>server:+SYNC
|
||||
server-->>213070643360932:-PARSE COMPLETE
|
||||
server-->>213070643360932:-BIND COMPLETE
|
||||
server-->>213070643360932:-PARAMETER STATUS name='application_name', value='PostgreSQL JDBC Driver'
|
||||
server-->>213070643360932:-COMMAND COMPLETE command='SET'
|
||||
server-->>213070643360932:-READY FOR QUERY type=<IDLE>
|
||||
213070643360932->>server:+PARSE name='', num_params=0, params_type=, query=
|
||||
213070643360932->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360932->>server:+DESCRIBE kind='P', name=''
|
||||
213070643360932->>server:+EXECUTE name='', nb_rows=1
|
||||
213070643360932->>server:+SYNC
|
||||
server-->>213070643360932:-PARSE COMPLETE
|
||||
server-->>213070643360932:-BIND COMPLETE
|
||||
server-->>213070643360932:-NO DATA
|
||||
server-->>213070643360932:-EMPTY QUERY RESPONSE
|
||||
server-->>213070643360932:-READY FOR QUERY type=<IDLE>
|
||||
213070643360932->>server:+PARSE name='', num_params=0, params_type=, query=SELECT VERSION() AS version
|
||||
213070643360932->>server:+BIND portal='', name='', num_formats=0, formats=, num_params=0, params=
|
||||
213070643360932->>server:+DESCRIBE kind='P', name=''
|
||||
213070643360932->>server:+EXECUTE name='', nb_rows=0
|
||||
213070643360932->>server:+SYNC
|
||||
server-->>213070643360932:-PARSE COMPLETE
|
||||
server-->>213070643360932:-BIND COMPLETE
|
||||
server-->>213070643360932:-ROW DESCRIPTION: num_fields=1 ---[Field 01]--- name='version' type=25 type_len=65535 type_mod=4294967295 relid=0 attnum=0 format=0
|
||||
server-->>213070643360932:-DATA ROW num_values=1 ---[Value 0001]--- length=112 value='PostgreSQL 13.3 (Debian 13.3-1.pgdg100+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit'
|
||||
server-->>213070643360932:-COMMAND COMPLETE command='SELECT 1'
|
||||
server-->>213070643360932:-READY FOR QUERY type=<IDLE>
|
||||
213070643360932->>server:+DISCONNECT
|
||||
```
|
||||
111
pg/message/io.go
111
pg/message/io.go
|
|
@ -1,111 +0,0 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
package message
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Reader reads messages.
|
||||
type Reader interface {
|
||||
ReadMessage() (Message, error)
|
||||
}
|
||||
|
||||
// Writer writes messages.
|
||||
type Writer interface {
|
||||
WriteMessage(Message) error
|
||||
Flush() error
|
||||
}
|
||||
|
||||
// WireReader reads messages in Postgres wire protocol format.
|
||||
type WireReader struct {
|
||||
buf []byte
|
||||
r *bufio.Reader
|
||||
scratch [4]byte
|
||||
}
|
||||
|
||||
// ReadMessage reads a single message off of the wire.
|
||||
// The returned message is only valid until the next read call, as the data buffer may be re-used.
|
||||
func (r *WireReader) ReadMessage() (Message, error) {
|
||||
t, err := r.r.ReadByte()
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
|
||||
_, err = r.r.Read(r.scratch[:])
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
|
||||
len := binary.BigEndian.Uint32(r.scratch[:4])
|
||||
if len < 4 {
|
||||
return Message{}, errors.New("invalid message length")
|
||||
}
|
||||
len -= 4
|
||||
|
||||
if cap(r.buf) < int(len) {
|
||||
r.buf = make([]byte, len)
|
||||
} else {
|
||||
r.buf = r.buf[:len]
|
||||
}
|
||||
_, err = io.ReadFull(r.r, r.buf)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
|
||||
return Message{
|
||||
Type: Type(t),
|
||||
Data: r.buf,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var _ Reader = (*WireReader)(nil)
|
||||
|
||||
// NewWireReader returns a message reader that reads postgres wire protocol format.
|
||||
func NewWireReader(r *bufio.Reader) *WireReader {
|
||||
return &WireReader{r: r}
|
||||
}
|
||||
|
||||
// ErrMessageTooBig is an error indicating that a message is too big to be sent or received.
|
||||
var ErrMessageTooBig = errors.New("message is too big")
|
||||
|
||||
// WireWriter writes messages in Postgres wire protocol.
|
||||
type WireWriter struct {
|
||||
w *bufio.Writer
|
||||
scratch [4]byte
|
||||
}
|
||||
|
||||
// WriteMessage writes a message onto the wire.
|
||||
func (w *WireWriter) WriteMessage(message Message) error {
|
||||
if uint(len(message.Data))+4 >= 1<<31 {
|
||||
return ErrMessageTooBig
|
||||
}
|
||||
|
||||
err := w.w.WriteByte(byte(message.Type))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint32(w.scratch[:], uint32(len(message.Data))+4)
|
||||
_, err = w.w.Write(w.scratch[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.w.Write(message.Data)
|
||||
return err
|
||||
}
|
||||
|
||||
// Flush writes any buffered data to the underlying stream.
|
||||
func (w *WireWriter) Flush() error {
|
||||
return w.w.Flush()
|
||||
}
|
||||
|
||||
var _ Writer = (*WireWriter)(nil)
|
||||
|
||||
// NewWireWriter returns a message writer that writes in postgres wire protocol format.
|
||||
func NewWireWriter(w *bufio.Writer) *WireWriter {
|
||||
return &WireWriter{w: w}
|
||||
}
|
||||
|
|
@ -1,531 +0,0 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
package message
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Type is a byte indicating the type of a Postgres message.
|
||||
type Type byte
|
||||
|
||||
const (
|
||||
// TypeAuthentication is a message used to transfer authentication info.
|
||||
TypeAuthentication Type = 'R'
|
||||
|
||||
// TypeReadyForQuery is a message used to indicate that the server is ready for another query.
|
||||
TypeReadyForQuery Type = 'Z'
|
||||
|
||||
// TypeCommandComplete is a Backend message used to indicate that a query has completed.
|
||||
TypeCommandComplete Type = 'C'
|
||||
|
||||
// TypeClos is a message used to indicate that a query has completed. Frontend
|
||||
TypeClose Type = 'C'
|
||||
|
||||
// TypeRowDescription is a message indicating the column types of the result rows from a query.
|
||||
TypeRowDescription Type = 'T'
|
||||
|
||||
// TypeDataRow is a message with the contents of a single row.
|
||||
TypeDataRow Type = 'D'
|
||||
|
||||
// TypeTermination is a message indicating a request to terminate a connection.
|
||||
TypeTermination Type = 'X'
|
||||
|
||||
// TypeNegotiateProtocolVersion is a message used when a client attempts to connect with a newer minor version than the server supports.
|
||||
TypeNegotiateProtocolVersion Type = 'v'
|
||||
|
||||
// TypeSimpleQuery is a simple query request.
|
||||
TypeSimpleQuery Type = 'Q'
|
||||
|
||||
// TypeBackendKeyData contains a cancellation key for the client to use later.
|
||||
TypeBackendKeyData Type = 'K'
|
||||
|
||||
TypeParse Type = 'P'
|
||||
TypeParseComplete Type = '1'
|
||||
|
||||
TypeBind Type = 'B'
|
||||
TypeBindComplete Type = '2'
|
||||
|
||||
TypeExecute Type = 'E' // Frontend
|
||||
TypeError Type = 'E' // Backend
|
||||
TypeSync Type = 'S' // Frontend
|
||||
TypeParameterStatus Type = 'S' // Backend
|
||||
TypeDescribe Type = 'D' // Frontend
|
||||
TypeNoData Type = 'n' // backend
|
||||
TypeEmptyQueryResponse Type = 'I' // backend
|
||||
)
|
||||
|
||||
// AuthenticationOK is a message indicating that authentication has completed.
|
||||
var AuthenticationOK = Message{
|
||||
Type: TypeAuthentication,
|
||||
Data: []byte{0, 0, 0, 0},
|
||||
}
|
||||
var ParseOK = Message{
|
||||
Type: TypeParseComplete,
|
||||
Data: []byte{},
|
||||
}
|
||||
var BindComplete = Message{
|
||||
Type: TypeBindComplete,
|
||||
Data: []byte{},
|
||||
}
|
||||
var NoData = Message{
|
||||
Type: TypeNoData,
|
||||
Data: []byte{},
|
||||
}
|
||||
var EmptyQueryResponse = Message{
|
||||
Type: TypeEmptyQueryResponse,
|
||||
Data: []byte{},
|
||||
}
|
||||
|
||||
// Message is a Postgres message value.
|
||||
type Message struct {
|
||||
Type Type
|
||||
Data []byte
|
||||
}
|
||||
|
||||
//debug tools
|
||||
func viewString(b []byte) string {
|
||||
r := []rune(string(b))
|
||||
for i := range r {
|
||||
if r[i] < 32 || r[i] > 126 {
|
||||
r[i] = '.'
|
||||
}
|
||||
}
|
||||
return string(r)
|
||||
}
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
func (m *Message) Dump(prefix string) {
|
||||
n := len(m.Data)
|
||||
rowcount := 0
|
||||
stop := (n / 8) * 8
|
||||
k := 0
|
||||
fmt.Printf("\n %s type: '%c'\n", prefix, m.Type)
|
||||
for i := 0; i <= stop; i += 8 {
|
||||
k++
|
||||
if i+8 < n {
|
||||
rowcount = 8
|
||||
} else {
|
||||
rowcount = min(k*8, n) % 8
|
||||
}
|
||||
|
||||
fmt.Printf("pos %02d hex: ", i)
|
||||
for j := 0; j < rowcount; j++ {
|
||||
fmt.Printf("%02x ", m.Data[i+j])
|
||||
}
|
||||
for j := rowcount; j < 8; j++ {
|
||||
fmt.Printf(" ")
|
||||
}
|
||||
fmt.Printf(" '%s'\n", viewString(m.Data[i:(i+rowcount)]))
|
||||
}
|
||||
}
|
||||
|
||||
//endTools
|
||||
|
||||
// TransactionStatus is the current transaction state.
|
||||
type TransactionStatus byte
|
||||
|
||||
const (
|
||||
// TransactionStatusIdle indicates that there is no active transaction.
|
||||
TransactionStatusIdle TransactionStatus = 'I'
|
||||
|
||||
// TransactionStatusActive indicates that the connection currently has an active transaction.
|
||||
TransactionStatusActive TransactionStatus = 'T'
|
||||
|
||||
// TransactionStatusFailed indicates that the connection currently has a failed transaction.
|
||||
TransactionStatusFailed TransactionStatus = 'E'
|
||||
)
|
||||
|
||||
// Encoder encodes messages.
|
||||
type Encoder struct {
|
||||
buf bytes.Buffer
|
||||
scratch [4]byte
|
||||
}
|
||||
|
||||
func (e *Encoder) i16(i int16) error {
|
||||
binary.BigEndian.PutUint16(e.scratch[:2], uint16(i))
|
||||
_, err := e.buf.Write(e.scratch[:2])
|
||||
return err
|
||||
}
|
||||
|
||||
func (e *Encoder) i32(i int32) error {
|
||||
binary.BigEndian.PutUint32(e.scratch[:], uint32(i))
|
||||
_, err := e.buf.Write(e.scratch[:])
|
||||
return err
|
||||
}
|
||||
|
||||
/* removed for linter now
|
||||
func (e *Encoder) u32(i uint32) error {
|
||||
binary.BigEndian.PutUint32(e.scratch[:], i)
|
||||
_, err := e.buf.Write(e.scratch[:])
|
||||
return err
|
||||
}
|
||||
*/
|
||||
|
||||
// ReadyForQuery encodes a "ready for query" message.
|
||||
func (e *Encoder) ReadyForQuery(status TransactionStatus) (Message, error) {
|
||||
e.buf.Reset()
|
||||
|
||||
err := e.buf.WriteByte(byte(status))
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
|
||||
return Message{
|
||||
Type: TypeReadyForQuery,
|
||||
Data: e.buf.Bytes(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CommandComplete encodes a command completion message.
|
||||
func (e *Encoder) CommandComplete(tag string) (Message, error) {
|
||||
e.buf.Reset()
|
||||
|
||||
_, err := e.buf.WriteString(tag)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
|
||||
err = e.buf.WriteByte(0)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
|
||||
return Message{
|
||||
Type: TypeCommandComplete,
|
||||
Data: e.buf.Bytes(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NoticeFieldType indicates the type of a notice/error field.
|
||||
// https://www.postgresql.org/docs/9.3/protocol-error-fields.html
|
||||
type NoticeFieldType byte
|
||||
|
||||
const (
|
||||
// NoticeFieldSeverity indicates the severity of a notice/error.
|
||||
NoticeFieldSeverity NoticeFieldType = 'S'
|
||||
|
||||
// NoticeFieldMessage is a short human-readable error/notice message.
|
||||
NoticeFieldMessage NoticeFieldType = 'M'
|
||||
|
||||
// NoticeFieldDetail is an optional extended description of the error.
|
||||
NoticeFieldDetail NoticeFieldType = 'D'
|
||||
|
||||
// NoticeFieldHint is a suggestion of how to address the issue.
|
||||
NoticeFieldHint NoticeFieldType = 'H'
|
||||
|
||||
// NoticeFieldHint the SQLSTATE code for the error (see Appendix A). Not localizable. Always present.
|
||||
NoticeFieldCode NoticeFieldType = 'C'
|
||||
)
|
||||
|
||||
// NoticeField is a field in an error or notice.
|
||||
type NoticeField struct {
|
||||
Type NoticeFieldType
|
||||
Data string
|
||||
}
|
||||
|
||||
func (e *Encoder) messageOrNotice(fields ...NoticeField) error {
|
||||
for _, f := range fields {
|
||||
err := e.buf.WriteByte(byte(f.Type))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = e.buf.WriteString(f.Data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = e.buf.WriteByte(0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return e.buf.WriteByte(0)
|
||||
}
|
||||
|
||||
// Error encodes a Postgres error message.
|
||||
func (e *Encoder) Error(fields ...NoticeField) (Message, error) {
|
||||
e.buf.Reset()
|
||||
err := e.messageOrNotice(fields...)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
return Message{
|
||||
Type: TypeError,
|
||||
Data: e.buf.Bytes(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GoError creates a simple Postgres error message from a Go error value.
|
||||
func (e *Encoder) GoError(err error) (Message, error) {
|
||||
return e.Error(
|
||||
NoticeField{
|
||||
Type: NoticeFieldSeverity,
|
||||
Data: "ERROR",
|
||||
},
|
||||
NoticeField{
|
||||
Type: NoticeFieldMessage,
|
||||
Data: err.Error(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// ColumnDescription is a description of a data column.
|
||||
type ColumnDescription struct {
|
||||
Name string
|
||||
TableID int32 //either a table/col id or 0
|
||||
FieldID int16 //either a table/col id or 0
|
||||
TypeID int32 //field type
|
||||
TypeLen int16 //size in bytes of field
|
||||
TypeModifier int32 //type modifer?
|
||||
Mode int16 //0=text 1=binary
|
||||
}
|
||||
|
||||
// RowDescription describes the response rows from a query.
|
||||
func (e *Encoder) RowDescription(cols ...ColumnDescription) (Message, error) {
|
||||
if len(cols) >= 1<<15 {
|
||||
return Message{}, ErrMessageTooBig
|
||||
}
|
||||
|
||||
e.buf.Reset()
|
||||
|
||||
err := e.i16(int16(len(cols)))
|
||||
if err != nil {
|
||||
return Message{}, nil
|
||||
}
|
||||
|
||||
for _, col := range cols {
|
||||
_, err := e.buf.WriteString(col.Name)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
err = e.buf.WriteByte(0)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
|
||||
err = e.i32(col.TableID)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
|
||||
err = e.i16(col.FieldID)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
|
||||
err = e.i32(col.TypeID)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
|
||||
err = e.i16(col.TypeLen)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
|
||||
err = e.i32(col.TypeModifier)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
|
||||
err = e.i16(col.Mode)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return Message{
|
||||
Type: TypeRowDescription,
|
||||
Data: e.buf.Bytes(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TextRow encodes a data row in textual format.
|
||||
func (e *Encoder) TextRow(row ...string) (Message, error) {
|
||||
if len(row) >= 1<<15 {
|
||||
return Message{}, ErrMessageTooBig
|
||||
}
|
||||
|
||||
e.buf.Reset()
|
||||
|
||||
err := e.i16(int16(len(row)))
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
|
||||
for _, val := range row {
|
||||
if uint(len(val)) >= 1<<31 {
|
||||
return Message{}, ErrMessageTooBig
|
||||
}
|
||||
|
||||
err = e.i32(int32(len(val)))
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
|
||||
_, err = e.buf.WriteString(val)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return Message{
|
||||
Type: TypeDataRow,
|
||||
Data: e.buf.Bytes(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NegotiateProtocolVersion encodes a protocol negotiation packet.
|
||||
func (e *Encoder) NegotiateProtocolVersion(maxMinor int32, unrecognizedOptions ...string) (Message, error) {
|
||||
if uint64(len(unrecognizedOptions)) >= 1<<31 {
|
||||
return Message{}, ErrMessageTooBig
|
||||
}
|
||||
|
||||
e.buf.Reset()
|
||||
|
||||
err := e.i32(maxMinor)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
|
||||
err = e.i32(int32(len(unrecognizedOptions)))
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
for _, opt := range unrecognizedOptions {
|
||||
_, err = e.buf.WriteString(opt)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
|
||||
err = e.buf.WriteByte(0)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return Message{
|
||||
Type: TypeNegotiateProtocolVersion,
|
||||
Data: e.buf.Bytes(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// BackendKeyData encodes a Message with a cancellation key.
|
||||
func (e *Encoder) BackendKeyData(pid, key int32) (Message, error) {
|
||||
e.buf.Reset()
|
||||
|
||||
err := e.i32(pid)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
|
||||
err = e.i32(key)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
|
||||
return Message{
|
||||
Type: TypeBackendKeyData,
|
||||
Data: e.buf.Bytes(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (e *Encoder) ParameterStatus(param, value string) (Message, error) {
|
||||
e.buf.Reset()
|
||||
//param + NULL + value+ NULL
|
||||
_, err := e.buf.WriteString(param)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
err = e.buf.WriteByte(0)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
_, err = e.buf.WriteString(value)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
err = e.buf.WriteByte(0)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
return Message{
|
||||
Type: TypeParameterStatus,
|
||||
Data: e.buf.Bytes(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type SimpleColumn struct {
|
||||
Name string
|
||||
Typeid int32
|
||||
Typelen int16
|
||||
}
|
||||
|
||||
func (e *Encoder) EncodeColumn(name string, typeid int32, typelen int16) (Message, error) {
|
||||
return e.EncodeColumns(SimpleColumn{
|
||||
Name: name,
|
||||
Typeid: typeid,
|
||||
Typelen: typelen,
|
||||
})
|
||||
}
|
||||
func (e *Encoder) EncodeColumns(cols ...SimpleColumn) (Message, error) {
|
||||
|
||||
e.buf.Reset()
|
||||
err := e.i16(int16(len(cols))) // number of columns in result
|
||||
if err != nil {
|
||||
return Message{}, nil
|
||||
}
|
||||
for _, col := range cols {
|
||||
|
||||
_, err = e.buf.WriteString(col.Name) // column name
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
err = e.buf.WriteByte(0) //null terminate
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
|
||||
err = e.i32(0) //tabel id
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
|
||||
err = e.i16(0) //field id(attnum)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
|
||||
err = e.i32(col.Typeid) //type_id
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
|
||||
err = e.i16(col.Typelen) //type_len
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
|
||||
err = e.i32(-1) //type_mod
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
|
||||
err = e.i16(0) //format 0 text 1 binary
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
}
|
||||
return Message{
|
||||
Type: TypeRowDescription,
|
||||
Data: e.buf.Bytes(),
|
||||
}, nil
|
||||
}
|
||||
|
|
@ -1,112 +0,0 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
package pgtest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/molecula/featurebase/v3/pg"
|
||||
)
|
||||
|
||||
// HandlerFunc implements a postgres query handler with a function.
|
||||
type HandlerFunc func(context.Context, pg.QueryResultWriter, pg.Query) error
|
||||
|
||||
// HandleQuery calls the user's query handler function.
|
||||
func (h HandlerFunc) HandleQuery(ctx context.Context, w pg.QueryResultWriter, q pg.Query) error {
|
||||
return h(ctx, w, q)
|
||||
}
|
||||
func (h HandlerFunc) HandleSchema(ctx context.Context, portal *pg.Portal) error {
|
||||
return nil
|
||||
}
|
||||
func (h HandlerFunc) Version() string {
|
||||
return "testv1"
|
||||
}
|
||||
|
||||
var _ pg.QueryHandler = HandlerFunc(nil)
|
||||
|
||||
// ResultSet is a QueryResultWriter that accumulates results in a slice.
|
||||
type ResultSet struct {
|
||||
Columns []pg.ColumnInfo
|
||||
Data [][]string
|
||||
ResultTag string
|
||||
}
|
||||
|
||||
func (s ResultSet) String() string {
|
||||
if len(s.Columns) == 0 || len(s.Data) == 0 {
|
||||
return "EMPTY"
|
||||
}
|
||||
colHdr := make([]string, len(s.Columns))
|
||||
for i, c := range s.Columns {
|
||||
colHdr[i] = fmt.Sprintf("%s:%v", c.Name, c.Type)
|
||||
}
|
||||
dataBody := make([][]string, len(s.Data))
|
||||
for i, v := range s.Data {
|
||||
dataBody[i] = append([]string(nil), v...)
|
||||
}
|
||||
colWidth := make([]int, len(s.Columns))
|
||||
for i, c := range colHdr {
|
||||
colWidth[i] = len(c)
|
||||
}
|
||||
for _, row := range dataBody {
|
||||
for i, c := range row {
|
||||
if len(c) > colWidth[i] {
|
||||
colWidth[i] = len(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
for i, c := range colHdr {
|
||||
c += strings.Repeat(" ", colWidth[i]-len(c))
|
||||
colHdr[i] = c
|
||||
}
|
||||
for _, row := range dataBody {
|
||||
for i, c := range row {
|
||||
c += strings.Repeat(" ", colWidth[i]-len(c))
|
||||
row[i] = c
|
||||
}
|
||||
}
|
||||
var totalWidth int
|
||||
for _, width := range colWidth {
|
||||
totalWidth += width
|
||||
}
|
||||
data := make([]string, len(dataBody))
|
||||
for i, row := range dataBody {
|
||||
data[i] = strings.Join(row, "|")
|
||||
}
|
||||
return strings.Join(colHdr, "|") + "\n" + strings.Repeat("-", totalWidth+(2*len(colHdr)-1)) + "\n" + strings.Join(data, "\n")
|
||||
}
|
||||
|
||||
// WriteHeader writes headers to the result set.
|
||||
func (rs *ResultSet) WriteHeader(cols ...pg.ColumnInfo) error {
|
||||
if rs.Columns != nil {
|
||||
return errors.New("double-write of headers")
|
||||
}
|
||||
|
||||
colsCopy := make([]pg.ColumnInfo, len(cols))
|
||||
copy(colsCopy, cols)
|
||||
rs.Columns = colsCopy
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteRowText writes a row to the result set.
|
||||
func (rs *ResultSet) WriteRowText(vals ...string) error {
|
||||
if rs.Columns == nil {
|
||||
return errors.New("wrote a row without headers")
|
||||
}
|
||||
|
||||
row := make([]string, len(vals))
|
||||
copy(row, vals)
|
||||
|
||||
rs.Data = append(rs.Data, row)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Tag applies a tag to the result set.
|
||||
func (rs *ResultSet) Tag(tag string) {
|
||||
rs.ResultTag = tag
|
||||
}
|
||||
|
||||
var _ pg.QueryResultWriter = (*ResultSet)(nil)
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
package pgtest
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// errListenerClosed is an error returned when the listener is closed.
|
||||
var errListenerClosed = errors.New("listener closed")
|
||||
|
||||
type inMemoryListener struct {
|
||||
ch chan net.Conn
|
||||
closed chan struct{}
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func (l *inMemoryListener) Accept() (net.Conn, error) {
|
||||
select {
|
||||
case <-l.closed:
|
||||
return nil, errListenerClosed
|
||||
default:
|
||||
}
|
||||
select {
|
||||
case conn := <-l.ch:
|
||||
return conn, nil
|
||||
case <-l.closed:
|
||||
return nil, errListenerClosed
|
||||
}
|
||||
}
|
||||
|
||||
func (l *inMemoryListener) Close() error {
|
||||
l.once.Do(func() { close(l.closed) })
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type memAddr struct{}
|
||||
|
||||
func (a memAddr) Network() string { return "memory" }
|
||||
func (a memAddr) String() string { return "memory" }
|
||||
|
||||
func (l *inMemoryListener) Addr() net.Addr {
|
||||
return memAddr{}
|
||||
}
|
||||
|
||||
func (l *inMemoryListener) Dial() (net.Conn, error) {
|
||||
serverConn, clientConn := net.Pipe()
|
||||
select {
|
||||
case l.ch <- serverConn:
|
||||
return clientConn, nil
|
||||
case <-l.closed:
|
||||
serverConn.Close()
|
||||
clientConn.Close()
|
||||
return nil, errListenerClosed
|
||||
}
|
||||
}
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
package pgtest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/molecula/featurebase/v3/pg"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// ShutdownFunc is a function to use to shut down a test fixture.
|
||||
// This function will send a shutdown signal and then wait for completion.
|
||||
type ShutdownFunc func() error
|
||||
|
||||
// Finish invokes the shutdown function and fails the test if an error occurs.
|
||||
func (f ShutdownFunc) Finish(tb testing.TB, name string) {
|
||||
err := f()
|
||||
if err != nil {
|
||||
tb.Errorf("failed to shut down %s: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
// ServeListener serves postgres wire protocol on a listener.
|
||||
func ServeListener(listener net.Listener, server *pg.Server) (net.Addr, ShutdownFunc, error) {
|
||||
laddr := listener.Addr()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
var eg errgroup.Group
|
||||
eg.Go(func() error { return server.Serve(ctx, listener) })
|
||||
|
||||
return laddr,
|
||||
func() error {
|
||||
cancel()
|
||||
return eg.Wait()
|
||||
},
|
||||
nil
|
||||
}
|
||||
|
||||
// ServeTCP creates a TCP listener and serves postgres wire protocol on it.
|
||||
func ServeTCP(addr string, server *pg.Server) (net.Addr, ShutdownFunc, error) {
|
||||
listener, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "listening on TCP")
|
||||
}
|
||||
return ServeListener(listener, server)
|
||||
}
|
||||
|
||||
// ServeTLSListener sets up TLS on the server and invokes ServeListener.
|
||||
func ServeTLSListener(listener net.Listener, server *pg.Server) (net.Addr, ShutdownFunc, error) {
|
||||
err := SetupTLS(server)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "server TLS setup failed")
|
||||
}
|
||||
|
||||
var tries int = 5
|
||||
var netAddr net.Addr
|
||||
var shutdown ShutdownFunc
|
||||
|
||||
for i := 0; i < tries; i++ {
|
||||
if i > 0 {
|
||||
fmt.Printf("--- try serving TLS again: %d\n", i)
|
||||
}
|
||||
if netAddr, shutdown, err = ServeListener(listener, server); err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return netAddr, shutdown, err
|
||||
}
|
||||
|
||||
// ServeTLS sets up TLS on the server and invokes ServeTCP.
|
||||
func ServeTLS(addr string, server *pg.Server) (net.Addr, ShutdownFunc, error) {
|
||||
err := SetupTLS(server)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "server TLS setup failed")
|
||||
}
|
||||
|
||||
var tries int = 5
|
||||
var netAddr net.Addr
|
||||
var shutdown ShutdownFunc
|
||||
|
||||
for i := 0; i < tries; i++ {
|
||||
if i > 0 {
|
||||
fmt.Printf("--- try serving TLS again: %d\n", i)
|
||||
}
|
||||
if netAddr, shutdown, err = ServeTCP(addr, server); err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return netAddr, shutdown, err
|
||||
}
|
||||
|
||||
// ConnectFunc is a function to connect to a server.
|
||||
type ConnectFunc func() (net.Conn, error)
|
||||
|
||||
// ServeMem serves postgres on in-memory connections.
|
||||
// TLS does not work here, as it relies on the OS to buffer and discard data.
|
||||
func ServeMem(server *pg.Server) (ConnectFunc, ShutdownFunc, error) {
|
||||
listener := &inMemoryListener{
|
||||
ch: make(chan net.Conn),
|
||||
closed: make(chan struct{}),
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
var eg errgroup.Group
|
||||
eg.Go(func() error { return server.Serve(ctx, listener) })
|
||||
|
||||
return listener.Dial,
|
||||
func() error {
|
||||
cancel()
|
||||
return eg.Wait()
|
||||
},
|
||||
nil
|
||||
}
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
package pgtest
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/molecula/featurebase/v3/pg"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// SetupTLS generates a TLS certificate and installs it into the server.
|
||||
// TODO: have the client properly trust this (generate a CA to install instead of using self-signed).
|
||||
func SetupTLS(server *pg.Server) error {
|
||||
// Generate an ecdsa key for the cert.
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "generating TLS key")
|
||||
}
|
||||
|
||||
// Generate a random 128-bit serial number.
|
||||
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "generating serial number")
|
||||
}
|
||||
|
||||
// Make the certificate valid starting now.
|
||||
now := time.Now()
|
||||
|
||||
// Create a certificate template.
|
||||
template := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"Molecula"},
|
||||
},
|
||||
NotBefore: now,
|
||||
NotAfter: now.Add(time.Hour),
|
||||
|
||||
KeyUsage: x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
|
||||
// Generate a self-signed x509 cert from the template and the key.
|
||||
certData, err := x509.CreateCertificate(rand.Reader, &template, &template, key.Public(), key)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "encoding certificate x509")
|
||||
}
|
||||
|
||||
// Encode the cert to PEM so that the TLS package can load it.
|
||||
certPEM := pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: certData,
|
||||
})
|
||||
|
||||
// Encode the private key to x509.
|
||||
keyData, err := x509.MarshalPKCS8PrivateKey(key)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "encoding key x509")
|
||||
}
|
||||
|
||||
// Encode the key to PEM so that the TLS package can load it.
|
||||
keyPEM := pem.EncodeToMemory(&pem.Block{
|
||||
Type: "PRIVATE KEY",
|
||||
Bytes: keyData,
|
||||
})
|
||||
|
||||
// Load the certificate and key from their PEM encodings.
|
||||
cert, err := tls.X509KeyPair(certPEM, keyPEM)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "loading TLS key pair")
|
||||
}
|
||||
|
||||
// Install the certificate into the server.
|
||||
if server.TLSConfig == nil {
|
||||
server.TLSConfig = &tls.Config{}
|
||||
}
|
||||
server.TLSConfig.Certificates = append(server.TLSConfig.Certificates, cert)
|
||||
|
||||
return nil
|
||||
}
|
||||
1101
pg/protocol.go
1101
pg/protocol.go
File diff suppressed because it is too large
Load diff
119
pg/query.go
119
pg/query.go
|
|
@ -1,119 +0,0 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
package pg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/molecula/featurebase/v3/pg/message"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Query is an interface to be implemented by queries.
|
||||
type Query interface {
|
||||
fmt.Stringer
|
||||
}
|
||||
|
||||
// SimpleQuery is a query sent as only a string.
|
||||
// It has no parameters.
|
||||
type SimpleQuery string
|
||||
|
||||
func (q SimpleQuery) String() string {
|
||||
return string(q)
|
||||
}
|
||||
|
||||
// ColumnInfo contains metadata about a column.
|
||||
type ColumnInfo struct {
|
||||
Name string
|
||||
Type Type
|
||||
TableID int32
|
||||
FieldID int16
|
||||
}
|
||||
|
||||
// QueryResultWriter is used to write the results of a query back over the connection.
|
||||
type QueryResultWriter interface {
|
||||
// WriteHeader sets the column header information.
|
||||
WriteHeader(...ColumnInfo) error
|
||||
|
||||
// WriteRowText sends a row of data in textual format.
|
||||
WriteRowText(...string) error
|
||||
|
||||
// Tag assigns a tag to the query.
|
||||
// This should be called before the query is completed.
|
||||
Tag(tag string)
|
||||
}
|
||||
|
||||
// QueryHandler handles a query.
|
||||
type QueryHandler interface {
|
||||
// HandleQuery executes a query and writes the results back.
|
||||
HandleQuery(context.Context, QueryResultWriter, Query) error
|
||||
HandleSchema(context.Context, *Portal) error
|
||||
Version() string
|
||||
}
|
||||
|
||||
// queryResultWriter implements QueryResultWrtiter over postgres wire protocol.
|
||||
// The underlying message writer must be flushed by the caller once the query has finished.
|
||||
type queryResultWriter struct {
|
||||
w message.Writer
|
||||
te TypeEngine
|
||||
enc message.Encoder
|
||||
width int
|
||||
wroteHeaders bool
|
||||
tag string
|
||||
}
|
||||
|
||||
func (w *queryResultWriter) WriteHeader(info ...ColumnInfo) error {
|
||||
if w.wroteHeaders {
|
||||
return errors.New("double-write of query headers")
|
||||
}
|
||||
|
||||
// Translate column information into a row description message.
|
||||
desc := make([]message.ColumnDescription, len(info))
|
||||
for i, c := range info {
|
||||
t, err := w.te.TranslateType(c.Type)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "translating column type")
|
||||
}
|
||||
t.Name = c.Name
|
||||
t.TableID = c.TableID
|
||||
t.FieldID = c.FieldID
|
||||
desc[i] = t
|
||||
}
|
||||
|
||||
// Encode the row description.
|
||||
msg, err := w.enc.RowDescription(desc...)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "encoding query header")
|
||||
}
|
||||
|
||||
w.wroteHeaders = true
|
||||
w.width = len(desc)
|
||||
|
||||
// Write the row description.
|
||||
return w.w.WriteMessage(msg)
|
||||
}
|
||||
|
||||
func (w *queryResultWriter) WriteRowText(text ...string) error {
|
||||
// Check preconditions of the call.
|
||||
switch {
|
||||
case !w.wroteHeaders:
|
||||
return errors.New("writing rows without headers")
|
||||
case len(text) != w.width:
|
||||
return errors.Errorf("expected %d columns but found %d", w.width, len(text))
|
||||
}
|
||||
|
||||
// Encode the row data as text into a DataRow message.
|
||||
msg, err := w.enc.TextRow(text...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write the data row over the network.
|
||||
return w.w.WriteMessage(msg)
|
||||
}
|
||||
|
||||
func (w *queryResultWriter) Tag(tag string) {
|
||||
w.tag = tag
|
||||
}
|
||||
|
||||
var _ QueryResultWriter = (*queryResultWriter)(nil)
|
||||
132
pg/server.go
132
pg/server.go
|
|
@ -1,132 +0,0 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
package pg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/molecula/featurebase/v3/logger"
|
||||
)
|
||||
|
||||
// Server is a postgres wire protocol server.
|
||||
type Server struct {
|
||||
// QueryHandler will be used to serve query requests.
|
||||
QueryHandler QueryHandler
|
||||
|
||||
// TypeEngine is the type engine to use to result columns in query requests.
|
||||
TypeEngine TypeEngine
|
||||
|
||||
// TLSConfig is the TLS configuration to use to serve postgres TLS connections.
|
||||
TLSConfig *tls.Config
|
||||
|
||||
// StartupTimeout is the timeout to use for connection startup.
|
||||
// If a connection fails to set up a protocol before this completes, it will be terminated.
|
||||
StartupTimeout time.Duration
|
||||
|
||||
// ReadTimeout is the timeout to apply for active reads (reads during the lifetime of a command).
|
||||
// This timeout does not apply to an idling connection.
|
||||
ReadTimeout time.Duration
|
||||
|
||||
// WriteTimeout is the timeout to apply to network writes.
|
||||
WriteTimeout time.Duration
|
||||
|
||||
// MaxStartupSize is the maximum size of the startup packet (in bytes).
|
||||
// This defaults to 2^31-1 bytes, which is the maximum size allowed by the protocol.
|
||||
MaxStartupSize uint32
|
||||
|
||||
// ConnectionLimit is the maximum number of connections to allow at once.
|
||||
ConnectionLimit uint16
|
||||
|
||||
// Logger is the logger to use for error conditions and state changes.
|
||||
Logger logger.Logger
|
||||
|
||||
// CancellationManager is the cancellation manager to use.
|
||||
// If this is not set, no cancellations will be applied.
|
||||
CancellationManager CancellationManager
|
||||
lookerChannel chan struct{}
|
||||
mu sync.Mutex
|
||||
portals []*Portal
|
||||
}
|
||||
|
||||
// ServeConn serves a single connection.
|
||||
func (s *Server) ServeConn(ctx context.Context, conn net.Conn) error {
|
||||
return s.handle(ctx, conn)
|
||||
}
|
||||
|
||||
// Serve accepts postgres connections from a listener and processes them.
|
||||
// If the context is cancelled, this will stop accepting requests and wait until all connections have terminated.
|
||||
// No error will be returned if terminated by context cancellation.
|
||||
// This will close the connection for the caller.
|
||||
func (s *Server) Serve(ctx context.Context, l net.Listener) (err error) {
|
||||
// Ignore errors triggered by a shutdown.
|
||||
// Also propogate any error from terminating the listener.
|
||||
var cerr error
|
||||
defer func(ctx context.Context) {
|
||||
if ctx.Err() == context.Canceled {
|
||||
err = cerr
|
||||
}
|
||||
}(ctx)
|
||||
// TODO (twg) added for looker
|
||||
s.lookerChannel = make(chan struct{})
|
||||
|
||||
// Wait for the listener to be closed and all connections to shut down.
|
||||
var wg sync.WaitGroup
|
||||
defer wg.Wait()
|
||||
|
||||
// Wrap the context to propogate a shutdown to the listeners and connection handlers.
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
// Start a goroutine to shut down the listener when the context is canceled.
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
<-ctx.Done()
|
||||
cerr = l.Close()
|
||||
}()
|
||||
|
||||
// Set up a semaphore for the connection limit.
|
||||
var limit chan struct{}
|
||||
done := ctx.Done()
|
||||
if s.ConnectionLimit != 0 {
|
||||
limit = make(chan struct{}, s.ConnectionLimit)
|
||||
}
|
||||
|
||||
for {
|
||||
if limit != nil {
|
||||
// Wait for connection limit.
|
||||
if len(limit) == cap(limit) {
|
||||
s.Logger.Warnf("postgres connection limit reached")
|
||||
}
|
||||
select {
|
||||
case limit <- struct{}{}:
|
||||
case <-done:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Accept a connection.
|
||||
conn, err := l.Accept()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Handle the connection in another goroutine.
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if limit != nil {
|
||||
// Restore connection limit when done.
|
||||
defer func() { <-limit }()
|
||||
}
|
||||
err := s.handle(ctx, conn)
|
||||
if err != nil {
|
||||
s.Logger.Errorf("postgres connection terminated with error: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,282 +0,0 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
package pg_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"net"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/lib/pq"
|
||||
"github.com/molecula/featurebase/v3/logger"
|
||||
"github.com/molecula/featurebase/v3/pg"
|
||||
"github.com/molecula/featurebase/v3/pg/pgtest"
|
||||
)
|
||||
|
||||
// TestStartupTimeout tests that an incoming connection that does nothing times out and gets closed.
|
||||
func TestStartupTimeout(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
connect, shutdown, err := pgtest.ServeMem(&pg.Server{
|
||||
StartupTimeout: time.Millisecond,
|
||||
Logger: logger.NopLogger,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("starting in-memory postgres server: %v", err)
|
||||
}
|
||||
defer shutdown.Finish(t, "in-memory postgres server")
|
||||
|
||||
conn, err := connect()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to acquire connection: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// The server isn't sending anything, so this should block until the connection dies.
|
||||
conn.Read(make([]byte, 1024)) //nolint:errcheck
|
||||
}
|
||||
|
||||
// TestStartupInvalidLength tests that sending an HTTP GET request does not cause the server to allocate 1.2 GiB of memory.
|
||||
func TestStartupInvalidLength(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
res := testing.Benchmark(func(b *testing.B) {
|
||||
connect, shutdown, err := pgtest.ServeMem(&pg.Server{
|
||||
MaxStartupSize: 1024,
|
||||
Logger: logger.NopLogger,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("starting in-memory postgres server: %v", err)
|
||||
}
|
||||
defer shutdown.Finish(t, "in-memory postgres server")
|
||||
|
||||
b.ReportAllocs()
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
conn, err := connect()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to acquire connection: %v", err)
|
||||
}
|
||||
|
||||
_, err = conn.Write([]byte("GET "))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to write invalid length: %v", err)
|
||||
}
|
||||
|
||||
// The server isn't sending anything, so this should block until the connection dies.
|
||||
conn.Read(make([]byte, 1024)) //nolint:errcheck
|
||||
|
||||
err = conn.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to close connection: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
bpo := res.AllocedBytesPerOp()
|
||||
t.Logf("allocated %d bytes per op", bpo)
|
||||
if bpo > 1024*1024 {
|
||||
t.Errorf("allocated too much memory: %d bytes/connection", bpo)
|
||||
}
|
||||
}
|
||||
|
||||
// TestPQConnect tests connecting the Go SQL driver `pq` to this postgres server.
|
||||
func TestPQConnect(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
server := &pg.Server{
|
||||
StartupTimeout: time.Second,
|
||||
Logger: logger.NopLogger,
|
||||
}
|
||||
|
||||
addr, shutdown, err := pgtest.ServeTCP("localhost:0", server)
|
||||
if err != nil {
|
||||
t.Fatalf("starting postgres server: %v", err)
|
||||
}
|
||||
defer shutdown.Finish(t, "postgres server")
|
||||
|
||||
tcpAddr := addr.(*net.TCPAddr)
|
||||
|
||||
connector, err := pq.NewConnector(fmt.Sprintf("user=molecula dbname=pilosa sslmode=disable host=%s port=%d", tcpAddr.IP, tcpAddr.Port))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create connector: %v", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
conn, err := connector.Connect(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to connect to postgres: %v", err)
|
||||
}
|
||||
defer pgtest.ShutdownFunc(conn.Close).Finish(t, "postgres TLS conn")
|
||||
}
|
||||
|
||||
// TestPQConnectSSL tests connecting the Go SQL driver `pq` to this postgres server, with SSL enabled.
|
||||
func TestPQConnectSSL(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
server := &pg.Server{
|
||||
StartupTimeout: time.Second,
|
||||
Logger: logger.NopLogger,
|
||||
}
|
||||
|
||||
addr, shutdown, err := pgtest.ServeTLS("localhost:0", server)
|
||||
if err != nil {
|
||||
t.Fatalf("starting postgres server: %v", err)
|
||||
}
|
||||
defer shutdown.Finish(t, "postgres TLS server")
|
||||
|
||||
tcpAddr := addr.(*net.TCPAddr)
|
||||
|
||||
connector, err := pq.NewConnector(fmt.Sprintf("user=molecula dbname=pilosa sslmode=require host=%s port=%d", tcpAddr.IP, tcpAddr.Port))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create connector: %v", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
conn, err := connector.Connect(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to connect to postgres: %v", err)
|
||||
}
|
||||
defer pgtest.ShutdownFunc(conn.Close).Finish(t, "postgres TLS conn")
|
||||
}
|
||||
|
||||
// TestPSQLQuery tests sending a query from the `psql` command line tool.
|
||||
func TestPSQLQuery(t *testing.T) {
|
||||
// Check if psql is present.
|
||||
// Skip this test if it is not.
|
||||
_, err := exec.LookPath("psql")
|
||||
if err != nil {
|
||||
if err, ok := err.(*exec.Error); ok {
|
||||
if err.Err == exec.ErrNotFound {
|
||||
t.Skip("psql is not available")
|
||||
}
|
||||
}
|
||||
t.Fatalf("searching for psql: %v", err)
|
||||
}
|
||||
|
||||
t.Run("Query", func(t *testing.T) {
|
||||
server := &pg.Server{
|
||||
QueryHandler: pgtest.HandlerFunc(func(ctx context.Context, w pg.QueryResultWriter, q pg.Query) error {
|
||||
err := w.WriteHeader(pg.ColumnInfo{
|
||||
Name: "field",
|
||||
Type: pg.TypeCharoid,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = w.WriteRowText("h")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = w.WriteRowText("xyzzy")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}),
|
||||
TypeEngine: pg.PrimitiveTypeEngine{},
|
||||
StartupTimeout: time.Second,
|
||||
Logger: logger.NopLogger,
|
||||
}
|
||||
|
||||
addr, shutdown, err := pgtest.ServeTCP("localhost:0", server)
|
||||
if err != nil {
|
||||
t.Fatalf("starting postgres server: %v", err)
|
||||
}
|
||||
defer shutdown.Finish(t, "postgres server")
|
||||
|
||||
tcpAddr := addr.(*net.TCPAddr)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
cmd := exec.CommandContext(ctx, "psql", "-h", tcpAddr.IP.String(), "-p", strconv.Itoa(tcpAddr.Port), "-c", "test query")
|
||||
data, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("psql failed: %v", string(data))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Cancel", func(t *testing.T) {
|
||||
var term func() error
|
||||
var qerr error
|
||||
var qdone bool
|
||||
defer func() {
|
||||
if qerr != nil {
|
||||
t.Fatal(qerr)
|
||||
}
|
||||
if !qdone {
|
||||
t.Fatal("query not done")
|
||||
}
|
||||
}()
|
||||
|
||||
var mutex sync.Mutex
|
||||
mutex.Lock()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
server := &pg.Server{
|
||||
QueryHandler: pgtest.HandlerFunc(func(qctx context.Context, w pg.QueryResultWriter, q pg.Query) error {
|
||||
defer func() { qdone = true }()
|
||||
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
qerr = term()
|
||||
if qerr != nil {
|
||||
return qerr
|
||||
}
|
||||
select {
|
||||
case <-qctx.Done():
|
||||
case <-ctx.Done():
|
||||
qerr = ctx.Err()
|
||||
return qerr
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
TypeEngine: pg.PrimitiveTypeEngine{},
|
||||
StartupTimeout: time.Second,
|
||||
Logger: logger.NopLogger,
|
||||
CancellationManager: pg.NewLocalCancellationManager(rand.Reader),
|
||||
}
|
||||
|
||||
addr, shutdown, err := pgtest.ServeTCP("localhost:0", server)
|
||||
if err != nil {
|
||||
t.Fatalf("starting postgres server: %v", err)
|
||||
}
|
||||
defer shutdown.Finish(t, "postgres server")
|
||||
|
||||
tcpAddr := addr.(*net.TCPAddr)
|
||||
|
||||
cmd := exec.CommandContext(ctx, "psql", "-h", tcpAddr.IP.String(), "-p", strconv.Itoa(tcpAddr.Port), "-c", "test query")
|
||||
term = func() error { return cmd.Process.Signal(syscall.SIGINT) }
|
||||
var buf bytes.Buffer
|
||||
cmd.Stderr = &buf
|
||||
cmd.Stdout = &buf
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("starting postgres client: %v", err)
|
||||
}
|
||||
mutex.Unlock()
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
t.Fatalf("psql failed (%v): %s", err, buf.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
62
pg/type.go
62
pg/type.go
|
|
@ -1,62 +0,0 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
package pg
|
||||
|
||||
import "github.com/molecula/featurebase/v3/pg/message"
|
||||
|
||||
// Type represents a postgres type.
|
||||
type Type struct {
|
||||
// I am not entirely sure what should be in here long term.
|
||||
// For now, I am just going to leave it like this.
|
||||
Id int32
|
||||
Typelen int16
|
||||
}
|
||||
|
||||
// TypeCharoid is a postgres type for text.
|
||||
// found in postgres source src/include/catalog/pg_type.h
|
||||
var TypeCharoid = Type{Id: 18, Typelen: -1}
|
||||
var TypeNAMEOID = Type{Id: 19, Typelen: 64}
|
||||
var TypeINT4OID = Type{Id: 23, Typelen: 4}
|
||||
var TypeTEXTOID = Type{Id: 25, Typelen: -1}
|
||||
var TypeFLOAT8OID = Type{Id: 701, Typelen: 8}
|
||||
|
||||
// TypeEngine is a system for managing types.
|
||||
// This is necessary for compound types like arrays which need ID generation.
|
||||
type TypeEngine interface {
|
||||
// TranslateType populates a column description with type information.
|
||||
TranslateType(Type) (message.ColumnDescription, error)
|
||||
}
|
||||
|
||||
// PrimitiveTypeEngine is a simple type engine that only works on primitive types.
|
||||
type PrimitiveTypeEngine struct{}
|
||||
|
||||
// TranslateType translates a type to a column description.
|
||||
func (pte PrimitiveTypeEngine) TranslateType(t Type) (message.ColumnDescription, error) {
|
||||
var TypeLen int16
|
||||
var TypeID int32
|
||||
switch t {
|
||||
case TypeCharoid:
|
||||
TypeID = TypeCharoid.Id
|
||||
TypeLen = TypeCharoid.Typelen
|
||||
case TypeNAMEOID:
|
||||
TypeID = TypeNAMEOID.Id
|
||||
TypeLen = TypeNAMEOID.Typelen
|
||||
case TypeINT4OID:
|
||||
TypeID = TypeINT4OID.Id
|
||||
TypeLen = TypeINT4OID.Typelen
|
||||
case TypeTEXTOID:
|
||||
TypeID = TypeTEXTOID.Id
|
||||
TypeLen = TypeTEXTOID.Typelen
|
||||
case TypeFLOAT8OID:
|
||||
TypeID = TypeFLOAT8OID.Id
|
||||
TypeLen = TypeFLOAT8OID.Typelen
|
||||
default: // treat like TypeCharoid:
|
||||
TypeID = TypeCharoid.Id
|
||||
TypeLen = TypeCharoid.Typelen
|
||||
}
|
||||
return message.ColumnDescription{
|
||||
TypeID: TypeID,
|
||||
TypeLen: TypeLen,
|
||||
TypeModifier: -1,
|
||||
Mode: 0, // send as text
|
||||
}, nil
|
||||
}
|
||||
1329
planner.go
1329
planner.go
File diff suppressed because it is too large
Load diff
530
planner_test.go
530
planner_test.go
|
|
@ -1,530 +0,0 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
package pilosa_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/molecula/featurebase/v3"
|
||||
"github.com/molecula/featurebase/v3/test"
|
||||
)
|
||||
|
||||
func TestPlanner_Count(t *testing.T) {
|
||||
c := test.MustRunCluster(t, 1)
|
||||
defer c.Close()
|
||||
|
||||
index, err := c.GetHolder(0).CreateIndex("i", pilosa.IndexOptions{TrackExistence: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := index.CreateField("f", pilosa.OptFieldTypeInt(0, 1000)); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if _, err := index.CreateField("x", pilosa.OptFieldTypeInt(0, 1000)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Populate with data.
|
||||
if _, err := c.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{
|
||||
Index: "i",
|
||||
Query: `
|
||||
Set(1, f=10)
|
||||
Set(2, f=10)
|
||||
Set(3, f=11)
|
||||
Set(4, f=12)
|
||||
Set(5, f=12)
|
||||
Set(6, f=13)
|
||||
|
||||
Set(1, x=100)
|
||||
Set(2, x=200)
|
||||
`}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Run("ALL", func(t *testing.T) {
|
||||
q := `SELECT COUNT(*) AS "count" FROM i`
|
||||
stmt, err := c.GetNode(0).Server.PlanSQL(context.Background(), q)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
var n int
|
||||
if err := stmt.QueryRowContext(context.Background()).Scan(&n); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if got, want := n, 6; got != want {
|
||||
t.Fatalf("Scan()=%d, want %d", got, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("WHERE", func(t *testing.T) {
|
||||
t.Run("EQ", func(t *testing.T) {
|
||||
q := `SELECT COUNT(*) AS "count" FROM i WHERE f = 10`
|
||||
stmt, err := c.GetNode(0).Server.PlanSQL(context.Background(), q)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
var n int
|
||||
if err := stmt.QueryRowContext(context.Background()).Scan(&n); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if got, want := n, 2; got != want {
|
||||
t.Fatalf("Scan()=%d, want %d", got, want)
|
||||
}
|
||||
})
|
||||
t.Run("NE", func(t *testing.T) {
|
||||
q := `SELECT COUNT(*) AS "count" FROM i WHERE f != 10`
|
||||
stmt, err := c.GetNode(0).Server.PlanSQL(context.Background(), q)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
var n int
|
||||
if err := stmt.QueryRowContext(context.Background()).Scan(&n); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if got, want := n, 4; got != want {
|
||||
t.Fatalf("Scan()=%d, want %d", got, want)
|
||||
}
|
||||
})
|
||||
t.Run("LT", func(t *testing.T) {
|
||||
q := `SELECT COUNT(*) AS "count" FROM i WHERE f < 12`
|
||||
stmt, err := c.GetNode(0).Server.PlanSQL(context.Background(), q)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
var n int
|
||||
if err := stmt.QueryRowContext(context.Background()).Scan(&n); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if got, want := n, 3; got != want {
|
||||
t.Fatalf("Scan()=%d, want %d", got, want)
|
||||
}
|
||||
})
|
||||
t.Run("GT", func(t *testing.T) {
|
||||
q := `SELECT COUNT(*) AS "count" FROM i WHERE f > 12`
|
||||
stmt, err := c.GetNode(0).Server.PlanSQL(context.Background(), q)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
var n int
|
||||
if err := stmt.QueryRowContext(context.Background()).Scan(&n); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if got, want := n, 1; got != want {
|
||||
t.Fatalf("Scan()=%d, want %d", got, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("AND", func(t *testing.T) {
|
||||
q := `SELECT COUNT(*) AS "count" FROM i WHERE f = 10 AND x = 100`
|
||||
stmt, err := c.GetNode(0).Server.PlanSQL(context.Background(), q)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
var n int
|
||||
if err := stmt.QueryRowContext(context.Background()).Scan(&n); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if got, want := n, 1; got != want {
|
||||
t.Fatalf("Scan()=%d, want %d", got, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("OR", func(t *testing.T) {
|
||||
q := `SELECT COUNT(*) AS "count" FROM i WHERE f = 10 OR x = 200 OR f = 12`
|
||||
stmt, err := c.GetNode(0).Server.PlanSQL(context.Background(), q)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
var n int
|
||||
if err := stmt.QueryRowContext(context.Background()).Scan(&n); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if got, want := n, 4; got != want {
|
||||
t.Fatalf("Scan()=%d, want %d", got, want)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestPlanner_Select(t *testing.T) {
|
||||
c := test.MustRunCluster(t, 1)
|
||||
defer c.Close()
|
||||
|
||||
i0, err := c.GetHolder(0).CreateIndex("i0", pilosa.IndexOptions{TrackExistence: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := i0.CreateField("a", pilosa.OptFieldTypeInt(0, 1000)); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if _, err := i0.CreateField("b", pilosa.OptFieldTypeInt(0, 1000)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
i1, err := c.GetHolder(0).CreateIndex("i1", pilosa.IndexOptions{TrackExistence: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := i1.CreateField("x", pilosa.OptFieldTypeInt(0, 1000)); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if _, err := i1.CreateField("y", pilosa.OptFieldTypeInt(0, 1000)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Populate with data.
|
||||
if _, err := c.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{
|
||||
Index: "i0",
|
||||
Query: `
|
||||
Set(1, a=10)
|
||||
Set(1, b=100)
|
||||
Set(2, a=20)
|
||||
Set(2, b=200)
|
||||
`}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Run("UnqualifiedColumns", func(t *testing.T) {
|
||||
results, columns := mustQueryRows(t, c.GetNode(0).Server, `SELECT a, b, _id FROM i0`)
|
||||
if diff := cmp.Diff([][]interface{}{
|
||||
{int64(10), int64(100), int64(1)},
|
||||
{int64(20), int64(200), int64(2)},
|
||||
}, results); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff([]*pilosa.StmtColumn{
|
||||
{Name: "a", Type: "INT"},
|
||||
{Name: "b", Type: "INT"},
|
||||
{Name: "_id", Type: "INT"},
|
||||
}, columns); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("QualifiedColumns", func(t *testing.T) {
|
||||
results, columns := mustQueryRows(t, c.GetNode(0).Server, `SELECT i0._id, i0.a, i0.b FROM i0`)
|
||||
if diff := cmp.Diff([][]interface{}{
|
||||
{int64(1), int64(10), int64(100)},
|
||||
{int64(2), int64(20), int64(200)},
|
||||
}, results); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff([]*pilosa.StmtColumn{
|
||||
{Name: "_id", Type: "INT"},
|
||||
{Name: "a", Type: "INT"},
|
||||
{Name: "b", Type: "INT"},
|
||||
}, columns); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("UnqualifiedStar", func(t *testing.T) {
|
||||
results, columns := mustQueryRows(t, c.GetNode(0).Server, `SELECT * FROM i0`)
|
||||
if diff := cmp.Diff([][]interface{}{
|
||||
{int64(1), int64(10), int64(100)},
|
||||
{int64(2), int64(20), int64(200)},
|
||||
}, results); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff([]*pilosa.StmtColumn{
|
||||
{Name: "_id", Type: "INT"},
|
||||
{Name: "a", Type: "INT"},
|
||||
{Name: "b", Type: "INT"},
|
||||
}, columns); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("QualifiedStar", func(t *testing.T) {
|
||||
results, columns := mustQueryRows(t, c.GetNode(0).Server, `SELECT i0.* FROM i0`)
|
||||
if diff := cmp.Diff([][]interface{}{
|
||||
{int64(1), int64(10), int64(100)},
|
||||
{int64(2), int64(20), int64(200)},
|
||||
}, results); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff([]*pilosa.StmtColumn{
|
||||
{Name: "_id", Type: "INT"},
|
||||
{Name: "a", Type: "INT"},
|
||||
{Name: "b", Type: "INT"},
|
||||
}, columns); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("NoIdentifier", func(t *testing.T) {
|
||||
results, columns := mustQueryRows(t, c.GetNode(0).Server, `SELECT a, b FROM i0`)
|
||||
if diff := cmp.Diff([][]interface{}{
|
||||
{int64(10), int64(100)},
|
||||
{int64(20), int64(200)},
|
||||
}, results); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff([]*pilosa.StmtColumn{
|
||||
{Name: "a", Type: "INT"},
|
||||
{Name: "b", Type: "INT"},
|
||||
}, columns); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ErrFieldNotFound", func(t *testing.T) {
|
||||
_, err := c.GetNode(0).Server.PlanSQL(context.Background(), `SELECT xyz FROM i0`)
|
||||
if err == nil || !strings.Contains(err.Error(), `xyz: field not found`) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestPlanner_GroupBy(t *testing.T) {
|
||||
c := test.MustRunCluster(t, 1)
|
||||
defer c.Close()
|
||||
|
||||
i0, err := c.GetHolder(0).CreateIndex("i0", pilosa.IndexOptions{TrackExistence: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := i0.CreateField("x"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if _, err := i0.CreateField("y", pilosa.OptFieldTypeInt(0, 1000)); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if _, err := i0.CreateField("z", pilosa.OptFieldTypeInt(0, 1000)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Populate with data.
|
||||
if _, err := c.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{
|
||||
Index: "i0",
|
||||
Query: `
|
||||
Set(1, x=10)
|
||||
Set(1, x=20)
|
||||
Set(1, y=100)
|
||||
Set(1, z=500)
|
||||
|
||||
Set(2, x=10)
|
||||
Set(2, y=200)
|
||||
Set(2, z=500)
|
||||
|
||||
Set(3, x=20)
|
||||
Set(3, z=600)
|
||||
`}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Run("Count", func(t *testing.T) {
|
||||
results, columns := mustQueryRows(t, c.GetNode(0).Server, `SELECT COUNT(*), x FROM i0 GROUP BY x`)
|
||||
if diff := cmp.Diff([][]interface{}{
|
||||
{int64(2), int64(10)},
|
||||
{int64(2), int64(20)},
|
||||
}, results); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff([]*pilosa.StmtColumn{
|
||||
{Name: "count", Type: "INT"},
|
||||
{Name: "x", Type: "INT"},
|
||||
}, columns); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DistinctCount", func(t *testing.T) {
|
||||
results, columns := mustQueryRows(t, c.GetNode(0).Server, `SELECT COUNT(DISTINCT z), x FROM i0 GROUP BY x`)
|
||||
if diff := cmp.Diff([][]interface{}{
|
||||
{int64(1), int64(10)},
|
||||
{int64(2), int64(20)},
|
||||
}, results); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff([]*pilosa.StmtColumn{
|
||||
{Name: "count", Type: "INT"},
|
||||
{Name: "x", Type: "INT"},
|
||||
}, columns); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Sum", func(t *testing.T) {
|
||||
results, columns := mustQueryRows(t, c.GetNode(0).Server, `SELECT sum(y), x FROM i0 GROUP BY x`)
|
||||
if diff := cmp.Diff([][]interface{}{
|
||||
{int64(300), int64(10)},
|
||||
{int64(100), int64(20)},
|
||||
}, results); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff([]*pilosa.StmtColumn{
|
||||
{Name: "sum", Type: "INT"},
|
||||
{Name: "x", Type: "INT"},
|
||||
}, columns); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ReorderColumns", func(t *testing.T) {
|
||||
results, columns := mustQueryRows(t, c.GetNode(0).Server, `SELECT x, COUNT(*) FROM i0 GROUP BY x`)
|
||||
if diff := cmp.Diff([][]interface{}{
|
||||
{int64(10), int64(2)},
|
||||
{int64(20), int64(2)},
|
||||
}, results); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff([]*pilosa.StmtColumn{
|
||||
{Name: "x", Type: "INT"},
|
||||
{Name: "count", Type: "INT"},
|
||||
}, columns); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("NoResultColumn", func(t *testing.T) {
|
||||
results, columns := mustQueryRows(t, c.GetNode(0).Server, `SELECT COUNT(*) FROM i0 GROUP BY x`)
|
||||
if diff := cmp.Diff([][]interface{}{
|
||||
{int64(2)},
|
||||
{int64(2)},
|
||||
}, results); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff([]*pilosa.StmtColumn{
|
||||
{Name: "count", Type: "INT"},
|
||||
}, columns); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestPlanner_InnerJoin(t *testing.T) {
|
||||
c := test.MustRunCluster(t, 1)
|
||||
defer c.Close()
|
||||
|
||||
i0, err := c.GetHolder(0).CreateIndex("i0", pilosa.IndexOptions{TrackExistence: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := i0.CreateField("a", pilosa.OptFieldTypeInt(0, 1000)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
i1, err := c.GetHolder(0).CreateIndex("i1", pilosa.IndexOptions{TrackExistence: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := i1.CreateField("parentid", pilosa.OptFieldTypeInt(0, 1000)); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if _, err := i1.CreateField("x", pilosa.OptFieldTypeInt(0, 1000)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Populate with data.
|
||||
if _, err := c.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{
|
||||
Index: "i0",
|
||||
Query: `
|
||||
Set(1, a=10)
|
||||
Set(2, a=20)
|
||||
Set(3, a=30)
|
||||
`}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := c.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{
|
||||
Index: "i1",
|
||||
Query: `
|
||||
Set(1, parentid=1)
|
||||
Set(1, x=100)
|
||||
|
||||
Set(2, parentid=1)
|
||||
Set(2, x=200)
|
||||
|
||||
Set(3, parentid=2)
|
||||
Set(3, x=300)
|
||||
`}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Run("Count", func(t *testing.T) {
|
||||
results, columns := mustQueryRows(t, c.GetNode(0).Server, `SELECT COUNT(*) FROM i0 INNER JOIN i1 ON i0._id = i1.parentid`)
|
||||
if diff := cmp.Diff([][]interface{}{
|
||||
{int64(2)},
|
||||
}, results); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff([]*pilosa.StmtColumn{
|
||||
{Name: "count", Type: "INT"},
|
||||
}, columns); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("CountWithParentCondition", func(t *testing.T) {
|
||||
results, columns := mustQueryRows(t, c.GetNode(0).Server, `SELECT COUNT(*) FROM i0 INNER JOIN i1 ON i0._id = i1.parentid WHERE i0.a = 10`)
|
||||
if diff := cmp.Diff([][]interface{}{
|
||||
{int64(1)},
|
||||
}, results); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff([]*pilosa.StmtColumn{
|
||||
{Name: "count", Type: "INT"},
|
||||
}, columns); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func mustQueryRows(tb testing.TB, svr *pilosa.Server, q string) (results [][]interface{}, columns []*pilosa.StmtColumn) {
|
||||
tb.Helper()
|
||||
|
||||
stmt, err := svr.PlanSQL(context.Background(), q)
|
||||
if err != nil {
|
||||
tb.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
rows, err := stmt.QueryContext(context.Background())
|
||||
if err != nil {
|
||||
tb.Fatal(err)
|
||||
}
|
||||
|
||||
results = make([][]interface{}, 0)
|
||||
for rows.Next() {
|
||||
result := make([]interface{}, len(rows.Columns()))
|
||||
|
||||
// Create list of scan destination pointers.
|
||||
dsts := make([]interface{}, len(result))
|
||||
for i := range result {
|
||||
dsts[i] = &result[i]
|
||||
}
|
||||
|
||||
if err := rows.Scan(dsts...); err != nil {
|
||||
tb.Fatal(err)
|
||||
}
|
||||
|
||||
results = append(results, result)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
tb.Fatal(err)
|
||||
}
|
||||
|
||||
return results, rows.Columns()
|
||||
}
|
||||
28
server.go
28
server.go
|
|
@ -21,7 +21,9 @@ import (
|
|||
pnet "github.com/molecula/featurebase/v3/net"
|
||||
rbfcfg "github.com/molecula/featurebase/v3/rbf/cfg"
|
||||
"github.com/molecula/featurebase/v3/roaring"
|
||||
"github.com/molecula/featurebase/v3/sql2"
|
||||
"github.com/molecula/featurebase/v3/sql3"
|
||||
"github.com/molecula/featurebase/v3/sql3/parser"
|
||||
planner_types "github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
"github.com/molecula/featurebase/v3/stats"
|
||||
"github.com/molecula/featurebase/v3/storage"
|
||||
"github.com/pkg/errors"
|
||||
|
|
@ -89,8 +91,12 @@ type Server struct { // nolint: maligned
|
|||
// Threshold for logging long-running queries
|
||||
longQueryTime time.Duration
|
||||
queryHistoryLength int
|
||||
|
||||
executionPlannerFn ExecutionPlannerFn
|
||||
}
|
||||
|
||||
type ExecutionPlannerFn func(executor Executor, api *API, sql string) sql3.CompilePlanner
|
||||
|
||||
// Holder returns the holder for server.
|
||||
func (s *Server) Holder() *Holder {
|
||||
return s.holder
|
||||
|
|
@ -423,6 +429,13 @@ func OptServerPartitionAssigner(p string) ServerOption {
|
|||
}
|
||||
}
|
||||
|
||||
func OptServerExecutionPlannerFn(fn ExecutionPlannerFn) ServerOption {
|
||||
return func(s *Server) error {
|
||||
s.executionPlannerFn = fn
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// NewServer returns a new instance of Server.
|
||||
func NewServer(opts ...ServerOption) (*Server, error) {
|
||||
cluster := newCluster()
|
||||
|
|
@ -453,6 +466,10 @@ func NewServer(opts ...ServerOption) (*Server, error) {
|
|||
resetTranslationSyncCh: make(chan struct{}, 1),
|
||||
|
||||
logger: logger.NopLogger,
|
||||
|
||||
executionPlannerFn: func(e Executor, a *API, s string) sql3.CompilePlanner {
|
||||
return sql3.NewNopCompilePlanner()
|
||||
},
|
||||
}
|
||||
s.cluster.InternalClient = s.defaultClient
|
||||
|
||||
|
|
@ -1412,13 +1429,14 @@ func (srv *Server) GetTransaction(ctx context.Context, id string, remote bool) (
|
|||
return trns, nil
|
||||
}
|
||||
|
||||
// PlanSQL parses and prepares a SQL statement.
|
||||
func (s *Server) PlanSQL(ctx context.Context, q string) (*Stmt, error) {
|
||||
st, err := sql2.NewParser(strings.NewReader(q)).ParseStatement()
|
||||
// CompileExecutionPlan parses and compiles an execution plan from a SQL
|
||||
// statement using a new parser and planner.
|
||||
func (s *Server) CompileExecutionPlan(ctx context.Context, q string) (planner_types.PlanOperator, error) {
|
||||
st, err := parser.NewParser(strings.NewReader(q)).ParseStatement()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewPlanner(s.executor).PlanStatement(ctx, st)
|
||||
return s.executionPlannerFn(s.executor, s.executor.client.api, q).CompilePlan(ctx, st)
|
||||
}
|
||||
|
||||
// countOpenFiles on operating systems that support lsof.
|
||||
|
|
|
|||
|
|
@ -176,27 +176,10 @@ type Config struct {
|
|||
MutexFraction int `toml:"mutex-fraction"`
|
||||
} `toml:"profile"`
|
||||
|
||||
Postgres struct {
|
||||
// Bind is the address to which to bind a postgres endpoint.
|
||||
// If this is empty, no endpoint will be created.
|
||||
Bind string `toml:"bind"`
|
||||
// TLS configuration for postgres connections.
|
||||
TLS TLSConfig `toml:"tls"`
|
||||
|
||||
StartupTimeout toml.Duration `toml:"startup-timeout"`
|
||||
ReadTimeout toml.Duration `toml:"read-timeout"`
|
||||
WriteTimeout toml.Duration `toml:"write-timout"`
|
||||
|
||||
MaxStartupSize uint32 `toml:"max-startup-size"`
|
||||
|
||||
// ConnectionLimit is the maximum number of postgres connections to allow simultaneously.
|
||||
// Setting this to 0 disables the limit.
|
||||
// This mostly exists because other DBs seem to have it.
|
||||
ConnectionLimit uint16 `toml:"max-connections"`
|
||||
// SqlVersion is which type of sqlhandling to be applied.
|
||||
// The constant SqlV2 can be used to try the new experimental Molecula SQL handling
|
||||
SqlVersion uint16 `toml:"sql-version"`
|
||||
} `toml:"postgres"`
|
||||
SQL struct {
|
||||
// EndpointEnabled enables the /sql endpoint.
|
||||
EndpointEnabled bool `toml:"endpoint-enabled"`
|
||||
} `toml:"sql"`
|
||||
|
||||
// Storage.Backend determines which Tx implementation the holder/Index will
|
||||
// use; one of the available transactional-storage engines. Choices are
|
||||
|
|
@ -286,7 +269,6 @@ func (c *Config) validate() error {
|
|||
"Etcd.LPeerURL", c.Etcd.LPeerURL, // ":"
|
||||
"Etcd.APeerURL", c.Etcd.APeerURL, // ""
|
||||
"Etcd.ClusterURL", c.Etcd.ClusterURL,
|
||||
"Postgres.Bind", c.Postgres.Bind,
|
||||
}
|
||||
|
||||
ports := make(map[int]bool)
|
||||
|
|
@ -382,12 +364,7 @@ func NewConfig() *Config {
|
|||
c.Profile.BlockRate = 10000000 // 1 sample per 10 ms
|
||||
c.Profile.MutexFraction = 100 // 1% sampling
|
||||
|
||||
// Postgres config (off by default).
|
||||
c.Postgres.MaxStartupSize = 8 * 1024 * 1024
|
||||
c.Postgres.StartupTimeout = toml.Duration(5 * time.Second)
|
||||
c.Postgres.ReadTimeout = toml.Duration(10 * time.Second)
|
||||
c.Postgres.WriteTimeout = toml.Duration(10 * time.Second)
|
||||
// we don't really need a connection limit
|
||||
c.SQL.EndpointEnabled = false
|
||||
|
||||
c.Etcd.AClientURL = ""
|
||||
c.Etcd.LClientURL = "http://localhost:10301"
|
||||
|
|
|
|||
664
server/pg.go
664
server/pg.go
|
|
@ -1,664 +0,0 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
pilosa "github.com/molecula/featurebase/v3"
|
||||
"github.com/molecula/featurebase/v3/logger"
|
||||
"github.com/molecula/featurebase/v3/pg"
|
||||
"github.com/molecula/featurebase/v3/sql2"
|
||||
|
||||
//"github.com/molecula/featurebase/v3/pg"
|
||||
"github.com/molecula/featurebase/v3/pql"
|
||||
pb "github.com/molecula/featurebase/v3/proto"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// PostgresServer provides a postgres endpoint on pilosa.
|
||||
type PostgresServer struct {
|
||||
api *pilosa.API
|
||||
logger logger.Logger
|
||||
eg errgroup.Group
|
||||
s pg.Server
|
||||
stop context.CancelFunc
|
||||
}
|
||||
type SqlVersion uint16
|
||||
|
||||
const (
|
||||
SqlV1 SqlVersion = 0
|
||||
SqlV2 SqlVersion = 2
|
||||
)
|
||||
|
||||
// NewPostgresServer creates a postgres server.
|
||||
func NewPostgresServer(api *pilosa.API, logger logger.Logger, tls *tls.Config, sqlVersion SqlVersion) *PostgresServer {
|
||||
return &PostgresServer{
|
||||
api: api,
|
||||
logger: logger,
|
||||
s: pg.Server{
|
||||
QueryHandler: NewPostgresHandler(api, logger, sqlVersion),
|
||||
TypeEngine: pg.PrimitiveTypeEngine{},
|
||||
StartupTimeout: 5 * time.Second,
|
||||
ReadTimeout: 10 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
MaxStartupSize: 8 * 1024 * 1024,
|
||||
Logger: logger,
|
||||
TLSConfig: tls,
|
||||
|
||||
// This is somewhat limited right now: it does not work with load balancers.
|
||||
CancellationManager: pg.NewLocalCancellationManager(rand.Reader),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewPostgresHandler creates a postgres query handler wrapping the pilosa API.
|
||||
func NewPostgresHandler(api *pilosa.API, logger logger.Logger, sqlVersion SqlVersion) pg.QueryHandler {
|
||||
return &QueryDecodeHandler{
|
||||
Child: &PilosaQueryHandler{
|
||||
Api: api,
|
||||
logger: logger,
|
||||
sqlVersion: sqlVersion,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Start a postgres endpoint at the specified address.
|
||||
func (s *PostgresServer) Start(addr string) error {
|
||||
l, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "creating listener")
|
||||
}
|
||||
|
||||
s.logger.Infof("serving postgres wire protocol on %s", l.Addr())
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
s.stop = cancel
|
||||
|
||||
s.eg.Go(func() error { return s.s.Serve(ctx, l) })
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *PostgresServer) Close() error {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if s.stop == nil {
|
||||
return nil
|
||||
}
|
||||
s.stop()
|
||||
|
||||
s.logger.Infof("waiting for postgres connections to shut down")
|
||||
|
||||
return s.eg.Wait()
|
||||
}
|
||||
|
||||
func (s *PostgresServer) GetAPI() *pilosa.API {
|
||||
return s.api
|
||||
}
|
||||
|
||||
type pgPQLQuery struct {
|
||||
index string
|
||||
query string
|
||||
}
|
||||
|
||||
func (q pgPQLQuery) String() string {
|
||||
return fmt.Sprintf("[%s]%s", q.index, q.query)
|
||||
}
|
||||
|
||||
func pgDecodePQL(str string) (q pg.Query, err error) {
|
||||
defer func() {
|
||||
err = errors.Wrap(err, "not a valid PQL-over-postgres query")
|
||||
}()
|
||||
|
||||
if !strings.HasPrefix(str, "[") {
|
||||
return nil, errors.New("missing index specification")
|
||||
}
|
||||
idx := strings.IndexRune(str, ']')
|
||||
if idx == -1 {
|
||||
return nil, errors.New("unclosed bracket in index specification")
|
||||
}
|
||||
|
||||
return pgPQLQuery{
|
||||
index: str[1:idx],
|
||||
query: str[idx+1:],
|
||||
}, nil
|
||||
}
|
||||
|
||||
type PilosaQueryHandler struct {
|
||||
Api *pilosa.API
|
||||
logger logger.Logger
|
||||
sqlVersion SqlVersion
|
||||
}
|
||||
|
||||
func pgWriteDistinctTimestamp(w pg.QueryResultWriter, val pilosa.DistinctTimestamp) error {
|
||||
err := w.WriteHeader(pg.ColumnInfo{
|
||||
Name: val.Name,
|
||||
Type: pg.TypeCharoid,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "writing result header")
|
||||
}
|
||||
|
||||
for _, k := range val.Values {
|
||||
err = w.WriteRowText(k)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "writing key")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func pgWriteRow(w pg.QueryResultWriter, row *pilosa.Row) error {
|
||||
err := w.WriteHeader(pg.ColumnInfo{
|
||||
Name: "_id",
|
||||
Type: pg.TypeCharoid,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "writing result header")
|
||||
}
|
||||
|
||||
if row.Keys != nil {
|
||||
for _, k := range row.Keys {
|
||||
err = w.WriteRowText(k)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "writing key")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, col := range row.Columns() {
|
||||
err = w.WriteRowText(strconv.FormatUint(col, 10))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "writing column ID")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func pgWriteRows(w pg.QueryResultWriter, rows pilosa.RowIdentifiers) error {
|
||||
err := w.WriteHeader(pg.ColumnInfo{
|
||||
Name: rows.Field(),
|
||||
Type: pg.TypeCharoid,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "writing result header")
|
||||
}
|
||||
|
||||
if rows.Keys != nil {
|
||||
for _, k := range rows.Keys {
|
||||
err = w.WriteRowText(k)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "writing key")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, row := range rows.Rows {
|
||||
err = w.WriteRowText(strconv.FormatUint(row, 10))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "writing row ID")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func pgFormatVal(val interface{}) string {
|
||||
switch val := val.(type) {
|
||||
case bool:
|
||||
return strconv.FormatBool(val)
|
||||
case int64:
|
||||
return strconv.FormatInt(val, 10)
|
||||
case uint64:
|
||||
return strconv.FormatUint(val, 10)
|
||||
case string:
|
||||
return val
|
||||
case pql.Decimal:
|
||||
return val.String()
|
||||
default:
|
||||
data, _ := json.Marshal(val)
|
||||
return string(data)
|
||||
}
|
||||
}
|
||||
|
||||
func pgWriteExtractedTable(w pg.QueryResultWriter, tbl pilosa.ExtractedTable) error {
|
||||
headers := make([]pg.ColumnInfo, len(tbl.Fields)+1)
|
||||
headers[0] = pg.ColumnInfo{
|
||||
Name: "_id",
|
||||
Type: pg.TypeCharoid,
|
||||
}
|
||||
dataHeaders := headers[1:]
|
||||
for i, f := range tbl.Fields {
|
||||
dataHeaders[i] = pg.ColumnInfo{
|
||||
Name: f.Name,
|
||||
Type: pg.TypeCharoid,
|
||||
}
|
||||
}
|
||||
err := w.WriteHeader(headers...)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "writing result header")
|
||||
}
|
||||
|
||||
vals := make([]string, len(headers))
|
||||
dataVals := vals[1:]
|
||||
for _, col := range tbl.Columns {
|
||||
if col.Column.Keyed {
|
||||
vals[0] = col.Column.Key
|
||||
} else {
|
||||
vals[0] = strconv.FormatUint(col.Column.ID, 10)
|
||||
}
|
||||
for i, v := range col.Rows {
|
||||
dataVals[i] = pgFormatVal(v)
|
||||
}
|
||||
err = w.WriteRowText(vals...)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "writing result row")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func pgWriteGroupCount(w pg.QueryResultWriter, counts *pilosa.GroupCounts) error {
|
||||
groups := counts.Groups()
|
||||
if len(groups) == 0 {
|
||||
// Not enough information is available to construct the header.
|
||||
// This is a significant flaw in the data type.
|
||||
return nil
|
||||
}
|
||||
expectedLen := len(groups[0].Group) + 1
|
||||
|
||||
agg := counts.AggregateColumn()
|
||||
if agg != "" {
|
||||
expectedLen++
|
||||
}
|
||||
|
||||
headers := make([]pg.ColumnInfo, expectedLen)
|
||||
for i, g := range groups[0].Group {
|
||||
headers[i] = pg.ColumnInfo{
|
||||
Name: g.Field,
|
||||
Type: pg.TypeCharoid,
|
||||
}
|
||||
}
|
||||
next := len(groups[0].Group)
|
||||
headers[next] = pg.ColumnInfo{
|
||||
Name: "count",
|
||||
Type: pg.TypeCharoid,
|
||||
}
|
||||
if agg != "" {
|
||||
next++
|
||||
headers[next] = pg.ColumnInfo{
|
||||
Name: agg,
|
||||
Type: pg.TypeCharoid,
|
||||
}
|
||||
}
|
||||
err := w.WriteHeader(headers...)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "writing result header")
|
||||
}
|
||||
|
||||
vals := make([]string, len(headers))
|
||||
for _, gc := range groups {
|
||||
var j int
|
||||
var g pilosa.FieldRow
|
||||
for j, g = range gc.Group {
|
||||
var v string
|
||||
switch {
|
||||
case g.Value != nil:
|
||||
if g.FieldOptions.Type == pilosa.FieldTypeTimestamp {
|
||||
ts, err := pilosa.ValToTimestamp(g.FieldOptions.TimeUnit, int64(*g.Value)+g.FieldOptions.Base)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "translating val to timestamp")
|
||||
}
|
||||
|
||||
v = ts.Format(time.RFC3339Nano)
|
||||
} else {
|
||||
v = strconv.FormatInt(*g.Value, 10)
|
||||
}
|
||||
case g.RowKey != "":
|
||||
v = g.RowKey
|
||||
default:
|
||||
v = strconv.FormatUint(g.RowID, 10)
|
||||
}
|
||||
vals[j] = v
|
||||
}
|
||||
j++
|
||||
vals[j] = strconv.FormatUint(gc.Count, 10)
|
||||
if agg != "" {
|
||||
j++
|
||||
vals[j] = strconv.FormatInt(gc.Agg, 10)
|
||||
}
|
||||
|
||||
err := w.WriteRowText(vals...)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "writing group count result")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO(twg) move this to a better area
|
||||
func getPgType(sql2type string) pg.Type {
|
||||
ret := pg.TypeCharoid
|
||||
switch sql2type {
|
||||
case sql2.DataTypeInt:
|
||||
ret = pg.TypeINT4OID
|
||||
}
|
||||
return ret
|
||||
}
|
||||
func pgWriteStmtRows(w pg.QueryResultWriter, rows *pilosa.StmtRows) error {
|
||||
//TODO(twg) writeHeader
|
||||
//TODO(twg) writeColumns
|
||||
first := true
|
||||
var data []string
|
||||
var err error
|
||||
for rows.Next() {
|
||||
if first {
|
||||
columns := rows.Columns()
|
||||
//TODO (twg) types:=rows.Types()
|
||||
headers := make([]pg.ColumnInfo, len(columns))
|
||||
for i, column := range columns {
|
||||
pgType := getPgType(column.Type)
|
||||
headers[i] = pg.ColumnInfo{
|
||||
Name: column.Name,
|
||||
Type: pgType,
|
||||
}
|
||||
}
|
||||
err := w.WriteHeader(headers...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data = make([]string, len(headers))
|
||||
first = false
|
||||
}
|
||||
result := make([]interface{}, len(rows.Columns()))
|
||||
// Create list of scan destination pointers.
|
||||
dsts := make([]interface{}, len(result))
|
||||
for i := range result {
|
||||
dsts[i] = &result[i]
|
||||
}
|
||||
|
||||
if err := rows.Scan(dsts...); err != nil {
|
||||
return err
|
||||
}
|
||||
//TODO(twg) conversion should be happening in Scan as described in https://pkg.go.dev/database/sql
|
||||
// ....
|
||||
// Scan also converts between string and numeric types, as long as no information
|
||||
// would be lost. While Scan stringifies all numbers scanned from numeric database columns into *string,
|
||||
// scans into numeric types are checked for overflow. For example, a float64 with value 300 or a string
|
||||
// with value "300" can scan into a uint16, but not into a uint8, though float64(255) or "255" can scan
|
||||
// into a uint8. One exception is that scans of some float64 numbers to strings may lose information when stringifying.
|
||||
// In general, scan floating point columns into *float64.
|
||||
// ...
|
||||
for i, col := range dsts {
|
||||
var v string
|
||||
switch col := col.(type) {
|
||||
case nil:
|
||||
v = "null"
|
||||
//
|
||||
//v = strconv.FormatUint(col.Uint64Val, 10)
|
||||
case *interface{}:
|
||||
v = fmt.Sprintf("%v", *col)
|
||||
default:
|
||||
return errors.Errorf("unable to process value of type %T", col)
|
||||
}
|
||||
|
||||
data[i] = v
|
||||
}
|
||||
|
||||
err = w.WriteRowText(data...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func getPgTypeFromColumnInfo(sql2type string) pg.Type {
|
||||
switch sql2type {
|
||||
case sql2.DataTypeInt:
|
||||
return pg.TypeINT4OID
|
||||
default:
|
||||
return pg.TypeCharoid
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var _ = getPgTypeFromColumnInfo //make linter happy for this function will be needed in future
|
||||
|
||||
func pgWriteRowser(w pg.QueryResultWriter, result pb.ToRowser) error {
|
||||
var data []string
|
||||
return result.ToRows(func(row *pb.RowResponse) error {
|
||||
if data == nil {
|
||||
headers := make([]pg.ColumnInfo, len(row.Columns))
|
||||
for i, h := range row.Headers {
|
||||
headers[i] = pg.ColumnInfo{
|
||||
Name: h.Name,
|
||||
Type: pg.TypeCharoid, // TODO(twg) this needs to be updated with type
|
||||
// information so it works from psql client
|
||||
}
|
||||
}
|
||||
err := w.WriteHeader(headers...)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "writing headers")
|
||||
}
|
||||
|
||||
data = make([]string, len(headers))
|
||||
}
|
||||
|
||||
for i, col := range row.Columns {
|
||||
var v string
|
||||
switch col := col.ColumnVal.(type) {
|
||||
case nil:
|
||||
v = "null"
|
||||
case *pb.ColumnResponse_BoolVal:
|
||||
v = strconv.FormatBool(col.BoolVal)
|
||||
case *pb.ColumnResponse_DecimalVal:
|
||||
v = pql.NewDecimal(
|
||||
col.DecimalVal.Value,
|
||||
col.DecimalVal.Scale,
|
||||
).String()
|
||||
case *pb.ColumnResponse_Float64Val:
|
||||
v = strconv.FormatFloat(col.Float64Val, 'g', -1, 64)
|
||||
case *pb.ColumnResponse_Int64Val:
|
||||
v = strconv.FormatInt(col.Int64Val, 10)
|
||||
case *pb.ColumnResponse_Uint64Val:
|
||||
v = strconv.FormatUint(col.Uint64Val, 10)
|
||||
case *pb.ColumnResponse_StringVal:
|
||||
v = col.StringVal
|
||||
case *pb.ColumnResponse_StringArrayVal:
|
||||
data, _ := json.Marshal(col.StringArrayVal.Vals)
|
||||
v = string(data)
|
||||
case *pb.ColumnResponse_Uint64ArrayVal:
|
||||
data, _ := json.Marshal(col.Uint64ArrayVal.Vals)
|
||||
v = string(data)
|
||||
case *pb.ColumnResponse_TimestampVal:
|
||||
v = col.TimestampVal
|
||||
default:
|
||||
return errors.Errorf("unable to process value of type %T", col)
|
||||
}
|
||||
|
||||
data[i] = v
|
||||
}
|
||||
|
||||
return w.WriteRowText(data...)
|
||||
})
|
||||
}
|
||||
|
||||
func pgWriteResult(w pg.QueryResultWriter, result interface{}) error {
|
||||
switch result := result.(type) {
|
||||
case *pilosa.Row:
|
||||
return pgWriteRow(w, result)
|
||||
case pilosa.RowIdentifiers:
|
||||
return pgWriteRows(w, result)
|
||||
case pilosa.ExtractedTable:
|
||||
return pgWriteExtractedTable(w, result)
|
||||
case []pilosa.GroupCount:
|
||||
gc := pilosa.NewGroupCounts("", result...)
|
||||
return pgWriteGroupCount(w, gc)
|
||||
case *pilosa.GroupCounts:
|
||||
return pgWriteGroupCount(w, result)
|
||||
case pb.ToRowser: // we should avoid protobuf where we can...
|
||||
return pgWriteRowser(w, result)
|
||||
case *pilosa.StmtRows:
|
||||
return pgWriteStmtRows(w, result)
|
||||
case uint64:
|
||||
err := w.WriteHeader(pg.ColumnInfo{
|
||||
Name: "count",
|
||||
Type: pg.TypeCharoid,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "writing headers")
|
||||
}
|
||||
|
||||
err = w.WriteRowText(strconv.FormatUint(result, 10))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "writing count")
|
||||
}
|
||||
|
||||
return nil
|
||||
case int64:
|
||||
err := w.WriteHeader(pg.ColumnInfo{
|
||||
Name: "value",
|
||||
Type: pg.TypeCharoid,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "writing headers")
|
||||
}
|
||||
|
||||
err = w.WriteRowText(strconv.FormatInt(result, 10))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "writing count")
|
||||
}
|
||||
|
||||
return nil
|
||||
case bool:
|
||||
err := w.WriteHeader(pg.ColumnInfo{
|
||||
Name: "result",
|
||||
Type: pg.TypeCharoid,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "writing headers")
|
||||
}
|
||||
|
||||
err = w.WriteRowText(strconv.FormatBool(result))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "writing count")
|
||||
}
|
||||
|
||||
return nil
|
||||
case pilosa.DistinctTimestamp:
|
||||
return pgWriteDistinctTimestamp(w, result)
|
||||
case nil:
|
||||
return nil
|
||||
|
||||
default:
|
||||
return errors.Errorf("result type %T not yet supported", result)
|
||||
}
|
||||
}
|
||||
|
||||
func (pqh *PilosaQueryHandler) HandleQuery(ctx context.Context, w pg.QueryResultWriter, q pg.Query) error {
|
||||
switch q := q.(type) {
|
||||
case pgPQLQuery:
|
||||
resp, err := pqh.Api.Query(ctx, &pilosa.QueryRequest{
|
||||
Index: q.index,
|
||||
Query: q.query,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "executing query")
|
||||
}
|
||||
if len(resp.Results) != 1 {
|
||||
return errors.Errorf("expected 1 query result but found %d", len(resp.Results))
|
||||
}
|
||||
return errors.Wrap(pgWriteResult(w, resp.Results[0]), "writing query result")
|
||||
|
||||
case pg.SimpleQuery:
|
||||
if pqh.sqlVersion == SqlV2 {
|
||||
stmt, err := pqh.Api.Plan(ctx, string(q))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := stmt.QueryContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return errors.Wrap(pgWriteResult(w, resp), "writing sql2 query result")
|
||||
//version 2.0
|
||||
} else {
|
||||
//version 1.0
|
||||
resp, err := execSQL(ctx, pqh.Api, pqh.logger, string(q))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "executing query")
|
||||
}
|
||||
return errors.Wrap(pgWriteResult(w, resp), "writing query result")
|
||||
}
|
||||
|
||||
default:
|
||||
return errors.Errorf("query type %T not yet supported (query: %s)", q, q)
|
||||
}
|
||||
}
|
||||
|
||||
type QueryDecodeHandler struct {
|
||||
Child pg.QueryHandler
|
||||
}
|
||||
|
||||
func (qdh *QueryDecodeHandler) HandleQuery(ctx context.Context, w pg.QueryResultWriter, q pg.Query) error {
|
||||
switch qv := q.(type) {
|
||||
case pg.SimpleQuery:
|
||||
if strings.HasPrefix(string(qv), "[") {
|
||||
pqlQuery, err := pgDecodePQL(strings.TrimSuffix(string(qv), ";"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "decoding query")
|
||||
}
|
||||
q = pqlQuery
|
||||
}
|
||||
}
|
||||
|
||||
return qdh.Child.HandleQuery(ctx, w, q)
|
||||
}
|
||||
func (qdh *QueryDecodeHandler) Version() string {
|
||||
return qdh.Child.Version()
|
||||
}
|
||||
|
||||
func (pqh *PilosaQueryHandler) Version() string {
|
||||
if pqh.sqlVersion > 0 {
|
||||
return "v2"
|
||||
}
|
||||
return "v1"
|
||||
}
|
||||
func (pqh *PilosaQueryHandler) HandleSchema(ctx context.Context, portal *pg.Portal) error {
|
||||
schema, err := pqh.Api.Schema(context.Background(), false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, ii := range schema {
|
||||
dataRow, err := portal.Encoder.TextRow("featurebase", ii.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
portal.Add(dataRow)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (qdh *QueryDecodeHandler) HandleSchema(ctx context.Context, portal *pg.Portal) error {
|
||||
return qdh.Child.HandleSchema(ctx, portal)
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
package server
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
pilosa "github.com/molecula/featurebase/v3"
|
||||
"github.com/molecula/featurebase/v3/pg"
|
||||
)
|
||||
|
||||
// pg_internal_test.go tests unexported methods from server/pg.go
|
||||
|
||||
// TestQueryResultWriter implements the QueryResultWriter interface for testing
|
||||
type TestQueryResultWriter struct {
|
||||
Header []pg.ColumnInfo
|
||||
RowText []string
|
||||
TagTag string
|
||||
}
|
||||
|
||||
func (t *TestQueryResultWriter) WriteHeader(headers ...pg.ColumnInfo) error {
|
||||
t.Header = append(t.Header, headers...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TestQueryResultWriter) WriteRowText(rowTexts ...string) error {
|
||||
t.RowText = append(t.RowText, rowTexts...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TestQueryResultWriter) Tag(tag string) {
|
||||
t.TagTag = tag
|
||||
}
|
||||
|
||||
func TestPgWriteDistinctTimestamp(t *testing.T) {
|
||||
w := TestQueryResultWriter{}
|
||||
expected := pilosa.DistinctTimestamp{Name: "test", Values: []string{"date1", "date2", "date3"}}
|
||||
pgWriteDistinctTimestamp(&w, expected)
|
||||
if w.Header[0].Name != expected.Name {
|
||||
t.Fatalf("Header Name is wrong. got %v, want %v", w.Header[0], expected.Name)
|
||||
}
|
||||
if w.Header[0].Type != pg.TypeCharoid {
|
||||
t.Fatalf("Header Type is wrong. got %v, want %v", w.Header[0].Type, pg.TypeCharoid)
|
||||
}
|
||||
for i, value := range w.RowText {
|
||||
if value != expected.Values[i] {
|
||||
t.Fatalf("Value not written properly. got %v, want %v", value, expected.Values[i])
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,313 +0,0 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
package server_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
pilosa "github.com/molecula/featurebase/v3"
|
||||
"github.com/molecula/featurebase/v3/logger"
|
||||
"github.com/molecula/featurebase/v3/pg"
|
||||
"github.com/molecula/featurebase/v3/pg/pgtest"
|
||||
"github.com/molecula/featurebase/v3/server"
|
||||
"github.com/molecula/featurebase/v3/test"
|
||||
)
|
||||
|
||||
func TestPostgresHandler(t *testing.T) {
|
||||
m := test.RunCommand(t)
|
||||
defer m.Close()
|
||||
|
||||
pgh := server.NewPostgresHandler(m.API, logger.NewLogfLogger(t), server.SqlV1)
|
||||
|
||||
m.MustCreateIndex(t, "i", pilosa.IndexOptions{TrackExistence: true})
|
||||
m.MustCreateField(t, "i", "set")
|
||||
m.MustCreateField(t, "i", "keyset", pilosa.OptFieldKeys())
|
||||
m.MustCreateField(t, "i", "mutex", pilosa.OptFieldTypeMutex(pilosa.CacheTypeNone, 0))
|
||||
m.MustCreateField(t, "i", "keymutex", pilosa.OptFieldKeys(), pilosa.OptFieldTypeMutex(pilosa.CacheTypeNone, 0))
|
||||
m.MustCreateField(t, "i", "int", pilosa.OptFieldTypeInt(math.MinInt64, math.MaxInt64))
|
||||
m.MustCreateField(t, "i", "decimal", pilosa.OptFieldTypeDecimal(2))
|
||||
m.MustCreateField(t, "i", "time", pilosa.OptFieldTypeTime("YMDH", "0"))
|
||||
m.MustCreateField(t, "i", "bool", pilosa.OptFieldTypeBool())
|
||||
|
||||
m.MustCreateIndex(t, "j", pilosa.IndexOptions{TrackExistence: true, Keys: true})
|
||||
m.MustCreateField(t, "j", "set")
|
||||
|
||||
storeOK := pgtest.ResultSet{
|
||||
Columns: []pg.ColumnInfo{
|
||||
{
|
||||
Name: "result",
|
||||
Type: pg.TypeCharoid,
|
||||
},
|
||||
},
|
||||
Data: [][]string{
|
||||
{
|
||||
"true",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
Name string
|
||||
Queries []string
|
||||
Results []pgtest.ResultSet
|
||||
}{
|
||||
{
|
||||
Name: "Extract-Nothing",
|
||||
Queries: []string{
|
||||
`[i]Extract(All(), Rows(set))`,
|
||||
},
|
||||
Results: []pgtest.ResultSet{
|
||||
{
|
||||
Columns: []pg.ColumnInfo{
|
||||
{
|
||||
Name: "_id",
|
||||
Type: pg.TypeCharoid,
|
||||
},
|
||||
{
|
||||
Name: "set",
|
||||
Type: pg.TypeCharoid,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Store",
|
||||
Queries: []string{
|
||||
`[i]Store(ConstRow(columns=[1, 2, 3]), set=4)`,
|
||||
`[i]Store(ConstRow(columns=[0, 2, 4]), set=5)`,
|
||||
`[j]Store(ConstRow(columns=[1, 2, 3]), set=4)`,
|
||||
`[j]Store(ConstRow(columns=[0, 2, 4]), set=5)`,
|
||||
},
|
||||
Results: []pgtest.ResultSet{
|
||||
storeOK,
|
||||
storeOK,
|
||||
storeOK,
|
||||
storeOK,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Set",
|
||||
Queries: []string{
|
||||
`[i]Set(1, keyset="a")`,
|
||||
`[i]Set(2, keyset="b")`,
|
||||
`[i]Set(3, mutex=3)`,
|
||||
`[i]Set(4, keymutex="d")`,
|
||||
`[i]Set(1, int=5)`,
|
||||
`[i]Set(2, decimal=6.01)`,
|
||||
`[i]Set(3, time=7, 2016-01-01T00:00)`,
|
||||
`[i]Set(4, bool=false)`,
|
||||
},
|
||||
Results: []pgtest.ResultSet{
|
||||
storeOK,
|
||||
storeOK,
|
||||
storeOK,
|
||||
storeOK,
|
||||
storeOK,
|
||||
storeOK,
|
||||
storeOK,
|
||||
storeOK,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Extract",
|
||||
Queries: []string{
|
||||
`[i]Extract(
|
||||
All(),
|
||||
Rows(set), Rows(keyset),
|
||||
Rows(mutex), Rows(keymutex),
|
||||
Rows(int), Rows(decimal),
|
||||
Rows(time),
|
||||
Rows(bool)
|
||||
)`,
|
||||
},
|
||||
Results: []pgtest.ResultSet{
|
||||
{
|
||||
Columns: []pg.ColumnInfo{
|
||||
{Name: "_id", Type: pg.TypeCharoid},
|
||||
{Name: "set", Type: pg.TypeCharoid},
|
||||
{Name: "keyset", Type: pg.TypeCharoid},
|
||||
{Name: "mutex", Type: pg.TypeCharoid},
|
||||
{Name: "keymutex", Type: pg.TypeCharoid},
|
||||
{Name: "int", Type: pg.TypeCharoid},
|
||||
{Name: "decimal", Type: pg.TypeCharoid},
|
||||
{Name: "time", Type: pg.TypeCharoid},
|
||||
{Name: "bool", Type: pg.TypeCharoid},
|
||||
},
|
||||
Data: [][]string{
|
||||
{`1`, `[4]`, `["a"]`, `null`, `null`, `5`, `null`, `[]`, `null`},
|
||||
{`2`, `[4,5]`, `["b"]`, `null`, `null`, `null`, `6.01`, `[]`, `null`},
|
||||
{`3`, `[4]`, `[]`, `3`, `null`, `null`, `null`, `[7]`, `null`},
|
||||
{`4`, `[5]`, `[]`, `null`, `d`, `null`, `null`, `[]`, `false`},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "GroupBy",
|
||||
Queries: []string{
|
||||
`[i]GroupBy(Rows(set))`,
|
||||
},
|
||||
Results: []pgtest.ResultSet{
|
||||
{
|
||||
Columns: []pg.ColumnInfo{
|
||||
{Name: "set", Type: pg.TypeCharoid},
|
||||
{Name: "count", Type: pg.TypeCharoid},
|
||||
},
|
||||
Data: [][]string{
|
||||
{"4", "3"},
|
||||
{"5", "3"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Count",
|
||||
Queries: []string{
|
||||
`[i]Count(Row(set=4))`,
|
||||
`[i]Count(Row(int > 0))`,
|
||||
},
|
||||
Results: []pgtest.ResultSet{
|
||||
{
|
||||
Columns: []pg.ColumnInfo{
|
||||
{Name: "count", Type: pg.TypeCharoid},
|
||||
},
|
||||
Data: [][]string{{"3"}},
|
||||
},
|
||||
{
|
||||
Columns: []pg.ColumnInfo{
|
||||
{Name: "count", Type: pg.TypeCharoid},
|
||||
},
|
||||
Data: [][]string{{"1"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "FieldValue",
|
||||
Queries: []string{
|
||||
`[i]FieldValue(field=int, column=1)`,
|
||||
`[i]FieldValue(field=decimal, column=2)`,
|
||||
},
|
||||
Results: []pgtest.ResultSet{
|
||||
{
|
||||
Columns: []pg.ColumnInfo{
|
||||
{Name: "value", Type: pg.TypeCharoid},
|
||||
{Name: "count", Type: pg.TypeCharoid},
|
||||
},
|
||||
Data: [][]string{
|
||||
{"5", "1"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Columns: []pg.ColumnInfo{
|
||||
{Name: "value", Type: pg.TypeCharoid},
|
||||
{Name: "count", Type: pg.TypeCharoid},
|
||||
},
|
||||
Data: [][]string{
|
||||
{"6.01", "1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Rows",
|
||||
Queries: []string{
|
||||
`[i]Rows(set)`,
|
||||
`[i]Rows(keyset)`,
|
||||
},
|
||||
Results: []pgtest.ResultSet{
|
||||
{
|
||||
Columns: []pg.ColumnInfo{
|
||||
{Name: "set", Type: pg.TypeCharoid},
|
||||
},
|
||||
Data: [][]string{
|
||||
{"4"},
|
||||
{"5"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Columns: []pg.ColumnInfo{
|
||||
{Name: "keyset", Type: pg.TypeCharoid},
|
||||
},
|
||||
Data: [][]string{
|
||||
{"a"},
|
||||
{"b"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TopN",
|
||||
Queries: []string{
|
||||
`[i]TopN(set)`,
|
||||
`[i]TopN(keyset)`,
|
||||
},
|
||||
Results: []pgtest.ResultSet{
|
||||
{
|
||||
Columns: []pg.ColumnInfo{
|
||||
{Name: "set", Type: pg.TypeCharoid},
|
||||
{Name: "count", Type: pg.TypeCharoid},
|
||||
},
|
||||
Data: [][]string{
|
||||
{"5", "3"},
|
||||
{"4", "3"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Columns: []pg.ColumnInfo{
|
||||
{Name: "keyset", Type: pg.TypeCharoid},
|
||||
{Name: "count", Type: pg.TypeCharoid},
|
||||
},
|
||||
Data: [][]string{
|
||||
{"b", "1"},
|
||||
{"a", "1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "SQL",
|
||||
Queries: []string{
|
||||
`select _id from i;`,
|
||||
},
|
||||
Results: []pgtest.ResultSet{
|
||||
{
|
||||
Columns: []pg.ColumnInfo{
|
||||
{Name: "_id", Type: pg.TypeCharoid},
|
||||
},
|
||||
Data: [][]string{
|
||||
{`1`},
|
||||
{`2`},
|
||||
{`3`},
|
||||
{`4`},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
c := c
|
||||
t.Run(c.Name, func(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
for i, q := range c.Queries {
|
||||
var res pgtest.ResultSet
|
||||
err := pgh.HandleQuery(ctx, &res, pg.SimpleQuery(q))
|
||||
if err != nil {
|
||||
t.Errorf("query %q failed: %v", q, err)
|
||||
continue
|
||||
}
|
||||
|
||||
expected := c.Results[i]
|
||||
if !reflect.DeepEqual(res, expected) {
|
||||
t.Errorf("query %q returned incorrect results: expected %v but got %v", q, expected, res)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -39,6 +39,8 @@ import (
|
|||
"github.com/molecula/featurebase/v3/logger"
|
||||
pnet "github.com/molecula/featurebase/v3/net"
|
||||
"github.com/molecula/featurebase/v3/prometheus"
|
||||
"github.com/molecula/featurebase/v3/sql3"
|
||||
"github.com/molecula/featurebase/v3/sql3/planner"
|
||||
"github.com/molecula/featurebase/v3/statik"
|
||||
"github.com/molecula/featurebase/v3/stats"
|
||||
"github.com/molecula/featurebase/v3/statsd"
|
||||
|
|
@ -81,7 +83,6 @@ type Command struct {
|
|||
listenURI *pnet.URI
|
||||
tlsConfig *tls.Config
|
||||
closeTimeout time.Duration
|
||||
pgserver *PostgresServer
|
||||
|
||||
serverOptions []pilosa.ServerOption
|
||||
auth *authn.Auth
|
||||
|
|
@ -248,29 +249,6 @@ func (m *Command) Start() (err error) {
|
|||
}
|
||||
}()
|
||||
|
||||
// Initialize postgres.
|
||||
m.pgserver = nil
|
||||
if m.Config.Postgres.Bind != "" {
|
||||
var tlsConf *tls.Config
|
||||
if m.Config.Postgres.TLS.CertificatePath != "" {
|
||||
conf, err := GetTLSConfig(&m.Config.Postgres.TLS, m.logger)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "setting up postgres TLS")
|
||||
}
|
||||
tlsConf = conf
|
||||
}
|
||||
m.pgserver = NewPostgresServer(m.API, m.logger, tlsConf, SqlVersion(m.Config.Postgres.SqlVersion))
|
||||
m.pgserver.s.StartupTimeout = time.Duration(m.Config.Postgres.StartupTimeout)
|
||||
m.pgserver.s.ReadTimeout = time.Duration(m.Config.Postgres.ReadTimeout)
|
||||
m.pgserver.s.WriteTimeout = time.Duration(m.Config.Postgres.WriteTimeout)
|
||||
m.pgserver.s.MaxStartupSize = m.Config.Postgres.MaxStartupSize
|
||||
m.pgserver.s.ConnectionLimit = m.Config.Postgres.ConnectionLimit
|
||||
err := m.pgserver.Start(m.Config.Postgres.Bind)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "starting postgres")
|
||||
}
|
||||
}
|
||||
|
||||
_ = testhook.Opened(pilosa.NewAuditor(), m, nil)
|
||||
close(m.Started)
|
||||
return nil
|
||||
|
|
@ -461,6 +439,11 @@ func (m *Command) SetupServer() error {
|
|||
m.Config.Etcd.Id = m.Config.Name // TODO(twg) rethink this
|
||||
e := petcd.NewEtcd(m.Config.Etcd, m.logger, m.Config.Cluster.ReplicaN, version)
|
||||
|
||||
executionPlannerFn := func(e pilosa.Executor, a *pilosa.API, s string) sql3.CompilePlanner {
|
||||
fapi := &pilosa.FeatureBaseSchemaAPI{API: a}
|
||||
return planner.NewExecutionPlanner(e, fapi, a, s)
|
||||
}
|
||||
|
||||
serverOptions := []pilosa.ServerOption{
|
||||
pilosa.OptServerAntiEntropyInterval(time.Duration(m.Config.AntiEntropy.Interval)),
|
||||
pilosa.OptServerLongQueryTime(time.Duration(longQueryTime)),
|
||||
|
|
@ -488,6 +471,7 @@ func (m *Command) SetupServer() error {
|
|||
pilosa.OptServerQueryHistoryLength(m.Config.QueryHistoryLength),
|
||||
pilosa.OptServerPartitionAssigner(m.Config.Cluster.PartitionToNodeAssignment),
|
||||
pilosa.OptServerDisCo(e, e, e, e),
|
||||
pilosa.OptServerExecutionPlannerFn(executionPlannerFn),
|
||||
}
|
||||
|
||||
if m.Config.LookupDBDSN != "" {
|
||||
|
|
@ -549,9 +533,6 @@ func (m *Command) SetupServer() error {
|
|||
m.queryLogger.Infof("Configured IPs for allowed networks: %v", ac.ConfiguredIPs)
|
||||
}
|
||||
|
||||
// disable postgres binding if auth is enabled
|
||||
m.Config.Postgres.Bind = ""
|
||||
|
||||
// TLS must be enabled if auth is
|
||||
if m.Config.TLS.CertificatePath == "" || m.Config.TLS.CertificateKeyPath == "" {
|
||||
return fmt.Errorf("transport layer security (TLS) is not configured properly. TLS is required when AuthN/Z is enabled, current configuration: %v", m.Config.TLS)
|
||||
|
|
@ -586,6 +567,7 @@ func (m *Command) SetupServer() error {
|
|||
pilosa.OptHandlerAuthZ(&p),
|
||||
pilosa.OptHandlerSerializer(proto.Serializer{}),
|
||||
pilosa.OptHandlerRoaringSerializer(proto.RoaringSerializer),
|
||||
pilosa.OptHandlerSQLEnabled(m.Config.SQL.EndpointEnabled),
|
||||
)
|
||||
return errors.Wrap(err, "new handler")
|
||||
}
|
||||
|
|
@ -673,7 +655,6 @@ func (m *Command) Close() error {
|
|||
eg.Go(m.Handler.Close)
|
||||
eg.Go(m.Server.Close)
|
||||
eg.Go(m.API.Close)
|
||||
eg.Go(m.pgserver.Close)
|
||||
if closer, ok := m.logOutput.(io.Closer); ok {
|
||||
// If closer is os.Stdout or os.Stderr, don't close it.
|
||||
if closer != os.Stdout && closer != os.Stderr {
|
||||
|
|
|
|||
1148
sql2/ast_test.go
1148
sql2/ast_test.go
File diff suppressed because it is too large
Load diff
|
|
@ -1,167 +0,0 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
package sql2_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
sql "github.com/molecula/featurebase/v3/sql2"
|
||||
)
|
||||
|
||||
func TestScanner_Scan(t *testing.T) {
|
||||
t.Run("IDENT", func(t *testing.T) {
|
||||
t.Run("Unquoted", func(t *testing.T) {
|
||||
AssertScan(t, `foo_BAR123`, sql.IDENT, `foo_BAR123`)
|
||||
})
|
||||
t.Run("Quoted", func(t *testing.T) {
|
||||
AssertScan(t, `"crazy ~!#*&# column name"" foo"`, sql.QIDENT, `crazy ~!#*&# column name" foo`)
|
||||
})
|
||||
t.Run("NoEndQuote", func(t *testing.T) {
|
||||
AssertScan(t, `"unfinished`, sql.ILLEGAL, `"unfinished`)
|
||||
})
|
||||
t.Run("x", func(t *testing.T) {
|
||||
AssertScan(t, `x`, sql.IDENT, `x`)
|
||||
})
|
||||
t.Run("StartingX", func(t *testing.T) {
|
||||
AssertScan(t, `xyz`, sql.IDENT, `xyz`)
|
||||
})
|
||||
t.Run("WithComment", func(t *testing.T) {
|
||||
AssertScan(t, "-- this is a comment\n\n-- more comments\nfoo", sql.IDENT, `foo`)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("KEYWORD", func(t *testing.T) {
|
||||
AssertScan(t, `BEGIN`, sql.BEGIN, `BEGIN`)
|
||||
})
|
||||
|
||||
t.Run("STRING", func(t *testing.T) {
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
AssertScan(t, `'this is ''a'' string'`, sql.STRING, `this is 'a' string`)
|
||||
})
|
||||
t.Run("NoEndQuote", func(t *testing.T) {
|
||||
AssertScan(t, `'unfinished`, sql.ILLEGAL, `'unfinished`)
|
||||
})
|
||||
})
|
||||
t.Run("BLOB", func(t *testing.T) {
|
||||
t.Run("LowerX", func(t *testing.T) {
|
||||
AssertScan(t, `x'0123456789abcdef'`, sql.BLOB, `0123456789abcdef`)
|
||||
})
|
||||
t.Run("UpperX", func(t *testing.T) {
|
||||
AssertScan(t, `X'0123456789ABCDEF'`, sql.BLOB, `0123456789ABCDEF`)
|
||||
})
|
||||
t.Run("NoEndQuote", func(t *testing.T) {
|
||||
AssertScan(t, `x'0123`, sql.ILLEGAL, `x'0123`)
|
||||
})
|
||||
t.Run("BadHex", func(t *testing.T) {
|
||||
AssertScan(t, `x'hello`, sql.ILLEGAL, `x'h`)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("INTEGER", func(t *testing.T) {
|
||||
AssertScan(t, `123`, sql.INTEGER, `123`)
|
||||
})
|
||||
|
||||
t.Run("FLOAT", func(t *testing.T) {
|
||||
AssertScan(t, `123.456`, sql.FLOAT, `123.456`)
|
||||
AssertScan(t, `.1`, sql.FLOAT, `.1`)
|
||||
AssertScan(t, `123e456`, sql.FLOAT, `123e456`)
|
||||
AssertScan(t, `123E456`, sql.FLOAT, `123E456`)
|
||||
AssertScan(t, `123.456E78`, sql.FLOAT, `123.456E78`)
|
||||
AssertScan(t, `123.E45`, sql.FLOAT, `123.E45`)
|
||||
AssertScan(t, `123E+4`, sql.FLOAT, `123E+4`)
|
||||
AssertScan(t, `123E-4`, sql.FLOAT, `123E-4`)
|
||||
AssertScan(t, `123E`, sql.ILLEGAL, `123E`)
|
||||
AssertScan(t, `123E+`, sql.ILLEGAL, `123E+`)
|
||||
AssertScan(t, `123E-`, sql.ILLEGAL, `123E-`)
|
||||
})
|
||||
t.Run("BIND", func(t *testing.T) {
|
||||
AssertScan(t, `?'`, sql.BIND, `?`)
|
||||
AssertScan(t, `?123'`, sql.BIND, `?123`)
|
||||
AssertScan(t, `:foo_bar123'`, sql.BIND, `:foo_bar123`)
|
||||
AssertScan(t, `@bar'`, sql.BIND, `@bar`)
|
||||
AssertScan(t, `$baz'`, sql.BIND, `$baz`)
|
||||
})
|
||||
|
||||
t.Run("EOF", func(t *testing.T) {
|
||||
AssertScan(t, " \n\t\r", sql.EOF, ``)
|
||||
})
|
||||
|
||||
t.Run("SEMI", func(t *testing.T) {
|
||||
AssertScan(t, ";", sql.SEMI, ";")
|
||||
})
|
||||
t.Run("LP", func(t *testing.T) {
|
||||
AssertScan(t, "(", sql.LP, "(")
|
||||
})
|
||||
t.Run("RP", func(t *testing.T) {
|
||||
AssertScan(t, ")", sql.RP, ")")
|
||||
})
|
||||
t.Run("COMMA", func(t *testing.T) {
|
||||
AssertScan(t, ",", sql.COMMA, ",")
|
||||
})
|
||||
t.Run("NE", func(t *testing.T) {
|
||||
AssertScan(t, "!=", sql.NE, "!=")
|
||||
})
|
||||
t.Run("BITNOT", func(t *testing.T) {
|
||||
AssertScan(t, "!", sql.BITNOT, "!")
|
||||
})
|
||||
t.Run("EQ", func(t *testing.T) {
|
||||
AssertScan(t, "=", sql.EQ, "=")
|
||||
})
|
||||
t.Run("LE", func(t *testing.T) {
|
||||
AssertScan(t, "<=", sql.LE, "<=")
|
||||
})
|
||||
t.Run("LSHIFT", func(t *testing.T) {
|
||||
AssertScan(t, "<<", sql.LSHIFT, "<<")
|
||||
})
|
||||
t.Run("LT", func(t *testing.T) {
|
||||
AssertScan(t, "<", sql.LT, "<")
|
||||
})
|
||||
t.Run("GE", func(t *testing.T) {
|
||||
AssertScan(t, ">=", sql.GE, ">=")
|
||||
})
|
||||
t.Run("RSHIFT", func(t *testing.T) {
|
||||
AssertScan(t, ">>", sql.RSHIFT, ">>")
|
||||
})
|
||||
t.Run("GT", func(t *testing.T) {
|
||||
AssertScan(t, ">", sql.GT, ">")
|
||||
})
|
||||
t.Run("BITAND", func(t *testing.T) {
|
||||
AssertScan(t, "&", sql.BITAND, "&")
|
||||
})
|
||||
t.Run("CONCAT", func(t *testing.T) {
|
||||
AssertScan(t, "||", sql.CONCAT, "||")
|
||||
})
|
||||
t.Run("BITOR", func(t *testing.T) {
|
||||
AssertScan(t, "|", sql.BITOR, "|")
|
||||
})
|
||||
t.Run("PLUS", func(t *testing.T) {
|
||||
AssertScan(t, "+", sql.PLUS, "+")
|
||||
})
|
||||
t.Run("MINUS", func(t *testing.T) {
|
||||
AssertScan(t, "-", sql.MINUS, "-")
|
||||
})
|
||||
t.Run("STAR", func(t *testing.T) {
|
||||
AssertScan(t, "*", sql.STAR, "*")
|
||||
})
|
||||
t.Run("SLASH", func(t *testing.T) {
|
||||
AssertScan(t, "/", sql.SLASH, "/")
|
||||
})
|
||||
t.Run("REM", func(t *testing.T) {
|
||||
AssertScan(t, "%", sql.REM, "%")
|
||||
})
|
||||
t.Run("DOT", func(t *testing.T) {
|
||||
AssertScan(t, ".", sql.DOT, ".")
|
||||
})
|
||||
t.Run("ILLEGAL", func(t *testing.T) {
|
||||
AssertScan(t, "^", sql.ILLEGAL, "^")
|
||||
})
|
||||
}
|
||||
|
||||
// AssertScan asserts the value of the first scan to s.
|
||||
func AssertScan(tb testing.TB, s string, expectedTok sql.Token, expectedLit string) {
|
||||
tb.Helper()
|
||||
_, tok, lit := sql.NewScanner(strings.NewReader(s)).Scan()
|
||||
if tok != expectedTok || lit != expectedLit {
|
||||
tb.Fatalf("Scan(%q)=<%s,%s>, want <%s,%s>", s, tok, lit, expectedTok, expectedLit)
|
||||
}
|
||||
}
|
||||
501
sql3/errors.go
Normal file
501
sql3/errors.go
Normal file
|
|
@ -0,0 +1,501 @@
|
|||
package sql3
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/molecula/featurebase/v3/errors"
|
||||
"github.com/molecula/featurebase/v3/sql3/parser"
|
||||
)
|
||||
|
||||
const (
|
||||
ErrInternal errors.Code = "ErrInternal"
|
||||
|
||||
ErrDuplicateColumn errors.Code = "ErrDuplicateColumn"
|
||||
ErrUnknownType errors.Code = "ErrUnknownType"
|
||||
|
||||
ErrTypeIncompatibleWithBitwiseOperator errors.Code = "ErrTypeIncompatibleWithBitwiseOperator"
|
||||
ErrTypeIncompatibleWithLogicalOperator errors.Code = "ErrTypeIncompatibleWithLogicalOperator"
|
||||
ErrTypeIncompatibleWithEqualityOperator errors.Code = "ErrTypeIncompatibleWithEqualityOperator"
|
||||
ErrTypeIncompatibleWithComparisonOperator errors.Code = "ErrTypeIncompatibleWithComparisonOperator"
|
||||
ErrTypeIncompatibleWithArithmeticOperator errors.Code = "ErrTypeIncompatibleWithArithmeticOperator"
|
||||
ErrTypeIncompatibleWithConcatOperator errors.Code = "ErrTypeIncompatibleWithConcatOperator"
|
||||
ErrTypeIncompatibleWithLikeOperator errors.Code = "ErrTypeIncompatibleWithLikeOperator"
|
||||
ErrTypeIncompatibleWithBetweenOperator errors.Code = "ErrTypeIncompatibleWithBetweenOperator"
|
||||
ErrTypeCannotBeUsedAsRangeSubscript errors.Code = "ErrTypeCannotBeUsedAsRangeSubscript"
|
||||
ErrTypesAreNotEquatable errors.Code = "ErrTypesAreNotEquatable"
|
||||
ErrTypeMismatch errors.Code = "ErrTypeMismatch"
|
||||
ErrIncompatibleTypesForRangeSubscripts errors.Code = "ErrIncompatibleTypesForRangeSubscripts"
|
||||
ErrExpressionListExpected errors.Code = "ErrExpressionListExpected"
|
||||
ErrBooleanExpressionExpected errors.Code = "ErrBooleanExpressionExpected"
|
||||
ErrIntExpressionExpected errors.Code = "ErrIntExpressionExpected"
|
||||
ErrIntOrDecimalExpressionExpected errors.Code = "ErrIntOrDecimalExpressionExpected"
|
||||
ErrIntOrDecimalOrTimestampExpressionExpected errors.Code = "ErrIntOrDecimalOrTimestampExpressionExpected"
|
||||
ErrStringExpressionExpected errors.Code = "ErrStringExpressionExpected"
|
||||
ErrSetExpressionExpected errors.Code = "ErrSetExpressionExpected"
|
||||
ErrSingleRowExpected errors.Code = "ErrSingleRowExpected"
|
||||
|
||||
ErrInvalidCast errors.Code = "ErrInvalidCast"
|
||||
ErrInvalidTypeCoercion errors.Code = "ErrInvalidTypeCoercion"
|
||||
|
||||
ErrLiteralExpected errors.Code = "ErrLiteralExpected"
|
||||
ErrIntegerLiteral errors.Code = "ErrIntegerLiteral"
|
||||
ErrStringLiteral errors.Code = "ErrStringLiteral"
|
||||
ErrLiteralEmptySetNotAllowed errors.Code = "ErrLiteralEmptySetNotAllowed"
|
||||
ErrSetLiteralMustContainIntOrString errors.Code = "ErrSetLiteralMustContainIntOrString"
|
||||
|
||||
ErrTypeAssignmentIncompatible errors.Code = "ErrTypeAssignmentIncompatible"
|
||||
|
||||
ErrInvalidTimeUnit errors.Code = "ErrInvalidTimeUnit"
|
||||
ErrInvalidTimeEpoch errors.Code = "ErrInvalidTimeEpoch"
|
||||
ErrInvalidTimeQuantum errors.Code = "ErrInvalidTimeQuantum"
|
||||
ErrInvalidDuration errors.Code = "ErrInvalidDuration"
|
||||
|
||||
ErrInsertExprTargetCountMismatch errors.Code = "ErrInsertExprTargetCountMismatch"
|
||||
ErrInsertMustHaveIDColumn errors.Code = "ErrInsertMustHaveIDColumn"
|
||||
ErrInsertMustAtLeastOneNonIDColumn errors.Code = "ErrInsertMustAtLeastOneNonIDColumn"
|
||||
|
||||
ErrTableMustHaveIDColumn errors.Code = "ErrTableMustHaveIDColumn"
|
||||
ErrTableIDColumnType errors.Code = "ErrTableIDColumnType"
|
||||
ErrTableIDColumnConstraints errors.Code = "ErrTableIDColumnConstraints"
|
||||
ErrTableIDColumnAlter errors.Code = "ErrTableIDColumnAlter"
|
||||
ErrTableNotFound errors.Code = "ErrTableNotFound"
|
||||
ErrColumnNotFound errors.Code = "ErrColumnNotFound"
|
||||
ErrTableColumnNotFound errors.Code = "ErrTableColumnNotFound"
|
||||
ErrInvalidKeyPartitionsValue errors.Code = "ErrInvalidKeyPartitionsValue"
|
||||
ErrInvalidShardWidthValue errors.Code = "ErrInvalidShardWidthValue"
|
||||
|
||||
ErrBadColumnConstraint errors.Code = "ErrBadColumnConstraint"
|
||||
ErrConflictingColumnConstraint errors.Code = "ErrConflictingColumnConstraint"
|
||||
|
||||
// expected errors
|
||||
ErrExpectedColumnReference errors.Code = "ErrExpectedColumnReference"
|
||||
|
||||
// call errors
|
||||
ErrCallUnknownFunction errors.Code = "ErrCallUnknownFunction"
|
||||
ErrCallParameterCountMismatch errors.Code = "ErrCallParameterCountMismatch"
|
||||
ErrIdColumnNotValidForAggregateFunction errors.Code = "ErrIdColumnNotValidForAggregateFunction"
|
||||
ErrParameterTypeMistmatch errors.Code = "ErrParameterTypeMistmatch"
|
||||
ErrCallParameterValueInvalid errors.Code = "ErrCallParameterValueInvalid"
|
||||
|
||||
//optimizer errors
|
||||
ErrAggregateNotAllowedInGroupBy errors.Code = "ErrIdPercentileNotAllowedInGroupBy"
|
||||
)
|
||||
|
||||
func NewErrDuplicateColumn(line int, col int, column string) error {
|
||||
return errors.New(
|
||||
ErrDuplicateColumn,
|
||||
fmt.Sprintf("[%d:%d] duplicate column '%s'", line, col, column),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrUnknownType(line int, col int, typ string) error {
|
||||
return errors.New(
|
||||
ErrUnknownType,
|
||||
fmt.Sprintf("[%d:%d] unknown type '%s'", line, col, typ),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrInternal(msg string) error {
|
||||
preamble := "internal error"
|
||||
_, filename, line, ok := runtime.Caller(1)
|
||||
if ok {
|
||||
preamble = fmt.Sprintf("internal error (%s:%d)", filename, line)
|
||||
}
|
||||
errorMessage := fmt.Sprintf("%s %s", preamble, msg)
|
||||
return errors.New(
|
||||
ErrInternal,
|
||||
errorMessage,
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrInternalf(format string, a ...interface{}) error {
|
||||
preamble := "internal error"
|
||||
_, filename, line, ok := runtime.Caller(1)
|
||||
if ok {
|
||||
preamble = fmt.Sprintf("internal error (%s:%d)", filename, line)
|
||||
}
|
||||
errorMessage := fmt.Sprintf(format, a...)
|
||||
errorMessage = fmt.Sprintf("%s %s", preamble, errorMessage)
|
||||
return errors.New(
|
||||
ErrInternal,
|
||||
errorMessage,
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrTypeAssignmentIncompatible(line, col int, type1, type2 string) error {
|
||||
return errors.New(
|
||||
ErrTypeAssignmentIncompatible,
|
||||
fmt.Sprintf("[%d:%d] an expression of type '%s' cannot be assigned to type '%s'", line, col, type1, type2),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrInvalidCast(line, col int, from, to string) error {
|
||||
return errors.New(
|
||||
ErrInvalidCast,
|
||||
fmt.Sprintf("[%d:%d] '%s' cannot be cast to '%s'", line, col, from, to),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrInvalidTypeCoercion(line, col int, from, to string) error {
|
||||
return errors.New(
|
||||
ErrInvalidTypeCoercion,
|
||||
fmt.Sprintf("[%d:%d] unable to convert '%s' to type '%s'", line, col, from, to),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrLiteralExpected(line, col int) error {
|
||||
return errors.New(
|
||||
ErrLiteralExpected,
|
||||
fmt.Sprintf("[%d:%d] literal expression expected", line, col),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrIntegerLiteral(line, col int) error {
|
||||
return errors.New(
|
||||
ErrIntegerLiteral,
|
||||
fmt.Sprintf("[%d:%d] integer literal expected", line, col),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrStringLiteral(line, col int) error {
|
||||
return errors.New(
|
||||
ErrStringLiteral,
|
||||
fmt.Sprintf("[%d:%d] string literal expected", line, col),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrLiteralEmptySetNotAllowed(line, col int) error {
|
||||
return errors.New(
|
||||
ErrLiteralEmptySetNotAllowed,
|
||||
fmt.Sprintf("[%d:%d] set literal must contain at least one member", line, col),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrSetLiteralMustContainIntOrString(line, col int) error {
|
||||
return errors.New(
|
||||
ErrSetLiteralMustContainIntOrString,
|
||||
fmt.Sprintf("[%d:%d] set literal must contain ints or strings", line, col),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrTypeIncompatibleWithBitwiseOperator(line, col int, operator, type1 string) error {
|
||||
return errors.New(
|
||||
ErrTypeIncompatibleWithBitwiseOperator,
|
||||
fmt.Sprintf("[%d:%d] operator '%s' incompatible with type '%s'", line, col, operator, type1),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrTypeIncompatibleWithLogicalOperator(line, col int, operator, type1 string) error {
|
||||
return errors.New(
|
||||
ErrTypeIncompatibleWithLogicalOperator,
|
||||
fmt.Sprintf("[%d:%d] operator '%s' incompatible with type '%s'", line, col, operator, type1),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrTypeIncompatibleWithEqualityOperator(line, col int, operator, type1 string) error {
|
||||
return errors.New(
|
||||
ErrTypeIncompatibleWithEqualityOperator,
|
||||
fmt.Sprintf("[%d:%d] operator '%s' incompatible with type '%s'", line, col, operator, type1),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrTypeIncompatibleWithComparisonOperator(line, col int, operator, type1 string) error {
|
||||
return errors.New(
|
||||
ErrTypeIncompatibleWithComparisonOperator,
|
||||
fmt.Sprintf("[%d:%d] operator '%s' incompatible with type '%s'", line, col, operator, type1),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrTypeIncompatibleWithArithmeticOperator(line, col int, operator, type1 string) error {
|
||||
return errors.New(
|
||||
ErrTypeIncompatibleWithArithmeticOperator,
|
||||
fmt.Sprintf("[%d:%d] operator '%s' incompatible with type '%s'", line, col, operator, type1),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrTypeIncompatibleWithConcatOperator(line, col int, operator, type1 string) error {
|
||||
return errors.New(
|
||||
ErrTypeIncompatibleWithConcatOperator,
|
||||
fmt.Sprintf("[%d:%d] operator '%s' incompatible with type '%s'", line, col, operator, type1),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrTypeIncompatibleWithLikeOperator(line, col int, operator, type1 string) error {
|
||||
return errors.New(
|
||||
ErrTypeIncompatibleWithLikeOperator,
|
||||
fmt.Sprintf("[%d:%d] operator '%s' incompatible with type '%s'", line, col, operator, type1),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrTypeIncompatibleWithBetweenOperator(line, col int, operator, type1 string) error {
|
||||
return errors.New(
|
||||
ErrTypeIncompatibleWithBetweenOperator,
|
||||
fmt.Sprintf("[%d:%d] operator '%s' incompatible with type '%s'", line, col, operator, type1),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrTypeCannotBeUsedAsRangeSubscript(line, col int, type1 string) error {
|
||||
return errors.New(
|
||||
ErrTypeCannotBeUsedAsRangeSubscript,
|
||||
fmt.Sprintf("[%d:%d] type '%s' cannot be used a range subscript", line, col, type1),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrIncompatibleTypesForRangeSubscripts(line, col int, type1 string, type2 string) error {
|
||||
return errors.New(
|
||||
ErrIncompatibleTypesForRangeSubscripts,
|
||||
fmt.Sprintf("[%d:%d] incompatible types '%s' and '%s' useds as range subscripts", line, col, type1, type2),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrTypesAreNotEquatable(line, col int, type1, type2 string) error {
|
||||
return errors.New(
|
||||
ErrTypesAreNotEquatable,
|
||||
fmt.Sprintf("[%d:%d] types '%s' and '%s' are not equatable", line, col, type1, type2),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrTypeMismatch(line, col int, type1, type2 string) error {
|
||||
return errors.New(
|
||||
ErrTypeMismatch,
|
||||
fmt.Sprintf("[%d:%d] types '%s' and '%s' do not match", line, col, type1, type2),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrExpressionListExpected(line, col int) error {
|
||||
return errors.New(
|
||||
ErrExpressionListExpected,
|
||||
fmt.Sprintf("[%d:%d] expression list expected", line, col),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrBooleanExpressionExpected(line, col int) error {
|
||||
return errors.New(
|
||||
ErrBooleanExpressionExpected,
|
||||
fmt.Sprintf("[%d:%d] boolean expression expected", line, col),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrIntExpressionExpected(line, col int) error {
|
||||
return errors.New(
|
||||
ErrIntExpressionExpected,
|
||||
fmt.Sprintf("[%d:%d] integer expression expected", line, col),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrIntOrDecimalExpressionExpected(line, col int) error {
|
||||
return errors.New(
|
||||
ErrIntOrDecimalExpressionExpected,
|
||||
fmt.Sprintf("[%d:%d] integer or decimal expression expected", line, col),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrIntOrDecimalOrTimestampExpressionExpected(line, col int) error {
|
||||
return errors.New(
|
||||
ErrIntOrDecimalOrTimestampExpressionExpected,
|
||||
fmt.Sprintf("[%d:%d] integer, decimal or timestamp expression expected", line, col),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrStringExpressionExpected(line, col int) error {
|
||||
return errors.New(
|
||||
ErrStringExpressionExpected,
|
||||
fmt.Sprintf("[%d:%d] string expression expected", line, col),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrSetExpressionExpected(line, col int) error {
|
||||
return errors.New(
|
||||
ErrSetExpressionExpected,
|
||||
fmt.Sprintf("[%d:%d] set expression expected", line, col),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrSingleRowExpected(line, col int) error {
|
||||
return errors.New(
|
||||
ErrSingleRowExpected,
|
||||
fmt.Sprintf("[%d:%d] single row expected", line, col),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrInvalidTimeUnit(line, col int, unit string) error {
|
||||
return errors.New(
|
||||
ErrInvalidTimeUnit,
|
||||
fmt.Sprintf("[%d:%d] '%s' is not a valid time unit", line, col, unit),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrInvalidTimeEpoch(line, col int, epoch string) error {
|
||||
return errors.New(
|
||||
ErrInvalidTimeEpoch,
|
||||
fmt.Sprintf("[%d:%d] '%s' is not a valid epoch", line, col, epoch),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrInvalidTimeQuantum(line, col int, quantum string) error {
|
||||
return errors.New(
|
||||
ErrInvalidTimeQuantum,
|
||||
fmt.Sprintf("[%d:%d] '%s' is not a valid time quantum", line, col, quantum),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrInvalidDuration(line, col int, duration string) error {
|
||||
return errors.New(
|
||||
ErrInvalidDuration,
|
||||
fmt.Sprintf("[%d:%d] '%s' is not a valid time duration", line, col, duration),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrInsertExprTargetCountMismatch(line int, col int) error {
|
||||
return errors.New(
|
||||
ErrInsertExprTargetCountMismatch,
|
||||
fmt.Sprintf("[%d:%d] mismatch in the count of expressions and target columns", line, col),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrInsertMustHaveIDColumn(line int, col int) error {
|
||||
return errors.New(
|
||||
ErrInsertMustHaveIDColumn,
|
||||
fmt.Sprintf("[%d:%d] insert column list must have '_id' column specified", line, col),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrInsertMustAtLeastOneNonIDColumn(line int, col int) error {
|
||||
return errors.New(
|
||||
ErrInsertMustAtLeastOneNonIDColumn,
|
||||
fmt.Sprintf("[%d:%d] insert column list must have at least one non '_id' column specified", line, col),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrTableMustHaveIDColumn(line, col int) error {
|
||||
return errors.New(
|
||||
ErrTableMustHaveIDColumn,
|
||||
fmt.Sprintf("[%d:%d] _id column must be specified", line, col),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrTableIDColumnType(line, col int) error {
|
||||
return errors.New(
|
||||
ErrTableIDColumnType,
|
||||
fmt.Sprintf("[%d:%d] _id column must be specified with type ID or STRING", line, col),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrTableIDColumnConstraints(line, col int) error {
|
||||
return errors.New(
|
||||
ErrTableIDColumnConstraints,
|
||||
fmt.Sprintf("[%d:%d] _id column must be specified with no constraints", line, col),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrTableIDColumnAlter(line, col int) error {
|
||||
return errors.New(
|
||||
ErrTableIDColumnAlter,
|
||||
fmt.Sprintf("[%d:%d] _id column cannot be added to an existing table", line, col),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrTableNotFound(line, col int, tableName string) error {
|
||||
return errors.New(
|
||||
ErrTableNotFound,
|
||||
fmt.Sprintf("[%d:%d] table '%s' not found", line, col, tableName),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrColumnNotFound(line, col int, columnName string) error {
|
||||
return errors.New(
|
||||
ErrColumnNotFound,
|
||||
fmt.Sprintf("[%d:%d] column '%s' not found", line, col, columnName),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrTableColumnNotFound(line, col int, tableName string, columnName string) error {
|
||||
return errors.New(
|
||||
ErrTableColumnNotFound,
|
||||
fmt.Sprintf("[%d:%d] column '%s' not found in table '%s'", line, col, columnName, tableName),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrInvalidKeyPartitionsValue(line, col int, keypartitions int64) error {
|
||||
return errors.New(
|
||||
ErrInvalidKeyPartitionsValue,
|
||||
fmt.Sprintf("[%d:%d] invalid value '%d' for key partitions (should be a number between 1-10000)", line, col, keypartitions),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrInvalidShardWidthValue(line, col int, shardwidth int64) error {
|
||||
return errors.New(
|
||||
ErrInvalidShardWidthValue,
|
||||
fmt.Sprintf("[%d:%d] invalid value '%d' for shardwidth (should be a number that is a power of 2 and greater or equal to 2^16)", line, col, shardwidth),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrBadColumnConstraint(line, col int, constraint, columnType string) error {
|
||||
return errors.New(
|
||||
ErrBadColumnConstraint,
|
||||
fmt.Sprintf("[%d:%d] '%s' constraint cannot be applied to a column of type '%s'", line, col, constraint, columnType),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrConflictingColumnConstraint(line, col int, token1, token2 parser.Token) error {
|
||||
return errors.New(
|
||||
ErrConflictingColumnConstraint,
|
||||
fmt.Sprintf("[%d:%d] '%s' constraint conflicts with '%s'", line, col, token1, token2),
|
||||
)
|
||||
}
|
||||
|
||||
// expected
|
||||
|
||||
func NewErrExpectedColumnReference(line, col int) error {
|
||||
return errors.New(
|
||||
ErrExpectedColumnReference,
|
||||
fmt.Sprintf("[%d:%d] column reference expected", line, col),
|
||||
)
|
||||
}
|
||||
|
||||
// calls
|
||||
|
||||
func NewErrCallParameterCountMismatch(line, col int, functionName string, formalCount, actualCount int) error {
|
||||
return errors.New(
|
||||
ErrCallParameterCountMismatch,
|
||||
fmt.Sprintf("[%d:%d] '%s': count of formal parameters (%d) does not match count of actual parameters (%d)", line, col, functionName, formalCount, actualCount),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrCallUnknownFunction(line, col int, functionName string) error {
|
||||
return errors.New(
|
||||
ErrCallUnknownFunction,
|
||||
fmt.Sprintf("[%d:%d] unknown function '%s'", line, col, functionName),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrIdColumnNotValidForAggregateFunction(line, col int, functionName string) error {
|
||||
return errors.New(
|
||||
ErrIdColumnNotValidForAggregateFunction,
|
||||
fmt.Sprintf("[%d:%d] _id column cannot be used in aggregate function '%s'", line, col, functionName),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrParameterTypeMistmatch(line, col int, type1, type2 string) error {
|
||||
return errors.New(
|
||||
ErrParameterTypeMistmatch,
|
||||
fmt.Sprintf("[%d:%d] an expression of type '%s' cannot be passed to a parameter of type '%s'", line, col, type1, type2),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrCallParameterValueInvalid(line, col int, badParameterValue string, parameterName string) error {
|
||||
return errors.New(
|
||||
ErrCallParameterValueInvalid,
|
||||
fmt.Sprintf("[%d:%d] invalid value '%s' for parameter '%s'", line, col, badParameterValue, parameterName),
|
||||
)
|
||||
}
|
||||
|
||||
// optimizer
|
||||
|
||||
func NewErrAggregateNotAllowedInGroupBy(line, col int, aggName string) error {
|
||||
return errors.New(
|
||||
ErrAggregateNotAllowedInGroupBy,
|
||||
fmt.Sprintf("[%d:%d] aggregate '%s' not allowed in GROUP BY", line, col, aggName),
|
||||
)
|
||||
}
|
||||
27
sql3/interfaces.go
Normal file
27
sql3/interfaces.go
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
// Package sql3 contains the latest version of FeatureBase SQL support.
|
||||
package sql3
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/molecula/featurebase/v3/sql3/parser"
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
)
|
||||
|
||||
type CompilePlanner interface {
|
||||
CompilePlan(context.Context, parser.Statement) (types.PlanOperator, error)
|
||||
}
|
||||
|
||||
// Ensure type implements interface.
|
||||
var _ CompilePlanner = (*NopCompilePlanner)(nil)
|
||||
|
||||
// NopCompilePlanner is a no-op implementation of the CompilePlanner interface.
|
||||
type NopCompilePlanner struct{}
|
||||
|
||||
func NewNopCompilePlanner() *NopCompilePlanner {
|
||||
return &NopCompilePlanner{}
|
||||
}
|
||||
|
||||
func (p *NopCompilePlanner) CompilePlan(ctx context.Context, stmt parser.Statement) (types.PlanOperator, error) {
|
||||
return nil, nil
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
1246
sql3/parser/ast_test.go
Normal file
1246
sql3/parser/ast_test.go
Normal file
File diff suppressed because it is too large
Load diff
182
sql3/parser/astdatatype.go
Normal file
182
sql3/parser/astdatatype.go
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/molecula/featurebase/v3/pql"
|
||||
)
|
||||
|
||||
const (
|
||||
FieldTypeBool = "BOOL"
|
||||
FieldTypeDecimal = "DECIMAL"
|
||||
FieldTypeID = "ID"
|
||||
FieldTypeIDSet = "IDSET"
|
||||
FieldTypeInt = "INT"
|
||||
FieldTypeString = "STRING"
|
||||
FieldTypeStringSet = "STRINGSET"
|
||||
FieldTypeTimestamp = "TIMESTAMP"
|
||||
)
|
||||
|
||||
func IsValidTypeName(typeName string) bool {
|
||||
switch strings.ToUpper(typeName) {
|
||||
case FieldTypeBool,
|
||||
FieldTypeDecimal,
|
||||
FieldTypeID,
|
||||
FieldTypeIDSet,
|
||||
FieldTypeInt,
|
||||
FieldTypeString,
|
||||
FieldTypeStringSet,
|
||||
FieldTypeTimestamp:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
type ExprDataType interface {
|
||||
exprDataType()
|
||||
TypeName() string
|
||||
}
|
||||
|
||||
func (*DataTypeVoid) exprDataType() {}
|
||||
func (*DataTypeRange) exprDataType() {}
|
||||
func (*DataTypeBool) exprDataType() {}
|
||||
func (*DataTypeDecimal) exprDataType() {}
|
||||
func (*DataTypeID) exprDataType() {}
|
||||
func (*DataTypeIDSet) exprDataType() {}
|
||||
func (*DataTypeInt) exprDataType() {}
|
||||
func (*DataTypeString) exprDataType() {}
|
||||
func (*DataTypeStringSet) exprDataType() {}
|
||||
func (*DataTypeTimestamp) exprDataType() {}
|
||||
|
||||
type DataTypeVoid struct {
|
||||
}
|
||||
|
||||
func NewDataTypeVoid() *DataTypeVoid {
|
||||
return &DataTypeVoid{}
|
||||
}
|
||||
|
||||
func (*DataTypeVoid) TypeName() string {
|
||||
return "VOID"
|
||||
}
|
||||
|
||||
type DataTypeRange struct {
|
||||
SubscriptType ExprDataType
|
||||
}
|
||||
|
||||
func NewDataTypeRange(subscriptType ExprDataType) *DataTypeRange {
|
||||
return &DataTypeRange{
|
||||
SubscriptType: subscriptType,
|
||||
}
|
||||
}
|
||||
|
||||
func (dt *DataTypeRange) TypeName() string {
|
||||
return fmt.Sprintf("RANGE(%s)", dt.SubscriptType.TypeName())
|
||||
}
|
||||
|
||||
type DataTypeBool struct {
|
||||
}
|
||||
|
||||
func NewDataTypeBool() *DataTypeBool {
|
||||
return &DataTypeBool{}
|
||||
}
|
||||
|
||||
func (*DataTypeBool) TypeName() string {
|
||||
return FieldTypeBool
|
||||
}
|
||||
|
||||
type DataTypeDecimal struct {
|
||||
Scale int64
|
||||
}
|
||||
|
||||
func NewDataTypeDecimal(scale int64) *DataTypeDecimal {
|
||||
return &DataTypeDecimal{
|
||||
Scale: scale,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DataTypeDecimal) TypeName() string {
|
||||
return fmt.Sprintf("%s(%d)", FieldTypeDecimal, d.Scale)
|
||||
}
|
||||
|
||||
type DataTypeID struct {
|
||||
}
|
||||
|
||||
func NewDataTypeID() *DataTypeID {
|
||||
return &DataTypeID{}
|
||||
}
|
||||
|
||||
func (*DataTypeID) TypeName() string {
|
||||
return FieldTypeID
|
||||
}
|
||||
|
||||
type DataTypeIDSet struct {
|
||||
}
|
||||
|
||||
func NewDataTypeIDSet() *DataTypeIDSet {
|
||||
return &DataTypeIDSet{}
|
||||
}
|
||||
|
||||
func (*DataTypeIDSet) TypeName() string {
|
||||
return FieldTypeIDSet
|
||||
}
|
||||
|
||||
type DataTypeInt struct {
|
||||
}
|
||||
|
||||
func NewDataTypeInt() *DataTypeInt {
|
||||
return &DataTypeInt{}
|
||||
}
|
||||
|
||||
func (*DataTypeInt) TypeName() string {
|
||||
return FieldTypeInt
|
||||
}
|
||||
|
||||
type DataTypeString struct {
|
||||
}
|
||||
|
||||
func NewDataTypeString() *DataTypeString {
|
||||
return &DataTypeString{}
|
||||
}
|
||||
|
||||
func (*DataTypeString) TypeName() string {
|
||||
return FieldTypeString
|
||||
}
|
||||
|
||||
type DataTypeStringSet struct {
|
||||
}
|
||||
|
||||
func NewDataTypeStringSet() *DataTypeStringSet {
|
||||
return &DataTypeStringSet{}
|
||||
}
|
||||
|
||||
func (*DataTypeStringSet) TypeName() string {
|
||||
return FieldTypeStringSet
|
||||
}
|
||||
|
||||
type DataTypeTimestamp struct {
|
||||
}
|
||||
|
||||
func NewDataTypeTimestamp() *DataTypeTimestamp {
|
||||
return &DataTypeTimestamp{}
|
||||
}
|
||||
|
||||
func (*DataTypeTimestamp) TypeName() string {
|
||||
return FieldTypeTimestamp
|
||||
}
|
||||
|
||||
func FloatToDecimal(v float64) pql.Decimal {
|
||||
scale := NumDecimalPlaces(fmt.Sprintf("%v", v))
|
||||
unscaledValue := int64(v * math.Pow(10, float64(scale)))
|
||||
return pql.NewDecimal(unscaledValue, int64(scale))
|
||||
}
|
||||
|
||||
func NumDecimalPlaces(v string) int {
|
||||
i := strings.IndexByte(v, '.')
|
||||
if i > -1 {
|
||||
return len(v) - i - 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,5 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
package sql2
|
||||
package parser
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
|
|
@ -41,8 +41,6 @@ func (s *Scanner) Scan() (pos Pos, token Token, lit string) {
|
|||
return s.scanQuotedIdent()
|
||||
} else if ch == '\'' {
|
||||
return s.scanString()
|
||||
} else if ch == '?' || ch == ':' || ch == '@' || ch == '$' {
|
||||
return s.scanBind()
|
||||
}
|
||||
|
||||
switch ch, pos := s.read(); ch {
|
||||
|
|
@ -52,6 +50,10 @@ func (s *Scanner) Scan() (pos Pos, token Token, lit string) {
|
|||
return pos, LP, "("
|
||||
case ')':
|
||||
return pos, RP, ")"
|
||||
case '[':
|
||||
return pos, LB, "["
|
||||
case ']':
|
||||
return pos, RB, "]"
|
||||
case ',':
|
||||
return pos, COMMA, ","
|
||||
case '!':
|
||||
|
|
@ -173,30 +175,6 @@ func (s *Scanner) scanString() (Pos, Token, string) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *Scanner) scanBind() (Pos, Token, string) {
|
||||
start, pos := s.read()
|
||||
|
||||
s.buf.Reset()
|
||||
s.buf.WriteRune(start)
|
||||
|
||||
// Question mark starts a numeric bind.
|
||||
if start == '?' {
|
||||
for isDigit(s.peek()) {
|
||||
ch, _ := s.read()
|
||||
s.buf.WriteRune(ch)
|
||||
}
|
||||
return pos, BIND, s.buf.String()
|
||||
}
|
||||
|
||||
// All other characters start an alphanumeric bind.
|
||||
assert(start == ':' || start == '@' || start == '$')
|
||||
for isUnquotedIdent(s.peek()) {
|
||||
ch, _ := s.read()
|
||||
s.buf.WriteRune(ch)
|
||||
}
|
||||
return pos, BIND, s.buf.String()
|
||||
}
|
||||
|
||||
func (s *Scanner) scanBlob() (Pos, Token, string) {
|
||||
start, pos := s.read()
|
||||
assert(start == 'x' || start == 'X')
|
||||
160
sql3/parser/scanner_test.go
Normal file
160
sql3/parser/scanner_test.go
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
package parser_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/molecula/featurebase/v3/sql3/parser"
|
||||
)
|
||||
|
||||
func TestScanner_Scan(t *testing.T) {
|
||||
t.Run("IDENT", func(t *testing.T) {
|
||||
t.Run("Unquoted", func(t *testing.T) {
|
||||
AssertScan(t, `foo_BAR123`, parser.IDENT, `foo_BAR123`)
|
||||
})
|
||||
t.Run("Quoted", func(t *testing.T) {
|
||||
AssertScan(t, `"crazy ~!#*&# column name"" foo"`, parser.QIDENT, `crazy ~!#*&# column name" foo`)
|
||||
})
|
||||
t.Run("NoEndQuote", func(t *testing.T) {
|
||||
AssertScan(t, `"unfinished`, parser.ILLEGAL, `"unfinished`)
|
||||
})
|
||||
t.Run("x", func(t *testing.T) {
|
||||
AssertScan(t, `x`, parser.IDENT, `x`)
|
||||
})
|
||||
t.Run("StartingX", func(t *testing.T) {
|
||||
AssertScan(t, `xyz`, parser.IDENT, `xyz`)
|
||||
})
|
||||
t.Run("WithComment", func(t *testing.T) {
|
||||
AssertScan(t, "-- this is a comment\n\n-- more comments\nfoo", parser.IDENT, `foo`)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("KEYWORD", func(t *testing.T) {
|
||||
AssertScan(t, `BEGIN`, parser.BEGIN, `BEGIN`)
|
||||
})
|
||||
|
||||
t.Run("STRING", func(t *testing.T) {
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
AssertScan(t, `'this is ''a'' string'`, parser.STRING, `this is 'a' string`)
|
||||
})
|
||||
t.Run("NoEndQuote", func(t *testing.T) {
|
||||
AssertScan(t, `'unfinished`, parser.ILLEGAL, `'unfinished`)
|
||||
})
|
||||
})
|
||||
t.Run("BLOB", func(t *testing.T) {
|
||||
t.Run("LowerX", func(t *testing.T) {
|
||||
AssertScan(t, `x'0123456789abcdef'`, parser.BLOB, `0123456789abcdef`)
|
||||
})
|
||||
t.Run("UpperX", func(t *testing.T) {
|
||||
AssertScan(t, `X'0123456789ABCDEF'`, parser.BLOB, `0123456789ABCDEF`)
|
||||
})
|
||||
t.Run("NoEndQuote", func(t *testing.T) {
|
||||
AssertScan(t, `x'0123`, parser.ILLEGAL, `x'0123`)
|
||||
})
|
||||
t.Run("BadHex", func(t *testing.T) {
|
||||
AssertScan(t, `x'hello`, parser.ILLEGAL, `x'h`)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("INTEGER", func(t *testing.T) {
|
||||
AssertScan(t, `123`, parser.INTEGER, `123`)
|
||||
})
|
||||
|
||||
t.Run("FLOAT", func(t *testing.T) {
|
||||
AssertScan(t, `123.456`, parser.FLOAT, `123.456`)
|
||||
AssertScan(t, `.1`, parser.FLOAT, `.1`)
|
||||
AssertScan(t, `123e456`, parser.FLOAT, `123e456`)
|
||||
AssertScan(t, `123E456`, parser.FLOAT, `123E456`)
|
||||
AssertScan(t, `123.456E78`, parser.FLOAT, `123.456E78`)
|
||||
AssertScan(t, `123.E45`, parser.FLOAT, `123.E45`)
|
||||
AssertScan(t, `123E+4`, parser.FLOAT, `123E+4`)
|
||||
AssertScan(t, `123E-4`, parser.FLOAT, `123E-4`)
|
||||
AssertScan(t, `123E`, parser.ILLEGAL, `123E`)
|
||||
AssertScan(t, `123E+`, parser.ILLEGAL, `123E+`)
|
||||
AssertScan(t, `123E-`, parser.ILLEGAL, `123E-`)
|
||||
})
|
||||
|
||||
t.Run("EOF", func(t *testing.T) {
|
||||
AssertScan(t, " \n\t\r", parser.EOF, ``)
|
||||
})
|
||||
|
||||
t.Run("SEMI", func(t *testing.T) {
|
||||
AssertScan(t, ";", parser.SEMI, ";")
|
||||
})
|
||||
t.Run("LP", func(t *testing.T) {
|
||||
AssertScan(t, "(", parser.LP, "(")
|
||||
})
|
||||
t.Run("RP", func(t *testing.T) {
|
||||
AssertScan(t, ")", parser.RP, ")")
|
||||
})
|
||||
t.Run("COMMA", func(t *testing.T) {
|
||||
AssertScan(t, ",", parser.COMMA, ",")
|
||||
})
|
||||
t.Run("NE", func(t *testing.T) {
|
||||
AssertScan(t, "!=", parser.NE, "!=")
|
||||
})
|
||||
t.Run("BITNOT", func(t *testing.T) {
|
||||
AssertScan(t, "!", parser.BITNOT, "!")
|
||||
})
|
||||
t.Run("EQ", func(t *testing.T) {
|
||||
AssertScan(t, "=", parser.EQ, "=")
|
||||
})
|
||||
t.Run("LE", func(t *testing.T) {
|
||||
AssertScan(t, "<=", parser.LE, "<=")
|
||||
})
|
||||
t.Run("LSHIFT", func(t *testing.T) {
|
||||
AssertScan(t, "<<", parser.LSHIFT, "<<")
|
||||
})
|
||||
t.Run("LT", func(t *testing.T) {
|
||||
AssertScan(t, "<", parser.LT, "<")
|
||||
})
|
||||
t.Run("GE", func(t *testing.T) {
|
||||
AssertScan(t, ">=", parser.GE, ">=")
|
||||
})
|
||||
t.Run("RSHIFT", func(t *testing.T) {
|
||||
AssertScan(t, ">>", parser.RSHIFT, ">>")
|
||||
})
|
||||
t.Run("GT", func(t *testing.T) {
|
||||
AssertScan(t, ">", parser.GT, ">")
|
||||
})
|
||||
t.Run("BITAND", func(t *testing.T) {
|
||||
AssertScan(t, "&", parser.BITAND, "&")
|
||||
})
|
||||
t.Run("CONCAT", func(t *testing.T) {
|
||||
AssertScan(t, "||", parser.CONCAT, "||")
|
||||
})
|
||||
t.Run("BITOR", func(t *testing.T) {
|
||||
AssertScan(t, "|", parser.BITOR, "|")
|
||||
})
|
||||
t.Run("PLUS", func(t *testing.T) {
|
||||
AssertScan(t, "+", parser.PLUS, "+")
|
||||
})
|
||||
t.Run("MINUS", func(t *testing.T) {
|
||||
AssertScan(t, "-", parser.MINUS, "-")
|
||||
})
|
||||
t.Run("STAR", func(t *testing.T) {
|
||||
AssertScan(t, "*", parser.STAR, "*")
|
||||
})
|
||||
t.Run("SLASH", func(t *testing.T) {
|
||||
AssertScan(t, "/", parser.SLASH, "/")
|
||||
})
|
||||
t.Run("REM", func(t *testing.T) {
|
||||
AssertScan(t, "%", parser.REM, "%")
|
||||
})
|
||||
t.Run("DOT", func(t *testing.T) {
|
||||
AssertScan(t, ".", parser.DOT, ".")
|
||||
})
|
||||
t.Run("ILLEGAL", func(t *testing.T) {
|
||||
AssertScan(t, "^", parser.ILLEGAL, "^")
|
||||
})
|
||||
}
|
||||
|
||||
// AssertScan asserts the value of the first scan to s.
|
||||
func AssertScan(tb testing.TB, s string, expectedTok parser.Token, expectedLit string) {
|
||||
tb.Helper()
|
||||
_, tok, lit := parser.NewScanner(strings.NewReader(s)).Scan()
|
||||
if tok != expectedTok || lit != expectedLit {
|
||||
tb.Fatalf("Scan(%q)=<%s,%s>, want <%s,%s>", s, tok, lit, expectedTok, expectedLit)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
package sql2
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
|
@ -22,6 +22,7 @@ func init() {
|
|||
// Token is the set of lexical tokens of the Go programming language.
|
||||
type Token int
|
||||
|
||||
//TODO (pok) remove unnecessary tokens
|
||||
// The list of tokens.
|
||||
const (
|
||||
// Special tokens
|
||||
|
|
@ -40,13 +41,14 @@ const (
|
|||
NULL // NULL
|
||||
TRUE // true
|
||||
FALSE // false
|
||||
BIND //? or ?NNN or :VVV or @VVV or $VVV
|
||||
literal_end
|
||||
|
||||
operator_beg
|
||||
SEMI // ;
|
||||
LP // (
|
||||
RP // )
|
||||
LB // [
|
||||
RB // ]
|
||||
COMMA // ,
|
||||
NE // !=
|
||||
EQ // =
|
||||
|
|
@ -88,11 +90,14 @@ const (
|
|||
BEGIN
|
||||
BETWEEN
|
||||
BY
|
||||
BULK
|
||||
CACHETYPE
|
||||
CASCADE
|
||||
CASE
|
||||
CAST
|
||||
CHECK
|
||||
COLUMN
|
||||
COLUMNS
|
||||
COLUMNKW
|
||||
COMMIT
|
||||
CONFLICT
|
||||
|
|
@ -101,7 +106,6 @@ const (
|
|||
CROSS
|
||||
CTIME_KW
|
||||
CURRENT
|
||||
CURRENT_TIME
|
||||
CURRENT_DATE
|
||||
CURRENT_TIMESTAMP
|
||||
DATABASE
|
||||
|
|
@ -117,6 +121,7 @@ const (
|
|||
EACH
|
||||
ELSE
|
||||
END
|
||||
EPOCH
|
||||
ESCAPE
|
||||
EXCEPT
|
||||
EXCLUDE
|
||||
|
|
@ -150,14 +155,16 @@ const (
|
|||
INTO
|
||||
IS
|
||||
ISNOT
|
||||
ISNULL // TODO: REMOVE?
|
||||
JOIN
|
||||
KEY
|
||||
KEYPARTITIONS
|
||||
LAST
|
||||
LEFT
|
||||
LIKE
|
||||
LIMIT
|
||||
LRU
|
||||
MATCH
|
||||
MAX
|
||||
MIN
|
||||
NATURAL
|
||||
NO
|
||||
NOT
|
||||
|
|
@ -168,11 +175,9 @@ const (
|
|||
NOTIN
|
||||
NOTLIKE
|
||||
NOTMATCH
|
||||
NOTNULL
|
||||
NOTREGEXP
|
||||
NULLS
|
||||
OF
|
||||
OFFSET
|
||||
ON
|
||||
OR
|
||||
ORDER
|
||||
|
|
@ -185,8 +190,8 @@ const (
|
|||
PRECEDING
|
||||
PRIMARY
|
||||
QUERY
|
||||
RAISE
|
||||
RANGE
|
||||
RANKED
|
||||
RECURSIVE
|
||||
REFERENCES
|
||||
REGEXP
|
||||
|
|
@ -203,15 +208,24 @@ const (
|
|||
SELECT
|
||||
SELECT_COLUMN
|
||||
SET
|
||||
SHARDWIDTH
|
||||
SIZE
|
||||
SHOW
|
||||
SPAN
|
||||
TABLE
|
||||
TABLES
|
||||
TEMP
|
||||
THEN
|
||||
TIES
|
||||
TIMEUNIT
|
||||
TIMEQUANTUM
|
||||
TO
|
||||
TOP
|
||||
TOPN
|
||||
TRANSACTION
|
||||
TRIGGER
|
||||
TRUTH
|
||||
TTL
|
||||
UNBOUNDED
|
||||
UNION
|
||||
UNIQUE
|
||||
|
|
@ -242,17 +256,17 @@ var tokens = [...]string{
|
|||
IDENT: "IDENT",
|
||||
QIDENT: "QIDENT",
|
||||
STRING: "STRING",
|
||||
BLOB: "BLOB",
|
||||
FLOAT: "FLOAT",
|
||||
INTEGER: "INTEGER",
|
||||
NULL: "NULL",
|
||||
TRUE: "TRUE",
|
||||
FALSE: "FALSE",
|
||||
BIND: "BIND",
|
||||
|
||||
SEMI: ";",
|
||||
LP: "(",
|
||||
RP: ")",
|
||||
LB: "[",
|
||||
RB: "]",
|
||||
COMMA: ",",
|
||||
NE: "!=",
|
||||
EQ: "=",
|
||||
|
|
@ -292,11 +306,14 @@ var tokens = [...]string{
|
|||
BEGIN: "BEGIN",
|
||||
BETWEEN: "BETWEEN",
|
||||
BY: "BY",
|
||||
BULK: "BULK",
|
||||
CACHETYPE: "CACHETYPE",
|
||||
CASCADE: "CASCADE",
|
||||
CASE: "CASE",
|
||||
CAST: "CAST",
|
||||
CHECK: "CHECK",
|
||||
COLUMN: "COLUMN",
|
||||
COLUMNS: "COLUMNS",
|
||||
COLUMNKW: "COLUMNKW",
|
||||
COMMIT: "COMMIT",
|
||||
CONFLICT: "CONFLICT",
|
||||
|
|
@ -305,7 +322,6 @@ var tokens = [...]string{
|
|||
CROSS: "CROSS",
|
||||
CTIME_KW: "CTIME_KW",
|
||||
CURRENT: "CURRENT",
|
||||
CURRENT_TIME: "CURRENT_TIME",
|
||||
CURRENT_DATE: "CURRENT_DATE",
|
||||
CURRENT_TIMESTAMP: "CURRENT_TIMESTAMP",
|
||||
DATABASE: "DATABASE",
|
||||
|
|
@ -321,6 +337,7 @@ var tokens = [...]string{
|
|||
EACH: "EACH",
|
||||
ELSE: "ELSE",
|
||||
END: "END",
|
||||
EPOCH: "EPOCH",
|
||||
ESCAPE: "ESCAPE",
|
||||
EXCEPT: "EXCEPT",
|
||||
EXCLUDE: "EXCLUDE",
|
||||
|
|
@ -354,14 +371,16 @@ var tokens = [...]string{
|
|||
INTO: "INTO",
|
||||
IS: "IS",
|
||||
ISNOT: "ISNOT",
|
||||
ISNULL: "ISNULL",
|
||||
JOIN: "JOIN",
|
||||
KEY: "KEY",
|
||||
KEYPARTITIONS: "KEYPARTITIONS",
|
||||
LAST: "LAST",
|
||||
LEFT: "LEFT",
|
||||
LIKE: "LIKE",
|
||||
LIMIT: "LIMIT",
|
||||
LRU: "LRU",
|
||||
MATCH: "MATCH",
|
||||
MAX: "MAX",
|
||||
MIN: "MIN",
|
||||
NO: "NO",
|
||||
NATURAL: "NATURAL",
|
||||
NOT: "NOT",
|
||||
|
|
@ -372,11 +391,9 @@ var tokens = [...]string{
|
|||
NOTIN: "NOTIN",
|
||||
NOTLIKE: "NOTLIKE",
|
||||
NOTMATCH: "NOTMATCH",
|
||||
NOTNULL: "NOTNULL",
|
||||
NOTREGEXP: "NOTREGEXP",
|
||||
NULLS: "NULLS",
|
||||
OF: "OF",
|
||||
OFFSET: "OFFSET",
|
||||
ON: "ON",
|
||||
OR: "OR",
|
||||
ORDER: "ORDER",
|
||||
|
|
@ -389,8 +406,8 @@ var tokens = [...]string{
|
|||
PRECEDING: "PRECEDING",
|
||||
PRIMARY: "PRIMARY",
|
||||
QUERY: "QUERY",
|
||||
RAISE: "RAISE",
|
||||
RANGE: "RANGE",
|
||||
RANKED: "RANKED",
|
||||
RECURSIVE: "RECURSIVE",
|
||||
REFERENCES: "REFERENCES",
|
||||
REGEXP: "REGEXP",
|
||||
|
|
@ -407,15 +424,24 @@ var tokens = [...]string{
|
|||
SELECT: "SELECT",
|
||||
SELECT_COLUMN: "SELECT_COLUMN",
|
||||
SET: "SET",
|
||||
SIZE: "SIZE",
|
||||
SHARDWIDTH: "SHARDWIDTH",
|
||||
SHOW: "SHOW",
|
||||
SPAN: "SPAN",
|
||||
TABLE: "TABLE",
|
||||
TABLES: "TABLES",
|
||||
TEMP: "TEMP",
|
||||
THEN: "THEN",
|
||||
TIES: "TIES",
|
||||
TIMEUNIT: "TIMEUNIT",
|
||||
TIMEQUANTUM: "TIMEQUANTUM",
|
||||
TO: "TO",
|
||||
TOP: "TOP",
|
||||
TOPN: "TOPN",
|
||||
TRANSACTION: "TRANSACTION",
|
||||
TRIGGER: "TRIGGER",
|
||||
TRUTH: "TRUTH",
|
||||
TTL: "TTL",
|
||||
UNBOUNDED: "UNBOUNDED",
|
||||
UNION: "UNION",
|
||||
UNIQUE: "UNIQUE",
|
||||
|
|
@ -493,7 +519,7 @@ func (op Token) Precedence() int {
|
|||
return 2
|
||||
case NOT:
|
||||
return 3
|
||||
case IS, MATCH, LIKE, GLOB, REGEXP, BETWEEN, IN, ISNULL, NOTNULL, NE, EQ:
|
||||
case IS, MATCH, LIKE, GLOB, REGEXP, BETWEEN, IN, ISNOT, NE, EQ:
|
||||
return 4
|
||||
case GT, LE, LT, GE:
|
||||
return 5
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
package sql2_test
|
||||
package parser_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
sql "github.com/molecula/featurebase/v3/sql2"
|
||||
"github.com/molecula/featurebase/v3/sql3/parser"
|
||||
)
|
||||
|
||||
func TestPos_String(t *testing.T) {
|
||||
if got, want := (sql.Pos{}).String(), `-`; got != want {
|
||||
if got, want := (parser.Pos{}).String(), `-`; got != want {
|
||||
t.Fatalf("String()=%q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
package sql2
|
||||
package parser
|
||||
|
||||
// A Visitor's Visit method is invoked for each node encountered by Walk.
|
||||
// If the result visitor w is not nil, Walk visits each of the children
|
||||
|
|
@ -86,10 +86,7 @@ func walk(v Visitor, node Node) (_ Node, err error) {
|
|||
if err := walkIdent(v, &n.Name); err != nil {
|
||||
return node, err
|
||||
}
|
||||
if err := walkIdent(v, &n.NewName); err != nil {
|
||||
return node, err
|
||||
}
|
||||
if err := walkIdent(v, &n.ColumnName); err != nil {
|
||||
if err := walkIdent(v, &n.OldColumnName); err != nil {
|
||||
return node, err
|
||||
}
|
||||
if err := walkIdent(v, &n.NewColumnName); err != nil {
|
||||
|
|
@ -104,6 +101,9 @@ func walk(v Visitor, node Node) (_ Node, err error) {
|
|||
n.ColumnDef = nil
|
||||
}
|
||||
}
|
||||
if err := walkIdent(v, &n.DropColumnName); err != nil {
|
||||
return node, err
|
||||
}
|
||||
|
||||
case *AnalyzeStatement:
|
||||
if err := walkIdent(v, &n.Name); err != nil {
|
||||
|
|
@ -194,7 +194,7 @@ func walk(v Visitor, node Node) (_ Node, err error) {
|
|||
n.WithClause = nil
|
||||
}
|
||||
}
|
||||
for i := range n.ValueLists {
|
||||
/*for i := range n.ValueLists {
|
||||
if list, err := walk(v, n.ValueLists[i]); err != nil {
|
||||
return node, err
|
||||
} else if list != nil {
|
||||
|
|
@ -202,7 +202,7 @@ func walk(v Visitor, node Node) (_ Node, err error) {
|
|||
} else {
|
||||
n.ValueLists[i] = nil
|
||||
}
|
||||
}
|
||||
}*/
|
||||
for i := range n.Columns {
|
||||
if col, err := walk(v, n.Columns[i]); err != nil {
|
||||
return node, err
|
||||
|
|
@ -255,15 +255,9 @@ func walk(v Visitor, node Node) (_ Node, err error) {
|
|||
n.OrderingTerms[i] = nil
|
||||
}
|
||||
}
|
||||
if err := walkExpr(v, &n.LimitExpr); err != nil {
|
||||
return node, err
|
||||
}
|
||||
if err := walkExpr(v, &n.OffsetExpr); err != nil {
|
||||
return node, err
|
||||
}
|
||||
|
||||
case *InsertStatement:
|
||||
if n.WithClause != nil {
|
||||
/*if n.WithClause != nil {
|
||||
if clause, err := walk(v, n.WithClause); err != nil {
|
||||
return node, err
|
||||
} else if clause != nil {
|
||||
|
|
@ -271,7 +265,7 @@ func walk(v Visitor, node Node) (_ Node, err error) {
|
|||
} else {
|
||||
n.WithClause = nil
|
||||
}
|
||||
}
|
||||
}*/
|
||||
if err := walkIdent(v, &n.Table); err != nil {
|
||||
return node, err
|
||||
}
|
||||
|
|
@ -281,16 +275,16 @@ func walk(v Visitor, node Node) (_ Node, err error) {
|
|||
if err := walkIdentList(v, n.Columns); err != nil {
|
||||
return node, err
|
||||
}
|
||||
for i := range n.ValueLists {
|
||||
if list, err := walk(v, n.ValueLists[i]); err != nil {
|
||||
return node, err
|
||||
} else if list != nil {
|
||||
n.ValueLists[i] = list.(*ExprList)
|
||||
} else {
|
||||
n.ValueLists[i] = nil
|
||||
}
|
||||
//for i := range n.ValueLists {
|
||||
if list, err := walk(v, n.ValueList); err != nil {
|
||||
return node, err
|
||||
} else if list != nil {
|
||||
n.ValueList = list.(*ExprList)
|
||||
} else {
|
||||
n.ValueList = nil
|
||||
}
|
||||
if n.Select != nil {
|
||||
//}
|
||||
/*if n.Select != nil {
|
||||
if sel, err := walk(v, n.Select); err != nil {
|
||||
return node, err
|
||||
} else if sel != nil {
|
||||
|
|
@ -298,8 +292,8 @@ func walk(v Visitor, node Node) (_ Node, err error) {
|
|||
} else {
|
||||
n.Select = nil
|
||||
}
|
||||
}
|
||||
if n.UpsertClause != nil {
|
||||
}*/
|
||||
/*if n.UpsertClause != nil {
|
||||
if clause, err := walk(v, n.UpsertClause); err != nil {
|
||||
return node, err
|
||||
} else if clause != nil {
|
||||
|
|
@ -307,7 +301,7 @@ func walk(v Visitor, node Node) (_ Node, err error) {
|
|||
} else {
|
||||
n.UpsertClause = nil
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
case *UpdateStatement:
|
||||
if n.WithClause != nil {
|
||||
|
|
@ -595,17 +589,6 @@ func walk(v Visitor, node Node) (_ Node, err error) {
|
|||
return node, err
|
||||
}
|
||||
|
||||
case *Raise:
|
||||
if n.Error != nil {
|
||||
if e, err := walk(v, n.Error); err != nil {
|
||||
return node, err
|
||||
} else if e != nil {
|
||||
n.Error = e.(*StringLit)
|
||||
} else {
|
||||
n.Error = nil
|
||||
}
|
||||
}
|
||||
|
||||
case *Exists:
|
||||
if n.Select != nil {
|
||||
if sel, err := walk(v, n.Select); err != nil {
|
||||
|
|
@ -768,7 +751,7 @@ func walk(v Visitor, node Node) (_ Node, err error) {
|
|||
if p, err := walk(v, n.Precision); err != nil {
|
||||
return node, err
|
||||
} else if p != nil {
|
||||
n.Precision = p.(*NumberLit)
|
||||
n.Precision = p.(*IntegerLit)
|
||||
} else {
|
||||
n.Precision = nil
|
||||
}
|
||||
|
|
@ -777,7 +760,7 @@ func walk(v Visitor, node Node) (_ Node, err error) {
|
|||
if scale, err := walk(v, n.Scale); err != nil {
|
||||
return node, err
|
||||
} else if scale != nil {
|
||||
n.Scale = scale.(*NumberLit)
|
||||
n.Scale = scale.(*IntegerLit)
|
||||
} else {
|
||||
n.Scale = nil
|
||||
}
|
||||
77
sql3/planner/compilealtertable.go
Normal file
77
sql3/planner/compilealtertable.go
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/molecula/featurebase/v3/sql3"
|
||||
"github.com/molecula/featurebase/v3/sql3/parser"
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
)
|
||||
|
||||
type alterOperation int64
|
||||
|
||||
const (
|
||||
alterOpAdd alterOperation = iota
|
||||
alterOpDrop
|
||||
alterOpRename
|
||||
)
|
||||
|
||||
// compileAlterTableStatement compiles an ALTER TABLE statement into a
|
||||
// PlanOperator.
|
||||
func (p *ExecutionPlanner) compileAlterTableStatement(stmt *parser.AlterTableStatement) (_ types.PlanOperator, err error) {
|
||||
tableName := parser.IdentName(stmt.Name)
|
||||
if stmt.Drop.IsValid() {
|
||||
columnName := parser.IdentName(stmt.DropColumnName)
|
||||
return NewPlanOpQuery(NewPlanOpAlterTable(p, tableName, alterOpDrop, columnName, "", nil), p.sql), nil
|
||||
} else if stmt.Add.IsValid() {
|
||||
col := stmt.ColumnDef
|
||||
columnName := parser.IdentName(col.Name)
|
||||
column, err := p.compileColumn(col)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewPlanOpQuery(NewPlanOpAlterTable(p, tableName, alterOpAdd, "", columnName, column), p.sql), nil
|
||||
|
||||
} else if stmt.Rename.IsValid() {
|
||||
oldColumnName := parser.IdentName(stmt.OldColumnName)
|
||||
newColumnName := parser.IdentName(stmt.NewColumnName)
|
||||
return NewPlanOpQuery(NewPlanOpAlterTable(p, tableName, alterOpRename, oldColumnName, newColumnName, nil), p.sql), nil
|
||||
} else {
|
||||
return nil, sql3.NewErrInternal("unhandled alter operation")
|
||||
}
|
||||
}
|
||||
|
||||
// analyzeAlterTableStatement analyze an ALTER TABLE statement and returns an
|
||||
// error if anything is invalid.
|
||||
func (p *ExecutionPlanner) analyzeAlterTableStatement(stmt *parser.AlterTableStatement) error {
|
||||
if stmt.Drop.IsValid() {
|
||||
//no checks for now
|
||||
} else if stmt.Add.IsValid() {
|
||||
col := stmt.ColumnDef
|
||||
columnName := parser.IdentName(col.Name)
|
||||
typeName := parser.IdentName(col.Type.Name)
|
||||
if !parser.IsValidTypeName(typeName) {
|
||||
return sql3.NewErrUnknownType(col.Type.Name.NamePos.Line, col.Type.Name.NamePos.Column, typeName)
|
||||
}
|
||||
|
||||
if strings.ToLower(columnName) == "_id" {
|
||||
//not allowed to add an _id column after the fact
|
||||
return sql3.NewErrTableIDColumnAlter(col.Name.NamePos.Line, col.Name.NamePos.Column)
|
||||
}
|
||||
|
||||
err := p.analyzeColumn(typeName, col)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if stmt.Rename.IsValid() {
|
||||
//check the new and old are not the same
|
||||
oldColumnName := parser.IdentName(stmt.OldColumnName)
|
||||
newColumnName := parser.IdentName(stmt.NewColumnName)
|
||||
if strings.EqualFold(oldColumnName, newColumnName) {
|
||||
return sql3.NewErrDuplicateColumn(stmt.NewColumnName.NamePos.Line, stmt.NewColumnName.NamePos.Column, newColumnName)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
46
sql3/planner/compilebulkinsert.go
Normal file
46
sql3/planner/compilebulkinsert.go
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
// Copyright 2022 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
pilosa "github.com/molecula/featurebase/v3"
|
||||
"github.com/molecula/featurebase/v3/sql3"
|
||||
"github.com/molecula/featurebase/v3/sql3/parser"
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// compileBulkInsertStatement compiles a BULK INSERT statement into a
|
||||
// PlanOperator.
|
||||
func (p *ExecutionPlanner) compileBulkInsertStatement(stmt *parser.BulkInsertStatement) (_ types.PlanOperator, err error) {
|
||||
tableName := parser.IdentName(stmt.Table)
|
||||
|
||||
/*table*/
|
||||
_, err = p.schemaAPI.IndexInfo(context.Background(), tableName)
|
||||
if err != nil {
|
||||
if errors.Is(err, pilosa.ErrIndexNotFound) {
|
||||
return nil, sql3.NewErrTableNotFound(stmt.Table.NamePos.Line, stmt.Table.NamePos.Column, tableName)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewPlanOpBulkInsert(p, tableName), nil
|
||||
}
|
||||
|
||||
// analyzeBulkInsertStatement analyzes a BULK INSERT statement and returns an
|
||||
// error if anything is invalid.
|
||||
func (p *ExecutionPlanner) analyzeBulkInsertStatement(stmt *parser.BulkInsertStatement) error {
|
||||
//check referred to table exists
|
||||
tableName := parser.IdentName(stmt.Table)
|
||||
/*table*/ _, err := p.schemaAPI.IndexInfo(context.Background(), tableName)
|
||||
if err != nil {
|
||||
if errors.Is(err, pilosa.ErrIndexNotFound) {
|
||||
return sql3.NewErrTableNotFound(stmt.Table.NamePos.Line, stmt.Table.NamePos.Column, tableName)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
424
sql3/planner/compilecreatetable.go
Normal file
424
sql3/planner/compilecreatetable.go
Normal file
|
|
@ -0,0 +1,424 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
pilosa "github.com/molecula/featurebase/v3"
|
||||
"github.com/molecula/featurebase/v3/pql"
|
||||
"github.com/molecula/featurebase/v3/sql3"
|
||||
"github.com/molecula/featurebase/v3/sql3/parser"
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
)
|
||||
|
||||
type createTableField struct {
|
||||
planner *ExecutionPlanner
|
||||
name string
|
||||
typeName string
|
||||
fos []pilosa.FieldOption
|
||||
}
|
||||
|
||||
// compileCreateTableStatement compiles a CREATE TABLE statement into a
|
||||
// PlanOperator.
|
||||
func (p *ExecutionPlanner) compileCreateTableStatement(stmt *parser.CreateTableStatement) (_ types.PlanOperator, err error) {
|
||||
tableName := parser.IdentName(stmt.Name)
|
||||
failIfExists := !stmt.IfNotExists.IsValid()
|
||||
|
||||
// apply table options
|
||||
keyPartitions := 0
|
||||
for _, option := range stmt.Options {
|
||||
switch o := option.(type) {
|
||||
case *parser.KeyPartitionsOption:
|
||||
e := o.Expr.(*parser.IntegerLit)
|
||||
i, err := strconv.ParseInt(e.Value, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keyPartitions = int(i)
|
||||
}
|
||||
}
|
||||
|
||||
isKeyed := false
|
||||
|
||||
var columns = []*createTableField{}
|
||||
for _, col := range stmt.Columns {
|
||||
columnName := parser.IdentName(col.Name)
|
||||
typeName := parser.IdentName(col.Type.Name)
|
||||
|
||||
if strings.ToLower(columnName) == "_id" {
|
||||
if strings.EqualFold(typeName, parser.FieldTypeString) {
|
||||
isKeyed = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
column, err := p.compileColumn(col)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
columns = append(columns, column)
|
||||
}
|
||||
return NewPlanOpQuery(NewPlanOpCreateTable(p, tableName, failIfExists, isKeyed, keyPartitions, columns), p.sql), nil
|
||||
}
|
||||
|
||||
// compiles a column def
|
||||
func (p *ExecutionPlanner) compileColumn(col *parser.ColumnDefinition) (*createTableField, error) {
|
||||
var err error
|
||||
columnName := parser.IdentName(col.Name)
|
||||
typeName := parser.IdentName(col.Type.Name)
|
||||
|
||||
column := &createTableField{
|
||||
planner: p,
|
||||
name: columnName,
|
||||
typeName: typeName,
|
||||
}
|
||||
// Possible FieldOptions. We define these here, but the contraints below
|
||||
// can set them to the values provided in the create table statement.
|
||||
// And finally, the correct pilosa.FieldOption functional option will be
|
||||
// created after that.
|
||||
var cacheType string = pilosa.DefaultCacheType
|
||||
var cacheSize uint32 = pilosa.DefaultCacheSize
|
||||
var scale int64
|
||||
min, max := pql.MinMax(0)
|
||||
var epoch = pilosa.DefaultEpoch
|
||||
var timeUnit string = pilosa.TimeUnitSeconds
|
||||
var timeQuantum pilosa.TimeQuantum
|
||||
var ttl = "0"
|
||||
|
||||
for _, con := range col.Constraints {
|
||||
switch c := con.(type) {
|
||||
case *parser.CacheTypeConstraint:
|
||||
cacheType = c.CacheTypeValue
|
||||
|
||||
if c.Size.IsValid() {
|
||||
e := c.SizeExpr.(*parser.IntegerLit)
|
||||
i, err := strconv.ParseInt(e.Value, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cacheSize = uint32(i)
|
||||
}
|
||||
|
||||
case *parser.MinConstraint:
|
||||
var val string
|
||||
switch e := c.Expr.(type) {
|
||||
case *parser.IntegerLit:
|
||||
val = e.Value
|
||||
|
||||
case *parser.UnaryExpr:
|
||||
// Call analyzeUnaryExpression() in order to set the value on
|
||||
// expr.ResultDataType so that we can rely on the DataType()
|
||||
// method. There is a case where a BITNOT expression could get
|
||||
// through, but that value as a string will fail in
|
||||
// strconv.ParseInt() conversion.
|
||||
if _, err := p.analyzeUnaryExpression(e, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if e.IsLiteral() && typeIsInteger(e.DataType()) {
|
||||
val = e.String()
|
||||
}
|
||||
}
|
||||
|
||||
i, err := strconv.ParseInt(val, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
min = pql.NewDecimal(i, 0)
|
||||
|
||||
case *parser.MaxConstraint:
|
||||
var val string
|
||||
switch e := c.Expr.(type) {
|
||||
case *parser.IntegerLit:
|
||||
val = e.Value
|
||||
|
||||
case *parser.UnaryExpr:
|
||||
// Call analyzeUnaryExpression() in order to set the value on
|
||||
// expr.ResultDataType so that we can rely on the DataType()
|
||||
// method. There is a case where a BITNOT expression could get
|
||||
// through, but that value as a string will fail in
|
||||
// strconv.ParseInt() conversion.
|
||||
if _, err := p.analyzeUnaryExpression(e, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if e.IsLiteral() && typeIsInteger(e.DataType()) {
|
||||
val = e.String()
|
||||
}
|
||||
}
|
||||
|
||||
i, err := strconv.ParseInt(val, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
max = pql.NewDecimal(i, 0)
|
||||
|
||||
case *parser.TimeUnitConstraint:
|
||||
unit := c.Expr.(*parser.StringLit)
|
||||
timeUnit = unit.Value
|
||||
|
||||
epochString := c.EpochExpr.(*parser.StringLit)
|
||||
tm, err := time.ParseInLocation(time.RFC3339, epochString.Value, time.UTC)
|
||||
if err != nil {
|
||||
return nil, sql3.NewErrInvalidTimeEpoch(c.EpochExpr.Pos().Line, c.EpochExpr.Pos().Line, epochString.Value)
|
||||
}
|
||||
epoch = tm
|
||||
|
||||
case *parser.TimeQuantumConstraint:
|
||||
unit := c.Expr.(*parser.StringLit)
|
||||
timeQuantum = pilosa.TimeQuantum(unit.Value)
|
||||
if c.TtlExpr != nil {
|
||||
e := c.TtlExpr.(*parser.StringLit)
|
||||
ttl = e.Value
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, sql3.NewErrInternalf("unhandled column constraint type '%T'", c)
|
||||
}
|
||||
}
|
||||
|
||||
switch strings.ToUpper(typeName) {
|
||||
case parser.FieldTypeBool:
|
||||
column.fos = append(column.fos, pilosa.OptFieldTypeBool())
|
||||
case parser.FieldTypeDecimal:
|
||||
// Get the scale value.
|
||||
scale, err = strconv.ParseInt(col.Type.Scale.Value, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Adjust min/max to fit within the scaled min/max.
|
||||
scaledMin, scaledMax := pql.MinMax(scale)
|
||||
if scaledMax.LessThan(max) {
|
||||
max = scaledMax
|
||||
}
|
||||
if scaledMin.GreaterThan(min) {
|
||||
min = scaledMin
|
||||
}
|
||||
|
||||
column.fos = append(column.fos, pilosa.OptFieldTypeDecimal(scale, min, max))
|
||||
case parser.FieldTypeID:
|
||||
column.fos = append(column.fos, pilosa.OptFieldTypeMutex(cacheType, cacheSize))
|
||||
case parser.FieldTypeIDSet:
|
||||
if timeQuantum != "" {
|
||||
column.fos = append(column.fos, pilosa.OptFieldTypeTime(timeQuantum, ttl))
|
||||
} else {
|
||||
column.fos = append(column.fos, pilosa.OptFieldTypeSet(cacheType, cacheSize))
|
||||
}
|
||||
case parser.FieldTypeInt:
|
||||
column.fos = append(column.fos, pilosa.OptFieldTypeInt(min.ToInt64(0), max.ToInt64(0)))
|
||||
case parser.FieldTypeString:
|
||||
column.fos = append(column.fos, pilosa.OptFieldTypeMutex(cacheType, cacheSize))
|
||||
column.fos = append(column.fos, pilosa.OptFieldKeys())
|
||||
case parser.FieldTypeStringSet:
|
||||
if timeQuantum != "" {
|
||||
column.fos = append(column.fos, pilosa.OptFieldTypeTime(timeQuantum, ttl))
|
||||
} else {
|
||||
column.fos = append(column.fos, pilosa.OptFieldTypeSet(cacheType, cacheSize))
|
||||
}
|
||||
column.fos = append(column.fos, pilosa.OptFieldKeys())
|
||||
case parser.FieldTypeTimestamp:
|
||||
column.fos = append(column.fos, pilosa.OptFieldTypeTimestamp(epoch, timeUnit))
|
||||
}
|
||||
return column, nil
|
||||
}
|
||||
|
||||
// analyzeCreateTableStatement analyzes a CREATE TABLE statement and returns an
|
||||
// error if anything is invalid.
|
||||
func (p *ExecutionPlanner) analyzeCreateTableStatement(stmt *parser.CreateTableStatement) error {
|
||||
//iterate columns, check types, check constraints, ensure we have no dupe names and make sure there is an _id column
|
||||
checkedColumns := make(map[string]string)
|
||||
for _, col := range stmt.Columns {
|
||||
columnName := parser.IdentName(col.Name)
|
||||
_, ok := checkedColumns[strings.ToLower(columnName)]
|
||||
if ok {
|
||||
return sql3.NewErrDuplicateColumn(col.Name.NamePos.Line, col.Name.NamePos.Column, columnName)
|
||||
}
|
||||
|
||||
typeName := parser.IdentName(col.Type.Name)
|
||||
if !parser.IsValidTypeName(typeName) {
|
||||
return sql3.NewErrUnknownType(col.Type.Name.NamePos.Line, col.Type.Name.NamePos.Column, typeName)
|
||||
}
|
||||
|
||||
if strings.ToLower(columnName) == "_id" {
|
||||
//check the type
|
||||
if !(strings.EqualFold(typeName, parser.FieldTypeID) || strings.EqualFold(typeName, parser.FieldTypeString)) {
|
||||
return sql3.NewErrTableIDColumnType(col.Type.Name.NamePos.Line, col.Type.Name.NamePos.Column)
|
||||
}
|
||||
//make sure we have no constraints
|
||||
if len(col.Constraints) > 0 {
|
||||
return sql3.NewErrTableIDColumnConstraints(col.Type.Name.NamePos.Line, col.Type.Name.NamePos.Column)
|
||||
}
|
||||
}
|
||||
checkedColumns[columnName] = strings.ToLower(columnName)
|
||||
|
||||
err := p.analyzeColumn(typeName, col)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, ok := checkedColumns["_id"]
|
||||
if !ok {
|
||||
return sql3.NewErrTableMustHaveIDColumn(stmt.Create.Line, stmt.Create.Column)
|
||||
}
|
||||
//check table options
|
||||
for _, option := range stmt.Options {
|
||||
|
||||
switch o := option.(type) {
|
||||
case *parser.KeyPartitionsOption:
|
||||
//check the type of the expression
|
||||
literal, ok := o.Expr.(*parser.IntegerLit)
|
||||
if !ok {
|
||||
return sql3.NewErrIntegerLiteral(o.Expr.Pos().Line, o.Expr.Pos().Column)
|
||||
}
|
||||
//key partittions needs to be >=1 and we'll cap conservatively at 10000
|
||||
i, err := strconv.ParseInt(literal.Value, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if i < 1 || i > 10000 {
|
||||
return sql3.NewErrInvalidKeyPartitionsValue(o.Expr.Pos().Line, o.Expr.Pos().Column, i)
|
||||
}
|
||||
|
||||
case *parser.ShardWidthOption:
|
||||
//check the type of the expression
|
||||
literal, ok := o.Expr.(*parser.IntegerLit)
|
||||
if !ok {
|
||||
return sql3.NewErrIntegerLiteral(o.Expr.Pos().Line, o.Expr.Pos().Column)
|
||||
}
|
||||
//shardwidth needs to be a power of 2 and > 2^16
|
||||
i, err := strconv.ParseInt(literal.Value, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
isPwrOf2 := (i & (i - 1)) == 0
|
||||
if (i == 0) || !isPwrOf2 || i < (1<<16) {
|
||||
return sql3.NewErrInvalidShardWidthValue(o.Expr.Pos().Line, o.Expr.Pos().Column, i)
|
||||
}
|
||||
|
||||
default:
|
||||
return sql3.NewErrInternalf("unhandled table option type '%T'", option)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// analyze the column def for a CREATE or ALTER TABLE
|
||||
func (p *ExecutionPlanner) analyzeColumn(typeName string, col *parser.ColumnDefinition) error {
|
||||
// handledConstraints keeps track of the constraints which have been
|
||||
// analyzed in the for loop below. This allows us to verify that two
|
||||
// different, incompatible constraints aren't included. For now, that
|
||||
// really only applies to TIMEQUANTUM and CACHETYPE. The other
|
||||
// constraints which may be incompatible are checked against the field
|
||||
// type. It may make sense, in the future, to add some logic which
|
||||
// analyzes all the constraints in a more flexible way, but this
|
||||
// addresses the immediate issue.
|
||||
handledConstraints := make(map[parser.Token]struct{})
|
||||
|
||||
for _, con := range col.Constraints {
|
||||
switch c := con.(type) {
|
||||
case *parser.CacheTypeConstraint:
|
||||
//make sure we have a set or mutex type
|
||||
if !(strings.EqualFold(typeName, parser.FieldTypeString) || strings.EqualFold(typeName, parser.FieldTypeStringSet) || strings.EqualFold(typeName, parser.FieldTypeID) || strings.EqualFold(typeName, parser.FieldTypeIDSet)) {
|
||||
return sql3.NewErrBadColumnConstraint(col.Name.NamePos.Line, col.Name.NamePos.Column, "CACHETYPE", typeName)
|
||||
}
|
||||
//check the type of the expression for SIZE
|
||||
if c.Size.IsValid() {
|
||||
if _, ok := c.SizeExpr.(*parser.IntegerLit); !ok {
|
||||
return sql3.NewErrIntegerLiteral(c.SizeExpr.Pos().Line, c.SizeExpr.Pos().Column)
|
||||
}
|
||||
}
|
||||
// Make sure a TIMEQUANTUM constraint (which is incompatible with
|
||||
// CACHETYPE) hasn't been specified.
|
||||
if _, ok := handledConstraints[parser.TIMEQUANTUM]; ok {
|
||||
return sql3.NewErrConflictingColumnConstraint(col.Name.NamePos.Line, col.Name.NamePos.Column, parser.CACHETYPE, parser.TIMEQUANTUM)
|
||||
}
|
||||
handledConstraints[parser.CACHETYPE] = struct{}{}
|
||||
|
||||
case *parser.MinConstraint:
|
||||
// Make sure we have either an integer or unary type.
|
||||
switch c.Expr.(type) {
|
||||
case *parser.IntegerLit, *parser.UnaryExpr:
|
||||
// pass
|
||||
default:
|
||||
return sql3.NewErrBadColumnConstraint(col.Name.NamePos.Line, col.Name.NamePos.Column, "MIN", typeName)
|
||||
}
|
||||
handledConstraints[parser.MIN] = struct{}{}
|
||||
|
||||
case *parser.MaxConstraint:
|
||||
// Make sure we have either an integer or unary type.
|
||||
switch c.Expr.(type) {
|
||||
case *parser.IntegerLit, *parser.UnaryExpr:
|
||||
// pass
|
||||
default:
|
||||
return sql3.NewErrBadColumnConstraint(col.Name.NamePos.Line, col.Name.NamePos.Column, "MAX", typeName)
|
||||
}
|
||||
handledConstraints[parser.MAX] = struct{}{}
|
||||
|
||||
case *parser.TimeUnitConstraint:
|
||||
//make sure we have an timestamp type
|
||||
if !strings.EqualFold(typeName, parser.FieldTypeTimestamp) {
|
||||
return sql3.NewErrBadColumnConstraint(col.Name.NamePos.Line, col.Name.NamePos.Column, "TIMEUNIT", typeName)
|
||||
}
|
||||
//check the type of the expression
|
||||
unit, ok := c.Expr.(*parser.StringLit)
|
||||
if !ok {
|
||||
return sql3.NewErrStringLiteral(c.Expr.Pos().Line, c.Expr.Pos().Column)
|
||||
}
|
||||
if !pilosa.IsValidTimeUnit(unit.Value) {
|
||||
return sql3.NewErrInvalidTimeUnit(c.Expr.Pos().Line, c.Expr.Pos().Column, unit.Value)
|
||||
}
|
||||
if c.EpochExpr != nil {
|
||||
//check the type of the expression
|
||||
_, ok := c.EpochExpr.(*parser.StringLit)
|
||||
if !ok {
|
||||
return sql3.NewErrStringLiteral(c.EpochExpr.Pos().Line, c.EpochExpr.Pos().Column)
|
||||
}
|
||||
}
|
||||
handledConstraints[parser.TIMEUNIT] = struct{}{}
|
||||
|
||||
case *parser.TimeQuantumConstraint:
|
||||
//make sure we have a set type
|
||||
if !(strings.EqualFold(typeName, parser.FieldTypeStringSet) || strings.EqualFold(typeName, parser.FieldTypeIDSet)) {
|
||||
return sql3.NewErrBadColumnConstraint(col.Name.NamePos.Line, col.Name.NamePos.Column, "TIMEQUANTUM", typeName)
|
||||
}
|
||||
//check the type of the expression
|
||||
unit, ok := c.Expr.(*parser.StringLit)
|
||||
if !ok {
|
||||
return sql3.NewErrStringLiteral(c.Expr.Pos().Line, c.Expr.Pos().Column)
|
||||
}
|
||||
quantum := pilosa.TimeQuantum(strings.ToUpper(unit.Value))
|
||||
if !quantum.Valid() {
|
||||
return sql3.NewErrInvalidTimeQuantum(c.Expr.Pos().Line, c.Expr.Pos().Column, unit.Value)
|
||||
}
|
||||
if c.TtlExpr != nil {
|
||||
//check the type of the expression
|
||||
ttl, ok := c.TtlExpr.(*parser.StringLit)
|
||||
if !ok {
|
||||
return sql3.NewErrStringLiteral(c.Expr.Pos().Line, c.Expr.Pos().Column)
|
||||
}
|
||||
_, err := time.ParseDuration(ttl.Value)
|
||||
if err != nil {
|
||||
return sql3.NewErrInvalidDuration(c.Expr.Pos().Line, c.Expr.Pos().Column, ttl.Value)
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure a CACHETYPE constraint (which is incompatible with
|
||||
// TIMEQUANTUM) hasn't been specified.
|
||||
if _, ok := handledConstraints[parser.CACHETYPE]; ok {
|
||||
return sql3.NewErrConflictingColumnConstraint(col.Name.NamePos.Line, col.Name.NamePos.Column, parser.CACHETYPE, parser.TIMEQUANTUM)
|
||||
}
|
||||
|
||||
handledConstraints[parser.TIMEQUANTUM] = struct{}{}
|
||||
|
||||
default:
|
||||
return sql3.NewErrInternalf("unhandled column constraint type '%T'", c)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
27
sql3/planner/compiledroptable.go
Normal file
27
sql3/planner/compiledroptable.go
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
pilosa "github.com/molecula/featurebase/v3"
|
||||
"github.com/molecula/featurebase/v3/sql3"
|
||||
"github.com/molecula/featurebase/v3/sql3/parser"
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// compileDropTableStatement compiles a DROP TABLE statement into a
|
||||
// PlanOperator.
|
||||
func (p *ExecutionPlanner) compileDropTableStatement(stmt *parser.DropTableStatement) (_ types.PlanOperator, err error) {
|
||||
tableName := parser.IdentName(stmt.Name)
|
||||
index, err := p.schemaAPI.IndexInfo(context.Background(), tableName)
|
||||
if err != nil {
|
||||
if errors.Is(err, pilosa.ErrIndexNotFound) {
|
||||
return nil, sql3.NewErrTableNotFound(stmt.Name.NamePos.Line, stmt.Name.NamePos.Column, tableName)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return NewPlanOpQuery(NewPlanOpDropTable(p, index), p.sql), nil
|
||||
}
|
||||
181
sql3/planner/compileinsert.go
Normal file
181
sql3/planner/compileinsert.go
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
// Copyright 2022 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
pilosa "github.com/molecula/featurebase/v3"
|
||||
"github.com/molecula/featurebase/v3/sql3"
|
||||
"github.com/molecula/featurebase/v3/sql3/parser"
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// compileInsertStatement compiles an INSERT statement into a PlanOperator.
|
||||
func (p *ExecutionPlanner) compileInsertStatement(stmt *parser.InsertStatement) (_ types.PlanOperator, err error) {
|
||||
tableName := parser.IdentName(stmt.Table)
|
||||
|
||||
targetColumns := []*qualifiedRefPlanExpression{}
|
||||
insertValues := []types.PlanExpression{}
|
||||
|
||||
table, err := p.schemaAPI.IndexInfo(context.Background(), tableName)
|
||||
if err != nil {
|
||||
if errors.Is(err, pilosa.ErrIndexNotFound) {
|
||||
return nil, sql3.NewErrTableNotFound(stmt.Table.NamePos.Line, stmt.Table.NamePos.Column, tableName)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(stmt.Columns) > 0 {
|
||||
for _, columnIdent := range stmt.Columns {
|
||||
colName := parser.IdentName(columnIdent)
|
||||
|
||||
if strings.EqualFold(colName, "_id") {
|
||||
targetColumns = append(targetColumns, newQualifiedRefPlanExpression(tableName, colName, 0, parser.NewDataTypeID()))
|
||||
continue
|
||||
}
|
||||
|
||||
for idx, field := range table.Fields {
|
||||
if strings.EqualFold(colName, field.Name) {
|
||||
targetColumns = append(targetColumns, newQualifiedRefPlanExpression(tableName, colName, idx, fieldSQLDataType(field)))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for idx, field := range table.Fields {
|
||||
if strings.EqualFold("_exists", field.Name) {
|
||||
continue
|
||||
}
|
||||
targetColumns = append(targetColumns, newQualifiedRefPlanExpression(tableName, field.Name, idx, fieldSQLDataType(field)))
|
||||
}
|
||||
}
|
||||
|
||||
//add expressions from values list
|
||||
for _, expr := range stmt.ValueList.Exprs {
|
||||
e, err := p.compileExpr(expr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
insertValues = append(insertValues, e)
|
||||
}
|
||||
|
||||
return NewPlanOpInsert(p, tableName, targetColumns, insertValues), nil
|
||||
}
|
||||
|
||||
// analyzeInsertStatement analyzes an INSERT statement and returns and error if
|
||||
// anything is invalid.
|
||||
func (p *ExecutionPlanner) analyzeInsertStatement(stmt *parser.InsertStatement) error {
|
||||
// Check that referred table exists.
|
||||
tableName := parser.IdentName(stmt.Table)
|
||||
table, err := p.schemaAPI.IndexInfo(context.Background(), tableName)
|
||||
if err != nil {
|
||||
if errors.Is(err, pilosa.ErrIndexNotFound) {
|
||||
return sql3.NewErrTableNotFound(stmt.Table.NamePos.Line, stmt.Table.NamePos.Column, tableName)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
typeNames := make([]parser.ExprDataType, 0)
|
||||
// If the insert statement does not provide the list of columns in which to
|
||||
// insert the values, then the assumption is that the values apply to ALL
|
||||
// fields in the table.
|
||||
if len(stmt.Columns) == 0 {
|
||||
// Generate the list of types from the FeatureBase index.
|
||||
for _, field := range table.Fields {
|
||||
if strings.EqualFold("_exists", field.Name) {
|
||||
continue
|
||||
}
|
||||
typeNames = append(typeNames, fieldSQLDataType(field))
|
||||
}
|
||||
// Make sure (implicit) insert list and expression list have the same
|
||||
// number of items.
|
||||
if len(typeNames) != len(stmt.ValueList.Exprs) {
|
||||
return sql3.NewErrInsertExprTargetCountMismatch(stmt.ValueList.Lparen.Line, stmt.ValueList.Lparen.Column)
|
||||
}
|
||||
} else {
|
||||
// Check column list refers to actual columns, and that there are no
|
||||
// dupes.
|
||||
columnNameMap := make(map[string]struct{})
|
||||
for _, columnIdent := range stmt.Columns {
|
||||
colName := parser.IdentName(columnIdent)
|
||||
var typeName parser.ExprDataType
|
||||
|
||||
if strings.EqualFold(colName, "_id") {
|
||||
columnNameMap["_id"] = struct{}{}
|
||||
|
||||
// Determine, from the existing table, whether the _id is of
|
||||
// type ID or STRING.
|
||||
var idType parser.ExprDataType
|
||||
if table.Options.Keys {
|
||||
idType = parser.NewDataTypeString()
|
||||
} else {
|
||||
idType = parser.NewDataTypeID()
|
||||
}
|
||||
typeNames = append(typeNames, idType)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// Find the column in the existing table.
|
||||
columnFound := false
|
||||
for _, field := range table.Fields {
|
||||
if strings.EqualFold(colName, field.Name) {
|
||||
typeName = fieldSQLDataType(field)
|
||||
columnFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !columnFound {
|
||||
return sql3.NewErrColumnNotFound(columnIdent.NamePos.Line, columnIdent.NamePos.Column, colName)
|
||||
}
|
||||
|
||||
// Ensure the column name hasn't already appeared in the list of
|
||||
// columns.
|
||||
if _, found := columnNameMap[colName]; found {
|
||||
return sql3.NewErrDuplicateColumn(columnIdent.NamePos.Line, columnIdent.NamePos.Column, colName)
|
||||
}
|
||||
|
||||
typeNames = append(typeNames, typeName)
|
||||
columnNameMap[colName] = struct{}{}
|
||||
}
|
||||
|
||||
// Ensure we have an _id column.
|
||||
if _, ok := columnNameMap["_id"]; !ok {
|
||||
return sql3.NewErrInsertMustHaveIDColumn(stmt.ColumnsLparen.Line, stmt.ColumnsLparen.Column)
|
||||
}
|
||||
|
||||
// Ensure we have at least one more than just the _id column.
|
||||
if len(stmt.Columns) < 2 {
|
||||
return sql3.NewErrInsertMustAtLeastOneNonIDColumn(stmt.ColumnsLparen.Line, stmt.ColumnsLparen.Column)
|
||||
}
|
||||
|
||||
// Make sure insert list and expression list have the same number of items.
|
||||
if len(stmt.Columns) != len(stmt.ValueList.Exprs) {
|
||||
return sql3.NewErrInsertExprTargetCountMismatch(stmt.ValueList.Lparen.Line, stmt.ValueList.Lparen.Column)
|
||||
}
|
||||
}
|
||||
|
||||
// Check each of the expressions.
|
||||
for i, expr := range stmt.ValueList.Exprs {
|
||||
e, err := p.analyzeExpression(expr, stmt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !e.IsLiteral() {
|
||||
return sql3.NewErrLiteralExpected(expr.Pos().Line, expr.Pos().Column)
|
||||
}
|
||||
|
||||
// Type check against same ordinal position in column type list.
|
||||
if !typesAreAssignmentCompatible(typeNames[i], e.DataType()) {
|
||||
return sql3.NewErrTypeAssignmentIncompatible(expr.Pos().Line, expr.Pos().Column, e.DataType().TypeName(), typeNames[i].TypeName())
|
||||
}
|
||||
|
||||
stmt.ValueList.Exprs[i] = e
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
376
sql3/planner/compileselect.go
Normal file
376
sql3/planner/compileselect.go
Normal file
|
|
@ -0,0 +1,376 @@
|
|||
// Copyright 2022 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
pilosa "github.com/molecula/featurebase/v3"
|
||||
"github.com/molecula/featurebase/v3/sql3"
|
||||
"github.com/molecula/featurebase/v3/sql3/parser"
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// compileSelectStatment compiles a parser.SelectStatment AST into a PlanOperator
|
||||
func (p *ExecutionPlanner) compileSelectStatement(stmt *parser.SelectStatement, isSubquery bool) (types.PlanOperator, error) {
|
||||
query := NewPlanOpQuery(NewPlanOpNullTable(), p.sql)
|
||||
//p.pushPlannerScope(query)
|
||||
p.scopeStack.push(query)
|
||||
|
||||
// handle select list
|
||||
projections := make([]types.PlanExpression, 0)
|
||||
for _, c := range stmt.Columns {
|
||||
planExpr, err := p.compileExpr(c.Expr)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "planning select column expression")
|
||||
}
|
||||
if c.Alias != nil {
|
||||
planExpr = newAliasPlanExpression(c.Alias.Name, planExpr)
|
||||
}
|
||||
projections = append(projections, planExpr)
|
||||
}
|
||||
|
||||
// group by clause.
|
||||
groupByExprs := make([]types.PlanExpression, 0)
|
||||
for _, expr := range stmt.GroupByExprs {
|
||||
switch expr := expr.(type) {
|
||||
case *parser.QualifiedRef:
|
||||
groupByExprs = append(groupByExprs, newQualifiedRefPlanExpression(expr.Table.Name, expr.Column.Name, expr.ColumnIndex, expr.DataType()))
|
||||
default:
|
||||
return nil, sql3.NewErrInternalf("unsupported expression type in GROUP BY clause: %T", expr)
|
||||
}
|
||||
}
|
||||
var err error
|
||||
|
||||
if stmt.Having.IsValid() {
|
||||
query.AddWarning("HAVING is not yet supported")
|
||||
}
|
||||
|
||||
// handle distinct
|
||||
if stmt.Distinct.IsValid() {
|
||||
query.AddWarning("DISTINCT not yet implemented")
|
||||
}
|
||||
|
||||
// source expression last
|
||||
source, err := p.compileSelectSource(query, stmt.WhereExpr, stmt.Source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//do we have straight projection or a group by?
|
||||
var compiledOp types.PlanOperator
|
||||
if len(query.aggregates) > 0 {
|
||||
compiledOp = NewPlanOpProjection(projections, NewPlanOpGroupBy(query.aggregates, groupByExprs, source))
|
||||
} else {
|
||||
compiledOp = NewPlanOpProjection(projections, source)
|
||||
}
|
||||
|
||||
// handle order by
|
||||
if len(stmt.OrderingTerms) > 0 {
|
||||
orderByFields := make([]*OrderByExpression, 0)
|
||||
for _, ot := range stmt.OrderingTerms {
|
||||
otExpr, err := p.compileExpr(ot.X)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f := &OrderByExpression{
|
||||
Expr: otExpr,
|
||||
}
|
||||
f.Order = orderByAsc
|
||||
if ot.Desc.IsValid() {
|
||||
f.Order = orderByDesc
|
||||
}
|
||||
orderByFields = append(orderByFields, f)
|
||||
}
|
||||
compiledOp = NewPlanOpOrderBy(orderByFields, compiledOp)
|
||||
}
|
||||
|
||||
//insert the top operator if it exists
|
||||
if stmt.Top.IsValid() {
|
||||
topExpr, err := p.compileExpr(stmt.TopExpr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
compiledOp = NewPlanOpTop(topExpr, compiledOp)
|
||||
}
|
||||
|
||||
//pop the scope
|
||||
//p.popPlannerScope()
|
||||
_ = p.scopeStack.pop()
|
||||
|
||||
//if it is a subquery, don't wrap in a PlanOpQuery
|
||||
if isSubquery {
|
||||
return compiledOp, nil
|
||||
}
|
||||
children := []types.PlanOperator{
|
||||
compiledOp,
|
||||
}
|
||||
return query.WithChildren(children...)
|
||||
}
|
||||
|
||||
func (p *ExecutionPlanner) compileSelectSource(scope *PlanOpQuery, whereExpr parser.Expr, source parser.Source) (types.PlanOperator, error) {
|
||||
if source == nil {
|
||||
return NewPlanOpNullTable(), nil
|
||||
}
|
||||
|
||||
switch sourceExpr := source.(type) {
|
||||
case *parser.JoinClause:
|
||||
topOp, err := p.compileSelectSource(scope, whereExpr, sourceExpr.X)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bottomOp, err := p.compileSelectSource(scope, whereExpr, sourceExpr.Y)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
scope.AddWarning("🦖 here there be dragons! JOINS are experimental.")
|
||||
if sourceExpr.Constraint == nil {
|
||||
scope.AddWarning("⚠️ cartesian products are never a good idea - are you missing a join constraint?")
|
||||
}
|
||||
return NewPlanOpNestedLoops(topOp, bottomOp), nil
|
||||
|
||||
case *parser.QualifiedTableName:
|
||||
//get all the qualified refs that refer to this table
|
||||
extractColumns := []types.PlanExpression{}
|
||||
|
||||
for _, r := range scope.referenceList {
|
||||
if sourceExpr.MatchesTablenameOrAlias(r.tableName) {
|
||||
extractColumns = append(extractColumns, r)
|
||||
}
|
||||
}
|
||||
|
||||
// handle the where clause
|
||||
where, err := p.compileExpr(whereExpr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//get for the table name
|
||||
tableName := parser.IdentName(sourceExpr.Name)
|
||||
|
||||
return NewPlanOpPQLTableScan(p, tableName, extractColumns, where), nil
|
||||
|
||||
case *parser.ParenSource:
|
||||
return p.compileSelectSource(scope, whereExpr, sourceExpr.X)
|
||||
|
||||
case *parser.SelectStatement:
|
||||
subQuery, err := p.compileSelectStatement(sourceExpr, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewPlanOpSubquery(subQuery), nil
|
||||
|
||||
default:
|
||||
return nil, sql3.NewErrInternalf("unexpected source type: %T", source)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ExecutionPlanner) analyzeSource(source parser.Source) error {
|
||||
if source == nil {
|
||||
return nil
|
||||
}
|
||||
switch source := source.(type) {
|
||||
case *parser.JoinClause:
|
||||
err := p.analyzeSource(source.X)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = p.analyzeSource(source.Y)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
case *parser.ParenSource:
|
||||
err := p.analyzeSource(source.X)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
case *parser.QualifiedTableName:
|
||||
//check table exists
|
||||
tableName := parser.IdentName(source.Name)
|
||||
table, err := p.schemaAPI.IndexInfo(context.Background(), tableName)
|
||||
if err != nil {
|
||||
if errors.Is(err, pilosa.ErrIndexNotFound) {
|
||||
return sql3.NewErrTableNotFound(source.Name.NamePos.Line, source.Name.NamePos.Column, tableName)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// populate the output columns from the source
|
||||
for idx, fld := range table.Fields {
|
||||
soc := &parser.SourceOutputColumn{
|
||||
TableName: tableName,
|
||||
ColumnName: fld.Name,
|
||||
ColumnIndex: idx,
|
||||
Datatype: fieldSQLDataType(fld),
|
||||
}
|
||||
source.OutputColumns = append(source.OutputColumns, soc)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
case *parser.SelectStatement:
|
||||
err := p.analyzeSelectStatement(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
default:
|
||||
return sql3.NewErrInternalf("unexpected source type: %T", source)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ExecutionPlanner) analyzeSelectStatement(stmt *parser.SelectStatement) error {
|
||||
//analyze source first - needed for name resolution
|
||||
err := p.analyzeSource(stmt.Source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := p.analyzeSelectStatementWildcards(stmt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, col := range stmt.Columns {
|
||||
expr, err := p.analyzeExpression(col.Expr, stmt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if expr != nil {
|
||||
col.Expr = expr
|
||||
}
|
||||
}
|
||||
|
||||
expr, err := p.analyzeExpression(stmt.TopExpr, stmt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if expr != nil {
|
||||
if !(expr.IsLiteral() && typeIsInteger(expr.DataType())) {
|
||||
return sql3.NewErrIntegerLiteral(stmt.TopExpr.Pos().Line, stmt.TopExpr.Pos().Column)
|
||||
}
|
||||
stmt.TopExpr = expr
|
||||
}
|
||||
|
||||
expr, err = p.analyzeExpression(stmt.WhereExpr, stmt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stmt.WhereExpr = expr
|
||||
|
||||
for i, g := range stmt.GroupByExprs {
|
||||
expr, err = p.analyzeExpression(g, stmt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if expr != nil {
|
||||
stmt.GroupByExprs[i] = expr
|
||||
}
|
||||
}
|
||||
|
||||
expr, err = p.analyzeExpression(stmt.HavingExpr, stmt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if expr != nil {
|
||||
stmt.HavingExpr = expr
|
||||
}
|
||||
|
||||
for _, term := range stmt.OrderingTerms {
|
||||
expr, err = p.analyzeExpression(term.X, stmt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if expr != nil {
|
||||
term.X = expr
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ExecutionPlanner) analyzeSelectStatementWildcards(stmt *parser.SelectStatement) error {
|
||||
if !stmt.HasWildcard() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// replace wildcards with column references
|
||||
newColumns := make([]*parser.ResultColumn, 0, len(stmt.Columns))
|
||||
for _, col := range stmt.Columns {
|
||||
|
||||
//handle the case of unqualified *
|
||||
if col.Star.IsValid() {
|
||||
|
||||
cols, err := p.columnsFromSource(stmt.Source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newColumns = append(newColumns, cols...)
|
||||
|
||||
} else {
|
||||
//handle the case of a qualified ref with a *
|
||||
if ref, ok := col.Expr.(*parser.QualifiedRef); ok && ref.Star.IsValid() {
|
||||
refName := parser.IdentName(ref.Table)
|
||||
src := stmt.Source.SourceFromAlias(refName)
|
||||
if src == nil {
|
||||
return sql3.NewErrTableNotFound(ref.Table.NamePos.Line, ref.Table.NamePos.Column, refName)
|
||||
}
|
||||
|
||||
cols, err := p.columnsFromSource(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newColumns = append(newColumns, cols...)
|
||||
|
||||
} else {
|
||||
//add the column as is...
|
||||
newColumns = append(newColumns, col)
|
||||
}
|
||||
}
|
||||
}
|
||||
stmt.Columns = newColumns
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ExecutionPlanner) columnsFromSource(source parser.Source) ([]*parser.ResultColumn, error) {
|
||||
result := []*parser.ResultColumn{}
|
||||
|
||||
switch src := source.(type) {
|
||||
case *parser.JoinClause:
|
||||
return nil, sql3.NewErrInternal("joins are not currently supported")
|
||||
case *parser.ParenSource:
|
||||
for _, oc := range src.PossibleOutputColumns() {
|
||||
result = append(result, &parser.ResultColumn{
|
||||
Expr: &parser.QualifiedRef{
|
||||
Table: &parser.Ident{Name: oc.TableName},
|
||||
Column: &parser.Ident{Name: oc.ColumnName},
|
||||
ColumnIndex: oc.ColumnIndex,
|
||||
},
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
|
||||
case *parser.QualifiedTableName:
|
||||
for _, oc := range src.PossibleOutputColumns() {
|
||||
result = append(result, &parser.ResultColumn{
|
||||
Expr: &parser.QualifiedRef{
|
||||
Table: &parser.Ident{Name: oc.TableName},
|
||||
Column: &parser.Ident{Name: oc.ColumnName},
|
||||
ColumnIndex: oc.ColumnIndex,
|
||||
},
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
|
||||
case *parser.SelectStatement:
|
||||
return nil, sql3.NewErrInternal("sub-selects are not currently supported")
|
||||
default:
|
||||
return nil, sql3.NewErrInternalf("unexpected source type: %T", source)
|
||||
}
|
||||
}
|
||||
134
sql3/planner/compileshow.go
Normal file
134
sql3/planner/compileshow.go
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
pilosa "github.com/molecula/featurebase/v3"
|
||||
"github.com/molecula/featurebase/v3/sql3"
|
||||
"github.com/molecula/featurebase/v3/sql3/parser"
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (p *ExecutionPlanner) compileShowTablesStatement(stmt parser.Statement) (types.PlanOperator, error) {
|
||||
indexInfo, err := p.schemaAPI.Schema(context.Background(), false)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "getting schema")
|
||||
}
|
||||
|
||||
columns := []types.PlanExpression{&qualifiedRefPlanExpression{
|
||||
tableName: "fb$tables",
|
||||
columnName: "name",
|
||||
columnIndex: 0,
|
||||
dataType: parser.NewDataTypeString(),
|
||||
}, &qualifiedRefPlanExpression{
|
||||
tableName: "fb$tables",
|
||||
columnName: "created_at",
|
||||
columnIndex: 1,
|
||||
dataType: parser.NewDataTypeTimestamp(),
|
||||
}, &qualifiedRefPlanExpression{
|
||||
tableName: "fb$tables",
|
||||
columnName: "track_existence",
|
||||
columnIndex: 2,
|
||||
dataType: parser.NewDataTypeBool(),
|
||||
}, &qualifiedRefPlanExpression{
|
||||
tableName: "fb$tables",
|
||||
columnName: "keys",
|
||||
columnIndex: 3,
|
||||
dataType: parser.NewDataTypeBool(),
|
||||
}, &qualifiedRefPlanExpression{
|
||||
tableName: "fb$tables",
|
||||
columnName: "shard_width",
|
||||
columnIndex: 4,
|
||||
dataType: parser.NewDataTypeInt(),
|
||||
}}
|
||||
|
||||
return NewPlanOpQuery(NewPlanOpProjection(columns, NewPlanOpFeatureBaseTables(indexInfo)), p.sql), nil
|
||||
}
|
||||
|
||||
func (p *ExecutionPlanner) compileShowColumnsStatement(stmt *parser.ShowColumnsStatement) (_ types.PlanOperator, err error) {
|
||||
tableName := parser.IdentName(stmt.TableName)
|
||||
index, err := p.schemaAPI.IndexInfo(context.Background(), tableName)
|
||||
if err != nil {
|
||||
if errors.Is(err, pilosa.ErrIndexNotFound) {
|
||||
return nil, sql3.NewErrTableNotFound(stmt.TableName.NamePos.Line, stmt.TableName.NamePos.Column, tableName)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
columns := []types.PlanExpression{&qualifiedRefPlanExpression{
|
||||
tableName: "fb$table_columns",
|
||||
columnName: "name",
|
||||
columnIndex: 0,
|
||||
dataType: parser.NewDataTypeString(),
|
||||
}, &qualifiedRefPlanExpression{ // the SQL3 data type description
|
||||
tableName: "fb$table_columns",
|
||||
columnName: "type",
|
||||
columnIndex: 1,
|
||||
dataType: parser.NewDataTypeString(),
|
||||
}, &qualifiedRefPlanExpression{ // the FeatureBase 'native' data type description
|
||||
tableName: "fb$table_columns",
|
||||
columnName: "internal_type",
|
||||
columnIndex: 2,
|
||||
dataType: parser.NewDataTypeString(),
|
||||
}, &qualifiedRefPlanExpression{
|
||||
tableName: "fb$table_columns",
|
||||
columnName: "created_at",
|
||||
columnIndex: 3,
|
||||
dataType: parser.NewDataTypeTimestamp(),
|
||||
}, &qualifiedRefPlanExpression{
|
||||
tableName: "fb$table_columns",
|
||||
columnName: "keys",
|
||||
columnIndex: 4,
|
||||
dataType: parser.NewDataTypeBool(),
|
||||
}, &qualifiedRefPlanExpression{
|
||||
tableName: "fb$table_columns",
|
||||
columnName: "cache_type",
|
||||
columnIndex: 5,
|
||||
dataType: parser.NewDataTypeString(),
|
||||
}, &qualifiedRefPlanExpression{
|
||||
tableName: "fb$table_columns",
|
||||
columnName: "cache_size",
|
||||
columnIndex: 6,
|
||||
dataType: parser.NewDataTypeInt(),
|
||||
}, &qualifiedRefPlanExpression{
|
||||
tableName: "fb$table_columns",
|
||||
columnName: "scale",
|
||||
columnIndex: 7,
|
||||
dataType: parser.NewDataTypeInt(),
|
||||
}, &qualifiedRefPlanExpression{
|
||||
tableName: "fb$table_columns",
|
||||
columnName: "min",
|
||||
columnIndex: 8,
|
||||
dataType: parser.NewDataTypeInt(),
|
||||
}, &qualifiedRefPlanExpression{
|
||||
tableName: "fb$table_columns",
|
||||
columnName: "max",
|
||||
columnIndex: 9,
|
||||
dataType: parser.NewDataTypeInt(),
|
||||
}, &qualifiedRefPlanExpression{
|
||||
tableName: "fb$table_columns",
|
||||
columnName: "timeunit",
|
||||
columnIndex: 10,
|
||||
dataType: parser.NewDataTypeString(),
|
||||
}, &qualifiedRefPlanExpression{
|
||||
tableName: "fb$table_columns",
|
||||
columnName: "epoch",
|
||||
columnIndex: 11,
|
||||
dataType: parser.NewDataTypeInt(),
|
||||
}, &qualifiedRefPlanExpression{
|
||||
tableName: "fb$table_columns",
|
||||
columnName: "timequantum",
|
||||
columnIndex: 12,
|
||||
dataType: parser.NewDataTypeString(),
|
||||
}, &qualifiedRefPlanExpression{
|
||||
tableName: "fb$table_columns",
|
||||
columnName: "ttl",
|
||||
columnIndex: 13,
|
||||
dataType: parser.NewDataTypeString(),
|
||||
}}
|
||||
|
||||
return NewPlanOpQuery(NewPlanOpProjection(columns, NewPlanOpFeatureBaseColumns(index)), p.sql), nil
|
||||
}
|
||||
197
sql3/planner/executionplanner.go
Normal file
197
sql3/planner/executionplanner.go
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
|
||||
pilosa "github.com/molecula/featurebase/v3"
|
||||
"github.com/molecula/featurebase/v3/sql3"
|
||||
"github.com/molecula/featurebase/v3/sql3/parser"
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
)
|
||||
|
||||
// PlannerScope holds scope for the planner
|
||||
// there is a stack of these in the ExecutionPlanner and some corresponding push/pop functions
|
||||
// this allows us to do scoped operations without passing stuff down into
|
||||
// every function
|
||||
type PlannerScope struct {
|
||||
scope types.PlanOperator
|
||||
}
|
||||
|
||||
// ExecutionPlanner compiles SQL text into a query plan
|
||||
type ExecutionPlanner struct {
|
||||
executor pilosa.Executor
|
||||
schemaAPI pilosa.SchemaAPI
|
||||
computeAPI pilosa.ComputeAPI
|
||||
sql string
|
||||
scopeStack *scopeStack
|
||||
}
|
||||
|
||||
func NewExecutionPlanner(executor pilosa.Executor, schemaAPI pilosa.SchemaAPI, computeAPI pilosa.ComputeAPI, sql string) *ExecutionPlanner {
|
||||
return &ExecutionPlanner{
|
||||
executor: executor,
|
||||
schemaAPI: schemaAPI,
|
||||
computeAPI: computeAPI,
|
||||
sql: sql,
|
||||
scopeStack: newScopeStack(),
|
||||
}
|
||||
}
|
||||
|
||||
// CompilePlan takes an AST (parser.Statement) and compiles into a query plan returning the root
|
||||
// PlanOperator
|
||||
// The act of compiling includes an analysis step that does semantic analysis of the AST, this includes
|
||||
// type checking, and sometimes AST rewriting. The compile phase uses the type-checked and rewritten AST
|
||||
// to produce a query plan.
|
||||
func (p *ExecutionPlanner) CompilePlan(ctx context.Context, stmt parser.Statement) (types.PlanOperator, error) {
|
||||
// call analyze first
|
||||
err := p.analyzePlan(stmt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var rootOperator types.PlanOperator
|
||||
switch stmt := stmt.(type) {
|
||||
case *parser.SelectStatement:
|
||||
rootOperator, err = p.compileSelectStatement(stmt, false)
|
||||
case *parser.ShowTablesStatement:
|
||||
rootOperator, err = p.compileShowTablesStatement(stmt)
|
||||
case *parser.ShowColumnsStatement:
|
||||
rootOperator, err = p.compileShowColumnsStatement(stmt)
|
||||
case *parser.CreateTableStatement:
|
||||
rootOperator, err = p.compileCreateTableStatement(stmt)
|
||||
case *parser.AlterTableStatement:
|
||||
rootOperator, err = p.compileAlterTableStatement(stmt)
|
||||
case *parser.DropTableStatement:
|
||||
rootOperator, err = p.compileDropTableStatement(stmt)
|
||||
case *parser.InsertStatement:
|
||||
rootOperator, err = p.compileInsertStatement(stmt)
|
||||
case *parser.BulkInsertStatement:
|
||||
rootOperator, err = p.compileBulkInsertStatement(stmt)
|
||||
default:
|
||||
return nil, sql3.NewErrInternalf("cannot plan statement: %T", stmt)
|
||||
}
|
||||
|
||||
// Optimize the plan.
|
||||
if err == nil {
|
||||
rootOperator, err = p.optimizePlan(ctx, rootOperator)
|
||||
}
|
||||
|
||||
// Log the plan. This happens even if an error occurred.
|
||||
if rootOperator != nil {
|
||||
plan := rootOperator.Plan()
|
||||
a, _ := json.MarshalIndent(plan, "", " ")
|
||||
log.Println(string(a))
|
||||
}
|
||||
|
||||
return rootOperator, err
|
||||
}
|
||||
|
||||
func (p *ExecutionPlanner) analyzePlan(stmt parser.Statement) error {
|
||||
switch stmt := stmt.(type) {
|
||||
case *parser.SelectStatement:
|
||||
return p.analyzeSelectStatement(stmt)
|
||||
case *parser.ShowTablesStatement:
|
||||
return nil
|
||||
case *parser.ShowColumnsStatement:
|
||||
return nil
|
||||
case *parser.CreateTableStatement:
|
||||
return p.analyzeCreateTableStatement(stmt)
|
||||
case *parser.AlterTableStatement:
|
||||
return p.analyzeAlterTableStatement(stmt)
|
||||
case *parser.DropTableStatement:
|
||||
return nil
|
||||
case *parser.InsertStatement:
|
||||
return p.analyzeInsertStatement(stmt)
|
||||
case *parser.BulkInsertStatement:
|
||||
return p.analyzeBulkInsertStatement(stmt)
|
||||
default:
|
||||
return sql3.NewErrInternalf("cannot analyze statement: %T", stmt)
|
||||
}
|
||||
}
|
||||
|
||||
type accessType byte
|
||||
|
||||
const (
|
||||
accessTypeReadData accessType = iota
|
||||
accessTypeWriteData
|
||||
accessTypeCreateObject
|
||||
accessTypeAlterObject
|
||||
accessTypeDropObject
|
||||
)
|
||||
|
||||
func (p *ExecutionPlanner) checkAccess(ctx context.Context, objectName string, _ accessType) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// convenience function that allows the planner to keep track of aggregates so we can
|
||||
// use them during optimization
|
||||
func (p *ExecutionPlanner) addAggregate(agg types.PlanExpression) error {
|
||||
table := p.scopeStack.read()
|
||||
if table == nil {
|
||||
return sql3.NewErrInternalf("unexpected symbol table state")
|
||||
}
|
||||
|
||||
switch s := table.scope.(type) {
|
||||
case *PlanOpQuery:
|
||||
s.aggregates = append(s.aggregates, agg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// addReference is a convenience function that allows the planner to keep track
|
||||
// of references so we can use them during optimization.
|
||||
func (p *ExecutionPlanner) addReference(ref *qualifiedRefPlanExpression) error {
|
||||
table := p.scopeStack.read()
|
||||
if table == nil {
|
||||
return sql3.NewErrInternalf("unexpected symbol table state")
|
||||
}
|
||||
|
||||
switch s := table.scope.(type) {
|
||||
case *PlanOpQuery:
|
||||
s.referenceList = append(s.referenceList, ref)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// scopeStack is a stack of PlannerScope with the usual push/pop methods.
|
||||
type scopeStack struct {
|
||||
st []*PlannerScope
|
||||
}
|
||||
|
||||
// newScopeStack returns a scope stack initialized with zero elements on the
|
||||
// stack.
|
||||
func newScopeStack() *scopeStack {
|
||||
return &scopeStack{
|
||||
st: make([]*PlannerScope, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// push adds the provided PlanOperator (as the scope of a PlannerScope) to the
|
||||
// scope stack.
|
||||
func (ss *scopeStack) push(scope types.PlanOperator) {
|
||||
ss.st = append(ss.st, &PlannerScope{
|
||||
scope: scope,
|
||||
})
|
||||
}
|
||||
|
||||
// pop removes (and returns) the last scope pushed to the stack.
|
||||
func (ss *scopeStack) pop() *PlannerScope {
|
||||
if len(ss.st) == 0 {
|
||||
return nil
|
||||
}
|
||||
ret := ss.st[len(ss.st)-1]
|
||||
ss.st = ss.st[:len(ss.st)-1]
|
||||
return ret
|
||||
}
|
||||
|
||||
// read returns the last scope pushed to the stack, but unlike pop, it does not
|
||||
// remove it.
|
||||
func (ss *scopeStack) read() *PlannerScope {
|
||||
if len(ss.st) == 0 {
|
||||
return nil
|
||||
}
|
||||
return ss.st[len(ss.st)-1]
|
||||
}
|
||||
1561
sql3/planner/executionplanner_test.go
Normal file
1561
sql3/planner/executionplanner_test.go
Normal file
File diff suppressed because it is too large
Load diff
2369
sql3/planner/expression.go
Normal file
2369
sql3/planner/expression.go
Normal file
File diff suppressed because it is too large
Load diff
708
sql3/planner/expressionagg.go
Normal file
708
sql3/planner/expressionagg.go
Normal file
|
|
@ -0,0 +1,708 @@
|
|||
// Copyright 2022 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/molecula/featurebase/v3/sql3"
|
||||
"github.com/molecula/featurebase/v3/sql3/parser"
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
)
|
||||
|
||||
// aggregator for the COUNT function
|
||||
type aggregateCount struct {
|
||||
count int64
|
||||
expr types.PlanExpression
|
||||
}
|
||||
|
||||
func NewAggCountBuffer(child types.PlanExpression) *aggregateCount {
|
||||
return &aggregateCount{0, child}
|
||||
}
|
||||
|
||||
func (c *aggregateCount) Update(ctx context.Context, row types.Row) error {
|
||||
var inc bool
|
||||
v, err := c.expr.Evaluate(row)
|
||||
if v != nil {
|
||||
inc = true
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if inc {
|
||||
c.count += 1
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *aggregateCount) Eval(ctx context.Context) (interface{}, error) {
|
||||
return c.count, nil
|
||||
}
|
||||
|
||||
// aggregator for the COUNT DISTINCT function
|
||||
type aggregateCountDistinct struct {
|
||||
valueSeen map[string]struct{}
|
||||
expr types.PlanExpression
|
||||
}
|
||||
|
||||
func NewAggCountDistinctBuffer(child types.PlanExpression) *aggregateCountDistinct {
|
||||
return &aggregateCountDistinct{make(map[string]struct{}), child}
|
||||
}
|
||||
|
||||
func (c *aggregateCountDistinct) Update(ctx context.Context, row types.Row) error {
|
||||
var value interface{}
|
||||
v, err := c.expr.Evaluate(row)
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
value = v
|
||||
|
||||
hash := fmt.Sprintf("%v", value)
|
||||
c.valueSeen[hash] = struct{}{}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *aggregateCountDistinct) Eval(ctx context.Context) (interface{}, error) {
|
||||
return int64(len(c.valueSeen)), nil
|
||||
}
|
||||
|
||||
// countPlanExpression handles COUNT()
|
||||
type countPlanExpression struct {
|
||||
arg types.PlanExpression
|
||||
returnDataType parser.ExprDataType
|
||||
}
|
||||
|
||||
var _ types.Aggregable = (*countPlanExpression)(nil)
|
||||
|
||||
func newCountPlanExpression(arg types.PlanExpression, returnDataType parser.ExprDataType) *countPlanExpression {
|
||||
return &countPlanExpression{
|
||||
arg: arg,
|
||||
returnDataType: returnDataType,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *countPlanExpression) Evaluate(currentRow []interface{}) (interface{}, error) {
|
||||
arg, ok := n.arg.(*qualifiedRefPlanExpression)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unexpected aggregate function arg type '%T'", n.arg)
|
||||
}
|
||||
return currentRow[arg.columnIndex], nil
|
||||
}
|
||||
|
||||
func (n *countPlanExpression) NewBuffer() (types.AggregationBuffer, error) {
|
||||
return NewAggCountBuffer(n), nil
|
||||
}
|
||||
|
||||
func (n *countPlanExpression) AggType() types.AggregateFunctionType {
|
||||
return types.AGGREGATE_COUNT
|
||||
}
|
||||
|
||||
func (n *countPlanExpression) AggExpression() types.PlanExpression {
|
||||
return n.arg
|
||||
}
|
||||
|
||||
func (n *countPlanExpression) AggAdditionalExpr() []types.PlanExpression {
|
||||
return []types.PlanExpression{}
|
||||
}
|
||||
|
||||
func (n *countPlanExpression) Type() parser.ExprDataType {
|
||||
return n.returnDataType
|
||||
}
|
||||
|
||||
func (n *countPlanExpression) Plan() map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
result["_expr"] = fmt.Sprintf("%T", n)
|
||||
result["dataType"] = n.Type().TypeName()
|
||||
result["arg"] = n.arg.Plan()
|
||||
return result
|
||||
}
|
||||
|
||||
func (n *countPlanExpression) Children() []types.PlanExpression {
|
||||
return []types.PlanExpression{}
|
||||
}
|
||||
|
||||
func (n *countPlanExpression) WithChildren(children ...types.PlanExpression) (types.PlanExpression, error) {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// countDistinctPlanExpression handles COUNT(DISTINCT)
|
||||
type countDistinctPlanExpression struct {
|
||||
arg types.PlanExpression
|
||||
returnDataType parser.ExprDataType
|
||||
}
|
||||
|
||||
var _ types.Aggregable = (*countDistinctPlanExpression)(nil)
|
||||
|
||||
func newCountDistinctPlanExpression(arg types.PlanExpression, returnDataType parser.ExprDataType) *countDistinctPlanExpression {
|
||||
return &countDistinctPlanExpression{
|
||||
arg: arg,
|
||||
returnDataType: returnDataType,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *countDistinctPlanExpression) Evaluate(currentRow []interface{}) (interface{}, error) {
|
||||
arg, ok := n.arg.(*qualifiedRefPlanExpression)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unexpected aggregate function arg type '%T'", n.arg)
|
||||
}
|
||||
return currentRow[arg.columnIndex], nil
|
||||
}
|
||||
|
||||
func (n *countDistinctPlanExpression) NewBuffer() (types.AggregationBuffer, error) {
|
||||
return NewAggCountDistinctBuffer(n), nil
|
||||
}
|
||||
|
||||
func (n *countDistinctPlanExpression) AggType() types.AggregateFunctionType {
|
||||
return types.AGGREGATE_COUNT_DISTINCT
|
||||
}
|
||||
|
||||
func (n *countDistinctPlanExpression) AggExpression() types.PlanExpression {
|
||||
return n.arg
|
||||
}
|
||||
|
||||
func (n *countDistinctPlanExpression) AggAdditionalExpr() []types.PlanExpression {
|
||||
return []types.PlanExpression{}
|
||||
}
|
||||
|
||||
func (n *countDistinctPlanExpression) Type() parser.ExprDataType {
|
||||
return n.returnDataType
|
||||
}
|
||||
|
||||
func (n *countDistinctPlanExpression) Plan() map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
result["_expr"] = fmt.Sprintf("%T", n)
|
||||
result["dataType"] = n.Type().TypeName()
|
||||
result["arg"] = n.arg.Plan()
|
||||
return result
|
||||
}
|
||||
|
||||
func (n *countDistinctPlanExpression) Children() []types.PlanExpression {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *countDistinctPlanExpression) WithChildren(children ...types.PlanExpression) (types.PlanExpression, error) {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// aggregator for the SUM function
|
||||
type aggregateSum struct {
|
||||
isnil bool
|
||||
sum float64
|
||||
expr types.PlanExpression
|
||||
}
|
||||
|
||||
func NewAggSumBuffer(child types.PlanExpression) *aggregateSum {
|
||||
return &aggregateSum{true, float64(0), child}
|
||||
}
|
||||
|
||||
func (m *aggregateSum) Update(ctx context.Context, row types.Row) error {
|
||||
v, err := m.expr.Evaluate(row)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var val interface{} = 0
|
||||
|
||||
if m.isnil {
|
||||
m.sum = 0
|
||||
m.isnil = false
|
||||
}
|
||||
|
||||
m.sum += val.(float64)
|
||||
|
||||
//return nil
|
||||
return sql3.NewErrInternalf("implement me")
|
||||
}
|
||||
|
||||
func (m *aggregateSum) Eval(ctx context.Context) (interface{}, error) {
|
||||
if m.isnil {
|
||||
return nil, nil
|
||||
}
|
||||
return m.sum, nil
|
||||
}
|
||||
|
||||
// sumPlanExpression handles SUM()
|
||||
type sumPlanExpression struct {
|
||||
arg types.PlanExpression
|
||||
returnDataType parser.ExprDataType
|
||||
}
|
||||
|
||||
var _ types.Aggregable = (*sumPlanExpression)(nil)
|
||||
|
||||
func newSumPlanExpression(arg types.PlanExpression, returnDataType parser.ExprDataType) *sumPlanExpression {
|
||||
return &sumPlanExpression{
|
||||
arg: arg,
|
||||
returnDataType: returnDataType,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *sumPlanExpression) Evaluate(currentRow []interface{}) (interface{}, error) {
|
||||
arg, ok := n.arg.(*qualifiedRefPlanExpression)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unexpected aggregate function arg type '%T'", n.arg)
|
||||
}
|
||||
return currentRow[arg.columnIndex], nil
|
||||
}
|
||||
|
||||
func (n *sumPlanExpression) NewBuffer() (types.AggregationBuffer, error) {
|
||||
return NewAggSumBuffer(n), nil
|
||||
}
|
||||
|
||||
func (n *sumPlanExpression) AggType() types.AggregateFunctionType {
|
||||
return types.AGGREGATE_SUM
|
||||
}
|
||||
|
||||
func (n *sumPlanExpression) AggExpression() types.PlanExpression {
|
||||
return n.arg
|
||||
}
|
||||
|
||||
func (n *sumPlanExpression) AggAdditionalExpr() []types.PlanExpression {
|
||||
return []types.PlanExpression{}
|
||||
}
|
||||
|
||||
func (n *sumPlanExpression) Type() parser.ExprDataType {
|
||||
return n.returnDataType
|
||||
}
|
||||
|
||||
func (n *sumPlanExpression) Plan() map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
result["_expr"] = fmt.Sprintf("%T", n)
|
||||
result["dataType"] = n.Type().TypeName()
|
||||
result["arg"] = n.arg.Plan()
|
||||
return result
|
||||
}
|
||||
|
||||
func (n *sumPlanExpression) Children() []types.PlanExpression {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *sumPlanExpression) WithChildren(children ...types.PlanExpression) (types.PlanExpression, error) {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// aggregator for AVG
|
||||
type aggregateAvg struct {
|
||||
sum float64
|
||||
rows int64
|
||||
expr types.PlanExpression
|
||||
}
|
||||
|
||||
func NewAggAvgBuffer(child types.PlanExpression) *aggregateAvg {
|
||||
const (
|
||||
sum = float64(0)
|
||||
rows = int64(0)
|
||||
)
|
||||
return &aggregateAvg{sum, rows, child}
|
||||
}
|
||||
|
||||
func (a *aggregateAvg) Update(ctx context.Context, row types.Row) error {
|
||||
v, err := a.expr.Evaluate(row)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
a.sum += v.(float64)
|
||||
a.rows += 1
|
||||
|
||||
//return nil
|
||||
return sql3.NewErrInternalf("implement me")
|
||||
}
|
||||
|
||||
func (a *aggregateAvg) Eval(ctx context.Context) (interface{}, error) {
|
||||
// This case is triggered when no rows exist.
|
||||
if a.sum == 0 && a.rows == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if a.rows == 0 {
|
||||
return float64(0), nil
|
||||
}
|
||||
|
||||
return a.sum / float64(a.rows), nil
|
||||
}
|
||||
|
||||
// avgPlanExpression handles AVG()
|
||||
type avgPlanExpression struct {
|
||||
arg types.PlanExpression
|
||||
returnDataType parser.ExprDataType
|
||||
}
|
||||
|
||||
var _ types.Aggregable = (*avgPlanExpression)(nil)
|
||||
|
||||
func newAvgPlanExpression(arg types.PlanExpression, returnDataType parser.ExprDataType) *avgPlanExpression {
|
||||
return &avgPlanExpression{
|
||||
arg: arg,
|
||||
returnDataType: returnDataType,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *avgPlanExpression) Evaluate(currentRow []interface{}) (interface{}, error) {
|
||||
arg, ok := n.arg.(*qualifiedRefPlanExpression)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unexpected aggregate function arg type '%T'", n.arg)
|
||||
}
|
||||
return currentRow[arg.columnIndex], nil
|
||||
}
|
||||
|
||||
func (n *avgPlanExpression) NewBuffer() (types.AggregationBuffer, error) {
|
||||
return NewAggAvgBuffer(n), nil
|
||||
}
|
||||
|
||||
func (n *avgPlanExpression) AggType() types.AggregateFunctionType {
|
||||
return types.AGGREGATE_AVG
|
||||
}
|
||||
|
||||
func (n *avgPlanExpression) AggExpression() types.PlanExpression {
|
||||
return n.arg
|
||||
}
|
||||
|
||||
func (n *avgPlanExpression) AggAdditionalExpr() []types.PlanExpression {
|
||||
return []types.PlanExpression{}
|
||||
}
|
||||
|
||||
func (n *avgPlanExpression) Type() parser.ExprDataType {
|
||||
return n.returnDataType
|
||||
}
|
||||
|
||||
func (n *avgPlanExpression) Plan() map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
result["_expr"] = fmt.Sprintf("%T", n)
|
||||
result["dataType"] = n.Type().TypeName()
|
||||
result["arg"] = n.arg.Plan()
|
||||
return result
|
||||
}
|
||||
|
||||
func (n *avgPlanExpression) Children() []types.PlanExpression {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *avgPlanExpression) WithChildren(children ...types.PlanExpression) (types.PlanExpression, error) {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// aggregator for MIN
|
||||
type aggreagateMin struct {
|
||||
val interface{}
|
||||
expr types.PlanExpression
|
||||
}
|
||||
|
||||
func NewAggMinBuffer(child types.PlanExpression) *aggreagateMin {
|
||||
return &aggreagateMin{nil, child}
|
||||
}
|
||||
|
||||
func (m *aggreagateMin) Update(ctx context.Context, row types.Row) error {
|
||||
v, err := m.expr.Evaluate(row)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if reflect.TypeOf(v) == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if m.val == nil {
|
||||
m.val = v
|
||||
return nil
|
||||
}
|
||||
|
||||
//return nil
|
||||
return sql3.NewErrInternalf("implement me")
|
||||
|
||||
}
|
||||
|
||||
func (m *aggreagateMin) Eval(ctx context.Context) (interface{}, error) {
|
||||
return m.val, nil
|
||||
}
|
||||
|
||||
// minPlanExpression handles MIN()
|
||||
type minPlanExpression struct {
|
||||
arg types.PlanExpression
|
||||
returnDataType parser.ExprDataType
|
||||
}
|
||||
|
||||
var _ types.Aggregable = (*minPlanExpression)(nil)
|
||||
|
||||
func newMinPlanExpression(arg types.PlanExpression, returnDataType parser.ExprDataType) *minPlanExpression {
|
||||
return &minPlanExpression{
|
||||
arg: arg,
|
||||
returnDataType: returnDataType,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *minPlanExpression) Evaluate(currentRow []interface{}) (interface{}, error) {
|
||||
arg, ok := n.arg.(*qualifiedRefPlanExpression)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unexpected aggregate function arg type '%T'", n.arg)
|
||||
}
|
||||
return currentRow[arg.columnIndex], nil
|
||||
}
|
||||
|
||||
func (n *minPlanExpression) NewBuffer() (types.AggregationBuffer, error) {
|
||||
return NewAggMinBuffer(n), nil
|
||||
}
|
||||
|
||||
func (n *minPlanExpression) AggType() types.AggregateFunctionType {
|
||||
return types.AGGREGATE_MIN
|
||||
}
|
||||
|
||||
func (n *minPlanExpression) AggExpression() types.PlanExpression {
|
||||
return n.arg
|
||||
}
|
||||
|
||||
func (n *minPlanExpression) AggAdditionalExpr() []types.PlanExpression {
|
||||
return []types.PlanExpression{}
|
||||
}
|
||||
|
||||
func (n *minPlanExpression) Type() parser.ExprDataType {
|
||||
return n.returnDataType
|
||||
}
|
||||
|
||||
func (n *minPlanExpression) Plan() map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
result["_expr"] = fmt.Sprintf("%T", n)
|
||||
result["dataType"] = n.Type().TypeName()
|
||||
result["arg"] = n.arg.Plan()
|
||||
return result
|
||||
}
|
||||
|
||||
func (n *minPlanExpression) Children() []types.PlanExpression {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *minPlanExpression) WithChildren(children ...types.PlanExpression) (types.PlanExpression, error) {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// aggregator for MAX
|
||||
type aggregateMax struct {
|
||||
val interface{}
|
||||
expr types.PlanExpression
|
||||
}
|
||||
|
||||
func NewAggMaxBuffer(child types.PlanExpression) *aggregateMax {
|
||||
return &aggregateMax{nil, child}
|
||||
}
|
||||
|
||||
func (m *aggregateMax) Update(ctx context.Context, row types.Row) error {
|
||||
v, err := m.expr.Evaluate(row)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if reflect.TypeOf(v) == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if m.val == nil {
|
||||
m.val = v
|
||||
return nil
|
||||
}
|
||||
|
||||
//return nil
|
||||
return sql3.NewErrInternalf("implement me")
|
||||
|
||||
}
|
||||
|
||||
func (m *aggregateMax) Eval(ctx context.Context) (interface{}, error) {
|
||||
return m.val, nil
|
||||
}
|
||||
|
||||
// maxPlanExpression handles MAX()
|
||||
type maxPlanExpression struct {
|
||||
arg types.PlanExpression
|
||||
returnDataType parser.ExprDataType
|
||||
}
|
||||
|
||||
var _ types.Aggregable = (*maxPlanExpression)(nil)
|
||||
|
||||
func newMaxPlanExpression(arg types.PlanExpression, returnDataType parser.ExprDataType) *maxPlanExpression {
|
||||
return &maxPlanExpression{
|
||||
arg: arg,
|
||||
returnDataType: returnDataType,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *maxPlanExpression) Evaluate(currentRow []interface{}) (interface{}, error) {
|
||||
arg, ok := n.arg.(*qualifiedRefPlanExpression)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unexpected aggregate function arg type '%T'", n.arg)
|
||||
}
|
||||
return currentRow[arg.columnIndex], nil
|
||||
}
|
||||
|
||||
func (n *maxPlanExpression) NewBuffer() (types.AggregationBuffer, error) {
|
||||
return NewAggMaxBuffer(n), nil
|
||||
}
|
||||
|
||||
func (n *maxPlanExpression) AggType() types.AggregateFunctionType {
|
||||
return types.AGGREGATE_MAX
|
||||
}
|
||||
|
||||
func (n *maxPlanExpression) AggExpression() types.PlanExpression {
|
||||
return n.arg
|
||||
}
|
||||
|
||||
func (n *maxPlanExpression) AggAdditionalExpr() []types.PlanExpression {
|
||||
return []types.PlanExpression{}
|
||||
}
|
||||
|
||||
func (n *maxPlanExpression) Type() parser.ExprDataType {
|
||||
return n.returnDataType
|
||||
}
|
||||
|
||||
func (n *maxPlanExpression) Plan() map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
result["_expr"] = fmt.Sprintf("%T", n)
|
||||
result["dataType"] = n.Type().TypeName()
|
||||
result["arg"] = n.arg.Plan()
|
||||
return result
|
||||
}
|
||||
|
||||
func (n *maxPlanExpression) Children() []types.PlanExpression {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *maxPlanExpression) WithChildren(children ...types.PlanExpression) (types.PlanExpression, error) {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// percentilePlanExpression handles PERCENTILE()
|
||||
type percentilePlanExpression struct {
|
||||
arg types.PlanExpression
|
||||
nthArg types.PlanExpression
|
||||
returnDataType parser.ExprDataType
|
||||
}
|
||||
|
||||
var _ types.Aggregable = (*percentilePlanExpression)(nil)
|
||||
|
||||
func newPercentilePlanExpression(arg types.PlanExpression, nthArg types.PlanExpression, returnDataType parser.ExprDataType) *percentilePlanExpression {
|
||||
return &percentilePlanExpression{
|
||||
arg: arg,
|
||||
nthArg: nthArg,
|
||||
returnDataType: returnDataType,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *percentilePlanExpression) Evaluate(currentRow []interface{}) (interface{}, error) {
|
||||
arg, ok := n.arg.(*qualifiedRefPlanExpression)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unexpected aggregate function arg type '%T'", n.arg)
|
||||
}
|
||||
return currentRow[arg.columnIndex], nil
|
||||
}
|
||||
|
||||
func (n *percentilePlanExpression) NewBuffer() (types.AggregationBuffer, error) {
|
||||
return NewAggCountBuffer(n), nil
|
||||
}
|
||||
|
||||
func (n *percentilePlanExpression) AggType() types.AggregateFunctionType {
|
||||
return types.AGGREGATE_PERCENTILE
|
||||
}
|
||||
|
||||
func (n *percentilePlanExpression) AggExpression() types.PlanExpression {
|
||||
return n.arg
|
||||
}
|
||||
|
||||
func (n *percentilePlanExpression) AggAdditionalExpr() []types.PlanExpression {
|
||||
return []types.PlanExpression{
|
||||
n.nthArg,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *percentilePlanExpression) Type() parser.ExprDataType {
|
||||
return n.returnDataType
|
||||
}
|
||||
|
||||
func (n *percentilePlanExpression) Plan() map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
result["_expr"] = fmt.Sprintf("%T", n)
|
||||
result["dataType"] = n.Type().TypeName()
|
||||
result["arg"] = n.arg.Plan()
|
||||
return result
|
||||
}
|
||||
|
||||
func (n *percentilePlanExpression) Children() []types.PlanExpression {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *percentilePlanExpression) WithChildren(children ...types.PlanExpression) (types.PlanExpression, error) {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
//aggregator for last
|
||||
type aggregateLast struct {
|
||||
val interface{}
|
||||
expr types.PlanExpression
|
||||
}
|
||||
|
||||
func NewAggLastBuffer(child types.PlanExpression) *aggregateLast {
|
||||
return &aggregateLast{nil, child}
|
||||
}
|
||||
|
||||
func (l *aggregateLast) Update(ctx context.Context, row types.Row) error {
|
||||
v, err := l.expr.Evaluate(row)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
l.val = v
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *aggregateLast) Eval(ctx context.Context) (interface{}, error) {
|
||||
return l.val, nil
|
||||
}
|
||||
|
||||
// aggregator for first
|
||||
type aggregateFirst struct {
|
||||
val interface{}
|
||||
expr types.PlanExpression
|
||||
}
|
||||
|
||||
func NewFirstBuffer(child types.PlanExpression) *aggregateFirst {
|
||||
return &aggregateFirst{nil, child}
|
||||
}
|
||||
|
||||
func (f *aggregateFirst) Update(ctx context.Context, row types.Row) error {
|
||||
if f.val != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
v, err := f.expr.Evaluate(row)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
f.val = v
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *aggregateFirst) Eval(ctx context.Context) (interface{}, error) {
|
||||
return f.val, nil
|
||||
}
|
||||
687
sql3/planner/expressionanalyzer.go
Normal file
687
sql3/planner/expressionanalyzer.go
Normal file
|
|
@ -0,0 +1,687 @@
|
|||
// Copyright 2022 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import (
|
||||
"github.com/molecula/featurebase/v3/sql3"
|
||||
"github.com/molecula/featurebase/v3/sql3/parser"
|
||||
)
|
||||
|
||||
// analyze a parser.Expr. returns the analyzed parser.Expr
|
||||
func (p *ExecutionPlanner) analyzeExpression(expr parser.Expr, scope parser.Statement) (parser.Expr, error) {
|
||||
if expr == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
switch e := expr.(type) {
|
||||
case *parser.BinaryExpr:
|
||||
return p.analyzeBinaryExpression(e, scope)
|
||||
|
||||
case *parser.BoolLit:
|
||||
return e, nil
|
||||
|
||||
case *parser.Call:
|
||||
return p.analyzeCallExpression(e, scope)
|
||||
|
||||
case *parser.CastExpr:
|
||||
analyzedExpr, err := p.analyzeExpression(e.X, scope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
targetType, err := dataTypeFromParserType(e.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !typesCanBeCast(analyzedExpr.DataType(), targetType) {
|
||||
return nil, sql3.NewErrInvalidCast(analyzedExpr.Pos().Line, analyzedExpr.Pos().Column, analyzedExpr.DataType().TypeName(), targetType.TypeName())
|
||||
}
|
||||
e.X = analyzedExpr
|
||||
e.ResultDataType = targetType
|
||||
return e, nil
|
||||
|
||||
case *parser.ExprList:
|
||||
for i, ex := range e.Exprs {
|
||||
listExpr, err := p.analyzeExpression(ex, scope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e.Exprs[i] = listExpr
|
||||
}
|
||||
return e, nil
|
||||
|
||||
case *parser.Ident:
|
||||
switch sc := scope.(type) {
|
||||
case *parser.SelectStatement:
|
||||
// turn *parser.Ident into *parser.QualifiedRef
|
||||
if sc.Source == nil {
|
||||
return nil, sql3.NewErrColumnNotFound(e.NamePos.Line, e.NamePos.Column, e.Name)
|
||||
}
|
||||
|
||||
// go find the first ident in the source that matches
|
||||
oc, err := sc.Source.OutputColumnNamed(e.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if oc == nil {
|
||||
return nil, sql3.NewErrColumnNotFound(e.NamePos.Line, e.NamePos.Column, e.Name)
|
||||
}
|
||||
|
||||
ident := &parser.QualifiedRef{
|
||||
Table: &parser.Ident{
|
||||
Name: oc.TableName,
|
||||
NamePos: e.NamePos,
|
||||
},
|
||||
Column: &parser.Ident{
|
||||
Name: oc.ColumnName,
|
||||
NamePos: e.NamePos,
|
||||
},
|
||||
ColumnIndex: oc.ColumnIndex,
|
||||
}
|
||||
return p.analyzeExpression(ident, scope)
|
||||
|
||||
case *parser.InsertStatement:
|
||||
return nil, sql3.NewErrColumnNotFound(e.NamePos.Line, e.NamePos.Column, e.Name)
|
||||
|
||||
default:
|
||||
return nil, sql3.NewErrInternalf("unhandled scope type '%T'", sc)
|
||||
}
|
||||
|
||||
case *parser.NullLit:
|
||||
return e, nil
|
||||
|
||||
case *parser.IntegerLit:
|
||||
return e, nil
|
||||
|
||||
case *parser.FloatLit:
|
||||
return e, nil
|
||||
|
||||
case *parser.StringLit:
|
||||
return e, nil
|
||||
|
||||
case *parser.DateLit:
|
||||
return e, nil
|
||||
|
||||
case *parser.ParenExpr:
|
||||
pexpr, err := p.analyzeExpression(e.X, scope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e.X = pexpr
|
||||
return e, nil
|
||||
|
||||
case *parser.SetLiteralExpr:
|
||||
for i, ex := range e.Members {
|
||||
listExpr, err := p.analyzeExpression(ex, scope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e.Members[i] = listExpr
|
||||
}
|
||||
|
||||
if len(e.Members) == 0 {
|
||||
return nil, sql3.NewErrLiteralEmptySetNotAllowed(e.Lbracket.Line, e.Lbracket.Column)
|
||||
}
|
||||
|
||||
setDataType := e.Members[0].DataType()
|
||||
switch setDataType.(type) {
|
||||
case *parser.DataTypeID, *parser.DataTypeInt:
|
||||
//make sure everything else is an int
|
||||
for _, mbr := range e.Members {
|
||||
if !typeIsInteger(mbr.DataType()) {
|
||||
return nil, sql3.NewErrIntExpressionExpected(mbr.Pos().Line, mbr.Pos().Column)
|
||||
}
|
||||
}
|
||||
e.ResultDataType = parser.NewDataTypeIDSet()
|
||||
|
||||
case *parser.DataTypeString:
|
||||
//make sure everything else is a string
|
||||
for _, mbr := range e.Members {
|
||||
if !typeIsString(mbr.DataType()) {
|
||||
return nil, sql3.NewErrStringExpressionExpected(mbr.Pos().Line, mbr.Pos().Column)
|
||||
}
|
||||
}
|
||||
e.ResultDataType = parser.NewDataTypeStringSet()
|
||||
|
||||
default:
|
||||
return nil, sql3.NewErrSetLiteralMustContainIntOrString(e.Members[0].Pos().Line, e.Members[0].Pos().Column)
|
||||
}
|
||||
|
||||
return e, nil
|
||||
|
||||
case *parser.QualifiedRef:
|
||||
switch sc := scope.(type) {
|
||||
case *parser.SelectStatement:
|
||||
|
||||
if e.Table.Name == "" {
|
||||
// there is no table or alias name in the qualifier so go look for the first matching column from any of the sources
|
||||
oc, err := sc.Source.OutputColumnNamed(e.Column.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if oc != nil {
|
||||
e.RefDataType = oc.Datatype
|
||||
e.ColumnIndex = oc.ColumnIndex
|
||||
return e, nil
|
||||
|
||||
}
|
||||
return nil, sql3.NewErrColumnNotFound(e.Column.NamePos.Line, e.Column.NamePos.Column, e.Column.Name)
|
||||
|
||||
} else {
|
||||
oc, err := sc.Source.OutputColumnQualifierNamed(e.Table.Name, e.Column.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if oc != nil {
|
||||
e.RefDataType = oc.Datatype
|
||||
e.ColumnIndex = oc.ColumnIndex
|
||||
return e, nil
|
||||
|
||||
}
|
||||
return nil, sql3.NewErrColumnNotFound(e.Column.NamePos.Line, e.Column.NamePos.Column, e.Column.Name)
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, sql3.NewErrInternalf("unhandled scope type '%T'", sc)
|
||||
}
|
||||
|
||||
case *parser.Range:
|
||||
return p.analyzeRangeExpression(e, scope)
|
||||
|
||||
case *parser.CaseExpr:
|
||||
operand, err := p.analyzeExpression(e.Operand, scope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e.Operand = operand
|
||||
|
||||
for i, ex := range e.Blocks {
|
||||
block, err := p.analyzeCaseBlockExpression(ex, e, scope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e.Blocks[i] = block
|
||||
}
|
||||
|
||||
elseExpr, err := p.analyzeExpression(e.ElseExpr, scope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e.ElseExpr = elseExpr
|
||||
|
||||
//type checking...
|
||||
if e.Operand != nil {
|
||||
//we are "case expr when" form, so need to make sure that 'expr' and all block conditions are equatable
|
||||
for _, blk := range e.Blocks {
|
||||
if !typesAreComparable(e.Operand.DataType(), blk.Condition.DataType()) {
|
||||
return nil, sql3.NewErrTypesAreNotEquatable(blk.Condition.Pos().Line, blk.Condition.Pos().Column, e.Operand.DataType().TypeName(), blk.Condition.DataType().TypeName())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//we are "case when" form, so need to make sure that all block conditions are bool
|
||||
for _, blk := range e.Blocks {
|
||||
if !typeIsBool(blk.Condition.DataType()) {
|
||||
return nil, sql3.NewErrBooleanExpressionExpected(blk.Condition.Pos().Line, blk.Condition.Pos().Column)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(e.Blocks) == 0 {
|
||||
return nil, sql3.NewErrInternalf("unexpected case blocks length")
|
||||
}
|
||||
|
||||
//set the result type for the case to the type of the first block
|
||||
caseType := e.Blocks[0].DataType()
|
||||
|
||||
//now check all the other blocks to make sure that each body is assignment compatible with that type
|
||||
for _, blk := range e.Blocks {
|
||||
if !typesAreAssignmentCompatible(caseType, blk.Body.DataType()) {
|
||||
return nil, sql3.NewErrTypeAssignmentIncompatible(blk.Body.Pos().Line, blk.Body.Pos().Column, caseType.TypeName(), blk.Body.DataType().TypeName())
|
||||
}
|
||||
}
|
||||
|
||||
//if there is an else check that too
|
||||
if e.ElseExpr != nil {
|
||||
if !typesAreAssignmentCompatible(caseType, e.ElseExpr.DataType()) {
|
||||
return nil, sql3.NewErrTypeAssignmentIncompatible(e.ElseExpr.Pos().Line, e.ElseExpr.Pos().Column, caseType.TypeName(), e.ElseExpr.DataType().TypeName())
|
||||
}
|
||||
}
|
||||
|
||||
e.ResultDataType = caseType
|
||||
|
||||
return e, nil
|
||||
|
||||
case *parser.UnaryExpr:
|
||||
return p.analyzeUnaryExpression(e, scope)
|
||||
|
||||
case *parser.SelectStatement:
|
||||
err := p.analyzeSelectStatement(e)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// if we return more than one column
|
||||
if len(e.Columns) > 1 {
|
||||
return nil, sql3.NewErrInternalf("subquery must return only one column")
|
||||
}
|
||||
return e, nil
|
||||
|
||||
default:
|
||||
return nil, sql3.NewErrInternalf("unexpected SQL expression type: %T", expr)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ExecutionPlanner) analyzeUnaryExpression(expr *parser.UnaryExpr, scope parser.Statement) (parser.Expr, error) {
|
||||
|
||||
x, err := p.analyzeExpression(expr.X, scope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
expr.X = x
|
||||
|
||||
switch op := expr.Op; op {
|
||||
|
||||
//bitwise operators
|
||||
case parser.BITNOT:
|
||||
if !typeIsCompatibleWithBitwiseOperator(x.DataType()) {
|
||||
return nil, sql3.NewErrTypeIncompatibleWithBitwiseOperator(x.Pos().Line, x.Pos().Column, op.String(), x.DataType().TypeName())
|
||||
}
|
||||
expr.ResultDataType = x.DataType()
|
||||
return expr, nil
|
||||
|
||||
//arithmetic operators
|
||||
case parser.PLUS, parser.MINUS:
|
||||
if !typeIsCompatibleWithArithmeticOperator(x.DataType(), op) {
|
||||
return nil, sql3.NewErrTypeIncompatibleWithArithmeticOperator(x.Pos().Line, x.Pos().Column, op.String(), x.DataType().TypeName())
|
||||
}
|
||||
if typeIsInteger(x.DataType()) {
|
||||
expr.ResultDataType = parser.NewDataTypeInt()
|
||||
} else if typeIsFloat(x.DataType()) {
|
||||
fd, ok := x.DataType().(*parser.DataTypeDecimal)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unexpected data type")
|
||||
}
|
||||
expr.ResultDataType = fd
|
||||
} else {
|
||||
return nil, sql3.NewErrInternalf("unexpected unary expression type: %T", x.DataType())
|
||||
}
|
||||
return expr, nil
|
||||
|
||||
default:
|
||||
return nil, sql3.NewErrInternalf("unexpected unary expression operator: %s", op)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ExecutionPlanner) analyzeBinaryExpression(expr *parser.BinaryExpr, scope parser.Statement) (parser.Expr, error) {
|
||||
|
||||
//analyze both sides first
|
||||
x, err := p.analyzeExpression(expr.X, scope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
expr.X = x
|
||||
y, err := p.analyzeExpression(expr.Y, scope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
expr.Y = y
|
||||
|
||||
//handle operator
|
||||
switch op := expr.Op; op {
|
||||
|
||||
//logical operators
|
||||
case parser.AND, parser.OR:
|
||||
if !typeIsCompatibleWithLogicalOperator(x.DataType()) {
|
||||
return nil, sql3.NewErrTypeIncompatibleWithLogicalOperator(x.Pos().Line, x.Pos().Column, op.String(), x.DataType().TypeName())
|
||||
}
|
||||
if !typeIsCompatibleWithLogicalOperator(y.DataType()) {
|
||||
return nil, sql3.NewErrTypeIncompatibleWithLogicalOperator(y.Pos().Line, y.Pos().Column, op.String(), y.DataType().TypeName())
|
||||
}
|
||||
//logical operator so type of expr is bool
|
||||
expr.ResultDataType = parser.NewDataTypeBool()
|
||||
return expr, nil
|
||||
|
||||
//equality operators
|
||||
case parser.EQ, parser.NE:
|
||||
if !typeIsCompatibleWithEqualityOperator(x.DataType()) {
|
||||
return nil, sql3.NewErrTypeIncompatibleWithEqualityOperator(x.Pos().Line, x.Pos().Column, op.String(), x.DataType().TypeName())
|
||||
}
|
||||
if !typeIsCompatibleWithEqualityOperator(y.DataType()) {
|
||||
return nil, sql3.NewErrTypeIncompatibleWithEqualityOperator(y.Pos().Line, y.Pos().Column, op.String(), y.DataType().TypeName())
|
||||
}
|
||||
if !typesAreComparable(x.DataType(), y.DataType()) {
|
||||
return nil, sql3.NewErrTypesAreNotEquatable(x.Pos().Line, x.Pos().Column, x.DataType().TypeName(), y.DataType().TypeName())
|
||||
}
|
||||
//equality operator so type of expr is bool
|
||||
expr.ResultDataType = parser.NewDataTypeBool()
|
||||
return expr, nil
|
||||
|
||||
//comparison operators
|
||||
case parser.LT, parser.LE, parser.GT, parser.GE:
|
||||
if !typeIsCompatibleWithComparisonOperator(x.DataType()) {
|
||||
return nil, sql3.NewErrTypeIncompatibleWithComparisonOperator(x.Pos().Line, x.Pos().Column, op.String(), x.DataType().TypeName())
|
||||
}
|
||||
if !typeIsCompatibleWithComparisonOperator(y.DataType()) {
|
||||
return nil, sql3.NewErrTypeIncompatibleWithComparisonOperator(y.Pos().Line, y.Pos().Column, op.String(), y.DataType().TypeName())
|
||||
}
|
||||
if !typesAreComparable(x.DataType(), y.DataType()) {
|
||||
return nil, sql3.NewErrTypesAreNotEquatable(x.Pos().Line, x.Pos().Column, x.DataType().TypeName(), y.DataType().TypeName())
|
||||
}
|
||||
//comparison operator so type of expr is bool
|
||||
expr.ResultDataType = parser.NewDataTypeBool()
|
||||
return expr, nil
|
||||
|
||||
//arithmetic operators
|
||||
case parser.PLUS, parser.MINUS, parser.STAR, parser.SLASH, parser.REM:
|
||||
if !typeIsCompatibleWithArithmeticOperator(x.DataType(), op) {
|
||||
return nil, sql3.NewErrTypeIncompatibleWithArithmeticOperator(x.Pos().Line, x.Pos().Column, op.String(), x.DataType().TypeName())
|
||||
}
|
||||
if !typeIsCompatibleWithArithmeticOperator(y.DataType(), op) {
|
||||
return nil, sql3.NewErrTypeIncompatibleWithArithmeticOperator(y.Pos().Line, y.Pos().Column, op.String(), y.DataType().TypeName())
|
||||
}
|
||||
|
||||
coercedType, err := typesCoercedForArithmeticOperator(x.DataType(), y.DataType(), x.Pos())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
expr.ResultDataType = coercedType
|
||||
return expr, nil
|
||||
|
||||
/*
|
||||
opx, okx := x.(*NumLiteralPlanExpresssion)
|
||||
opy, oky := y.(*NumLiteralPlanExpresssion)
|
||||
if okx && oky {
|
||||
//both literals so we can fold
|
||||
numx, err := strconv.Atoi(opx.value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
numy, err := strconv.Atoi(opy.value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch op {
|
||||
case parser.PLUS:
|
||||
value := numx + numy
|
||||
return NewNumLiteralPlanExpresssion(p, strconv.Itoa(value)), nil
|
||||
|
||||
case parser.MINUS:
|
||||
value := numx - numy
|
||||
return NewNumLiteralPlanExpresssion(p, strconv.Itoa(value)), nil
|
||||
|
||||
case parser.STAR:
|
||||
value := numx * numy
|
||||
return NewNumLiteralPlanExpresssion(p, strconv.Itoa(value)), nil
|
||||
|
||||
case parser.SLASH:
|
||||
value := numx / numy
|
||||
return NewNumLiteralPlanExpresssion(p, strconv.Itoa(value)), nil
|
||||
|
||||
case parser.REM:
|
||||
value := numx % numy
|
||||
return NewNumLiteralPlanExpresssion(p, strconv.Itoa(value)), nil
|
||||
|
||||
default:
|
||||
//run home to momma
|
||||
return NewBinOpPlanExpression(p, x, expr.Op, y), nil
|
||||
}
|
||||
} else {
|
||||
return NewBinOpPlanExpression(p, x, expr.Op, y), nil
|
||||
}*/
|
||||
|
||||
//bitwise operators
|
||||
case parser.BITAND, parser.BITOR, parser.LSHIFT, parser.RSHIFT:
|
||||
if !typeIsCompatibleWithBitwiseOperator(x.DataType()) {
|
||||
return nil, sql3.NewErrTypeIncompatibleWithBitwiseOperator(x.Pos().Line, x.Pos().Column, op.String(), x.DataType().TypeName())
|
||||
}
|
||||
if !typeIsCompatibleWithBitwiseOperator(y.DataType()) {
|
||||
return nil, sql3.NewErrTypeIncompatibleWithBitwiseOperator(y.Pos().Line, y.Pos().Column, op.String(), y.DataType().TypeName())
|
||||
}
|
||||
coercedType, err := typesCoercedForBitwiseOperator(x.DataType(), y.DataType(), x.Pos())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
expr.ResultDataType = coercedType
|
||||
return expr, nil
|
||||
|
||||
/*
|
||||
opx, okx := x.(*NumLiteralPlanExpression)
|
||||
opy, oky := y.(*NumLiteralPlanExpression)
|
||||
if okx && oky {
|
||||
//both literals so we can fold
|
||||
numx, err := strconv.Atoi(opx.value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
numy, err := strconv.Atoi(opy.value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch op {
|
||||
case parser.PLUS:
|
||||
value := numx + numy
|
||||
return NewNumLiteralPlanExpression(p, strconv.Itoa(value)), nil
|
||||
|
||||
case parser.MINUS:
|
||||
value := numx - numy
|
||||
return NewNumLiteralPlanExpression(p, strconv.Itoa(value)), nil
|
||||
|
||||
case parser.STAR:
|
||||
value := numx * numy
|
||||
return NewNumLiteralPlanExpression(p, strconv.Itoa(value)), nil
|
||||
|
||||
case parser.SLASH:
|
||||
value := numx / numy
|
||||
return NewNumLiteralPlanExpression(p, strconv.Itoa(value)), nil
|
||||
|
||||
case parser.REM:
|
||||
value := numx % numy
|
||||
return NewNumLiteralPlanExpression(p, strconv.Itoa(value)), nil
|
||||
|
||||
default:
|
||||
//run home to momma
|
||||
return newBinOpPlanExpression(x, expr.Op, y), nil
|
||||
}
|
||||
} else {
|
||||
return newBinOpPlanExpression(x, expr.Op, y), nil
|
||||
}*/
|
||||
|
||||
//null test
|
||||
case parser.IS, parser.ISNOT:
|
||||
_, ok := expr.Y.(*parser.NullLit)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("NULL expected")
|
||||
}
|
||||
//no type check against null...logical operator so type of expr is bool
|
||||
expr.ResultDataType = parser.NewDataTypeBool()
|
||||
return expr, nil
|
||||
|
||||
case parser.IN, parser.NOTIN:
|
||||
lst, ok := y.(*parser.ExprList)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrExpressionListExpected(y.Pos().Line, y.Pos().Column)
|
||||
}
|
||||
|
||||
for idx, ex := range lst.Exprs {
|
||||
|
||||
//check to see if our expression is a select statement
|
||||
//if it is it needs special handling
|
||||
sel, ok := ex.(*parser.SelectStatement)
|
||||
if ok {
|
||||
//we have a select in the expression list so make sure it is the only thing in the expression list
|
||||
if len(lst.Exprs) > 1 {
|
||||
return nil, sql3.NewErrInternalf("expresion list should only contain one select statement")
|
||||
}
|
||||
//make sure select only returns one column
|
||||
if len(sel.Columns) > 1 {
|
||||
return nil, sql3.NewErrInternalf("select used as part of IN expression should only return one column")
|
||||
}
|
||||
if !typesAreComparable(x.DataType(), sel.Columns[0].Expr.DataType()) {
|
||||
return nil, sql3.NewErrTypesAreNotEquatable(x.Pos().Line, x.Pos().Column, x.DataType().TypeName(), ex.DataType().TypeName())
|
||||
}
|
||||
|
||||
//need to turn this into an inner join
|
||||
selStmt, ok := scope.(*parser.SelectStatement)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unexpected scope type '%T'", scope)
|
||||
}
|
||||
|
||||
operator := &parser.JoinOperator{
|
||||
Inner: expr.OpPos,
|
||||
}
|
||||
|
||||
constraint := &parser.OnConstraint{
|
||||
X: &parser.BinaryExpr{
|
||||
X: expr.X,
|
||||
Op: parser.EQ,
|
||||
Y: sel.Columns[0].Expr,
|
||||
},
|
||||
}
|
||||
|
||||
if lhs, ok := selStmt.Source.(*parser.JoinClause); ok {
|
||||
selStmt.Source = &parser.JoinClause{
|
||||
X: lhs.X,
|
||||
Operator: lhs.Operator,
|
||||
Y: &parser.JoinClause{
|
||||
X: lhs.Y,
|
||||
Operator: operator,
|
||||
Y: sel,
|
||||
Constraint: constraint,
|
||||
},
|
||||
Constraint: lhs.Constraint,
|
||||
}
|
||||
} else {
|
||||
selStmt.Source = &parser.JoinClause{
|
||||
X: selStmt.Source,
|
||||
Operator: operator,
|
||||
Y: sel,
|
||||
Constraint: constraint,
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
//not a sql statement
|
||||
|
||||
//handle the case of of tthe LHS of the expression being a timestamp, the RHS being a string literal
|
||||
//if so, try to coerce to a timestamp
|
||||
if typeIsTimestamp(x.DataType()) && typeIsString(ex.DataType()) && ex.IsLiteral() {
|
||||
litExpr, ok := ex.(*parser.StringLit)
|
||||
if ok {
|
||||
tsLit := litExpr.ConvertToTimestamp()
|
||||
if tsLit != nil {
|
||||
ex = tsLit
|
||||
lst.Exprs[idx] = ex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//make sure LHS and RHS types are comparable
|
||||
if !typesAreComparable(x.DataType(), ex.DataType()) {
|
||||
return nil, sql3.NewErrTypesAreNotEquatable(x.Pos().Line, x.Pos().Column, x.DataType().TypeName(), ex.DataType().TypeName())
|
||||
}
|
||||
}
|
||||
|
||||
expr.ResultDataType = parser.NewDataTypeBool()
|
||||
return expr, nil
|
||||
|
||||
case parser.BETWEEN, parser.NOTBETWEEN:
|
||||
if !typeIsRange(y.DataType()) {
|
||||
return nil, sql3.NewErrTypeIncompatibleWithBetweenOperator(x.Pos().Line, x.Pos().Column, op.String(), x.DataType().TypeName())
|
||||
}
|
||||
|
||||
ok, err := typesAreRangeComparable(x.DataType(), y.DataType())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ok {
|
||||
return nil, sql3.NewErrTypeIncompatibleWithBetweenOperator(x.Pos().Line, x.Pos().Column, op.String(), x.DataType().TypeName())
|
||||
}
|
||||
expr.ResultDataType = parser.NewDataTypeBool()
|
||||
return expr, nil
|
||||
|
||||
case parser.CONCAT:
|
||||
if !typeIsCompatibleWithConcatOperator(x.DataType()) {
|
||||
return nil, sql3.NewErrTypeIncompatibleWithConcatOperator(x.Pos().Line, x.Pos().Column, op.String(), x.DataType().TypeName())
|
||||
}
|
||||
if !typeIsCompatibleWithConcatOperator(y.DataType()) {
|
||||
return nil, sql3.NewErrTypeIncompatibleWithConcatOperator(y.Pos().Line, y.Pos().Column, op.String(), y.DataType().TypeName())
|
||||
}
|
||||
expr.ResultDataType = parser.NewDataTypeString()
|
||||
return expr, nil
|
||||
|
||||
case parser.LIKE, parser.NOTLIKE:
|
||||
if !typeIsCompatibleWithLikeOperator(x.DataType()) {
|
||||
return nil, sql3.NewErrTypeIncompatibleWithLikeOperator(x.Pos().Line, x.Pos().Column, op.String(), x.DataType().TypeName())
|
||||
}
|
||||
if !typeIsCompatibleWithLikeOperator(y.DataType()) {
|
||||
return nil, sql3.NewErrTypeIncompatibleWithLikeOperator(y.Pos().Line, y.Pos().Column, op.String(), y.DataType().TypeName())
|
||||
}
|
||||
//comparison operator so type of expr is bool
|
||||
expr.ResultDataType = parser.NewDataTypeBool()
|
||||
return expr, nil
|
||||
|
||||
default:
|
||||
return nil, sql3.NewErrInternalf("unexpected binary expression operator: %s", op)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ExecutionPlanner) analyzeRangeExpression(expr *parser.Range, scope parser.Statement) (parser.Expr, error) {
|
||||
//analyze subscripts
|
||||
x, err := p.analyzeExpression(expr.X, scope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
expr.X = x
|
||||
y, err := p.analyzeExpression(expr.Y, scope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
expr.Y = y
|
||||
|
||||
//check to see if we have string literals that are actually dates
|
||||
xLiteral, ok := x.(*parser.StringLit)
|
||||
if ok {
|
||||
tsLiteral := xLiteral.ConvertToTimestamp()
|
||||
if tsLiteral != nil {
|
||||
expr.X = tsLiteral
|
||||
}
|
||||
}
|
||||
|
||||
yLiteral, ok := y.(*parser.StringLit)
|
||||
if ok {
|
||||
tsLiteral := yLiteral.ConvertToTimestamp()
|
||||
if tsLiteral != nil {
|
||||
expr.Y = tsLiteral
|
||||
}
|
||||
}
|
||||
|
||||
if !typeCanBeUsedInRange(expr.X.DataType()) {
|
||||
return nil, sql3.NewErrTypeCannotBeUsedAsRangeSubscript(expr.X.Pos().Line, expr.X.Pos().Column, expr.X.DataType().TypeName())
|
||||
}
|
||||
if !typeCanBeUsedInRange(expr.Y.DataType()) {
|
||||
return nil, sql3.NewErrTypeCannotBeUsedAsRangeSubscript(expr.Y.Pos().Line, expr.Y.Pos().Column, expr.Y.DataType().TypeName())
|
||||
}
|
||||
if !typesOfRangeBoundsAreTheSame(expr.X.DataType(), expr.Y.DataType()) {
|
||||
return nil, sql3.NewErrIncompatibleTypesForRangeSubscripts(expr.Pos().Line, expr.Pos().Column, expr.X.DataType().TypeName(), expr.Y.DataType().TypeName())
|
||||
}
|
||||
|
||||
expr.ResultDataType = parser.NewDataTypeRange(expr.X.DataType())
|
||||
|
||||
return expr, nil
|
||||
}
|
||||
|
||||
func (p *ExecutionPlanner) analyzeCaseBlockExpression(expr *parser.CaseBlock, caseScope *parser.CaseExpr, scope parser.Statement) (*parser.CaseBlock, error) {
|
||||
x, err := p.analyzeExpression(expr.Body, scope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
expr.Body = x
|
||||
y, err := p.analyzeExpression(expr.Condition, scope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
expr.Condition = y
|
||||
|
||||
return expr, nil
|
||||
}
|
||||
246
sql3/planner/expressionanalyzercall.go
Normal file
246
sql3/planner/expressionanalyzercall.go
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
// Copyright 2022 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/molecula/featurebase/v3/sql3"
|
||||
"github.com/molecula/featurebase/v3/sql3/parser"
|
||||
)
|
||||
|
||||
// analyze a *parser.Call and return the parser.Expr
|
||||
func (p *ExecutionPlanner) analyzeCallExpression(call *parser.Call, scope parser.Statement) (parser.Expr, error) {
|
||||
//analyze all the args
|
||||
for i, a := range call.Args {
|
||||
arg, err := p.analyzeExpression(a, scope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
call.Args[i] = arg
|
||||
}
|
||||
switch strings.ToUpper(call.Name.Name) {
|
||||
case "COUNT":
|
||||
//check to see if we have a star, if we do turn it into a qualified ref to _id
|
||||
if call.Star.IsValid() && len(call.Args) == 0 {
|
||||
newArg := &parser.Ident{
|
||||
NamePos: call.Star,
|
||||
Name: "_id",
|
||||
}
|
||||
arg, err := p.analyzeExpression(newArg, scope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
call.Args = append(call.Args, arg)
|
||||
}
|
||||
// one argument only
|
||||
if len(call.Args) != 1 {
|
||||
return nil, sql3.NewErrCallParameterCountMismatch(call.Rparen.Line, call.Rparen.Column, call.Name.Name, 1, len(call.Args))
|
||||
}
|
||||
//make sure it's a qualified ref
|
||||
_, ok := call.Args[0].(*parser.QualifiedRef)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrExpectedColumnReference(call.Args[0].Pos().Line, call.Args[0].Pos().Column)
|
||||
}
|
||||
//COUNT always returns int
|
||||
call.ResultDataType = parser.NewDataTypeInt()
|
||||
|
||||
case "SUM":
|
||||
// can't do a sum on *
|
||||
if call.Star.IsValid() && len(call.Args) == 0 {
|
||||
return nil, sql3.NewErrExpectedColumnReference(call.Star.Line, call.Star.Column)
|
||||
}
|
||||
// one argument only
|
||||
if len(call.Args) != 1 {
|
||||
return nil, sql3.NewErrCallParameterCountMismatch(call.Rparen.Line, call.Rparen.Column, call.Name.Name, 1, len(call.Args))
|
||||
}
|
||||
|
||||
//make sure it's a qualified ref
|
||||
ref, ok := call.Args[0].(*parser.QualifiedRef)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrExpectedColumnReference(call.Args[0].Pos().Line, call.Args[0].Pos().Column)
|
||||
}
|
||||
|
||||
//can't do a sum on _id
|
||||
if strings.EqualFold(ref.Column.Name, "_id") {
|
||||
return nil, sql3.NewErrIdColumnNotValidForAggregateFunction(call.Args[0].Pos().Line, call.Args[0].Pos().Column, call.Name.Name)
|
||||
}
|
||||
|
||||
//make sure the ref is sum-able
|
||||
if !(typeIsInteger(ref.DataType()) || typeIsFloat(ref.DataType())) {
|
||||
return nil, sql3.NewErrIntOrDecimalExpressionExpected(ref.Table.NamePos.Line, ref.Table.NamePos.Column)
|
||||
}
|
||||
|
||||
call.ResultDataType = ref.DataType()
|
||||
|
||||
case "AVG":
|
||||
// can't do an avg on a *
|
||||
if call.Star.IsValid() && len(call.Args) == 0 {
|
||||
return nil, sql3.NewErrExpectedColumnReference(call.Star.Line, call.Star.Column)
|
||||
}
|
||||
|
||||
// one argument only
|
||||
if len(call.Args) != 1 {
|
||||
return nil, sql3.NewErrCallParameterCountMismatch(call.Rparen.Line, call.Rparen.Column, call.Name.Name, 1, len(call.Args))
|
||||
}
|
||||
|
||||
ref, ok := call.Args[0].(*parser.QualifiedRef)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrExpectedColumnReference(call.Args[0].Pos().Line, call.Args[0].Pos().Column)
|
||||
}
|
||||
|
||||
//can't do a avg on _id
|
||||
if strings.EqualFold(ref.Column.Name, "_id") {
|
||||
return nil, sql3.NewErrIdColumnNotValidForAggregateFunction(call.Args[0].Pos().Line, call.Args[0].Pos().Column, call.Name.Name)
|
||||
}
|
||||
|
||||
//make sure the ref is avg-able
|
||||
if !(typeIsInteger(ref.DataType()) || typeIsFloat(ref.DataType())) {
|
||||
return nil, sql3.NewErrIntOrDecimalExpressionExpected(ref.Table.NamePos.Line, ref.Table.NamePos.Column)
|
||||
}
|
||||
|
||||
call.ResultDataType = parser.NewDataTypeDecimal(4)
|
||||
|
||||
case "PERCENTILE":
|
||||
// can't do an percentile on a *
|
||||
if call.Star.IsValid() && len(call.Args) == 0 {
|
||||
return nil, sql3.NewErrExpectedColumnReference(call.Star.Line, call.Star.Column)
|
||||
}
|
||||
|
||||
if len(call.Args) != 2 {
|
||||
return nil, sql3.NewErrCallParameterCountMismatch(call.Rparen.Line, call.Rparen.Column, call.Name.Name, 1, len(call.Args))
|
||||
}
|
||||
|
||||
//first arg should be a qualified ref
|
||||
ref, ok := call.Args[0].(*parser.QualifiedRef)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrExpectedColumnReference(call.Args[0].Pos().Line, call.Args[0].Pos().Column)
|
||||
}
|
||||
|
||||
//can't do a percentile on _id
|
||||
if strings.EqualFold(ref.Column.Name, "_id") {
|
||||
return nil, sql3.NewErrIdColumnNotValidForAggregateFunction(call.Args[0].Pos().Line, call.Args[0].Pos().Column, call.Name.Name)
|
||||
}
|
||||
|
||||
//make sure the ref is percentilable-able
|
||||
if !(typeIsInteger(ref.DataType()) || typeIsFloat(ref.DataType()) || typeIsTimestamp(ref.DataType())) {
|
||||
return nil, sql3.NewErrIntOrDecimalOrTimestampExpressionExpected(ref.Table.NamePos.Line, ref.Table.NamePos.Column)
|
||||
}
|
||||
|
||||
//second column is the nth value
|
||||
targetType := parser.NewDataTypeDecimal(4)
|
||||
if !typesAreAssignmentCompatible(targetType, call.Args[1].DataType()) {
|
||||
return nil, sql3.NewErrParameterTypeMistmatch(call.Args[1].Pos().Line, call.Args[1].Pos().Column, targetType.TypeName(), call.Args[1].DataType().TypeName())
|
||||
}
|
||||
|
||||
//make sure it's literal
|
||||
if !call.Args[1].IsLiteral() {
|
||||
return nil, sql3.NewErrLiteralExpected(call.Args[1].Pos().Line, call.Args[1].Pos().Column)
|
||||
}
|
||||
|
||||
//return the data type of the referenced column
|
||||
call.ResultDataType = ref.DataType()
|
||||
|
||||
case "MIN", "MAX":
|
||||
// can't do an min/max on a *
|
||||
if call.Star.IsValid() && len(call.Args) == 0 {
|
||||
return nil, sql3.NewErrExpectedColumnReference(call.Star.Line, call.Star.Column)
|
||||
}
|
||||
|
||||
// one argument
|
||||
if len(call.Args) != 1 {
|
||||
return nil, sql3.NewErrCallParameterCountMismatch(call.Rparen.Line, call.Rparen.Column, call.Name.Name, 1, len(call.Args))
|
||||
}
|
||||
|
||||
// first arg should be a qualified ref
|
||||
ref, ok := call.Args[0].(*parser.QualifiedRef)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrExpectedColumnReference(call.Args[0].Pos().Line, call.Args[0].Pos().Column)
|
||||
}
|
||||
|
||||
// can't do a min/max on _id
|
||||
if strings.EqualFold(ref.Column.Name, "_id") {
|
||||
return nil, sql3.NewErrIdColumnNotValidForAggregateFunction(call.Args[0].Pos().Line, call.Args[0].Pos().Column, call.Name.Name)
|
||||
}
|
||||
|
||||
// make sure the ref is min/max-able
|
||||
if !(typeIsInteger(ref.DataType()) || typeIsFloat(ref.DataType()) || typeIsTimestamp(ref.DataType())) {
|
||||
return nil, sql3.NewErrIntOrDecimalOrTimestampExpressionExpected(ref.Table.NamePos.Line, ref.Table.NamePos.Column)
|
||||
}
|
||||
|
||||
// return the data type of the referenced column
|
||||
call.ResultDataType = ref.DataType()
|
||||
|
||||
case "SETCONTAINS":
|
||||
// two arguments
|
||||
if len(call.Args) != 2 {
|
||||
return nil, sql3.NewErrCallParameterCountMismatch(call.Rparen.Line, call.Rparen.Column, call.Name.Name, 2, len(call.Args))
|
||||
}
|
||||
|
||||
ok, baseType := typeIsSet(call.Args[0].DataType())
|
||||
if !ok {
|
||||
return nil, sql3.NewErrSetExpressionExpected(call.Args[0].Pos().Line, call.Args[0].Pos().Column)
|
||||
}
|
||||
|
||||
if !typesAreComparable(baseType, call.Args[1].DataType()) {
|
||||
return nil, sql3.NewErrTypesAreNotEquatable(call.Args[1].Pos().Line, call.Args[1].Pos().Column, call.Args[0].DataType().TypeName(), call.Args[1].DataType().TypeName())
|
||||
}
|
||||
call.ResultDataType = parser.NewDataTypeBool()
|
||||
|
||||
case "SETCONTAINSALL":
|
||||
//two arguments
|
||||
if len(call.Args) != 2 {
|
||||
return nil, sql3.NewErrCallParameterCountMismatch(call.Rparen.Line, call.Rparen.Column, call.Name.Name, 2, len(call.Args))
|
||||
}
|
||||
|
||||
// first arg should be set
|
||||
ok, baseType1 := typeIsSet(call.Args[0].DataType())
|
||||
if !ok {
|
||||
return nil, sql3.NewErrSetExpressionExpected(call.Args[0].Pos().Line, call.Args[0].Pos().Column)
|
||||
}
|
||||
|
||||
// second arg should be set
|
||||
ok, baseType2 := typeIsSet(call.Args[1].DataType())
|
||||
if !ok {
|
||||
return nil, sql3.NewErrSetExpressionExpected(call.Args[0].Pos().Line, call.Args[0].Pos().Column)
|
||||
}
|
||||
|
||||
//types from both set should be comparable
|
||||
if !typesAreComparable(baseType1, baseType2) {
|
||||
return nil, sql3.NewErrTypesAreNotEquatable(call.Args[1].Pos().Line, call.Args[1].Pos().Column, baseType1.TypeName(), baseType2.TypeName())
|
||||
}
|
||||
|
||||
call.ResultDataType = parser.NewDataTypeBool()
|
||||
|
||||
case "SETCONTAINSANY":
|
||||
if len(call.Args) != 2 {
|
||||
return nil, sql3.NewErrCallParameterCountMismatch(call.Rparen.Line, call.Rparen.Column, call.Name.Name, 2, len(call.Args))
|
||||
}
|
||||
|
||||
// first arg should be set
|
||||
ok, baseType1 := typeIsSet(call.Args[0].DataType())
|
||||
if !ok {
|
||||
return nil, sql3.NewErrSetExpressionExpected(call.Args[0].Pos().Line, call.Args[0].Pos().Column)
|
||||
}
|
||||
|
||||
// second arg should be set
|
||||
ok, baseType2 := typeIsSet(call.Args[1].DataType())
|
||||
if !ok {
|
||||
return nil, sql3.NewErrSetExpressionExpected(call.Args[0].Pos().Line, call.Args[0].Pos().Column)
|
||||
}
|
||||
|
||||
//types from both set should be comparable
|
||||
if !typesAreComparable(baseType1, baseType2) {
|
||||
return nil, sql3.NewErrTypesAreNotEquatable(call.Args[1].Pos().Line, call.Args[1].Pos().Column, baseType1.TypeName(), baseType2.TypeName())
|
||||
}
|
||||
|
||||
call.ResultDataType = parser.NewDataTypeBool()
|
||||
|
||||
case "DATEPART":
|
||||
return p.analyzeFunctionDatePart(call, scope)
|
||||
|
||||
default:
|
||||
return nil, sql3.NewErrCallUnknownFunction(call.Name.NamePos.Line, call.Name.NamePos.Column, call.Name.Name)
|
||||
}
|
||||
return call, nil
|
||||
}
|
||||
226
sql3/planner/expressionpql.go
Normal file
226
sql3/planner/expressionpql.go
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
// Copyright 2022 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/molecula/featurebase/v3/pql"
|
||||
"github.com/molecula/featurebase/v3/sql3"
|
||||
"github.com/molecula/featurebase/v3/sql3/parser"
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
)
|
||||
|
||||
// generatePQLCallFromExpr returns a *pql.Call tree for a given plan expression
|
||||
func (p *ExecutionPlanner) generatePQLCallFromExpr(ctx context.Context, expr types.PlanExpression) (_ *pql.Call, err error) {
|
||||
if expr == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
switch expr := expr.(type) {
|
||||
case *binOpPlanExpression:
|
||||
return p.generatePQLCallFromBinaryExpr(ctx, expr)
|
||||
|
||||
case *callPlanExpression:
|
||||
switch strings.ToUpper(expr.name) {
|
||||
case "SETCONTAINS":
|
||||
col := expr.args[0].(*qualifiedRefPlanExpression)
|
||||
|
||||
pqlValue, err := planExprToValue(expr.args[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pql.Call{
|
||||
Name: "Row",
|
||||
Args: map[string]interface{}{
|
||||
col.columnName: pqlValue,
|
||||
},
|
||||
}, nil
|
||||
|
||||
case "SETCONTAINSALL":
|
||||
col := expr.args[0].(*qualifiedRefPlanExpression)
|
||||
|
||||
set, ok := expr.args[1].(*exprSetLiteralPlanExpression)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unexpected argument type '%T'", expr.args[1])
|
||||
}
|
||||
|
||||
call := &pql.Call{
|
||||
Name: "Intersect",
|
||||
Children: []*pql.Call{},
|
||||
}
|
||||
|
||||
for _, m := range set.members {
|
||||
pqlValue, err := planExprToValue(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rc := &pql.Call{
|
||||
Name: "Row",
|
||||
Args: map[string]interface{}{
|
||||
col.columnName: pqlValue,
|
||||
},
|
||||
}
|
||||
call.Children = append(call.Children, rc)
|
||||
}
|
||||
return call, nil
|
||||
|
||||
case "SETCONTAINSANY":
|
||||
col := expr.args[0].(*qualifiedRefPlanExpression)
|
||||
|
||||
set, ok := expr.args[1].(*exprSetLiteralPlanExpression)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unexpected argument type '%T'", expr.args[1])
|
||||
}
|
||||
|
||||
call := &pql.Call{
|
||||
Name: "Union",
|
||||
Children: []*pql.Call{},
|
||||
}
|
||||
|
||||
for _, m := range set.members {
|
||||
pqlValue, err := planExprToValue(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rc := &pql.Call{
|
||||
Name: "Row",
|
||||
Args: map[string]interface{}{
|
||||
col.columnName: pqlValue,
|
||||
},
|
||||
}
|
||||
call.Children = append(call.Children, rc)
|
||||
}
|
||||
return call, nil
|
||||
|
||||
default:
|
||||
return nil, sql3.NewErrInternalf("unsupported scalar function '%s'", expr.name)
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, sql3.NewErrInternalf("unexpected expression type: %T", expr)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ExecutionPlanner) generatePQLCallFromBinaryExpr(ctx context.Context, expr *binOpPlanExpression) (_ *pql.Call, err error) {
|
||||
switch op := expr.op; op {
|
||||
case parser.AND, parser.OR:
|
||||
name := "Intersect"
|
||||
if op == parser.OR {
|
||||
name = "Union"
|
||||
}
|
||||
|
||||
x, err := p.generatePQLCallFromExpr(ctx, expr.lhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
y, err := p.generatePQLCallFromExpr(ctx, expr.rhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pql.Call{
|
||||
Name: name,
|
||||
Children: []*pql.Call{x, y},
|
||||
}, nil
|
||||
|
||||
case parser.EQ, parser.NE, parser.LT, parser.LE, parser.GT, parser.GE:
|
||||
lhs, ok := expr.lhs.(*qualifiedRefPlanExpression)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unexpected lhs %T", expr.lhs)
|
||||
}
|
||||
|
||||
pqlValue, err := planExprToValue(expr.rhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch typ := expr.lhs.Type().(type) {
|
||||
case *parser.DataTypeInt:
|
||||
pqlOp, err := sqlToPQLOp(op)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pql.Call{
|
||||
Name: "Row",
|
||||
Args: map[string]interface{}{
|
||||
lhs.columnName: &pql.Condition{
|
||||
Op: pqlOp,
|
||||
Value: pqlValue,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
|
||||
case *parser.DataTypeID:
|
||||
return &pql.Call{
|
||||
Name: "Row",
|
||||
Args: map[string]interface{}{
|
||||
lhs.columnName: pqlValue,
|
||||
},
|
||||
}, nil
|
||||
|
||||
case *parser.DataTypeString:
|
||||
return &pql.Call{
|
||||
Name: "Row",
|
||||
Args: map[string]interface{}{
|
||||
lhs.columnName: pqlValue,
|
||||
},
|
||||
}, nil
|
||||
|
||||
default:
|
||||
return nil, sql3.NewErrInternalf("unsupported type for binary expression: %v (%T)", typ, typ)
|
||||
}
|
||||
|
||||
case parser.BITAND, parser.BITOR, parser.BITNOT, parser.LSHIFT, parser.RSHIFT:
|
||||
return nil, sql3.NewErrInternal("bitwise operators are not supported here")
|
||||
|
||||
case parser.PLUS, parser.MINUS, parser.STAR, parser.SLASH, parser.REM: // +
|
||||
return nil, sql3.NewErrInternal("aritmetic operators are not supported here")
|
||||
|
||||
case parser.CONCAT:
|
||||
return nil, sql3.NewErrInternal("concatenation operator is not supported here")
|
||||
|
||||
case parser.IN, parser.NOTIN:
|
||||
return nil, sql3.NewErrInternal("IN operator is not supported")
|
||||
|
||||
case parser.BETWEEN, parser.NOTBETWEEN:
|
||||
return nil, sql3.NewErrInternal("BETWEEN operator is not supported")
|
||||
|
||||
default:
|
||||
return nil, sql3.NewErrInternalf("unexpected binary expression operator: %s", expr.op)
|
||||
}
|
||||
}
|
||||
|
||||
// sqlToPQLOp converts a parser operation token to PQL.
|
||||
func sqlToPQLOp(op parser.Token) (pql.Token, error) {
|
||||
switch op {
|
||||
case parser.EQ:
|
||||
return pql.EQ, nil
|
||||
case parser.NE:
|
||||
return pql.NEQ, nil
|
||||
case parser.LT:
|
||||
return pql.LT, nil
|
||||
case parser.LE:
|
||||
return pql.LTE, nil
|
||||
case parser.GT:
|
||||
return pql.GT, nil
|
||||
case parser.GE:
|
||||
return pql.GTE, nil
|
||||
default:
|
||||
return pql.ILLEGAL, sql3.NewErrInternalf("cannot convert SQL op %q to PQL", op)
|
||||
}
|
||||
}
|
||||
|
||||
// planExprToValue converts a literal parser expression node to a value.
|
||||
func planExprToValue(expr types.PlanExpression) (interface{}, error) {
|
||||
switch expr := expr.(type) {
|
||||
case *intLiteralPlanExpression:
|
||||
return strconv.ParseInt(expr.value, 10, 64)
|
||||
case *stringLiteralPlanExpression:
|
||||
return expr.value, nil
|
||||
default:
|
||||
return nil, sql3.NewErrInternalf("cannot convert SQL expression %T to a literal value", expr)
|
||||
}
|
||||
}
|
||||
711
sql3/planner/expressiontypes.go
Normal file
711
sql3/planner/expressiontypes.go
Normal file
|
|
@ -0,0 +1,711 @@
|
|||
// Copyright 2022 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
pilosa "github.com/molecula/featurebase/v3"
|
||||
"github.com/molecula/featurebase/v3/sql3"
|
||||
"github.com/molecula/featurebase/v3/sql3/parser"
|
||||
)
|
||||
|
||||
// takes a *pilosa.FieldInfo and returns a sql data type
|
||||
func fieldSQLDataType(f *pilosa.FieldInfo) parser.ExprDataType {
|
||||
// This is special handling for the primary key (_id) field. The normal
|
||||
// handling below was not well suited to this field because there isn't a
|
||||
// `pilosa.FieldTypeID` to compare against. One option would have been to
|
||||
// add that to field.go, but it seemed risky to add a FieldType which
|
||||
// FeatureBase is currently not using. In the future, this logic should use
|
||||
// the next generation of FieldTypes in the dax package, which does include
|
||||
// a FieldTypeID. Another thing to be updated is the "_id" value itself; in
|
||||
// the dax package there is a constant called `PrimaryKeyFieldName` which
|
||||
// would be used here instead.
|
||||
if f.Name == "_id" {
|
||||
switch f.Options.Type {
|
||||
case "id":
|
||||
return parser.NewDataTypeID()
|
||||
case "string":
|
||||
return parser.NewDataTypeString()
|
||||
default:
|
||||
return parser.NewDataTypeVoid()
|
||||
}
|
||||
}
|
||||
|
||||
switch f.Options.Type {
|
||||
case pilosa.FieldTypeInt:
|
||||
return parser.NewDataTypeInt()
|
||||
|
||||
case pilosa.FieldTypeMutex:
|
||||
if f.Options.Keys {
|
||||
return parser.NewDataTypeString()
|
||||
} else {
|
||||
return parser.NewDataTypeID()
|
||||
}
|
||||
|
||||
case pilosa.FieldTypeSet:
|
||||
if f.Options.Keys {
|
||||
return parser.NewDataTypeStringSet()
|
||||
} else {
|
||||
return parser.NewDataTypeIDSet()
|
||||
}
|
||||
|
||||
case pilosa.FieldTypeBool:
|
||||
return parser.NewDataTypeBool()
|
||||
|
||||
case pilosa.FieldTypeDecimal:
|
||||
return parser.NewDataTypeDecimal(f.Options.Scale)
|
||||
|
||||
case pilosa.FieldTypeTime, pilosa.FieldTypeTimestamp:
|
||||
return parser.NewDataTypeTimestamp()
|
||||
|
||||
default:
|
||||
return parser.NewDataTypeVoid()
|
||||
}
|
||||
}
|
||||
|
||||
// resolves type names to type representations
|
||||
func dataTypeFromParserType(typ *parser.Type) (parser.ExprDataType, error) {
|
||||
typeName := parser.IdentName(typ.Name)
|
||||
switch strings.ToUpper(typeName) {
|
||||
case parser.FieldTypeBool:
|
||||
return parser.NewDataTypeBool(), nil
|
||||
|
||||
case parser.FieldTypeDecimal:
|
||||
scale, err := strconv.Atoi(typ.Scale.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return parser.NewDataTypeDecimal(int64(scale)), nil
|
||||
|
||||
case parser.FieldTypeID:
|
||||
return parser.NewDataTypeID(), nil
|
||||
|
||||
case parser.FieldTypeIDSet:
|
||||
return parser.NewDataTypeIDSet(), nil
|
||||
|
||||
case parser.FieldTypeInt:
|
||||
return parser.NewDataTypeInt(), nil
|
||||
|
||||
case parser.FieldTypeString:
|
||||
return parser.NewDataTypeString(), nil
|
||||
|
||||
case parser.FieldTypeStringSet:
|
||||
return parser.NewDataTypeStringSet(), nil
|
||||
|
||||
case parser.FieldTypeTimestamp:
|
||||
return parser.NewDataTypeTimestamp(), nil
|
||||
|
||||
default:
|
||||
return nil, sql3.NewErrUnknownType(typ.Name.NamePos.Line, typ.Name.NamePos.Column, typeName)
|
||||
}
|
||||
}
|
||||
|
||||
// returns true if type is compatible with logical operators (AND, OR)
|
||||
func typeIsCompatibleWithLogicalOperator(testType parser.ExprDataType) bool {
|
||||
switch testType.(type) {
|
||||
case *parser.DataTypeID, *parser.DataTypeInt, *parser.DataTypeBool:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// returns true if type is compatible with equality operators (=, <>)
|
||||
func typeIsCompatibleWithEqualityOperator(testType parser.ExprDataType) bool {
|
||||
switch testType.(type) {
|
||||
case *parser.DataTypeID, *parser.DataTypeInt,
|
||||
*parser.DataTypeDecimal, *parser.DataTypeBool,
|
||||
*parser.DataTypeString, *parser.DataTypeTimestamp,
|
||||
*parser.DataTypeIDSet, *parser.DataTypeStringSet:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// returns true if type is compatible with comparison operators (<, <=, >, >=)
|
||||
func typeIsCompatibleWithComparisonOperator(testType parser.ExprDataType) bool {
|
||||
switch testType.(type) {
|
||||
case *parser.DataTypeID, *parser.DataTypeInt, *parser.DataTypeDecimal, *parser.DataTypeTimestamp:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// returns true if type is compatible with arithmetic operators (+, -, *, /, %)
|
||||
func typeIsCompatibleWithArithmeticOperator(testType parser.ExprDataType, op parser.Token) bool {
|
||||
switch testType.(type) {
|
||||
case *parser.DataTypeID, *parser.DataTypeInt:
|
||||
return true
|
||||
case *parser.DataTypeDecimal:
|
||||
return op != parser.REM
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// returns true if type is compatible with concatenation operator (||)
|
||||
func typeIsCompatibleWithConcatOperator(testType parser.ExprDataType) bool {
|
||||
switch testType.(type) {
|
||||
case *parser.DataTypeString:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// returns true if type is compatible using a range comparison. rhs must be a range type and the lhs must be
|
||||
// compatible with the range subscript type
|
||||
func typesAreRangeComparable(testTypeL parser.ExprDataType, testTypeR parser.ExprDataType) (bool, error) {
|
||||
switch rhsType := testTypeR.(type) {
|
||||
case *parser.DataTypeRange:
|
||||
switch rhsType.SubscriptType.(type) {
|
||||
case *parser.DataTypeTimestamp:
|
||||
switch lhsType := testTypeL.(type) {
|
||||
case *parser.DataTypeTimestamp:
|
||||
return true, nil
|
||||
|
||||
default:
|
||||
return false, sql3.NewErrInternalf("unhandled rhs type '%T' for lhs type '%T'", rhsType, lhsType)
|
||||
|
||||
}
|
||||
case *parser.DataTypeInt, *parser.DataTypeID:
|
||||
switch lhsType := testTypeL.(type) {
|
||||
case *parser.DataTypeID:
|
||||
return true, nil
|
||||
case *parser.DataTypeInt:
|
||||
return true, nil
|
||||
|
||||
default:
|
||||
return false, sql3.NewErrInternalf("unhandled rhs type '%T' for lhs type '%T'", rhsType, lhsType)
|
||||
|
||||
}
|
||||
}
|
||||
default:
|
||||
return false, sql3.NewErrInternalf("type '%T' is not a range type", rhsType)
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// returns true if type is compatible with the (NOT) LIKE operator
|
||||
func typeIsCompatibleWithLikeOperator(testType parser.ExprDataType) bool {
|
||||
switch testType.(type) {
|
||||
case *parser.DataTypeString:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// returns true if type is compatible with bitwise operators (&, |, <<, >>)
|
||||
func typeIsCompatibleWithBitwiseOperator(testType parser.ExprDataType) bool {
|
||||
switch testType.(type) {
|
||||
case *parser.DataTypeID, *parser.DataTypeInt:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// returns true if type is assignemt compatible. Assignment compatibility can be either because the
|
||||
// comparison types are the same or because the source type can be coerced into the the target type
|
||||
func typesAreAssignmentCompatible(targetType parser.ExprDataType, sourceType parser.ExprDataType) bool {
|
||||
//ok to assign null to something
|
||||
_, ok := sourceType.(*parser.DataTypeVoid)
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
|
||||
switch lhs := targetType.(type) {
|
||||
|
||||
case *parser.DataTypeInt:
|
||||
switch sourceType.(type) {
|
||||
case *parser.DataTypeInt:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
case *parser.DataTypeBool:
|
||||
switch sourceType.(type) {
|
||||
case *parser.DataTypeBool:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
case *parser.DataTypeID:
|
||||
switch sourceType.(type) {
|
||||
case *parser.DataTypeInt:
|
||||
return true
|
||||
case *parser.DataTypeID:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
case *parser.DataTypeStringSet:
|
||||
switch sourceType.(type) {
|
||||
case *parser.DataTypeStringSet:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
case *parser.DataTypeIDSet:
|
||||
switch sourceType.(type) {
|
||||
case *parser.DataTypeIDSet:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
case *parser.DataTypeDecimal:
|
||||
switch rhs := sourceType.(type) {
|
||||
case *parser.DataTypeDecimal:
|
||||
//if lhs scale is >= rhs scale, we're good
|
||||
return lhs.Scale >= rhs.Scale
|
||||
case *parser.DataTypeInt:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
case *parser.DataTypeTimestamp:
|
||||
switch sourceType.(type) {
|
||||
case *parser.DataTypeTimestamp:
|
||||
return true
|
||||
case *parser.DataTypeString:
|
||||
//could be a string parseable as a date
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
case *parser.DataTypeString:
|
||||
switch sourceType.(type) {
|
||||
case *parser.DataTypeString:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// returns true if the type can be used as a range subscript
|
||||
func typeCanBeUsedInRange(testType parser.ExprDataType) bool {
|
||||
switch testType.(type) {
|
||||
case *parser.DataTypeID, *parser.DataTypeInt, *parser.DataTypeTimestamp:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// returns true if the types can be considered the same when used as a range bounds
|
||||
func typesOfRangeBoundsAreTheSame(testTypeL parser.ExprDataType, testTypeR parser.ExprDataType) bool {
|
||||
switch testTypeL.(type) {
|
||||
case *parser.DataTypeInt:
|
||||
switch testTypeR.(type) {
|
||||
case *parser.DataTypeInt:
|
||||
return true
|
||||
|
||||
case *parser.DataTypeID:
|
||||
return true
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
case *parser.DataTypeID:
|
||||
switch testTypeR.(type) {
|
||||
case *parser.DataTypeID:
|
||||
return true
|
||||
|
||||
case *parser.DataTypeInt:
|
||||
return true
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
case *parser.DataTypeTimestamp:
|
||||
switch testTypeR.(type) {
|
||||
case *parser.DataTypeTimestamp:
|
||||
return true
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// returns true if the type is a range type
|
||||
func typeIsRange(testType parser.ExprDataType) bool {
|
||||
switch testType.(type) {
|
||||
case *parser.DataTypeRange:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// returns true if the type is a set type
|
||||
func typeIsSet(testType parser.ExprDataType) (bool, parser.ExprDataType) {
|
||||
switch testType.(type) {
|
||||
case *parser.DataTypeIDSet:
|
||||
return true, parser.NewDataTypeID()
|
||||
case *parser.DataTypeStringSet:
|
||||
return true, parser.NewDataTypeString()
|
||||
default:
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
// returns true if the type is bool
|
||||
func typeIsBool(testType parser.ExprDataType) bool {
|
||||
switch testType.(type) {
|
||||
case *parser.DataTypeBool:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// returns true if the type is an integer or can be treated as one
|
||||
func typeIsInteger(testType parser.ExprDataType) bool {
|
||||
switch testType.(type) {
|
||||
case *parser.DataTypeID, *parser.DataTypeInt:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// returns true if the type is string
|
||||
func typeIsString(testType parser.ExprDataType) bool {
|
||||
switch testType.(type) {
|
||||
case *parser.DataTypeString:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// returns true if the type is timestamp
|
||||
func typeIsTimestamp(testType parser.ExprDataType) bool {
|
||||
switch testType.(type) {
|
||||
case *parser.DataTypeTimestamp:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// returns true if the type is a float
|
||||
func typeIsFloat(testType parser.ExprDataType) bool {
|
||||
switch testType.(type) {
|
||||
case *parser.DataTypeDecimal:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// returns true if the types can be compared
|
||||
func typesAreComparable(testTypeL parser.ExprDataType, testTypeR parser.ExprDataType) bool {
|
||||
switch testTypeL.(type) {
|
||||
case *parser.DataTypeInt:
|
||||
switch testTypeR.(type) {
|
||||
case *parser.DataTypeInt:
|
||||
return true
|
||||
case *parser.DataTypeID:
|
||||
return true
|
||||
case *parser.DataTypeDecimal:
|
||||
return true
|
||||
|
||||
}
|
||||
|
||||
case *parser.DataTypeID:
|
||||
switch testTypeR.(type) {
|
||||
case *parser.DataTypeID:
|
||||
return true
|
||||
case *parser.DataTypeInt:
|
||||
return true
|
||||
case *parser.DataTypeDecimal:
|
||||
return true
|
||||
|
||||
}
|
||||
|
||||
case *parser.DataTypeDecimal:
|
||||
switch testTypeR.(type) {
|
||||
case *parser.DataTypeID:
|
||||
return true
|
||||
case *parser.DataTypeInt:
|
||||
return true
|
||||
case *parser.DataTypeDecimal:
|
||||
return true
|
||||
}
|
||||
|
||||
case *parser.DataTypeBool:
|
||||
switch testTypeR.(type) {
|
||||
case *parser.DataTypeBool:
|
||||
return true
|
||||
}
|
||||
|
||||
case *parser.DataTypeTimestamp:
|
||||
switch testTypeR.(type) {
|
||||
case *parser.DataTypeTimestamp:
|
||||
return true
|
||||
}
|
||||
|
||||
case *parser.DataTypeIDSet:
|
||||
switch testTypeR.(type) {
|
||||
case *parser.DataTypeIDSet:
|
||||
return true
|
||||
|
||||
}
|
||||
|
||||
case *parser.DataTypeString:
|
||||
switch testTypeR.(type) {
|
||||
case *parser.DataTypeString:
|
||||
return true
|
||||
|
||||
}
|
||||
|
||||
case *parser.DataTypeStringSet:
|
||||
switch testTypeR.(type) {
|
||||
case *parser.DataTypeStringSet:
|
||||
return true
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// returns the target type given two operand types for an artitmentic operation (or an error)
|
||||
func typesCoercedForArithmeticOperator(testTypeL parser.ExprDataType, testTypeR parser.ExprDataType, atPos parser.Pos) (parser.ExprDataType, error) {
|
||||
switch lhsType := testTypeL.(type) {
|
||||
case *parser.DataTypeInt:
|
||||
switch rhsType := testTypeR.(type) {
|
||||
case *parser.DataTypeInt:
|
||||
return rhsType, nil
|
||||
|
||||
case *parser.DataTypeID:
|
||||
return lhsType, nil
|
||||
|
||||
case *parser.DataTypeDecimal:
|
||||
return rhsType, nil
|
||||
}
|
||||
|
||||
case *parser.DataTypeID:
|
||||
switch testTypeR.(type) {
|
||||
case *parser.DataTypeID:
|
||||
return testTypeR, nil
|
||||
|
||||
case *parser.DataTypeInt:
|
||||
return testTypeR, nil
|
||||
}
|
||||
|
||||
case *parser.DataTypeDecimal:
|
||||
switch rhsType := testTypeR.(type) {
|
||||
case *parser.DataTypeInt:
|
||||
return testTypeL, nil
|
||||
|
||||
case *parser.DataTypeID:
|
||||
return testTypeL, nil
|
||||
|
||||
case *parser.DataTypeDecimal:
|
||||
if lhsType.Scale > rhsType.Scale {
|
||||
return lhsType, nil
|
||||
}
|
||||
return rhsType, nil
|
||||
}
|
||||
|
||||
}
|
||||
return nil, sql3.NewErrTypeMismatch(atPos.Line, atPos.Column, testTypeL.TypeName(), testTypeR.TypeName())
|
||||
}
|
||||
|
||||
// returns the target type given two operand types for a bitwise operation (or an error)
|
||||
func typesCoercedForBitwiseOperator(testTypeL parser.ExprDataType, testTypeR parser.ExprDataType, atPos parser.Pos) (parser.ExprDataType, error) {
|
||||
switch testTypeL.(type) {
|
||||
|
||||
case *parser.DataTypeInt:
|
||||
switch rhsType := testTypeR.(type) {
|
||||
case *parser.DataTypeInt:
|
||||
return testTypeL, nil
|
||||
|
||||
case *parser.DataTypeID:
|
||||
return testTypeL, nil
|
||||
|
||||
case *parser.DataTypeDecimal:
|
||||
return parser.NewDataTypeDecimal(rhsType.Scale), nil
|
||||
|
||||
}
|
||||
|
||||
case *parser.DataTypeID:
|
||||
switch rhsType := testTypeR.(type) {
|
||||
case *parser.DataTypeID:
|
||||
return testTypeR, nil
|
||||
|
||||
case *parser.DataTypeInt:
|
||||
return testTypeR, nil
|
||||
|
||||
case *parser.DataTypeDecimal:
|
||||
return parser.NewDataTypeDecimal(rhsType.Scale), nil
|
||||
}
|
||||
}
|
||||
return nil, sql3.NewErrTypeMismatch(atPos.Line, atPos.Column, testTypeL.TypeName(), testTypeR.TypeName())
|
||||
}
|
||||
|
||||
// returns the target type given two operand types type coercion operation (or an error)
|
||||
func typeCoerceType(testTypeL parser.ExprDataType, testTypeR parser.ExprDataType, atPos parser.Pos) (parser.ExprDataType, error) {
|
||||
switch testTypeL.(type) {
|
||||
case *parser.DataTypeBool:
|
||||
switch testTypeR.(type) {
|
||||
case *parser.DataTypeBool:
|
||||
return testTypeL, nil
|
||||
|
||||
}
|
||||
|
||||
case *parser.DataTypeInt:
|
||||
switch testTypeR.(type) {
|
||||
case *parser.DataTypeInt:
|
||||
return testTypeL, nil
|
||||
case *parser.DataTypeID:
|
||||
return testTypeL, nil
|
||||
case *parser.DataTypeDecimal:
|
||||
return testTypeR, nil
|
||||
}
|
||||
|
||||
case *parser.DataTypeDecimal:
|
||||
switch testTypeR.(type) {
|
||||
case *parser.DataTypeDecimal:
|
||||
return testTypeL, nil
|
||||
case *parser.DataTypeInt:
|
||||
return testTypeL, nil
|
||||
case *parser.DataTypeID:
|
||||
return testTypeL, nil
|
||||
|
||||
}
|
||||
|
||||
case *parser.DataTypeID:
|
||||
switch testTypeR.(type) {
|
||||
case *parser.DataTypeID:
|
||||
return testTypeL, nil
|
||||
case *parser.DataTypeInt:
|
||||
return testTypeR, nil
|
||||
|
||||
}
|
||||
|
||||
case *parser.DataTypeIDSet:
|
||||
switch testTypeR.(type) {
|
||||
case *parser.DataTypeIDSet:
|
||||
return testTypeL, nil
|
||||
|
||||
}
|
||||
|
||||
case *parser.DataTypeString:
|
||||
switch testTypeR.(type) {
|
||||
case *parser.DataTypeString:
|
||||
return testTypeL, nil
|
||||
|
||||
}
|
||||
|
||||
case *parser.DataTypeStringSet:
|
||||
switch testTypeR.(type) {
|
||||
case *parser.DataTypeStringSet:
|
||||
return testTypeL, nil
|
||||
|
||||
}
|
||||
|
||||
case *parser.DataTypeTimestamp:
|
||||
switch testTypeR.(type) {
|
||||
case *parser.DataTypeTimestamp:
|
||||
return testTypeL, nil
|
||||
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, sql3.NewErrInternalf("unhandled lhs type '%t'", testTypeL)
|
||||
}
|
||||
return nil, sql3.NewErrTypeMismatch(atPos.Line, atPos.Line, testTypeL.TypeName(), testTypeR.TypeName())
|
||||
}
|
||||
|
||||
// returns true if source type can be cast to target type
|
||||
func typesCanBeCast(sourceType parser.ExprDataType, targetType parser.ExprDataType) bool {
|
||||
switch st := sourceType.(type) {
|
||||
case *parser.DataTypeInt:
|
||||
switch targetType.(type) {
|
||||
case *parser.DataTypeInt,
|
||||
*parser.DataTypeBool,
|
||||
*parser.DataTypeDecimal,
|
||||
*parser.DataTypeID,
|
||||
*parser.DataTypeString,
|
||||
*parser.DataTypeTimestamp:
|
||||
return true
|
||||
}
|
||||
|
||||
case *parser.DataTypeBool:
|
||||
switch targetType.(type) {
|
||||
case *parser.DataTypeBool,
|
||||
*parser.DataTypeInt,
|
||||
*parser.DataTypeString:
|
||||
return true
|
||||
}
|
||||
|
||||
case *parser.DataTypeDecimal:
|
||||
switch tt := targetType.(type) {
|
||||
case *parser.DataTypeDecimal:
|
||||
return tt.Scale >= st.Scale
|
||||
case *parser.DataTypeString:
|
||||
return true
|
||||
}
|
||||
|
||||
case *parser.DataTypeID:
|
||||
switch targetType.(type) {
|
||||
case *parser.DataTypeInt,
|
||||
*parser.DataTypeBool,
|
||||
*parser.DataTypeDecimal,
|
||||
*parser.DataTypeID:
|
||||
return true
|
||||
}
|
||||
|
||||
case *parser.DataTypeIDSet:
|
||||
switch targetType.(type) {
|
||||
case *parser.DataTypeIDSet, *parser.DataTypeString:
|
||||
return true
|
||||
}
|
||||
|
||||
case *parser.DataTypeString:
|
||||
switch targetType.(type) {
|
||||
case *parser.DataTypeInt,
|
||||
*parser.DataTypeBool,
|
||||
*parser.DataTypeDecimal,
|
||||
*parser.DataTypeID,
|
||||
*parser.DataTypeString,
|
||||
*parser.DataTypeTimestamp:
|
||||
return true
|
||||
}
|
||||
case *parser.DataTypeStringSet:
|
||||
switch targetType.(type) {
|
||||
case *parser.DataTypeStringSet, *parser.DataTypeString:
|
||||
return true
|
||||
}
|
||||
|
||||
case *parser.DataTypeTimestamp:
|
||||
switch targetType.(type) {
|
||||
case *parser.DataTypeInt, *parser.DataTypeTimestamp, *parser.DataTypeString:
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
return false
|
||||
}
|
||||
123
sql3/planner/inbuiltfunctionsdate.go
Normal file
123
sql3/planner/inbuiltfunctionsdate.go
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
package planner
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/molecula/featurebase/v3/sql3"
|
||||
"github.com/molecula/featurebase/v3/sql3/parser"
|
||||
)
|
||||
|
||||
const intervalYear = "YY"
|
||||
const intervalYearDay = "YD"
|
||||
const intervalMonth = "M"
|
||||
const intervalDay = "D"
|
||||
const intervalWeeKDay = "W"
|
||||
const intervalWeek = "WK"
|
||||
const intervalHour = "HH"
|
||||
const intervalMinute = "MI"
|
||||
const intervalSecond = "S"
|
||||
const intervalMillisecond = "MS"
|
||||
const intervalNanosecond = "NS"
|
||||
|
||||
func (p *ExecutionPlanner) analyzeFunctionDatePart(call *parser.Call, scope parser.Statement) (parser.Expr, error) {
|
||||
|
||||
if len(call.Args) != 2 {
|
||||
return nil, sql3.NewErrCallParameterCountMismatch(call.Rparen.Line, call.Rparen.Column, call.Name.Name, 2, len(call.Args))
|
||||
}
|
||||
// interval
|
||||
intervalType := parser.NewDataTypeString()
|
||||
if !typesAreAssignmentCompatible(intervalType, call.Args[0].DataType()) {
|
||||
return nil, sql3.NewErrParameterTypeMistmatch(call.Args[0].Pos().Line, call.Args[0].Pos().Column, call.Args[0].DataType().TypeName(), intervalType.TypeName())
|
||||
}
|
||||
|
||||
// date
|
||||
dateType := parser.NewDataTypeTimestamp()
|
||||
if !typesAreAssignmentCompatible(dateType, call.Args[1].DataType()) {
|
||||
return nil, sql3.NewErrParameterTypeMistmatch(call.Args[1].Pos().Line, call.Args[1].Pos().Column, call.Args[1].DataType().TypeName(), dateType.TypeName())
|
||||
}
|
||||
|
||||
//return int
|
||||
call.ResultDataType = parser.NewDataTypeInt()
|
||||
|
||||
return call, nil
|
||||
}
|
||||
|
||||
func (n *callPlanExpression) EvaluateDatepart(currentRow []interface{}) (interface{}, error) {
|
||||
intervalEval, err := n.args[0].Evaluate(currentRow)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dateEval, err := n.args[1].Evaluate(currentRow)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// nil if anything is nil
|
||||
if intervalEval == nil || dateEval == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
//get the date value
|
||||
coercedDate, err := coerceValue(n.args[1].Type(), parser.NewDataTypeTimestamp(), dateEval, parser.Pos{Line: 0, Column: 0})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
date, dateOk := coercedDate.(time.Time)
|
||||
if !dateOk {
|
||||
return nil, sql3.NewErrInternalf("unable to convert value")
|
||||
}
|
||||
|
||||
//get the interval value
|
||||
coercedInterval, err := coerceValue(n.args[0].Type(), parser.NewDataTypeString(), intervalEval, parser.Pos{Line: 0, Column: 0})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
interval, intervalOk := coercedInterval.(string)
|
||||
if !intervalOk {
|
||||
return nil, sql3.NewErrInternalf("unable to convert value")
|
||||
}
|
||||
|
||||
switch strings.ToUpper(interval) {
|
||||
case intervalYear:
|
||||
return int64(date.Year()), nil
|
||||
|
||||
case intervalYearDay:
|
||||
return int64(date.YearDay()), nil
|
||||
|
||||
case intervalMonth:
|
||||
return int64(date.Month()), nil
|
||||
|
||||
case intervalDay:
|
||||
return int64(date.Day()), nil
|
||||
|
||||
case intervalWeeKDay:
|
||||
return int64(date.Weekday()), nil
|
||||
|
||||
case intervalWeek:
|
||||
_, isoWeek := date.ISOWeek()
|
||||
return int64(isoWeek), nil
|
||||
|
||||
case intervalHour:
|
||||
return int64(date.Hour()), nil
|
||||
|
||||
case intervalMinute:
|
||||
return int64(date.Minute()), nil
|
||||
|
||||
case intervalSecond:
|
||||
return int64(date.Second()), nil
|
||||
|
||||
case intervalMillisecond:
|
||||
return int64(date.Nanosecond() * 1000 * 1000), nil
|
||||
|
||||
case intervalNanosecond:
|
||||
return int64(date.Nanosecond()), nil
|
||||
|
||||
default:
|
||||
return nil, sql3.NewErrCallParameterValueInvalid(0, 0, interval, "interval")
|
||||
}
|
||||
|
||||
}
|
||||
208
sql3/planner/inbuiltfunctionsset.go
Normal file
208
sql3/planner/inbuiltfunctionsset.go
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
package planner
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/molecula/featurebase/v3/sql3"
|
||||
"github.com/molecula/featurebase/v3/sql3/parser"
|
||||
)
|
||||
|
||||
func (n *callPlanExpression) EvaluateSetContains(currentRow []interface{}) (interface{}, error) {
|
||||
targetSetEval, err := n.args[0].Evaluate(currentRow)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
testValueEval, err := n.args[1].Evaluate(currentRow)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//if either term is null, then null
|
||||
if testValueEval == nil || targetSetEval == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if targetSetEval != nil {
|
||||
switch typ := n.args[0].Type().(type) {
|
||||
case *parser.DataTypeStringSet:
|
||||
targetSet, ok := targetSetEval.([]string)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unable to convert value")
|
||||
}
|
||||
|
||||
testValue, ok := testValueEval.(string)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unable to convert value")
|
||||
}
|
||||
|
||||
return stringSetContains(targetSet, testValue), nil
|
||||
|
||||
case *parser.DataTypeIDSet:
|
||||
targetSet, ok := targetSetEval.([]int64)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unable to convert value")
|
||||
}
|
||||
|
||||
testValue, ok := testValueEval.(int64)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unable to convert value")
|
||||
}
|
||||
|
||||
return intSetContains(targetSet, int64(testValue)), nil
|
||||
|
||||
default:
|
||||
return nil, sql3.NewErrInternalf("unexpected data type '%T'", typ)
|
||||
}
|
||||
}
|
||||
return nil, sql3.NewErrInternalf("unable to to find column '%s' in currentColumns", n.name)
|
||||
}
|
||||
|
||||
func (n *callPlanExpression) EvaluateSetContainsAny(currentRow []interface{}) (interface{}, error) {
|
||||
targetSetEval, err := n.args[0].Evaluate(currentRow)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
testSetEval, err := n.args[1].Evaluate(currentRow)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if targetSetEval != nil {
|
||||
switch typ := n.args[0].Type().(type) {
|
||||
case *parser.DataTypeStringSet:
|
||||
targetSet, ok := targetSetEval.([]string)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unable to convert value")
|
||||
}
|
||||
|
||||
testSet, ok := testSetEval.([]string)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unable to convert value")
|
||||
}
|
||||
|
||||
return stringSetContainsAny(targetSet, testSet), nil
|
||||
|
||||
case *parser.DataTypeIDSet:
|
||||
targetSet, ok := targetSetEval.([]int64)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unable to convert value")
|
||||
}
|
||||
|
||||
testSet, ok := testSetEval.([]int64)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unable to convert value")
|
||||
}
|
||||
|
||||
return intSetContainsAny(targetSet, testSet), nil
|
||||
|
||||
default:
|
||||
return nil, sql3.NewErrInternalf("unexpected data type '%T'", typ)
|
||||
}
|
||||
|
||||
}
|
||||
return nil, sql3.NewErrInternalf("unable to to find column '%s' in currentColumns", n.name)
|
||||
}
|
||||
|
||||
func (n *callPlanExpression) EvaluateSetContainsAll(currentRow []interface{}) (interface{}, error) {
|
||||
targetSetEval, err := n.args[0].Evaluate(currentRow)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
testSetEval, err := n.args[1].Evaluate(currentRow)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if targetSetEval != nil {
|
||||
switch typ := n.args[0].Type().(type) {
|
||||
case *parser.DataTypeStringSet:
|
||||
targetSet, ok := targetSetEval.([]string)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unable to convert value")
|
||||
}
|
||||
|
||||
testSet, ok := testSetEval.([]string)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unable to convert value")
|
||||
}
|
||||
|
||||
return stringSetContainsAll(targetSet, testSet), nil
|
||||
|
||||
case *parser.DataTypeIDSet:
|
||||
targetSet, ok := targetSetEval.([]int64)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unable to convert value")
|
||||
}
|
||||
|
||||
testSet, ok := testSetEval.([]int64)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unable to convert value")
|
||||
}
|
||||
|
||||
return intSetContainsAll(targetSet, testSet), nil
|
||||
|
||||
default:
|
||||
return nil, sql3.NewErrInternalf("unexpected data type '%T'", typ)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil, sql3.NewErrInternalf("unable to to find column '%s' in currentColumns", n.name)
|
||||
}
|
||||
|
||||
func stringSetContains(set []string, val string) bool {
|
||||
for _, v := range set {
|
||||
if strings.EqualFold(v, val) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func stringSetContainsAny(targetSet []string, testSet []string) bool {
|
||||
for _, test := range testSet {
|
||||
if stringSetContains(targetSet, test) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func stringSetContainsAll(targetSet []string, testSet []string) bool {
|
||||
for _, test := range testSet {
|
||||
if !stringSetContains(targetSet, test) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func intSetContains(set []int64, val int64) bool {
|
||||
for _, v := range set {
|
||||
if v == val {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func intSetContainsAny(targetSet []int64, testSet []int64) bool {
|
||||
for _, test := range testSet {
|
||||
if intSetContains(targetSet, test) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func intSetContainsAll(targetSet []int64, testSet []int64) bool {
|
||||
for _, test := range testSet {
|
||||
if !intSetContains(targetSet, test) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
72
sql3/planner/memoryobj.go
Normal file
72
sql3/planner/memoryobj.go
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
// Copyright 2022 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import "github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
|
||||
// RowCache is a cache of rows used during row iteration
|
||||
type RowCache interface {
|
||||
Add(row types.Row) error
|
||||
|
||||
// AllRows returns all rows.
|
||||
AllRows() []types.Row
|
||||
}
|
||||
|
||||
// KeyedRowCache is a cache of keyed rows used during row iteration
|
||||
type KeyedRowCache interface {
|
||||
// Put adds row to the cache at the given key.
|
||||
Put(key string, row types.Row) error
|
||||
|
||||
// Get returns the rows specified by key.
|
||||
Get(key string) (types.Row, error)
|
||||
|
||||
// Size returns the number of rows in the cache.
|
||||
Size() int
|
||||
}
|
||||
|
||||
// Ensure type implements interface.
|
||||
var _ KeyedRowCache = (*inMemoryKeyedRowCache)(nil)
|
||||
|
||||
// default implementation of KeyedRowCache (in memory)
|
||||
type inMemoryKeyedRowCache struct {
|
||||
store map[string][]interface{}
|
||||
}
|
||||
|
||||
func newinMemoryKeyedRowCache() *inMemoryKeyedRowCache {
|
||||
return &inMemoryKeyedRowCache{
|
||||
store: make(map[string][]interface{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (m inMemoryKeyedRowCache) Put(u string, i types.Row) error {
|
||||
m.store[u] = i
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m inMemoryKeyedRowCache) Get(u string) (types.Row, error) {
|
||||
return m.store[u], nil
|
||||
}
|
||||
|
||||
func (m inMemoryKeyedRowCache) Size() int {
|
||||
return len(m.store)
|
||||
}
|
||||
|
||||
// Ensure type implements interface.
|
||||
var _ RowCache = (*inMemoryRowCache)(nil)
|
||||
|
||||
type inMemoryRowCache struct {
|
||||
rows []types.Row
|
||||
}
|
||||
|
||||
func newInMemoryRowCache() *inMemoryRowCache {
|
||||
return &inMemoryRowCache{}
|
||||
}
|
||||
|
||||
func (c *inMemoryRowCache) Add(row types.Row) error {
|
||||
c.rows = append(c.rows, row)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *inMemoryRowCache) AllRows() []types.Row {
|
||||
return c.rows
|
||||
}
|
||||
105
sql3/planner/opaltertable.go
Normal file
105
sql3/planner/opaltertable.go
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/molecula/featurebase/v3/sql3"
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
)
|
||||
|
||||
// PlanOpAlterTable plan operator to alter a table.
|
||||
type PlanOpAlterTable struct {
|
||||
planner *ExecutionPlanner
|
||||
tableName string
|
||||
operation alterOperation
|
||||
oldColumnName string
|
||||
newColumnName string
|
||||
columnDef *createTableField
|
||||
warnings []string
|
||||
}
|
||||
|
||||
func NewPlanOpAlterTable(p *ExecutionPlanner, tableName string, operation alterOperation, oldColumnName string, newColumnName string, columnDef *createTableField) *PlanOpAlterTable {
|
||||
return &PlanOpAlterTable{
|
||||
planner: p,
|
||||
tableName: tableName,
|
||||
operation: operation,
|
||||
oldColumnName: oldColumnName,
|
||||
newColumnName: newColumnName,
|
||||
columnDef: columnDef,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PlanOpAlterTable) Plan() map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
result["_op"] = fmt.Sprintf("%T", p)
|
||||
result["tableName"] = p.tableName
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *PlanOpAlterTable) AddWarning(warning string) {
|
||||
p.warnings = append(p.warnings, warning)
|
||||
}
|
||||
|
||||
func (p *PlanOpAlterTable) Warnings() []string {
|
||||
return p.warnings
|
||||
}
|
||||
|
||||
func (p *PlanOpAlterTable) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *PlanOpAlterTable) Schema() types.Schema {
|
||||
return types.Schema{}
|
||||
}
|
||||
|
||||
func (p *PlanOpAlterTable) Children() []types.PlanOperator {
|
||||
return []types.PlanOperator{}
|
||||
}
|
||||
|
||||
func (p *PlanOpAlterTable) Iterator(ctx context.Context, row types.Row) (types.RowIterator, error) {
|
||||
return &alterTableRowIter{
|
||||
planner: p.planner,
|
||||
operation: p.operation,
|
||||
tableName: p.tableName,
|
||||
columnDef: p.columnDef,
|
||||
oldColumnName: p.oldColumnName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *PlanOpAlterTable) WithChildren(children ...types.PlanOperator) (types.PlanOperator, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type alterTableRowIter struct {
|
||||
planner *ExecutionPlanner
|
||||
operation alterOperation
|
||||
tableName string
|
||||
columnDef *createTableField
|
||||
oldColumnName string
|
||||
}
|
||||
|
||||
var _ types.RowIterator = (*alterTableRowIter)(nil)
|
||||
|
||||
func (i *alterTableRowIter) Next(ctx context.Context) (types.Row, error) {
|
||||
switch i.operation {
|
||||
case alterOpAdd:
|
||||
_, err := i.planner.schemaAPI.CreateField(ctx, i.tableName, i.columnDef.name, i.columnDef.fos...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case alterOpDrop:
|
||||
err := i.planner.schemaAPI.DeleteField(ctx, i.tableName, i.oldColumnName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case alterOpRename:
|
||||
return nil, sql3.NewErrInternal("column rename is unimplemented")
|
||||
|
||||
}
|
||||
return nil, types.ErrNoMoreRows
|
||||
}
|
||||
79
sql3/planner/opbulkinsert.go
Normal file
79
sql3/planner/opbulkinsert.go
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
)
|
||||
|
||||
// PlanOpBulkInsert plan operator to handle INSERT.
|
||||
type PlanOpBulkInsert struct {
|
||||
planner *ExecutionPlanner
|
||||
tableName string
|
||||
warnings []string
|
||||
}
|
||||
|
||||
func NewPlanOpBulkInsert(p *ExecutionPlanner, tableName string) *PlanOpBulkInsert {
|
||||
return &PlanOpBulkInsert{
|
||||
planner: p,
|
||||
tableName: tableName,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PlanOpBulkInsert) Plan() map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
result["_op"] = fmt.Sprintf("%T", p)
|
||||
sc := make([]string, 0)
|
||||
for _, e := range p.Schema() {
|
||||
sc = append(sc, fmt.Sprintf("'%s', '%s', '%s'", e.Name, e.Table, e.Type.TypeName()))
|
||||
}
|
||||
result["_schema"] = sc
|
||||
result["tableName"] = p.tableName
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *PlanOpBulkInsert) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *PlanOpBulkInsert) AddWarning(warning string) {
|
||||
p.warnings = append(p.warnings, warning)
|
||||
}
|
||||
|
||||
func (p *PlanOpBulkInsert) Warnings() []string {
|
||||
return p.warnings
|
||||
}
|
||||
|
||||
func (p *PlanOpBulkInsert) Schema() types.Schema {
|
||||
return types.Schema{}
|
||||
}
|
||||
|
||||
func (p *PlanOpBulkInsert) Children() []types.PlanOperator {
|
||||
return []types.PlanOperator{}
|
||||
}
|
||||
|
||||
func (p *PlanOpBulkInsert) Iterator(ctx context.Context, row types.Row) (types.RowIterator, error) {
|
||||
return &bulkInsertRowIter{
|
||||
planner: p.planner,
|
||||
tableName: p.tableName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *PlanOpBulkInsert) WithChildren(children ...types.PlanOperator) (types.PlanOperator, error) {
|
||||
return NewPlanOpBulkInsert(p.planner, p.tableName), nil
|
||||
}
|
||||
|
||||
type bulkInsertRowIter struct {
|
||||
planner *ExecutionPlanner
|
||||
tableName string
|
||||
}
|
||||
|
||||
var _ types.RowIterator = (*bulkInsertRowIter)(nil)
|
||||
|
||||
func (i *bulkInsertRowIter) Next(ctx context.Context) (types.Row, error) {
|
||||
|
||||
return nil, types.ErrNoMoreRows
|
||||
}
|
||||
121
sql3/planner/opcreatetable.go
Normal file
121
sql3/planner/opcreatetable.go
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
pilosa "github.com/molecula/featurebase/v3"
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// PlanOpCreateTable plan operator that creates a table.
|
||||
type PlanOpCreateTable struct {
|
||||
planner *ExecutionPlanner
|
||||
tableName string
|
||||
failIfExists bool
|
||||
isKeyed bool
|
||||
keyPartitions int
|
||||
columns []*createTableField
|
||||
warnings []string
|
||||
}
|
||||
|
||||
func NewPlanOpCreateTable(p *ExecutionPlanner, tableName string, failIfExists bool, isKeyed bool, keyPartitions int, columns []*createTableField) *PlanOpCreateTable {
|
||||
return &PlanOpCreateTable{
|
||||
planner: p,
|
||||
tableName: tableName,
|
||||
failIfExists: failIfExists,
|
||||
isKeyed: isKeyed,
|
||||
keyPartitions: keyPartitions,
|
||||
columns: columns,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PlanOpCreateTable) Plan() map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
result["_op"] = fmt.Sprintf("%T", p)
|
||||
ps := make([]string, 0)
|
||||
for _, e := range p.Schema() {
|
||||
ps = append(ps, fmt.Sprintf("'%s', '%s', '%s'", e.Name, e.Table, e.Type.TypeName()))
|
||||
}
|
||||
result["_schema"] = ps
|
||||
result["name"] = p.tableName
|
||||
result["failIfExists"] = p.failIfExists
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *PlanOpCreateTable) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *PlanOpCreateTable) AddWarning(warning string) {
|
||||
p.warnings = append(p.warnings, warning)
|
||||
}
|
||||
|
||||
func (p *PlanOpCreateTable) Warnings() []string {
|
||||
return p.warnings
|
||||
}
|
||||
|
||||
func (p *PlanOpCreateTable) Schema() types.Schema {
|
||||
return types.Schema{}
|
||||
}
|
||||
|
||||
func (p *PlanOpCreateTable) Children() []types.PlanOperator {
|
||||
return []types.PlanOperator{}
|
||||
}
|
||||
|
||||
func (p *PlanOpCreateTable) Iterator(ctx context.Context, row types.Row) (types.RowIterator, error) {
|
||||
return &createTableRowIter{
|
||||
planner: p.planner,
|
||||
tableName: p.tableName,
|
||||
failIfExists: p.failIfExists,
|
||||
isKeyed: p.isKeyed,
|
||||
keyPartitions: p.keyPartitions,
|
||||
columns: p.columns,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *PlanOpCreateTable) WithChildren(children ...types.PlanOperator) (types.PlanOperator, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type createTableRowIter struct {
|
||||
planner *ExecutionPlanner
|
||||
tableName string
|
||||
failIfExists bool
|
||||
isKeyed bool
|
||||
keyPartitions int
|
||||
columns []*createTableField
|
||||
}
|
||||
|
||||
var _ types.RowIterator = (*createTableRowIter)(nil)
|
||||
|
||||
func (i *createTableRowIter) Next(ctx context.Context) (types.Row, error) {
|
||||
//create the table
|
||||
options := pilosa.IndexOptions{
|
||||
Keys: i.isKeyed,
|
||||
TrackExistence: true,
|
||||
PartitionN: i.keyPartitions,
|
||||
}
|
||||
|
||||
fields := make([]pilosa.CreateFieldObj, len(i.columns))
|
||||
for i, f := range i.columns {
|
||||
fields[i] = pilosa.CreateFieldObj{
|
||||
Name: f.name,
|
||||
Options: f.fos,
|
||||
}
|
||||
}
|
||||
|
||||
if err := i.planner.schemaAPI.CreateIndexAndFields(ctx, i.tableName, options, fields); err != nil {
|
||||
if _, ok := errors.Cause(err).(pilosa.ConflictError); ok {
|
||||
if i.failIfExists {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return nil, types.ErrNoMoreRows
|
||||
}
|
||||
40
sql3/planner/opdistinct.go
Normal file
40
sql3/planner/opdistinct.go
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright 2022 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
)
|
||||
|
||||
// PlanOpDistinct plan operator handles DISTINCT
|
||||
type PlanOpDistinct struct {
|
||||
planner *ExecutionPlanner
|
||||
source types.PlanOperator
|
||||
warnings []string
|
||||
}
|
||||
|
||||
func NewPlanOpDistinct(p *ExecutionPlanner, source types.PlanOperator) *PlanOpDistinct {
|
||||
return &PlanOpDistinct{
|
||||
planner: p,
|
||||
source: source,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *PlanOpDistinct) Plan() map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
result["_op"] = fmt.Sprintf("%T", n)
|
||||
return result
|
||||
}
|
||||
|
||||
func (n *PlanOpDistinct) AddWarning(warning string) {
|
||||
n.warnings = append(n.warnings, warning)
|
||||
}
|
||||
|
||||
func (n *PlanOpDistinct) Warnings() []string {
|
||||
var w []string
|
||||
w = append(w, n.warnings...)
|
||||
w = append(w, n.source.Warnings()...)
|
||||
return w
|
||||
}
|
||||
88
sql3/planner/opdroptable.go
Normal file
88
sql3/planner/opdroptable.go
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
pilosa "github.com/molecula/featurebase/v3"
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
)
|
||||
|
||||
// PlanOpDropTable plan operator to drop a table.
|
||||
type PlanOpDropTable struct {
|
||||
planner *ExecutionPlanner
|
||||
index *pilosa.IndexInfo
|
||||
warnings []string
|
||||
}
|
||||
|
||||
func NewPlanOpDropTable(p *ExecutionPlanner, index *pilosa.IndexInfo) *PlanOpDropTable {
|
||||
return &PlanOpDropTable{
|
||||
planner: p,
|
||||
index: index,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PlanOpDropTable) Plan() map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
result["_op"] = fmt.Sprintf("%T", p)
|
||||
ps := make([]string, 0)
|
||||
for _, e := range p.Schema() {
|
||||
ps = append(ps, fmt.Sprintf("'%s', '%s', '%s'", e.Name, e.Table, e.Type.TypeName()))
|
||||
}
|
||||
result["_schema"] = ps
|
||||
result["tableName"] = p.index.Name
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *PlanOpDropTable) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *PlanOpDropTable) AddWarning(warning string) {
|
||||
p.warnings = append(p.warnings, warning)
|
||||
}
|
||||
|
||||
func (p *PlanOpDropTable) Warnings() []string {
|
||||
return p.warnings
|
||||
}
|
||||
|
||||
func (p *PlanOpDropTable) Schema() types.Schema {
|
||||
return types.Schema{}
|
||||
}
|
||||
|
||||
func (p *PlanOpDropTable) Children() []types.PlanOperator {
|
||||
return []types.PlanOperator{}
|
||||
}
|
||||
|
||||
func (p *PlanOpDropTable) Iterator(ctx context.Context, row types.Row) (types.RowIterator, error) {
|
||||
return &dropTableRowIter{
|
||||
planner: p.planner,
|
||||
index: p.index,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *PlanOpDropTable) WithChildren(children ...types.PlanOperator) (types.PlanOperator, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type dropTableRowIter struct {
|
||||
planner *ExecutionPlanner
|
||||
index *pilosa.IndexInfo
|
||||
}
|
||||
|
||||
var _ types.RowIterator = (*dropTableRowIter)(nil)
|
||||
|
||||
func (i *dropTableRowIter) Next(ctx context.Context) (types.Row, error) {
|
||||
err := i.planner.checkAccess(ctx, i.index.Name, accessTypeDropObject)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = i.planner.schemaAPI.DeleteIndex(ctx, i.index.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, types.ErrNoMoreRows
|
||||
}
|
||||
174
sql3/planner/opfeaturebasecolumns.go
Normal file
174
sql3/planner/opfeaturebasecolumns.go
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
pilosa "github.com/molecula/featurebase/v3"
|
||||
"github.com/molecula/featurebase/v3/sql3/parser"
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
)
|
||||
|
||||
// PlanOpFeatureBaseColumns wraps an Index that is returned from schemaAPI.Schema().
|
||||
type PlanOpFeatureBaseColumns struct {
|
||||
index *pilosa.IndexInfo
|
||||
warnings []string
|
||||
}
|
||||
|
||||
func NewPlanOpFeatureBaseColumns(index *pilosa.IndexInfo) *PlanOpFeatureBaseColumns {
|
||||
node := &PlanOpFeatureBaseColumns{
|
||||
index: index,
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
func (p *PlanOpFeatureBaseColumns) Plan() map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
result["_op"] = fmt.Sprintf("%T", p)
|
||||
ps := make([]string, 0)
|
||||
for _, e := range p.Schema() {
|
||||
ps = append(ps, fmt.Sprintf("'%s', '%s', '%s'", e.Name, e.Table, e.Type.TypeName()))
|
||||
}
|
||||
result["_schema"] = ps
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *PlanOpFeatureBaseColumns) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *PlanOpFeatureBaseColumns) AddWarning(warning string) {
|
||||
p.warnings = append(p.warnings, warning)
|
||||
}
|
||||
|
||||
func (p *PlanOpFeatureBaseColumns) Warnings() []string {
|
||||
return p.warnings
|
||||
}
|
||||
|
||||
func (p *PlanOpFeatureBaseColumns) Schema() types.Schema {
|
||||
return types.Schema{
|
||||
&types.PlannerColumn{
|
||||
Table: "fb$table_columns",
|
||||
Name: "name",
|
||||
Type: parser.NewDataTypeString(),
|
||||
},
|
||||
&types.PlannerColumn{
|
||||
Table: "fb$table_columns",
|
||||
Name: "type",
|
||||
Type: parser.NewDataTypeString(),
|
||||
},
|
||||
&types.PlannerColumn{
|
||||
Table: "fb$table_columns",
|
||||
Name: "internal_type",
|
||||
Type: parser.NewDataTypeString(),
|
||||
},
|
||||
&types.PlannerColumn{
|
||||
Table: "fb$table_columns",
|
||||
Name: "created_at",
|
||||
Type: parser.NewDataTypeTimestamp(),
|
||||
},
|
||||
&types.PlannerColumn{
|
||||
Table: "fb$table_columns",
|
||||
Name: "keys",
|
||||
Type: parser.NewDataTypeBool(),
|
||||
},
|
||||
&types.PlannerColumn{
|
||||
Table: "fb$table_columns",
|
||||
Name: "cache_type",
|
||||
Type: parser.NewDataTypeString(),
|
||||
},
|
||||
&types.PlannerColumn{
|
||||
Table: "fb$table_columns",
|
||||
Name: "cache_size",
|
||||
Type: parser.NewDataTypeInt(),
|
||||
},
|
||||
&types.PlannerColumn{
|
||||
Table: "fb$table_columns",
|
||||
Name: "scale",
|
||||
Type: parser.NewDataTypeInt(),
|
||||
},
|
||||
&types.PlannerColumn{
|
||||
Table: "fb$table_columns",
|
||||
Name: "min",
|
||||
Type: parser.NewDataTypeInt(),
|
||||
},
|
||||
&types.PlannerColumn{
|
||||
Table: "fb$table_columns",
|
||||
Name: "max",
|
||||
Type: parser.NewDataTypeInt(),
|
||||
},
|
||||
&types.PlannerColumn{
|
||||
Table: "fb$table_columns",
|
||||
Name: "timeunit",
|
||||
Type: parser.NewDataTypeString(),
|
||||
},
|
||||
&types.PlannerColumn{
|
||||
Table: "fb$table_columns",
|
||||
Name: "epoch",
|
||||
Type: parser.NewDataTypeInt(),
|
||||
},
|
||||
&types.PlannerColumn{
|
||||
Table: "fb$table_columns",
|
||||
Name: "timequantum",
|
||||
Type: parser.NewDataTypeString(),
|
||||
},
|
||||
&types.PlannerColumn{
|
||||
Table: "fb$table_columns",
|
||||
Name: "ttl",
|
||||
Type: parser.NewDataTypeInt(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PlanOpFeatureBaseColumns) Children() []types.PlanOperator {
|
||||
return []types.PlanOperator{}
|
||||
}
|
||||
|
||||
func (p *PlanOpFeatureBaseColumns) Iterator(ctx context.Context, row types.Row) (types.RowIterator, error) {
|
||||
return &showColumnsRowIter{
|
||||
index: p.index,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *PlanOpFeatureBaseColumns) WithChildren(children ...types.PlanOperator) (types.PlanOperator, error) {
|
||||
return NewPlanOpFeatureBaseColumns(p.index), nil
|
||||
}
|
||||
|
||||
type showColumnsRowIter struct {
|
||||
index *pilosa.IndexInfo
|
||||
rowIndex int
|
||||
}
|
||||
|
||||
var _ types.RowIterator = (*showColumnsRowIter)(nil)
|
||||
|
||||
func (i *showColumnsRowIter) Next(ctx context.Context) (types.Row, error) {
|
||||
if i.rowIndex < len(i.index.Fields) {
|
||||
fields := i.index.Fields
|
||||
|
||||
tm := time.Unix(0, fields[i.rowIndex].CreatedAt)
|
||||
|
||||
row := []interface{}{
|
||||
fields[i.rowIndex].Name,
|
||||
fieldSQLDataType(fields[i.rowIndex]).TypeName(),
|
||||
fields[i.rowIndex].Options.Type,
|
||||
tm.Format(time.RFC3339),
|
||||
fields[i.rowIndex].Options.Keys,
|
||||
fields[i.rowIndex].Options.CacheType,
|
||||
fields[i.rowIndex].Options.CacheSize,
|
||||
fields[i.rowIndex].Options.Scale,
|
||||
fields[i.rowIndex].Options.Min.ToInt64(0),
|
||||
fields[i.rowIndex].Options.Max.ToInt64(0),
|
||||
fields[i.rowIndex].Options.TimeUnit,
|
||||
0, //TODO(pok) get Epoch from somewhere?
|
||||
fields[i.rowIndex].Options.TimeQuantum.String(),
|
||||
fields[i.rowIndex].Options.TTL.String(),
|
||||
}
|
||||
|
||||
i.rowIndex += 1
|
||||
return row, nil
|
||||
}
|
||||
return nil, types.ErrNoMoreRows
|
||||
}
|
||||
116
sql3/planner/opfeaturebasetables.go
Normal file
116
sql3/planner/opfeaturebasetables.go
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
pilosa "github.com/molecula/featurebase/v3"
|
||||
"github.com/molecula/featurebase/v3/sql3/parser"
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
)
|
||||
|
||||
// PlanOpFeatureBaseTables wraps a []*IndexInfo that is returned from
|
||||
// schemaAPI.Schema().
|
||||
type PlanOpFeatureBaseTables struct {
|
||||
indexInfo []*pilosa.IndexInfo
|
||||
warnings []string
|
||||
}
|
||||
|
||||
func NewPlanOpFeatureBaseTables(indexInfo []*pilosa.IndexInfo) *PlanOpFeatureBaseTables {
|
||||
return &PlanOpFeatureBaseTables{
|
||||
indexInfo: indexInfo,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PlanOpFeatureBaseTables) Plan() map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
result["_op"] = fmt.Sprintf("%T", p)
|
||||
ps := make([]string, 0)
|
||||
for _, e := range p.Schema() {
|
||||
ps = append(ps, fmt.Sprintf("'%s', '%s', '%s'", e.Name, e.Table, e.Type.TypeName()))
|
||||
}
|
||||
result["_schema"] = ps
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *PlanOpFeatureBaseTables) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *PlanOpFeatureBaseTables) AddWarning(warning string) {
|
||||
p.warnings = append(p.warnings, warning)
|
||||
}
|
||||
|
||||
func (p *PlanOpFeatureBaseTables) Warnings() []string {
|
||||
return p.warnings
|
||||
}
|
||||
|
||||
func (p *PlanOpFeatureBaseTables) Schema() types.Schema {
|
||||
return types.Schema{
|
||||
&types.PlannerColumn{
|
||||
Table: "fb$tables",
|
||||
Name: "name",
|
||||
Type: parser.NewDataTypeString(),
|
||||
},
|
||||
&types.PlannerColumn{
|
||||
Table: "fb$tables",
|
||||
Name: "created_at",
|
||||
Type: parser.NewDataTypeTimestamp(),
|
||||
},
|
||||
&types.PlannerColumn{
|
||||
Table: "fb$tables",
|
||||
Name: "track_existence",
|
||||
Type: parser.NewDataTypeBool(),
|
||||
},
|
||||
&types.PlannerColumn{
|
||||
Table: "fb$tables",
|
||||
Name: "keys",
|
||||
Type: parser.NewDataTypeBool(),
|
||||
},
|
||||
&types.PlannerColumn{
|
||||
Table: "fb$tables",
|
||||
Name: "shard_width",
|
||||
Type: parser.NewDataTypeInt(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PlanOpFeatureBaseTables) Children() []types.PlanOperator {
|
||||
return []types.PlanOperator{}
|
||||
}
|
||||
|
||||
func (p *PlanOpFeatureBaseTables) Iterator(ctx context.Context, row types.Row) (types.RowIterator, error) {
|
||||
return &showTablesRowIter{
|
||||
indexInfo: p.indexInfo,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *PlanOpFeatureBaseTables) WithChildren(children ...types.PlanOperator) (types.PlanOperator, error) {
|
||||
return NewPlanOpFeatureBaseTables(p.indexInfo), nil
|
||||
}
|
||||
|
||||
type showTablesRowIter struct {
|
||||
indexInfo []*pilosa.IndexInfo
|
||||
rowIndex int
|
||||
}
|
||||
|
||||
var _ types.RowIterator = (*showTablesRowIter)(nil)
|
||||
|
||||
func (i *showTablesRowIter) Next(ctx context.Context) (types.Row, error) {
|
||||
if i.rowIndex < len(i.indexInfo) {
|
||||
tm := time.Unix(0, i.indexInfo[i.rowIndex].CreatedAt)
|
||||
row := []interface{}{
|
||||
i.indexInfo[i.rowIndex].Name,
|
||||
tm.Format(time.RFC3339),
|
||||
i.indexInfo[i.rowIndex].Options.TrackExistence,
|
||||
i.indexInfo[i.rowIndex].Options.Keys,
|
||||
i.indexInfo[i.rowIndex].ShardWidth,
|
||||
}
|
||||
i.rowIndex += 1
|
||||
return row, nil
|
||||
}
|
||||
return nil, types.ErrNoMoreRows
|
||||
}
|
||||
198
sql3/planner/opgroupby.go
Normal file
198
sql3/planner/opgroupby.go
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
// Copyright 2022 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/molecula/featurebase/v3/sql3"
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
)
|
||||
|
||||
// PlanOpGroupBy handles the GROUP BY clause
|
||||
// this is the default GROUP BY operator and may be replaced by the optimizer
|
||||
// with one or more of the PQL related group by or aggregate operators
|
||||
type PlanOpGroupBy struct {
|
||||
ChildOp types.PlanOperator
|
||||
Aggregates []types.PlanExpression
|
||||
GroupByExprs []types.PlanExpression
|
||||
warnings []string
|
||||
}
|
||||
|
||||
func NewPlanOpGroupBy(aggregates []types.PlanExpression, groupByExprs []types.PlanExpression, child types.PlanOperator) *PlanOpGroupBy {
|
||||
return &PlanOpGroupBy{
|
||||
ChildOp: child,
|
||||
Aggregates: aggregates,
|
||||
GroupByExprs: groupByExprs,
|
||||
}
|
||||
}
|
||||
|
||||
// Schema for GroupBy is the group by expressions followed by the aggregate expressions
|
||||
func (p *PlanOpGroupBy) Schema() types.Schema {
|
||||
result := make(types.Schema, len(p.GroupByExprs)+len(p.Aggregates))
|
||||
for idx, expr := range p.GroupByExprs {
|
||||
ref, ok := expr.(*qualifiedRefPlanExpression)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
s := &types.PlannerColumn{
|
||||
Name: ref.columnName,
|
||||
Table: ref.tableName,
|
||||
Type: expr.Type(),
|
||||
}
|
||||
result[idx] = s
|
||||
}
|
||||
offset := len(p.GroupByExprs)
|
||||
for idx, agg := range p.Aggregates {
|
||||
s := &types.PlannerColumn{
|
||||
Name: "",
|
||||
Table: "",
|
||||
Type: agg.Type(),
|
||||
}
|
||||
result[idx+offset] = s
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *PlanOpGroupBy) Iterator(ctx context.Context, row types.Row) (types.RowIterator, error) {
|
||||
// TODO(pok) implement group by with group by expressions
|
||||
i, err := p.ChildOp.Iterator(ctx, row)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aggs := []types.PlanExpression{}
|
||||
aggs = append(aggs, p.GroupByExprs...)
|
||||
aggs = append(aggs, p.Aggregates...)
|
||||
return newGroupByIter(ctx, aggs, i), nil
|
||||
}
|
||||
|
||||
func (p *PlanOpGroupBy) Children() []types.PlanOperator {
|
||||
return []types.PlanOperator{
|
||||
p.ChildOp,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PlanOpGroupBy) WithChildren(children ...types.PlanOperator) (types.PlanOperator, error) {
|
||||
if len(children) != 1 {
|
||||
return nil, sql3.NewErrInternalf("unexpected number of children '%d'", len(children))
|
||||
}
|
||||
return NewPlanOpGroupBy(p.Aggregates, p.GroupByExprs, children[0]), nil
|
||||
}
|
||||
|
||||
func (p *PlanOpGroupBy) Plan() map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
result["_op"] = fmt.Sprintf("%T", p)
|
||||
sc := make([]string, 0)
|
||||
for _, e := range p.Schema() {
|
||||
sc = append(sc, fmt.Sprintf("'%s', '%s', '%s'", e.Name, e.Table, e.Type.TypeName()))
|
||||
}
|
||||
result["_schema"] = sc
|
||||
result["child"] = p.ChildOp.Plan()
|
||||
ps := make([]interface{}, 0)
|
||||
for _, e := range p.Aggregates {
|
||||
ps = append(ps, e.Plan())
|
||||
}
|
||||
result["aggregates"] = ps
|
||||
ps = make([]interface{}, 0)
|
||||
for _, e := range p.GroupByExprs {
|
||||
ps = append(ps, e.Plan())
|
||||
}
|
||||
result["groupByExprs"] = ps
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *PlanOpGroupBy) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *PlanOpGroupBy) AddWarning(warning string) {
|
||||
p.warnings = append(p.warnings, warning)
|
||||
}
|
||||
|
||||
func (p *PlanOpGroupBy) Warnings() []string {
|
||||
var w []string
|
||||
w = append(w, p.warnings...)
|
||||
w = append(w, p.ChildOp.Warnings()...)
|
||||
return w
|
||||
}
|
||||
|
||||
type groupByIter struct {
|
||||
aggregates []types.PlanExpression
|
||||
child types.RowIterator
|
||||
ctx context.Context
|
||||
buf []types.AggregationBuffer
|
||||
done bool
|
||||
}
|
||||
|
||||
func newGroupByIter(ctx context.Context, aggregates []types.PlanExpression, child types.RowIterator) *groupByIter {
|
||||
return &groupByIter{
|
||||
aggregates: aggregates,
|
||||
child: child,
|
||||
ctx: ctx,
|
||||
buf: make([]types.AggregationBuffer, len(aggregates)),
|
||||
}
|
||||
}
|
||||
|
||||
func (i *groupByIter) Next(ctx context.Context) (types.Row, error) {
|
||||
if i.done {
|
||||
return nil, types.ErrNoMoreRows
|
||||
}
|
||||
|
||||
i.done = true
|
||||
|
||||
var err error
|
||||
for j, a := range i.aggregates {
|
||||
i.buf[j], err = newAggregationBuffer(a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
row, err := i.child.Next(ctx)
|
||||
if err != nil {
|
||||
if err == types.ErrNoMoreRows {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := updateBuffers(ctx, i.buf, row); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return evalBuffers(ctx, i.buf)
|
||||
}
|
||||
|
||||
func newAggregationBuffer(expr types.PlanExpression) (types.AggregationBuffer, error) {
|
||||
switch n := expr.(type) {
|
||||
case types.Aggregable:
|
||||
return n.NewBuffer()
|
||||
default:
|
||||
return NewAggLastBuffer(expr), nil
|
||||
}
|
||||
}
|
||||
|
||||
func updateBuffers(ctx context.Context, buffers []types.AggregationBuffer, row types.Row) error {
|
||||
for _, b := range buffers {
|
||||
if err := b.Update(ctx, row); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func evalBuffers(ctx context.Context, buffers []types.AggregationBuffer) (types.Row, error) {
|
||||
var row = make(types.Row, len(buffers))
|
||||
var err error
|
||||
for i, b := range buffers {
|
||||
row[i], err = b.Eval(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return row, nil
|
||||
}
|
||||
364
sql3/planner/opinsert.go
Normal file
364
sql3/planner/opinsert.go
Normal file
|
|
@ -0,0 +1,364 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
pilosa "github.com/molecula/featurebase/v3"
|
||||
"github.com/molecula/featurebase/v3/pql"
|
||||
"github.com/molecula/featurebase/v3/sql3"
|
||||
"github.com/molecula/featurebase/v3/sql3/parser"
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
)
|
||||
|
||||
// PlanOpInsert plan operator to handle INSERT.
|
||||
type PlanOpInsert struct {
|
||||
planner *ExecutionPlanner
|
||||
tableName string
|
||||
targetColumns []*qualifiedRefPlanExpression
|
||||
insertValues []types.PlanExpression
|
||||
warnings []string
|
||||
}
|
||||
|
||||
func NewPlanOpInsert(p *ExecutionPlanner, tableName string, targetColumns []*qualifiedRefPlanExpression, insertValues []types.PlanExpression) *PlanOpInsert {
|
||||
return &PlanOpInsert{
|
||||
planner: p,
|
||||
tableName: tableName,
|
||||
targetColumns: targetColumns,
|
||||
insertValues: insertValues,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PlanOpInsert) Plan() map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
result["_op"] = fmt.Sprintf("%T", p)
|
||||
sc := make([]string, 0)
|
||||
for _, e := range p.Schema() {
|
||||
sc = append(sc, fmt.Sprintf("'%s', '%s', '%s'", e.Name, e.Table, e.Type.TypeName()))
|
||||
}
|
||||
result["_schema"] = sc
|
||||
result["tableName"] = p.tableName
|
||||
ps := make([]interface{}, 0)
|
||||
for _, e := range p.targetColumns {
|
||||
ps = append(ps, e.Plan())
|
||||
}
|
||||
result["targetColumns"] = ps
|
||||
ps = make([]interface{}, 0)
|
||||
for _, e := range p.insertValues {
|
||||
ps = append(ps, e.Plan())
|
||||
}
|
||||
result["insertValues"] = ps
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *PlanOpInsert) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *PlanOpInsert) AddWarning(warning string) {
|
||||
p.warnings = append(p.warnings, warning)
|
||||
}
|
||||
|
||||
func (p *PlanOpInsert) Warnings() []string {
|
||||
return p.warnings
|
||||
}
|
||||
|
||||
func (p *PlanOpInsert) Schema() types.Schema {
|
||||
return types.Schema{}
|
||||
}
|
||||
|
||||
func (p *PlanOpInsert) Children() []types.PlanOperator {
|
||||
return []types.PlanOperator{}
|
||||
}
|
||||
|
||||
func (p *PlanOpInsert) Iterator(ctx context.Context, row types.Row) (types.RowIterator, error) {
|
||||
return &insertRowIter{
|
||||
planner: p.planner,
|
||||
tableName: p.tableName,
|
||||
targetColumns: p.targetColumns,
|
||||
insertValues: p.insertValues,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *PlanOpInsert) WithChildren(children ...types.PlanOperator) (types.PlanOperator, error) {
|
||||
return NewPlanOpInsert(p.planner, p.tableName, p.targetColumns, p.insertValues), nil
|
||||
}
|
||||
|
||||
type insertRowIter struct {
|
||||
planner *ExecutionPlanner
|
||||
tableName string
|
||||
targetColumns []*qualifiedRefPlanExpression
|
||||
insertValues []types.PlanExpression
|
||||
}
|
||||
|
||||
var _ types.RowIterator = (*insertRowIter)(nil)
|
||||
|
||||
func (i *insertRowIter) Next(ctx context.Context) (types.Row, error) {
|
||||
qcx := i.planner.computeAPI.Txf().NewQcx()
|
||||
|
||||
colIDs := make([]uint64, 0)
|
||||
colKeys := make([]string, 0)
|
||||
|
||||
addColID := func(v interface{}) error {
|
||||
switch id := v.(type) {
|
||||
case int64:
|
||||
colIDs = append(colIDs, uint64(id))
|
||||
case uint64:
|
||||
colIDs = append(colIDs, id)
|
||||
case string:
|
||||
colKeys = append(colKeys, id)
|
||||
default:
|
||||
return sql3.NewErrInternalf("unhandled _id data type '%T'", id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//find the _id column and evaluate
|
||||
var err error
|
||||
var columnID interface{}
|
||||
for idx, iv := range i.insertValues {
|
||||
targetColumn := i.targetColumns[idx]
|
||||
if strings.EqualFold(targetColumn.columnName, "_id") {
|
||||
columnID, err = iv.Evaluate(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
//eval all the expressions and do the insert
|
||||
for idx, iv := range i.insertValues {
|
||||
colIDs = make([]uint64, 0)
|
||||
colKeys = make([]string, 0)
|
||||
|
||||
targetColumn := i.targetColumns[idx]
|
||||
|
||||
if strings.EqualFold(targetColumn.columnName, "_id") {
|
||||
continue
|
||||
}
|
||||
|
||||
eval, err := iv.Evaluate(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//nothing to do if a value is null
|
||||
if eval == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
sourceType := iv.Type()
|
||||
switch targetType := i.targetColumns[idx].dataType.(type) {
|
||||
case *parser.DataTypeInt:
|
||||
|
||||
err = addColID(columnID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vals := make([]int64, 1)
|
||||
vals[0] = eval.(int64)
|
||||
|
||||
req := &pilosa.ImportValueRequest{
|
||||
Index: i.tableName,
|
||||
Field: targetColumn.columnName,
|
||||
Shard: 0, //TODO: handle non-0 shards
|
||||
ColumnIDs: colIDs,
|
||||
ColumnKeys: colKeys,
|
||||
Values: vals,
|
||||
}
|
||||
|
||||
err = i.planner.computeAPI.ImportValue(ctx, qcx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case *parser.DataTypeBool:
|
||||
err = addColID(columnID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
val := eval.(bool)
|
||||
vals := make([]uint64, 1)
|
||||
if val {
|
||||
vals[0] = 1
|
||||
} else {
|
||||
vals[0] = 0
|
||||
}
|
||||
|
||||
req := &pilosa.ImportRequest{
|
||||
Index: i.tableName,
|
||||
Field: targetColumn.columnName,
|
||||
Shard: 0, //TODO: handle non-0 shards
|
||||
ColumnIDs: colIDs,
|
||||
ColumnKeys: colKeys,
|
||||
RowIDs: vals,
|
||||
}
|
||||
|
||||
err = i.planner.computeAPI.Import(ctx, qcx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case *parser.DataTypeDecimal:
|
||||
err = addColID(columnID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vals := make([]float64, 1)
|
||||
vals[0] = eval.(pql.Decimal).Float64()
|
||||
|
||||
req := &pilosa.ImportValueRequest{
|
||||
Index: i.tableName,
|
||||
Field: targetColumn.columnName,
|
||||
Shard: 0, //TODO: handle non-0 shards
|
||||
ColumnIDs: colIDs,
|
||||
ColumnKeys: colKeys,
|
||||
FloatValues: vals,
|
||||
}
|
||||
|
||||
err = i.planner.computeAPI.ImportValue(ctx, qcx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case *parser.DataTypeID:
|
||||
err = addColID(columnID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
coercedVal, err := coerceValue(sourceType, targetType, eval, parser.Pos{Line: 0, Column: 0})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vals := make([]uint64, 1)
|
||||
vals[0] = coercedVal.(uint64)
|
||||
|
||||
req := &pilosa.ImportRequest{
|
||||
Index: i.tableName,
|
||||
Field: targetColumn.columnName,
|
||||
Shard: 0, //TODO: handle non-0 shards
|
||||
ColumnIDs: colIDs,
|
||||
ColumnKeys: colKeys,
|
||||
RowIDs: vals,
|
||||
}
|
||||
|
||||
err = i.planner.computeAPI.Import(ctx, qcx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case *parser.DataTypeIDSet:
|
||||
rowIDs := make([]uint64, 0)
|
||||
rowSet := eval.([]int64)
|
||||
for k := range rowSet {
|
||||
err = addColID(columnID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rowIDs = append(rowIDs, uint64(rowSet[k]))
|
||||
}
|
||||
|
||||
req := &pilosa.ImportRequest{
|
||||
Index: i.tableName,
|
||||
Field: targetColumn.columnName,
|
||||
Shard: 0, //TODO: handle non-0 shards
|
||||
ColumnIDs: colIDs,
|
||||
ColumnKeys: colKeys,
|
||||
RowIDs: rowIDs,
|
||||
}
|
||||
err = i.planner.computeAPI.Import(ctx, qcx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case *parser.DataTypeString:
|
||||
err = addColID(columnID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rowKeys := make([]string, 1)
|
||||
rowKeys[0] = eval.(string)
|
||||
|
||||
req := &pilosa.ImportRequest{
|
||||
Index: i.tableName,
|
||||
Field: targetColumn.columnName,
|
||||
Shard: 0, //TODO: handle non-0 shards
|
||||
ColumnIDs: colIDs,
|
||||
ColumnKeys: colKeys,
|
||||
RowKeys: rowKeys,
|
||||
}
|
||||
err = i.planner.computeAPI.Import(ctx, qcx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case *parser.DataTypeStringSet:
|
||||
rowKeys := make([]string, 0)
|
||||
rowSet := eval.([]string)
|
||||
for k := range rowSet {
|
||||
err = addColID(columnID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rowKeys = append(rowKeys, rowSet[k])
|
||||
}
|
||||
|
||||
req := &pilosa.ImportRequest{
|
||||
Index: i.tableName,
|
||||
Field: targetColumn.columnName,
|
||||
Shard: 0, //TODO: handle non-0 shards
|
||||
ColumnIDs: colIDs,
|
||||
ColumnKeys: colKeys,
|
||||
RowKeys: rowKeys,
|
||||
}
|
||||
err = i.planner.computeAPI.Import(ctx, qcx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case *parser.DataTypeTimestamp:
|
||||
err = addColID(columnID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
coercedVal, err := coerceValue(sourceType, targetType, eval, parser.Pos{Line: 0, Column: 0})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vals := make([]time.Time, 1)
|
||||
vals[0] = coercedVal.(time.Time)
|
||||
|
||||
req := &pilosa.ImportValueRequest{
|
||||
Index: i.tableName,
|
||||
Field: targetColumn.columnName,
|
||||
Shard: 0, //TODO: handle non-0 shards
|
||||
ColumnIDs: colIDs,
|
||||
ColumnKeys: colKeys,
|
||||
TimestampValues: vals,
|
||||
}
|
||||
|
||||
err = i.planner.computeAPI.ImportValue(ctx, qcx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, sql3.NewErrInternalf("unhandled data type '%T'", iv.Type())
|
||||
}
|
||||
}
|
||||
|
||||
return nil, types.ErrNoMoreRows
|
||||
}
|
||||
340
sql3/planner/opnestedloops.go
Normal file
340
sql3/planner/opnestedloops.go
Normal file
|
|
@ -0,0 +1,340 @@
|
|||
// Copyright 2022 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/molecula/featurebase/v3/sql3"
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
)
|
||||
|
||||
// PlanOpNestedLoops plan operator handles a join
|
||||
// For each row in the top input, scan the bottom input and output matching rows
|
||||
type PlanOpNestedLoops struct {
|
||||
top types.PlanOperator
|
||||
bottom types.PlanOperator
|
||||
warnings []string
|
||||
}
|
||||
|
||||
func NewPlanOpNestedLoops(top, bottom types.PlanOperator) *PlanOpNestedLoops {
|
||||
return &PlanOpNestedLoops{
|
||||
top: top,
|
||||
bottom: bottom,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PlanOpNestedLoops) Plan() map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
result["_op"] = fmt.Sprintf("%T", p)
|
||||
ps := make([]string, 0)
|
||||
for _, e := range p.Schema() {
|
||||
ps = append(ps, fmt.Sprintf("'%s', '%s', '%s'", e.Name, e.Table, e.Type.TypeName()))
|
||||
}
|
||||
result["_schema"] = ps
|
||||
result["top"] = p.top.Plan()
|
||||
result["bottom"] = p.bottom.Plan()
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *PlanOpNestedLoops) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *PlanOpNestedLoops) AddWarning(warning string) {
|
||||
p.warnings = append(p.warnings, warning)
|
||||
}
|
||||
|
||||
func (p *PlanOpNestedLoops) Warnings() []string {
|
||||
return p.warnings
|
||||
}
|
||||
|
||||
func (p *PlanOpNestedLoops) Schema() types.Schema {
|
||||
result := types.Schema{}
|
||||
result = append(result, p.top.Schema()...)
|
||||
result = append(result, p.bottom.Schema()...)
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *PlanOpNestedLoops) Children() []types.PlanOperator {
|
||||
return []types.PlanOperator{
|
||||
p.top,
|
||||
p.bottom,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PlanOpNestedLoops) Iterator(ctx context.Context, row types.Row) (types.RowIterator, error) {
|
||||
topIter, err := p.top.Iterator(ctx, row)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rowWidth := len(row) + len(p.top.Schema()) + len(p.bottom.Schema())
|
||||
return newNestedLoopsIter(ctx, joinTypeInner, topIter, p.bottom, row, nil, rowWidth, row), nil
|
||||
}
|
||||
|
||||
func (p *PlanOpNestedLoops) WithChildren(children ...types.PlanOperator) (types.PlanOperator, error) {
|
||||
if len(children) != 2 {
|
||||
return nil, sql3.NewErrInternalf("unexpected number of children '%d'", len(children))
|
||||
}
|
||||
return NewPlanOpNestedLoops(children[0], children[1]), nil
|
||||
}
|
||||
|
||||
type joinType byte
|
||||
|
||||
const (
|
||||
joinTypeInner joinType = iota
|
||||
joinTypeLeft
|
||||
joinTypeRight
|
||||
)
|
||||
|
||||
// joinMode defines the mode in which a join will be performed.
|
||||
type joinMode byte
|
||||
|
||||
const (
|
||||
// unknownMode is the default mode. It will start iterating without really
|
||||
// knowing in which mode it will end up computing the join. If it
|
||||
// iterates the right side fully one time and so far it fits in memory,
|
||||
// then it will switch to memory mode. Otherwise, if at some point during
|
||||
// this first iteration it finds that it does not fit in memory, will
|
||||
// switch to multipass mode.
|
||||
unknownMode joinMode = iota
|
||||
// memoryMode computes all the join directly in memory iterating each
|
||||
// side of the join exactly once.
|
||||
//memoryMode
|
||||
// multipassMode computes the join by iterating the left side once,
|
||||
// and the right side one time for each row in the left side.
|
||||
multipassMode
|
||||
)
|
||||
|
||||
type nestedLoopsIter struct {
|
||||
typ joinType
|
||||
|
||||
top types.RowIterator
|
||||
bottom types.RowIterator
|
||||
ctx context.Context
|
||||
cond types.PlanExpression
|
||||
|
||||
secondaryProvider types.RowIterable
|
||||
|
||||
primaryRow types.Row
|
||||
foundMatch bool
|
||||
rowSize int
|
||||
|
||||
originalRow types.Row
|
||||
scopeLen int
|
||||
|
||||
mode joinMode
|
||||
secondaryRows RowCache
|
||||
}
|
||||
|
||||
func newNestedLoopsIter(ctx context.Context, jt joinType, top types.RowIterator, bottom types.RowIterable, scopeRow types.Row, joinCondition types.PlanExpression, rowWidth int, originalRow types.Row) *nestedLoopsIter {
|
||||
return &nestedLoopsIter{
|
||||
typ: jt,
|
||||
top: top,
|
||||
secondaryProvider: bottom,
|
||||
cond: joinCondition,
|
||||
rowSize: rowWidth,
|
||||
originalRow: originalRow,
|
||||
secondaryRows: newInMemoryRowCache(),
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *nestedLoopsIter) loadPrimary(ctx context.Context) error {
|
||||
// If primary has already been loaded, it's safe to no-op.
|
||||
if i.primaryRow != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
r, err := i.top.Next(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i.primaryRow = i.originalRow.Append(r)
|
||||
i.foundMatch = false
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/*func (i *nestedLoopsIter) loadSecondaryInMemory(ctx context.Context) error {
|
||||
iter, err := i.secondaryProvider.Iterator(ctx, i.primaryRow)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
row, err := iter.Next(ctx)
|
||||
if err == types.ErrNoMoreRows {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
//iter.Close(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := i.secondaryRows.Add(row); err != nil {
|
||||
//iter.Close(ctx)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
//err = iter.Close(ctx)
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
|
||||
if len(i.secondaryRows.Get()) == 0 {
|
||||
return types.ErrNoMoreRows
|
||||
}
|
||||
|
||||
return nil
|
||||
}*/
|
||||
|
||||
func (i *nestedLoopsIter) loadSecondary(ctx context.Context) (row types.Row, err error) {
|
||||
/*if i.mode == memoryMode {
|
||||
if len(i.secondaryRows.Get()) == 0 {
|
||||
if err = i.loadSecondaryInMemory(ctx); err != nil {
|
||||
if err == types.ErrNoMoreRows {
|
||||
i.primaryRow = nil
|
||||
i.pos = 0
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if i.pos >= len(i.secondaryRows.Get()) {
|
||||
i.primaryRow = nil
|
||||
i.pos = 0
|
||||
return nil, types.ErrNoMoreRows
|
||||
}
|
||||
|
||||
row := i.secondaryRows.Get()[i.pos]
|
||||
i.pos++
|
||||
return row, nil
|
||||
}*/
|
||||
|
||||
if i.bottom == nil {
|
||||
var iter types.RowIterator
|
||||
iter, err = i.secondaryProvider.Iterator(ctx, i.primaryRow)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
i.bottom = iter
|
||||
}
|
||||
|
||||
rightRow, err := i.bottom.Next(ctx)
|
||||
if err != nil {
|
||||
if err == types.ErrNoMoreRows {
|
||||
//err = i.bottom.Close(ctx)
|
||||
i.bottom = nil
|
||||
//if err != nil {
|
||||
// return nil, err
|
||||
//}
|
||||
i.primaryRow = nil
|
||||
|
||||
// If we got to this point and the mode is still unknown it means
|
||||
// the right side fits in memory, so the mode changes to memory
|
||||
// join.
|
||||
//if i.mode == unknownMode {
|
||||
// i.mode = memoryMode
|
||||
//}
|
||||
|
||||
return nil, types.ErrNoMoreRows
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if i.mode == unknownMode {
|
||||
var switchToMultipass bool
|
||||
//if !ctx.Memory.HasAvailable() {
|
||||
//switchToMultipass = true
|
||||
//} else {
|
||||
err := i.secondaryRows.Add(rightRow)
|
||||
if err != nil { //&& !sql.ErrNoMemoryAvailable.Is(err) {
|
||||
return nil, err
|
||||
}
|
||||
//}
|
||||
|
||||
if switchToMultipass {
|
||||
//i.Dispose()
|
||||
i.secondaryRows = nil
|
||||
i.mode = multipassMode
|
||||
}
|
||||
}
|
||||
|
||||
return rightRow, nil
|
||||
}
|
||||
|
||||
func (i *nestedLoopsIter) buildRow(primary, secondary types.Row) types.Row {
|
||||
toCut := len(i.originalRow) - i.scopeLen
|
||||
row := make(types.Row, i.rowSize-toCut)
|
||||
|
||||
scope := primary[:i.scopeLen]
|
||||
primary = primary[len(i.originalRow):]
|
||||
|
||||
var first, second types.Row
|
||||
var secondOffset int
|
||||
switch i.typ {
|
||||
case joinTypeRight:
|
||||
first = secondary
|
||||
second = primary
|
||||
secondOffset = len(row) - len(second)
|
||||
default:
|
||||
first = primary
|
||||
second = secondary
|
||||
secondOffset = i.scopeLen + len(first)
|
||||
}
|
||||
|
||||
copy(row, scope)
|
||||
copy(row[i.scopeLen:], first)
|
||||
copy(row[secondOffset:], second)
|
||||
return row
|
||||
}
|
||||
|
||||
func conditionIsTrue(ctx context.Context, row types.Row, cond types.PlanExpression) (bool, error) {
|
||||
if cond == nil {
|
||||
return true, nil
|
||||
}
|
||||
v, err := cond.Evaluate(row)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return v == true, nil
|
||||
}
|
||||
|
||||
func (i *nestedLoopsIter) Next(ctx context.Context) (types.Row, error) {
|
||||
for {
|
||||
if err := i.loadPrimary(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
primary := i.primaryRow
|
||||
secondary, err := i.loadSecondary(ctx)
|
||||
if err != nil {
|
||||
if err == types.ErrNoMoreRows {
|
||||
if !i.foundMatch && (i.typ == joinTypeLeft || i.typ == joinTypeRight) {
|
||||
row := i.buildRow(primary, nil)
|
||||
return row, nil
|
||||
}
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
row := i.buildRow(primary, secondary)
|
||||
matches, err := conditionIsTrue(ctx, row, i.cond)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !matches {
|
||||
continue
|
||||
}
|
||||
|
||||
i.foundMatch = true
|
||||
return row, nil
|
||||
}
|
||||
}
|
||||
71
sql3/planner/opnulltable.go
Normal file
71
sql3/planner/opnulltable.go
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
// Copyright 2022 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
)
|
||||
|
||||
// PlanOpNullTable is an operator for a null table
|
||||
// basically when you do select 1, you're using the null table
|
||||
type PlanOpNullTable struct {
|
||||
warnings []string
|
||||
}
|
||||
|
||||
func NewPlanOpNullTable() *PlanOpNullTable {
|
||||
return &PlanOpNullTable{}
|
||||
}
|
||||
|
||||
func (p *PlanOpNullTable) Schema() types.Schema {
|
||||
return types.Schema{}
|
||||
}
|
||||
|
||||
func (p *PlanOpNullTable) Iterator(ctx context.Context, row types.Row) (types.RowIterator, error) {
|
||||
return &nullTableIterator{}, nil
|
||||
}
|
||||
|
||||
func (p *PlanOpNullTable) WithChildren(children ...types.PlanOperator) (types.PlanOperator, error) {
|
||||
return NewPlanOpNullTable(), nil
|
||||
}
|
||||
|
||||
func (p *PlanOpNullTable) Children() []types.PlanOperator {
|
||||
return []types.PlanOperator{}
|
||||
}
|
||||
|
||||
func (p *PlanOpNullTable) Plan() map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
result["_op"] = fmt.Sprintf("%T", p)
|
||||
ps := make([]string, 0)
|
||||
for _, e := range p.Schema() {
|
||||
ps = append(ps, fmt.Sprintf("'%s', '%s', '%s'", e.Name, e.Table, e.Type.TypeName()))
|
||||
}
|
||||
result["_schema"] = ps
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *PlanOpNullTable) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *PlanOpNullTable) AddWarning(warning string) {
|
||||
p.warnings = append(p.warnings, warning)
|
||||
}
|
||||
|
||||
func (p *PlanOpNullTable) Warnings() []string {
|
||||
return p.warnings
|
||||
}
|
||||
|
||||
type nullTableIterator struct {
|
||||
rowConsumed bool
|
||||
}
|
||||
|
||||
func (i *nullTableIterator) Next(ctx context.Context) (types.Row, error) {
|
||||
if !i.rowConsumed {
|
||||
i.rowConsumed = true
|
||||
return make([]interface{}, 0), nil
|
||||
}
|
||||
return nil, types.ErrNoMoreRows
|
||||
}
|
||||
274
sql3/planner/oporderby.go
Normal file
274
sql3/planner/oporderby.go
Normal file
|
|
@ -0,0 +1,274 @@
|
|||
// Copyright 2022 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/molecula/featurebase/v3/sql3"
|
||||
"github.com/molecula/featurebase/v3/sql3/parser"
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
)
|
||||
|
||||
// orderByOrder is the direction of the order by (ascending or descending).
|
||||
type orderByOrder int
|
||||
|
||||
const (
|
||||
orderByAsc orderByOrder = 1
|
||||
orderByDesc orderByOrder = 2
|
||||
)
|
||||
|
||||
// nullOrdering specifies how to handle null values during order by.
|
||||
type nullOrdering byte
|
||||
|
||||
const (
|
||||
nullOrderingFirst nullOrdering = iota
|
||||
nullOrderingLast nullOrdering = 2
|
||||
)
|
||||
|
||||
// OrderByExpression is the expression on which an order by can be computed
|
||||
type OrderByExpression struct {
|
||||
Expr types.PlanExpression
|
||||
Order orderByOrder
|
||||
NullOrdering nullOrdering
|
||||
}
|
||||
|
||||
// PlanOpOrderBy plan operator handles ORDER BY
|
||||
type PlanOpOrderBy struct {
|
||||
ChildOp types.PlanOperator
|
||||
orderByFields []*OrderByExpression
|
||||
|
||||
warnings []string
|
||||
}
|
||||
|
||||
func NewPlanOpOrderBy(orderByFields []*OrderByExpression, child types.PlanOperator) *PlanOpOrderBy {
|
||||
return &PlanOpOrderBy{
|
||||
ChildOp: child,
|
||||
orderByFields: orderByFields,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *PlanOpOrderBy) Schema() types.Schema {
|
||||
return n.ChildOp.Schema()
|
||||
}
|
||||
|
||||
func (n *PlanOpOrderBy) Iterator(ctx context.Context, row types.Row) (types.RowIterator, error) {
|
||||
iter, err := n.ChildOp.Iterator(ctx, row)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newOrderByIter(ctx, n, iter), nil
|
||||
}
|
||||
|
||||
func (n *PlanOpOrderBy) Children() []types.PlanOperator {
|
||||
return []types.PlanOperator{
|
||||
n.ChildOp,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *PlanOpOrderBy) WithChildren(children ...types.PlanOperator) (types.PlanOperator, error) {
|
||||
if len(children) != 1 {
|
||||
return nil, sql3.NewErrInternalf("unexpected number of children '%d'", len(children))
|
||||
}
|
||||
return NewPlanOpOrderBy(n.orderByFields, children[0]), nil
|
||||
}
|
||||
|
||||
func (n *PlanOpOrderBy) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (n *PlanOpOrderBy) Plan() map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
result["_op"] = fmt.Sprintf("%T", n)
|
||||
sc := make([]string, 0)
|
||||
for _, e := range n.Schema() {
|
||||
sc = append(sc, fmt.Sprintf("'%s', '%s', '%s'", e.Name, e.Table, e.Type.TypeName()))
|
||||
}
|
||||
result["_schema"] = sc
|
||||
|
||||
result["child"] = n.ChildOp.Plan()
|
||||
ps := make([]interface{}, 0)
|
||||
for _, e := range n.orderByFields {
|
||||
ps = append(ps, &map[string]interface{}{
|
||||
"expr": e.Expr.Plan(),
|
||||
"order": e.Order,
|
||||
"nullOrdering": e.NullOrdering,
|
||||
})
|
||||
}
|
||||
result["orderByFields"] = ps
|
||||
return result
|
||||
}
|
||||
|
||||
func (n *PlanOpOrderBy) AddWarning(warning string) {
|
||||
n.warnings = append(n.warnings, warning)
|
||||
}
|
||||
|
||||
func (n *PlanOpOrderBy) Warnings() []string {
|
||||
var w []string
|
||||
w = append(w, n.warnings...)
|
||||
w = append(w, n.ChildOp.Warnings()...)
|
||||
return w
|
||||
}
|
||||
|
||||
type orderByIter struct {
|
||||
s *PlanOpOrderBy
|
||||
childIter types.RowIterator
|
||||
sortedRows []types.Row
|
||||
}
|
||||
|
||||
var _ types.RowIterator = (*orderByIter)(nil)
|
||||
|
||||
func newOrderByIter(ctx context.Context, s *PlanOpOrderBy, child types.RowIterator) *orderByIter {
|
||||
return &orderByIter{
|
||||
s: s,
|
||||
childIter: child,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *orderByIter) Next(ctx context.Context) (types.Row, error) {
|
||||
if i.sortedRows == nil {
|
||||
err := i.computeOrderByRows(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(i.sortedRows) > 0 {
|
||||
row := i.sortedRows[0]
|
||||
// Move to next result element.
|
||||
i.sortedRows = i.sortedRows[1:]
|
||||
return row, nil
|
||||
}
|
||||
return nil, types.ErrNoMoreRows
|
||||
}
|
||||
|
||||
func (i *orderByIter) computeOrderByRows(ctx context.Context) error {
|
||||
cache := newInMemoryRowCache()
|
||||
|
||||
for {
|
||||
row, err := i.childIter.Next(ctx)
|
||||
|
||||
if err == types.ErrNoMoreRows {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := cache.Add(row); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
rows := cache.AllRows()
|
||||
sorter := &OrderBySorter{
|
||||
SortFields: i.s.orderByFields,
|
||||
Rows: rows,
|
||||
LastError: nil,
|
||||
Ctx: ctx,
|
||||
}
|
||||
sort.Stable(sorter)
|
||||
if sorter.LastError != nil {
|
||||
return sorter.LastError
|
||||
}
|
||||
i.sortedRows = rows
|
||||
return nil
|
||||
}
|
||||
|
||||
type OrderBySorter struct {
|
||||
SortFields []*OrderByExpression
|
||||
Rows []types.Row
|
||||
LastError error
|
||||
Ctx context.Context
|
||||
}
|
||||
|
||||
func (s *OrderBySorter) Len() int {
|
||||
return len(s.Rows)
|
||||
}
|
||||
|
||||
func (s *OrderBySorter) Swap(i, j int) {
|
||||
s.Rows[i], s.Rows[j] = s.Rows[j], s.Rows[i]
|
||||
}
|
||||
|
||||
func (s *OrderBySorter) Less(i, j int) bool {
|
||||
if s.LastError != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
//TODO(pok) handle multi column sort
|
||||
|
||||
a := s.Rows[i]
|
||||
b := s.Rows[j]
|
||||
for _, sf := range s.SortFields {
|
||||
av, err := sf.Expr.Evaluate(a)
|
||||
if err != nil {
|
||||
s.LastError = sql3.NewErrInternalf("unable to sort '%s'", err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
bv, err := sf.Expr.Evaluate(b)
|
||||
if err != nil {
|
||||
s.LastError = sql3.NewErrInternalf("unable to sort '%s'", err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
if sf.Order == orderByDesc {
|
||||
av, bv = bv, av
|
||||
}
|
||||
|
||||
if av == nil && bv == nil {
|
||||
continue
|
||||
} else if av == nil {
|
||||
return sf.NullOrdering == nullOrderingFirst
|
||||
} else if bv == nil {
|
||||
return sf.NullOrdering != nullOrderingFirst
|
||||
}
|
||||
|
||||
switch sf.Expr.Type().(type) {
|
||||
case *parser.DataTypeInt, *parser.DataTypeID:
|
||||
avInt, aok := av.(int64)
|
||||
bvInt, bok := bv.(int64)
|
||||
if !(aok && bok) {
|
||||
s.LastError = sql3.NewErrInternalf("unexpected type conversion result")
|
||||
return false
|
||||
}
|
||||
if avInt > bvInt {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
||||
case *parser.DataTypeBool:
|
||||
avBool, aok := av.(bool)
|
||||
bvBool, bok := bv.(bool)
|
||||
if !(aok && bok) {
|
||||
s.LastError = sql3.NewErrInternalf("unexpected type conversion result")
|
||||
return false
|
||||
}
|
||||
if avBool == bvBool {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
||||
case *parser.DataTypeTimestamp:
|
||||
avTime, aok := av.(time.Time)
|
||||
bvTime, bok := bv.(time.Time)
|
||||
if !(aok && bok) {
|
||||
s.LastError = sql3.NewErrInternalf("unexpected type conversion result")
|
||||
return false
|
||||
}
|
||||
if avTime.After(bvTime) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
||||
default:
|
||||
s.LastError = sql3.NewErrInternalf("unhandled data type '%T'", sf.Expr.Type())
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
263
sql3/planner/oppqlaggregate.go
Normal file
263
sql3/planner/oppqlaggregate.go
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
pilosa "github.com/molecula/featurebase/v3"
|
||||
"github.com/molecula/featurebase/v3/pql"
|
||||
"github.com/molecula/featurebase/v3/sql3"
|
||||
"github.com/molecula/featurebase/v3/sql3/parser"
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
)
|
||||
|
||||
// PlanOpPQLAggregate plan operator handles a single pql aggregate
|
||||
type PlanOpPQLAggregate struct {
|
||||
planner *ExecutionPlanner
|
||||
tableName string
|
||||
filter types.PlanExpression
|
||||
aggregate types.Aggregable
|
||||
|
||||
warnings []string
|
||||
}
|
||||
|
||||
func NewPlanOpPQLAggregate(p *ExecutionPlanner, tableName string, aggregate types.Aggregable, filter types.PlanExpression) *PlanOpPQLAggregate {
|
||||
return &PlanOpPQLAggregate{
|
||||
planner: p,
|
||||
tableName: tableName,
|
||||
filter: filter,
|
||||
aggregate: aggregate,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLAggregate) Plan() map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
result["_op"] = fmt.Sprintf("%T", p)
|
||||
ps := make([]string, 0)
|
||||
for _, e := range p.Schema() {
|
||||
ps = append(ps, fmt.Sprintf("'%s', '%s', '%s'", e.Name, e.Table, e.Type.TypeName()))
|
||||
}
|
||||
result["_schema"] = ps
|
||||
result["tableName"] = p.tableName
|
||||
if p.filter != nil {
|
||||
result["filter"] = p.filter.Plan()
|
||||
}
|
||||
result["aggregate"] = p.aggregate.AggExpression().Plan()
|
||||
return result
|
||||
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLAggregate) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLAggregate) AddWarning(warning string) {
|
||||
p.warnings = append(p.warnings, warning)
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLAggregate) Warnings() []string {
|
||||
return p.warnings
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLAggregate) Schema() types.Schema {
|
||||
result := make(types.Schema, 1)
|
||||
s := &types.PlannerColumn{
|
||||
Name: "",
|
||||
Table: "",
|
||||
Type: p.aggregate.AggExpression().Type(),
|
||||
}
|
||||
result[0] = s
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLAggregate) Children() []types.PlanOperator {
|
||||
return []types.PlanOperator{}
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLAggregate) Iterator(ctx context.Context, row types.Row) (types.RowIterator, error) {
|
||||
return &pqlAggregateRowIter{
|
||||
planner: p.planner,
|
||||
tableName: p.tableName,
|
||||
filter: p.filter,
|
||||
aggregate: p.aggregate,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLAggregate) WithChildren(children ...types.PlanOperator) (types.PlanOperator, error) {
|
||||
return NewPlanOpPQLAggregate(p.planner, p.tableName, p.aggregate, p.filter), nil
|
||||
}
|
||||
|
||||
type pqlAggregateRowIter struct {
|
||||
planner *ExecutionPlanner
|
||||
tableName string
|
||||
filter types.PlanExpression
|
||||
aggregate types.Aggregable
|
||||
|
||||
resultValue interface{}
|
||||
}
|
||||
|
||||
var _ types.RowIterator = (*pqlAggregateRowIter)(nil)
|
||||
|
||||
func (i *pqlAggregateRowIter) Next(ctx context.Context) (types.Row, error) {
|
||||
if i.resultValue == nil {
|
||||
var call *pql.Call
|
||||
var cond *pql.Call
|
||||
var err error
|
||||
|
||||
err = i.planner.checkAccess(ctx, i.tableName, accessTypeReadData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cond, err = i.planner.generatePQLCallFromExpr(ctx, i.filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
expr, ok := i.aggregate.AggExpression().(*qualifiedRefPlanExpression)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unexpected aggregate expression type '%T'", i.aggregate.AggExpression())
|
||||
}
|
||||
|
||||
switch i.aggregate.AggType() {
|
||||
case types.AGGREGATE_COUNT_DISTINCT:
|
||||
//make a distinct call
|
||||
distinctCond := &pql.Call{
|
||||
Name: "Distinct",
|
||||
Args: map[string]interface{}{"field": expr.columnName},
|
||||
Type: pql.PrecallGlobal,
|
||||
}
|
||||
//add the cond to the distinct
|
||||
if cond != nil {
|
||||
distinctCond.Children = []*pql.Call{cond}
|
||||
}
|
||||
cond = distinctCond
|
||||
|
||||
call = &pql.Call{Name: "Count", Children: []*pql.Call{cond}}
|
||||
|
||||
case types.AGGREGATE_COUNT:
|
||||
if cond == nil {
|
||||
cond = &pql.Call{Name: "All"}
|
||||
}
|
||||
|
||||
call = &pql.Call{Name: "Count", Children: []*pql.Call{cond}}
|
||||
|
||||
case types.AGGREGATE_AVG:
|
||||
if cond == nil {
|
||||
cond = &pql.Call{Name: "All"}
|
||||
}
|
||||
|
||||
call = &pql.Call{
|
||||
Name: "Sum",
|
||||
Args: map[string]interface{}{"field": expr.columnName},
|
||||
Children: []*pql.Call{cond},
|
||||
}
|
||||
|
||||
case types.AGGREGATE_SUM:
|
||||
if cond == nil {
|
||||
cond = &pql.Call{Name: "All"}
|
||||
}
|
||||
call = &pql.Call{
|
||||
Name: "Sum",
|
||||
Args: map[string]interface{}{"field": expr.columnName},
|
||||
Children: []*pql.Call{cond},
|
||||
}
|
||||
|
||||
case types.AGGREGATE_MAX:
|
||||
if cond == nil {
|
||||
cond = &pql.Call{Name: "All"}
|
||||
}
|
||||
|
||||
call = &pql.Call{
|
||||
Name: "Max",
|
||||
Args: map[string]interface{}{"field": expr.columnName},
|
||||
Children: []*pql.Call{cond},
|
||||
}
|
||||
|
||||
case types.AGGREGATE_MIN:
|
||||
if cond == nil {
|
||||
cond = &pql.Call{Name: "All"}
|
||||
}
|
||||
|
||||
call = &pql.Call{
|
||||
Name: "Min",
|
||||
Args: map[string]interface{}{"field": expr.columnName},
|
||||
Children: []*pql.Call{cond},
|
||||
}
|
||||
|
||||
case types.AGGREGATE_PERCENTILE:
|
||||
|
||||
additionalExprs := i.aggregate.AggAdditionalExpr()
|
||||
if len(additionalExprs) != 1 {
|
||||
return nil, sql3.NewErrInternalf("unexpected AggAdditionalExpr() length (%d)", len(additionalExprs))
|
||||
}
|
||||
nthExpr := additionalExprs[0]
|
||||
|
||||
nthValue, err := nthExpr.Evaluate(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
coercedNthValue, err := coerceValue(nthExpr.Type(), parser.NewDataTypeDecimal(4), nthValue, parser.Pos{Line: 0, Column: 0})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nth, ok := coercedNthValue.(pql.Decimal)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unexpected aggregate nth arg type '%T'", coercedNthValue)
|
||||
}
|
||||
|
||||
if cond == nil {
|
||||
cond = &pql.Call{Name: "All"}
|
||||
}
|
||||
|
||||
call = &pql.Call{
|
||||
Name: "Percentile",
|
||||
Args: map[string]interface{}{
|
||||
"field": expr.columnName,
|
||||
"nth": nth,
|
||||
},
|
||||
Children: []*pql.Call{cond},
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, sql3.NewErrInternalf("unhandled aggregate type '%d'", i.aggregate.AggType())
|
||||
}
|
||||
|
||||
queryResponse, err := i.planner.executor.Execute(ctx, i.tableName, &pql.Query{Calls: []*pql.Call{call}}, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch actualResult := queryResponse.Results[0].(type) {
|
||||
case uint64:
|
||||
i.resultValue = int64(actualResult)
|
||||
|
||||
case pilosa.ValCount:
|
||||
if actualResult.DecimalVal == nil {
|
||||
if i.aggregate.AggType() == types.AGGREGATE_AVG {
|
||||
average := float64(actualResult.Val) / float64(actualResult.Count)
|
||||
i.resultValue = pql.NewDecimal(int64(average*10000), 4)
|
||||
} else {
|
||||
i.resultValue = int64(actualResult.Val)
|
||||
}
|
||||
} else {
|
||||
if i.aggregate.AggType() == types.AGGREGATE_AVG {
|
||||
average := actualResult.DecimalVal.Float64() / float64(actualResult.Count)
|
||||
i.resultValue = pql.NewDecimal(int64(average*10000), 4)
|
||||
} else {
|
||||
i.resultValue = *actualResult.DecimalVal
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, sql3.NewErrInternalf("unexpected result type '%T'", queryResponse.Results[0])
|
||||
}
|
||||
|
||||
row := make([]interface{}, 1)
|
||||
row[0] = i.resultValue
|
||||
return row, nil
|
||||
|
||||
}
|
||||
return nil, types.ErrNoMoreRows
|
||||
}
|
||||
267
sql3/planner/oppqlgroupby.go
Normal file
267
sql3/planner/oppqlgroupby.go
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
// Copyright 2022 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
pilosa "github.com/molecula/featurebase/v3"
|
||||
"github.com/molecula/featurebase/v3/pql"
|
||||
"github.com/molecula/featurebase/v3/sql3"
|
||||
"github.com/molecula/featurebase/v3/sql3/parser"
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
)
|
||||
|
||||
// PlanOpPQLGroupBy plan operator handles a PQL group by with a single aggregate
|
||||
type PlanOpPQLGroupBy struct {
|
||||
planner *ExecutionPlanner
|
||||
tableName string
|
||||
filter types.PlanExpression
|
||||
aggregate types.Aggregable
|
||||
groupByExprs []types.PlanExpression
|
||||
|
||||
warnings []string
|
||||
}
|
||||
|
||||
func NewPlanOpPQLGroupBy(p *ExecutionPlanner, tableName string, groupByExprs []types.PlanExpression, filter types.PlanExpression, aggregate types.Aggregable) *PlanOpPQLGroupBy {
|
||||
return &PlanOpPQLGroupBy{
|
||||
planner: p,
|
||||
tableName: tableName,
|
||||
groupByExprs: groupByExprs,
|
||||
filter: filter,
|
||||
aggregate: aggregate,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLGroupBy) Plan() map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
result["_op"] = fmt.Sprintf("%T", p)
|
||||
sc := make([]string, 0)
|
||||
for _, e := range p.Schema() {
|
||||
sc = append(sc, fmt.Sprintf("'%s', '%s', '%s'", e.Name, e.Table, e.Type.TypeName()))
|
||||
}
|
||||
result["_schema"] = sc
|
||||
result["tableName"] = p.tableName
|
||||
if p.filter != nil {
|
||||
result["filter"] = p.filter.Plan()
|
||||
|
||||
}
|
||||
result["aggregate"] = p.aggregate.AggExpression().Plan()
|
||||
ps := make([]interface{}, 0)
|
||||
for _, e := range p.groupByExprs {
|
||||
ps = append(ps, e.Plan())
|
||||
}
|
||||
result["groupByColumns"] = ps
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLGroupBy) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLGroupBy) AddWarning(warning string) {
|
||||
p.warnings = append(p.warnings, warning)
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLGroupBy) Warnings() []string {
|
||||
return p.warnings
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLGroupBy) Schema() types.Schema {
|
||||
result := make(types.Schema, len(p.groupByExprs)+1)
|
||||
for idx, expr := range p.groupByExprs {
|
||||
ref, ok := expr.(*qualifiedRefPlanExpression)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
s := &types.PlannerColumn{
|
||||
Name: ref.columnName,
|
||||
Table: ref.tableName,
|
||||
Type: expr.Type(),
|
||||
}
|
||||
result[idx] = s
|
||||
}
|
||||
s := &types.PlannerColumn{
|
||||
Name: "",
|
||||
Table: "",
|
||||
Type: p.aggregate.AggExpression().Type(),
|
||||
}
|
||||
result[len(p.groupByExprs)] = s
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLGroupBy) Children() []types.PlanOperator {
|
||||
return []types.PlanOperator{}
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLGroupBy) Iterator(ctx context.Context, row types.Row) (types.RowIterator, error) {
|
||||
return &pqlGroupByRowIter{
|
||||
planner: p.planner,
|
||||
tableName: p.tableName,
|
||||
groupByColumns: p.groupByExprs,
|
||||
aggregate: p.aggregate,
|
||||
filter: p.filter,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLGroupBy) WithChildren(children ...types.PlanOperator) (types.PlanOperator, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// pqlGroupByRowIter is an iterator for the PlanOpPQLGroupBy operator
|
||||
// it provides rows consisting of the group by columns in the order they
|
||||
// were specified and lastly the aggregate
|
||||
type pqlGroupByRowIter struct {
|
||||
planner *ExecutionPlanner
|
||||
tableName string
|
||||
groupByColumns []types.PlanExpression
|
||||
filter types.PlanExpression
|
||||
aggregate types.Aggregable
|
||||
|
||||
result []pilosa.GroupCount
|
||||
}
|
||||
|
||||
var _ types.RowIterator = (*pqlGroupByRowIter)(nil)
|
||||
|
||||
func (i *pqlGroupByRowIter) Next(ctx context.Context) (types.Row, error) {
|
||||
if i.result == nil {
|
||||
|
||||
var cond *pql.Call
|
||||
var err error
|
||||
|
||||
err = i.planner.checkAccess(ctx, i.tableName, accessTypeReadData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cond, err = i.planner.generatePQLCallFromExpr(ctx, i.filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
call := &pql.Call{
|
||||
Name: "GroupBy",
|
||||
Args: map[string]interface{}{},
|
||||
}
|
||||
for _, c := range i.groupByColumns {
|
||||
ref, ok := c.(types.SchemaIdentifiable)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unexpected expression type in group by list '%T'", c)
|
||||
}
|
||||
//don't ask for the _id field
|
||||
if ref.Name() != "_id" {
|
||||
call.Children = append(call.Children,
|
||||
&pql.Call{
|
||||
Name: "Rows",
|
||||
Args: map[string]interface{}{"_field": ref.Name()},
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Apply filter & aggregate, if set.
|
||||
aggExpr, ok := i.aggregate.AggExpression().(*qualifiedRefPlanExpression)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unexpected aggregate expression type '%T'", i.aggregate.AggExpression())
|
||||
}
|
||||
|
||||
switch i.aggregate.AggType() {
|
||||
case types.AGGREGATE_COUNT:
|
||||
//nop
|
||||
|
||||
case types.AGGREGATE_COUNT_DISTINCT:
|
||||
aggregate := &pql.Call{
|
||||
Name: "Count",
|
||||
Children: []*pql.Call{{
|
||||
Name: "Distinct",
|
||||
Args: map[string]interface{}{"field": aggExpr.columnName},
|
||||
}},
|
||||
}
|
||||
call.Args["aggregate"] = aggregate
|
||||
|
||||
case types.AGGREGATE_SUM, types.AGGREGATE_AVG:
|
||||
aggregate := &pql.Call{
|
||||
Name: "Sum",
|
||||
Args: map[string]interface{}{"field": aggExpr.columnName},
|
||||
}
|
||||
call.Args["aggregate"] = aggregate
|
||||
|
||||
case types.AGGREGATE_PERCENTILE:
|
||||
return nil, sql3.NewErrAggregateNotAllowedInGroupBy(0, 0, "PERCENTILE()")
|
||||
|
||||
case types.AGGREGATE_MIN:
|
||||
return nil, sql3.NewErrAggregateNotAllowedInGroupBy(0, 0, "MIN()")
|
||||
|
||||
case types.AGGREGATE_MAX:
|
||||
return nil, sql3.NewErrAggregateNotAllowedInGroupBy(0, 0, "MAX()")
|
||||
|
||||
default:
|
||||
return nil, sql3.NewErrInternalf("unexpected agg function type: %d", i.aggregate.AggType())
|
||||
}
|
||||
if cond != nil {
|
||||
call.Args["filter"] = cond
|
||||
}
|
||||
|
||||
queryResponse, err := i.planner.executor.Execute(ctx, i.tableName, &pql.Query{Calls: []*pql.Call{call}}, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tbl, ok := queryResponse.Results[0].(*pilosa.GroupCounts)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unexpected Extract() result type: %T", queryResponse.Results[0])
|
||||
}
|
||||
i.result = tbl.Groups()
|
||||
}
|
||||
|
||||
if len(i.result) > 0 {
|
||||
//row width is group by columns + aggregate
|
||||
row := make([]interface{}, len(i.groupByColumns)+1)
|
||||
|
||||
group := i.result[0]
|
||||
|
||||
//populate all the group by columns
|
||||
for idx, c := range i.groupByColumns {
|
||||
|
||||
g := group.Group[idx]
|
||||
if g.Value != nil {
|
||||
row[idx] = *g.Value
|
||||
} else if g.RowKey != "" {
|
||||
row[idx] = g.RowKey
|
||||
} else {
|
||||
switch c.Type().(type) {
|
||||
case *parser.DataTypeIDSet:
|
||||
row[idx] = []uint64{g.RowID}
|
||||
default:
|
||||
row[idx] = int64(g.RowID)
|
||||
}
|
||||
}
|
||||
}
|
||||
//now populate the aggregate value
|
||||
aggIdx := len(i.groupByColumns)
|
||||
switch i.aggregate.AggType() {
|
||||
case types.AGGREGATE_COUNT:
|
||||
row[aggIdx] = int64(group.Count)
|
||||
|
||||
case types.AGGREGATE_COUNT_DISTINCT, types.AGGREGATE_SUM:
|
||||
row[aggIdx] = int64(group.Agg)
|
||||
|
||||
case types.AGGREGATE_AVG:
|
||||
if group.DecimalAgg == nil {
|
||||
average := float64(group.Agg) / float64(group.Count)
|
||||
row[aggIdx] = pql.NewDecimal(int64(average*10000), 4)
|
||||
} else {
|
||||
average := group.DecimalAgg.Float64() / float64(group.Count)
|
||||
row[aggIdx] = pql.NewDecimal(int64(average*10000), 4)
|
||||
}
|
||||
default:
|
||||
return nil, sql3.NewErrInternalf("unhandled aggregate function type '%v'", i.aggregate.AggType())
|
||||
}
|
||||
|
||||
// Move to next result element.
|
||||
i.result = i.result[1:]
|
||||
return row, nil
|
||||
}
|
||||
return nil, types.ErrNoMoreRows
|
||||
}
|
||||
115
sql3/planner/oppqlmultiaggregate.go
Normal file
115
sql3/planner/oppqlmultiaggregate.go
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
// Copyright 2022 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
)
|
||||
|
||||
// PlanOpPQLMultiAggregate plan operator handles executing multiple 'sibling' pql aggregate queries
|
||||
type PlanOpPQLMultiAggregate struct {
|
||||
planner *ExecutionPlanner
|
||||
operators []*PlanOpPQLAggregate
|
||||
warnings []string
|
||||
}
|
||||
|
||||
func NewPlanOpPQLMultiAggregate(p *ExecutionPlanner, operators []*PlanOpPQLAggregate) *PlanOpPQLMultiAggregate {
|
||||
return &PlanOpPQLMultiAggregate{
|
||||
planner: p,
|
||||
operators: operators,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLMultiAggregate) Plan() map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
result["_op"] = fmt.Sprintf("%T", p)
|
||||
sc := make([]string, 0)
|
||||
for _, e := range p.Schema() {
|
||||
sc = append(sc, fmt.Sprintf("'%s', '%s', '%s'", e.Name, e.Table, e.Type.TypeName()))
|
||||
}
|
||||
result["_schema"] = sc
|
||||
|
||||
ps := make([]interface{}, 0)
|
||||
for _, e := range p.operators {
|
||||
ps = append(ps, e.Plan())
|
||||
}
|
||||
result["operators"] = ps
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLMultiAggregate) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLMultiAggregate) AddWarning(warning string) {
|
||||
p.warnings = append(p.warnings, warning)
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLMultiAggregate) Warnings() []string {
|
||||
return p.warnings
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLMultiAggregate) Schema() types.Schema {
|
||||
result := make(types.Schema, len(p.operators))
|
||||
for idx, aggOp := range p.operators {
|
||||
s := &types.PlannerColumn{
|
||||
Name: "",
|
||||
Table: "",
|
||||
Type: aggOp.aggregate.AggExpression().Type(),
|
||||
}
|
||||
result[idx] = s
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLMultiAggregate) Children() []types.PlanOperator {
|
||||
return []types.PlanOperator{}
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLMultiAggregate) Iterator(ctx context.Context, row types.Row) (types.RowIterator, error) {
|
||||
iterators := make([]types.RowIterator, 0)
|
||||
|
||||
for _, op := range p.operators {
|
||||
iter, err := op.Iterator(ctx, row)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
iterators = append(iterators, iter)
|
||||
}
|
||||
|
||||
return &pqlMultiAggregateRowIter{
|
||||
planner: p.planner,
|
||||
iterators: iterators,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLMultiAggregate) WithChildren(children ...types.PlanOperator) (types.PlanOperator, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type pqlMultiAggregateRowIter struct {
|
||||
planner *ExecutionPlanner
|
||||
iterators []types.RowIterator
|
||||
doneLatch bool
|
||||
}
|
||||
|
||||
var _ types.RowIterator = (*pqlMultiAggregateRowIter)(nil)
|
||||
|
||||
func (i *pqlMultiAggregateRowIter) Next(ctx context.Context) (types.Row, error) {
|
||||
if !i.doneLatch {
|
||||
var row = make(types.Row, len(i.iterators))
|
||||
for idx, iter := range i.iterators {
|
||||
irow, err := iter.Next(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
row[idx] = irow[0]
|
||||
}
|
||||
i.doneLatch = true
|
||||
return row, nil
|
||||
}
|
||||
return nil, types.ErrNoMoreRows
|
||||
}
|
||||
229
sql3/planner/oppqlmultigroupby.go
Normal file
229
sql3/planner/oppqlmultigroupby.go
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
// Copyright 2022 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
)
|
||||
|
||||
// PlanOpPQLMultiGroupBy plan operator handles executing multiple 'sibling' pql group by queries
|
||||
// it will materialize the result sets from each of its operators and then merge them.
|
||||
// Its iterator will return a row consisting of all the group by columns in the order specified
|
||||
// followed by all aggregates in order
|
||||
type PlanOpPQLMultiGroupBy struct {
|
||||
planner *ExecutionPlanner
|
||||
operators []*PlanOpPQLGroupBy
|
||||
groupByExprs []types.PlanExpression
|
||||
warnings []string
|
||||
}
|
||||
|
||||
func NewPlanOpPQLMultiGroupBy(p *ExecutionPlanner, operators []*PlanOpPQLGroupBy, groupByExprs []types.PlanExpression) *PlanOpPQLMultiGroupBy {
|
||||
return &PlanOpPQLMultiGroupBy{
|
||||
planner: p,
|
||||
operators: operators,
|
||||
groupByExprs: groupByExprs,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLMultiGroupBy) Plan() map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
result["_op"] = fmt.Sprintf("%T", p)
|
||||
sc := make([]string, 0)
|
||||
for _, e := range p.Schema() {
|
||||
sc = append(sc, fmt.Sprintf("'%s', '%s', '%s'", e.Name, e.Table, e.Type.TypeName()))
|
||||
}
|
||||
result["_schema"] = sc
|
||||
|
||||
ps := make([]interface{}, 0)
|
||||
for _, e := range p.operators {
|
||||
ps = append(ps, e.Plan())
|
||||
}
|
||||
result["operators"] = ps
|
||||
ps = make([]interface{}, 0)
|
||||
for _, e := range p.groupByExprs {
|
||||
ps = append(ps, e.Plan())
|
||||
}
|
||||
result["groupByColumns"] = ps
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLMultiGroupBy) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLMultiGroupBy) AddWarning(warning string) {
|
||||
p.warnings = append(p.warnings, warning)
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLMultiGroupBy) Warnings() []string {
|
||||
return p.warnings
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLMultiGroupBy) Schema() types.Schema {
|
||||
result := make(types.Schema, len(p.groupByExprs)+len(p.operators))
|
||||
for idx, expr := range p.groupByExprs {
|
||||
ref, ok := expr.(*qualifiedRefPlanExpression)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
s := &types.PlannerColumn{
|
||||
Name: ref.columnName,
|
||||
Table: ref.tableName,
|
||||
Type: expr.Type(),
|
||||
}
|
||||
result[idx] = s
|
||||
}
|
||||
offset := len(p.groupByExprs)
|
||||
for idx, aggOp := range p.operators {
|
||||
s := &types.PlannerColumn{
|
||||
Name: "",
|
||||
Table: "",
|
||||
Type: aggOp.aggregate.AggExpression().Type(),
|
||||
}
|
||||
result[idx+offset] = s
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLMultiGroupBy) Children() []types.PlanOperator {
|
||||
return []types.PlanOperator{}
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLMultiGroupBy) Iterator(ctx context.Context, row types.Row) (types.RowIterator, error) {
|
||||
iterators := make([]types.RowIterator, 0)
|
||||
|
||||
for _, op := range p.operators {
|
||||
iter, err := op.Iterator(ctx, row)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
iterators = append(iterators, iter)
|
||||
}
|
||||
|
||||
return &pqlMultiGroupByRowIter{
|
||||
planner: p.planner,
|
||||
groupByColumns: p.groupByExprs,
|
||||
iterators: iterators,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLMultiGroupBy) WithChildren(children ...types.PlanOperator) (types.PlanOperator, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// pqlMultiGroupByRowIter is an iterator for the PlanOpPQLMultiGroupBy operator
|
||||
// it provides rows consisting of the group by columns in the order they
|
||||
// were specified and lastly the aggregates in the order they were specified
|
||||
type pqlMultiGroupByRowIter struct {
|
||||
planner *ExecutionPlanner
|
||||
groupByColumns []types.PlanExpression
|
||||
iterators []types.RowIterator
|
||||
groupCache KeyedRowCache
|
||||
|
||||
groupKeys []string
|
||||
}
|
||||
|
||||
var _ types.RowIterator = (*pqlMultiGroupByRowIter)(nil)
|
||||
|
||||
func (i *pqlMultiGroupByRowIter) Next(ctx context.Context) (types.Row, error) {
|
||||
if i.groupCache == nil {
|
||||
//consume all the rows from the child iterators
|
||||
i.groupCache = newinMemoryKeyedRowCache()
|
||||
if err := i.computeMultiGroupBy(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(i.groupKeys) > 0 {
|
||||
key := i.groupKeys[0]
|
||||
|
||||
row, err := i.groupCache.Get(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Move to next result element.
|
||||
i.groupKeys = i.groupKeys[1:]
|
||||
return row, nil
|
||||
}
|
||||
return nil, types.ErrNoMoreRows
|
||||
}
|
||||
|
||||
func (i *pqlMultiGroupByRowIter) computeMultiGroupBy(ctx context.Context) error {
|
||||
//for each operator, consume all rows
|
||||
for iteratorIdx, iter := range i.iterators {
|
||||
|
||||
//get the first row
|
||||
irow, err := iter.Next(ctx)
|
||||
if err != nil {
|
||||
if err == types.ErrNoMoreRows {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
//build a key for the group by columns for this row
|
||||
key, err := groupingKey(ctx, i.groupByColumns, irow)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get the group from the cache
|
||||
cachedRow, err := i.groupCache.Get(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aggIndex := iteratorIdx + len(i.groupByColumns)
|
||||
if cachedRow != nil {
|
||||
// if the group exists then update the row
|
||||
// NB: the aggregate for this iterator is at the end of irow
|
||||
cachedRow[aggIndex] = irow[len(irow)-1]
|
||||
} else {
|
||||
// if the group does not exist, add a row; set length to be number of group by columns + number of aggregates
|
||||
cachedRow := make([]interface{}, len(i.groupByColumns)+len(i.iterators))
|
||||
// copy the group by values into the new row
|
||||
for gidx := range i.groupByColumns {
|
||||
cachedRow[gidx] = irow[gidx]
|
||||
}
|
||||
// write the aggregate value in
|
||||
cachedRow[aggIndex] = irow[len(irow)-1]
|
||||
|
||||
// write the row to the cache
|
||||
err = i.groupCache.Put(key, cachedRow)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//record the new key
|
||||
i.groupKeys = append(i.groupKeys, key)
|
||||
}
|
||||
|
||||
irow, err = iter.Next(ctx)
|
||||
if err != nil {
|
||||
if err == types.ErrNoMoreRows {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func groupingKey(ctx context.Context, exprs []types.PlanExpression, row types.Row) (string, error) {
|
||||
key := ""
|
||||
for _, expr := range exprs {
|
||||
v, err := expr.Evaluate(row)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
key += fmt.Sprintf(":%v", v)
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
144
sql3/planner/opprojection.go
Normal file
144
sql3/planner/opprojection.go
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
// Copyright 2022 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/molecula/featurebase/v3/sql3"
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
)
|
||||
|
||||
// PlanOpProjection handles row projection and expression evaluation
|
||||
type PlanOpProjection struct {
|
||||
ChildOp types.PlanOperator
|
||||
Projections []types.PlanExpression
|
||||
warnings []string
|
||||
}
|
||||
|
||||
func NewPlanOpProjection(expressions []types.PlanExpression, child types.PlanOperator) *PlanOpProjection {
|
||||
return &PlanOpProjection{
|
||||
ChildOp: child,
|
||||
Projections: expressions,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PlanOpProjection) Schema() types.Schema {
|
||||
var s = make(types.Schema, len(p.Projections))
|
||||
for i, e := range p.Projections {
|
||||
s[i] = ExpressionToColumn(e)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (p *PlanOpProjection) Iterator(ctx context.Context, row types.Row) (types.RowIterator, error) {
|
||||
i, err := p.ChildOp.Iterator(ctx, row)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &iter{
|
||||
p: p,
|
||||
childIter: i,
|
||||
row: row,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *PlanOpProjection) Children() []types.PlanOperator {
|
||||
return []types.PlanOperator{
|
||||
p.ChildOp,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PlanOpProjection) WithChildren(children ...types.PlanOperator) (types.PlanOperator, error) {
|
||||
if len(children) != 1 {
|
||||
return nil, sql3.NewErrInternalf("unexpected number of children '%d'", len(children))
|
||||
}
|
||||
return NewPlanOpProjection(p.Projections, children[0]), nil
|
||||
}
|
||||
|
||||
func (p *PlanOpProjection) Plan() map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
result["_op"] = fmt.Sprintf("%T", p)
|
||||
sc := make([]string, 0)
|
||||
for _, e := range p.Schema() {
|
||||
sc = append(sc, fmt.Sprintf("'%s', '%s', '%s'", e.Name, e.Table, e.Type.TypeName()))
|
||||
}
|
||||
result["_schema"] = sc
|
||||
|
||||
result["child"] = p.ChildOp.Plan()
|
||||
|
||||
ps := make([]interface{}, 0)
|
||||
for _, e := range p.Projections {
|
||||
ps = append(ps, e.Plan())
|
||||
}
|
||||
result["projections"] = ps
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *PlanOpProjection) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *PlanOpProjection) AddWarning(warning string) {
|
||||
p.warnings = append(p.warnings, warning)
|
||||
}
|
||||
|
||||
func (p *PlanOpProjection) Warnings() []string {
|
||||
var w []string
|
||||
w = append(w, p.warnings...)
|
||||
if p.ChildOp != nil {
|
||||
w = append(w, p.ChildOp.Warnings()...)
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
func ExpressionToColumn(e types.PlanExpression) *types.PlannerColumn {
|
||||
var name string
|
||||
if n, ok := e.(types.SchemaIdentifiable); ok {
|
||||
name = n.Name()
|
||||
} else {
|
||||
//TODO(pok) - work out what this should be
|
||||
name = "" //e.String()
|
||||
}
|
||||
|
||||
var table string
|
||||
if t, ok := e.(types.SchemaObject); ok {
|
||||
table = t.ObjectName()
|
||||
}
|
||||
|
||||
return &types.PlannerColumn{
|
||||
Name: name,
|
||||
Type: e.Type(),
|
||||
Table: table,
|
||||
}
|
||||
}
|
||||
|
||||
type iter struct {
|
||||
p *PlanOpProjection
|
||||
childIter types.RowIterator
|
||||
row types.Row
|
||||
}
|
||||
|
||||
func (i *iter) Next(ctx context.Context) (types.Row, error) {
|
||||
childRow, err := i.childIter.Next(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ProjectRow(ctx, i.p.Projections, childRow)
|
||||
}
|
||||
|
||||
// ProjectRow evaluates a set of projections.
|
||||
func ProjectRow(ctx context.Context, projections []types.PlanExpression, row types.Row) (types.Row, error) {
|
||||
var fields types.Row
|
||||
for _, expr := range projections {
|
||||
f, fErr := expr.Evaluate(row)
|
||||
if fErr != nil {
|
||||
return nil, fErr
|
||||
}
|
||||
fields = append(fields, f)
|
||||
}
|
||||
return fields, nil
|
||||
}
|
||||
88
sql3/planner/opquery.go
Normal file
88
sql3/planner/opquery.go
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
package planner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/molecula/featurebase/v3/sql3"
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
)
|
||||
|
||||
// PlanOpQuery is a query - this is the root node of an execution plan
|
||||
type PlanOpQuery struct {
|
||||
ChildOp types.PlanOperator
|
||||
|
||||
// the list of aggregate terms
|
||||
aggregates []types.PlanExpression
|
||||
|
||||
// all the identifiers that are referenced
|
||||
referenceList []*qualifiedRefPlanExpression
|
||||
|
||||
sql string
|
||||
warnings []string
|
||||
}
|
||||
|
||||
var _ types.PlanOperator = (*PlanOpQuery)(nil)
|
||||
|
||||
func NewPlanOpQuery(child types.PlanOperator, sql string) *PlanOpQuery {
|
||||
return &PlanOpQuery{
|
||||
ChildOp: child,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PlanOpQuery) Schema() types.Schema {
|
||||
return p.ChildOp.Schema()
|
||||
}
|
||||
|
||||
func (p *PlanOpQuery) Child() types.PlanOperator {
|
||||
return p.ChildOp
|
||||
}
|
||||
|
||||
func (p *PlanOpQuery) Iterator(ctx context.Context, row types.Row) (types.RowIterator, error) {
|
||||
return p.Child().Iterator(ctx, row)
|
||||
}
|
||||
|
||||
func (p *PlanOpQuery) Children() []types.PlanOperator {
|
||||
return []types.PlanOperator{
|
||||
p.ChildOp,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PlanOpQuery) WithChildren(children ...types.PlanOperator) (types.PlanOperator, error) {
|
||||
if len(children) != 1 {
|
||||
return nil, sql3.NewErrInternalf("unexpected number of children '%d'", len(children))
|
||||
}
|
||||
return NewPlanOpQuery(children[0], p.sql), nil
|
||||
}
|
||||
|
||||
func (p *PlanOpQuery) Plan() map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
result["_op"] = fmt.Sprintf("%T", p)
|
||||
sc := make([]string, 0)
|
||||
for _, e := range p.Schema() {
|
||||
sc = append(sc, fmt.Sprintf("'%s', '%s', '%s'", e.Name, e.Table, e.Type.TypeName()))
|
||||
}
|
||||
result["_schema"] = sc
|
||||
|
||||
result["sql"] = p.sql
|
||||
result["warnings"] = p.warnings
|
||||
result["child"] = p.ChildOp.Plan()
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *PlanOpQuery) AddWarning(warning string) {
|
||||
p.warnings = append(p.warnings, warning)
|
||||
}
|
||||
|
||||
func (p *PlanOpQuery) Warnings() []string {
|
||||
var w []string
|
||||
w = append(w, p.warnings...)
|
||||
if p.ChildOp != nil {
|
||||
w = append(w, p.ChildOp.Warnings()...)
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
func (p *PlanOpQuery) String() string {
|
||||
return ""
|
||||
}
|
||||
71
sql3/planner/opsubquery.go
Normal file
71
sql3/planner/opsubquery.go
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
// Copyright 2022 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
)
|
||||
|
||||
// PlanOpSubquery is an operator for a subquery
|
||||
type PlanOpSubquery struct {
|
||||
ChildOp types.PlanOperator
|
||||
warnings []string
|
||||
}
|
||||
|
||||
func NewPlanOpSubquery(child types.PlanOperator) *PlanOpSubquery {
|
||||
return &PlanOpSubquery{
|
||||
ChildOp: child,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PlanOpSubquery) Schema() types.Schema {
|
||||
return p.ChildOp.Schema()
|
||||
}
|
||||
|
||||
func (p *PlanOpSubquery) Iterator(ctx context.Context, row types.Row) (types.RowIterator, error) {
|
||||
return p.ChildOp.Iterator(ctx, row)
|
||||
}
|
||||
|
||||
func (p *PlanOpSubquery) Children() []types.PlanOperator {
|
||||
return []types.PlanOperator{
|
||||
p.ChildOp,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PlanOpSubquery) WithChildren(children ...types.PlanOperator) (types.PlanOperator, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (p *PlanOpSubquery) Plan() map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
result["_op"] = fmt.Sprintf("%T", p)
|
||||
sc := make([]string, 0)
|
||||
for _, e := range p.Schema() {
|
||||
sc = append(sc, fmt.Sprintf("'%s', '%s', '%s'", e.Name, e.Table, e.Type.TypeName()))
|
||||
}
|
||||
result["_schema"] = sc
|
||||
|
||||
result["child"] = p.ChildOp.Plan()
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *PlanOpSubquery) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *PlanOpSubquery) AddWarning(warning string) {
|
||||
p.warnings = append(p.warnings, warning)
|
||||
}
|
||||
|
||||
func (p *PlanOpSubquery) Warnings() []string {
|
||||
var w []string
|
||||
w = append(w, p.warnings...)
|
||||
if p.ChildOp != nil {
|
||||
w = append(w, p.ChildOp.Warnings()...)
|
||||
}
|
||||
return w
|
||||
|
||||
}
|
||||
271
sql3/planner/optablescan.go
Normal file
271
sql3/planner/optablescan.go
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
// Copyright 2022 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
pilosa "github.com/molecula/featurebase/v3"
|
||||
"github.com/molecula/featurebase/v3/pql"
|
||||
"github.com/molecula/featurebase/v3/sql3"
|
||||
"github.com/molecula/featurebase/v3/sql3/parser"
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// PlanOpPQLTableScan plan operator handles a PQL table scan
|
||||
type PlanOpPQLTableScan struct {
|
||||
planner *ExecutionPlanner
|
||||
tableName string
|
||||
columns []types.PlanExpression
|
||||
filter types.PlanExpression
|
||||
topExpr types.PlanExpression
|
||||
warnings []string
|
||||
}
|
||||
|
||||
func NewPlanOpPQLTableScan(p *ExecutionPlanner, tableName string, columns []types.PlanExpression, filter types.PlanExpression) *PlanOpPQLTableScan {
|
||||
return &PlanOpPQLTableScan{
|
||||
planner: p,
|
||||
tableName: tableName,
|
||||
columns: columns,
|
||||
filter: filter,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLTableScan) Plan() map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
result["_op"] = fmt.Sprintf("%T", p)
|
||||
sc := make([]string, 0)
|
||||
for _, e := range p.Schema() {
|
||||
sc = append(sc, fmt.Sprintf("'%s', '%s', '%s'", e.Name, e.Table, e.Type.TypeName()))
|
||||
}
|
||||
result["_schema"] = sc
|
||||
|
||||
result["tableName"] = p.tableName
|
||||
|
||||
if p.topExpr != nil {
|
||||
result["topExpr"] = p.topExpr.Plan()
|
||||
}
|
||||
if p.filter != nil {
|
||||
result["filter"] = p.filter.Plan()
|
||||
}
|
||||
|
||||
ps := make([]interface{}, 0)
|
||||
for _, c := range p.columns {
|
||||
ps = append(ps, c.Plan())
|
||||
}
|
||||
result["columns"] = ps
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLTableScan) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLTableScan) AddWarning(warning string) {
|
||||
p.warnings = append(p.warnings, warning)
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLTableScan) Warnings() []string {
|
||||
return p.warnings
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLTableScan) Schema() types.Schema {
|
||||
result := make(types.Schema, 0)
|
||||
for _, col := range p.columns {
|
||||
si, ok := col.(types.SchemaIdentifiable)
|
||||
if ok {
|
||||
result = append(result, &types.PlannerColumn{
|
||||
Name: si.Name(),
|
||||
Table: p.tableName,
|
||||
Type: col.Type(),
|
||||
})
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLTableScan) Children() []types.PlanOperator {
|
||||
return []types.PlanOperator{}
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLTableScan) Iterator(ctx context.Context, row types.Row) (types.RowIterator, error) {
|
||||
return &tableScanRowIter{
|
||||
planner: p.planner,
|
||||
tableName: p.tableName,
|
||||
columns: p.columns,
|
||||
predicate: p.filter,
|
||||
topExpr: p.topExpr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *PlanOpPQLTableScan) WithChildren(children ...types.PlanOperator) (types.PlanOperator, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// TODO(pok) remove the name mapping here and do it by ordinal position
|
||||
|
||||
type tableScanRowIter struct {
|
||||
planner *ExecutionPlanner
|
||||
tableName string
|
||||
columns []types.PlanExpression
|
||||
predicate types.PlanExpression
|
||||
topExpr types.PlanExpression
|
||||
|
||||
result []pilosa.ExtractedTableColumn
|
||||
rowWidth int
|
||||
sourceColumnMap map[string]int
|
||||
targetColumnMap map[string]int
|
||||
}
|
||||
|
||||
var _ types.RowIterator = (*tableScanRowIter)(nil)
|
||||
|
||||
func (i *tableScanRowIter) Next(ctx context.Context) (types.Row, error) {
|
||||
if i.result == nil {
|
||||
err := i.planner.checkAccess(ctx, i.tableName, accessTypeReadData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//go get the schema def and map names to indexes in the resultant row
|
||||
table, err := i.planner.schemaAPI.IndexInfo(context.Background(), i.tableName)
|
||||
if err != nil {
|
||||
if errors.Is(err, pilosa.ErrIndexNotFound) {
|
||||
return nil, sql3.NewErrInternalf("table not found '%s'", i.tableName)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
i.rowWidth = len(table.Fields)
|
||||
|
||||
i.targetColumnMap = make(map[string]int)
|
||||
for idx, fld := range table.Fields {
|
||||
i.targetColumnMap[fld.Name] = idx
|
||||
}
|
||||
|
||||
var cond *pql.Call
|
||||
|
||||
cond, err = i.planner.generatePQLCallFromExpr(ctx, i.predicate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cond == nil {
|
||||
cond = &pql.Call{Name: "All"}
|
||||
}
|
||||
|
||||
if i.topExpr != nil {
|
||||
_, ok := i.topExpr.(*intLiteralPlanExpression)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unexpected top expression type: %T", i.topExpr)
|
||||
}
|
||||
pqlValue, err := planExprToValue(i.topExpr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cond = &pql.Call{
|
||||
Name: "Limit",
|
||||
Children: []*pql.Call{cond},
|
||||
Args: map[string]interface{}{"limit": pqlValue},
|
||||
Type: pql.PrecallGlobal,
|
||||
}
|
||||
}
|
||||
|
||||
call := &pql.Call{Name: "Extract", Children: []*pql.Call{cond}}
|
||||
for _, c := range i.columns {
|
||||
col, ok := c.(types.SchemaIdentifiable)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unexpected column type '%T'", c)
|
||||
}
|
||||
|
||||
// Skip the _id field.
|
||||
if col.Name() == "_id" {
|
||||
continue
|
||||
}
|
||||
call.Children = append(call.Children,
|
||||
&pql.Call{
|
||||
Name: "Rows",
|
||||
Args: map[string]interface{}{"field": col.Name()},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
queryResponse, err := i.planner.executor.Execute(ctx, i.tableName, &pql.Query{Calls: []*pql.Call{call}}, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tbl, ok := queryResponse.Results[0].(pilosa.ExtractedTable)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unexpected Extract() result type: %T", queryResponse.Results[0])
|
||||
}
|
||||
i.result = tbl.Columns
|
||||
i.sourceColumnMap = make(map[string]int)
|
||||
for idx, fld := range tbl.Fields {
|
||||
i.sourceColumnMap[fld.Name] = idx
|
||||
}
|
||||
}
|
||||
|
||||
if len(i.result) > 0 {
|
||||
row := make([]interface{}, i.rowWidth)
|
||||
|
||||
for _, c := range i.columns {
|
||||
result := i.result[0]
|
||||
|
||||
col, ok := c.(types.SchemaIdentifiable)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unexpected column type '%T'", c)
|
||||
}
|
||||
|
||||
targetColIdx, ok := i.targetColumnMap[col.Name()]
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("target index not found for column named %s", col.Name())
|
||||
}
|
||||
|
||||
if col.Name() == "_id" {
|
||||
if result.Column.Keyed {
|
||||
row[targetColIdx] = result.Column.Key
|
||||
} else {
|
||||
row[targetColIdx] = int64(result.Column.ID)
|
||||
}
|
||||
} else {
|
||||
|
||||
sourceColIdx, ok := i.sourceColumnMap[col.Name()]
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("source index not found for column named %s", col.Name())
|
||||
}
|
||||
switch c.Type().(type) {
|
||||
case *parser.DataTypeIDSet:
|
||||
//empty sets are null
|
||||
val, ok := result.Rows[sourceColIdx].([]uint64)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unexpected type for column value '%T'", result.Rows[sourceColIdx])
|
||||
}
|
||||
if len(val) == 0 {
|
||||
row[targetColIdx] = nil
|
||||
} else {
|
||||
row[targetColIdx] = val
|
||||
}
|
||||
|
||||
case *parser.DataTypeStringSet:
|
||||
//empty sets are null
|
||||
val, ok := result.Rows[sourceColIdx].([]string)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unexpected type for column value '%T'", result.Rows[sourceColIdx])
|
||||
}
|
||||
if len(val) == 0 {
|
||||
row[targetColIdx] = nil
|
||||
} else {
|
||||
row[targetColIdx] = val
|
||||
}
|
||||
|
||||
default:
|
||||
row[targetColIdx] = result.Rows[sourceColIdx]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Move to next result element.
|
||||
i.result = i.result[1:]
|
||||
return row, nil
|
||||
}
|
||||
return nil, types.ErrNoMoreRows
|
||||
}
|
||||
71
sql3/planner/optop.go
Normal file
71
sql3/planner/optop.go
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
// Copyright 2022 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
)
|
||||
|
||||
// PlanOpTop implements the TOP operator
|
||||
type PlanOpTop struct {
|
||||
ChildOp types.PlanOperator
|
||||
expr types.PlanExpression
|
||||
warnings []string
|
||||
}
|
||||
|
||||
func NewPlanOpTop(expr types.PlanExpression, child types.PlanOperator) *PlanOpTop {
|
||||
return &PlanOpTop{
|
||||
ChildOp: child,
|
||||
expr: expr,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PlanOpTop) Schema() types.Schema {
|
||||
return p.ChildOp.Schema()
|
||||
}
|
||||
|
||||
func (p *PlanOpTop) Iterator(ctx context.Context, row types.Row) (types.RowIterator, error) {
|
||||
return p.ChildOp.Iterator(ctx, row)
|
||||
}
|
||||
|
||||
func (p *PlanOpTop) Children() []types.PlanOperator {
|
||||
return []types.PlanOperator{
|
||||
p.ChildOp,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PlanOpTop) WithChildren(children ...types.PlanOperator) (types.PlanOperator, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (p *PlanOpTop) Plan() map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
result["_op"] = fmt.Sprintf("%T", p)
|
||||
sc := make([]string, 0)
|
||||
for _, e := range p.Schema() {
|
||||
sc = append(sc, fmt.Sprintf("'%s', '%s', '%s'", e.Name, e.Table, e.Type.TypeName()))
|
||||
}
|
||||
result["_schema"] = sc
|
||||
|
||||
result["expr"] = p.expr
|
||||
result["child"] = p.ChildOp.Plan()
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *PlanOpTop) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *PlanOpTop) AddWarning(warning string) {
|
||||
p.warnings = append(p.warnings, warning)
|
||||
}
|
||||
|
||||
func (p *PlanOpTop) Warnings() []string {
|
||||
var w []string
|
||||
w = append(w, p.warnings...)
|
||||
w = append(w, p.ChildOp.Warnings()...)
|
||||
return w
|
||||
}
|
||||
3
sql3/planner/planner.go
Normal file
3
sql3/planner/planner.go
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
// Package planner contains everything required to build a query plan from a SQL
|
||||
// statement.
|
||||
package planner
|
||||
503
sql3/planner/planoptimizer.go
Normal file
503
sql3/planner/planoptimizer.go
Normal file
|
|
@ -0,0 +1,503 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
|
||||
package planner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/molecula/featurebase/v3/sql3"
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
)
|
||||
|
||||
//TODO(pok) push order by down as far as possible
|
||||
//TODO(pok) handle the case of the order by expressions not being in a projection list
|
||||
//TODO(pok) you can't group by _id in PQL, so we need to not use a PQL group by operator here
|
||||
//TODO(pok) move constant folding to the here
|
||||
|
||||
var optimizerFunctions = []OptimizerFunc{
|
||||
// if we have a group by that has one TableScanOperator,
|
||||
// no Top or TopN or Distincts, try to use a PQL(multi)
|
||||
// groupby operator instead
|
||||
tryToReplaceGroupByWithPQLGroupBy,
|
||||
|
||||
// if we have a group by with no group by exprs that has
|
||||
// one TableScanOperator, no Top or TopN or Distincts, try
|
||||
// to use a PQL aggregate operators instead
|
||||
tryToReplaceGroupByWithPQLAggregate,
|
||||
|
||||
// update the columnIdx for all the references in the projections
|
||||
// based on the child operator for a projection
|
||||
fixGroupByProjections,
|
||||
|
||||
// update the columnIdx for all the references in the projections
|
||||
// based on the child operator for a projection
|
||||
fixJoinProjections,
|
||||
|
||||
// if the query has one TableScanOperator then push the top
|
||||
// expression down into that operator
|
||||
pushdownPQLTop,
|
||||
}
|
||||
|
||||
type OptimizerScope struct {
|
||||
}
|
||||
|
||||
type OptimizerFunc func(context.Context, *ExecutionPlanner, types.PlanOperator, *OptimizerScope) (types.PlanOperator, bool, error)
|
||||
|
||||
// optimizePlan takes a plan from the compiler and executes a series of transforms on it to optimize it
|
||||
func (p *ExecutionPlanner) optimizePlan(ctx context.Context, plan types.PlanOperator) (types.PlanOperator, error) {
|
||||
var err error
|
||||
var result = plan
|
||||
for _, ofunc := range optimizerFunctions {
|
||||
result, err = p.optimizeNode(ctx, result, ofunc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (p *ExecutionPlanner) optimizeNode(ctx context.Context, node types.PlanOperator, ofunc OptimizerFunc) (types.PlanOperator, error) {
|
||||
op, same, err := ofunc(ctx, p, node, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !same {
|
||||
return op, nil
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func tryToReplaceGroupByWithPQLAggregate(ctx context.Context, a *ExecutionPlanner, n types.PlanOperator, scope *OptimizerScope) (types.PlanOperator, bool, error) {
|
||||
//bail if there are any joins
|
||||
scans, err := hasOnlyTableScans(ctx, a, n, scope)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if !scans {
|
||||
return n, true, nil
|
||||
}
|
||||
//bail if there is a top
|
||||
top, err := hasTop(ctx, a, n, scope)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if top {
|
||||
return n, true, nil
|
||||
}
|
||||
|
||||
//go find the table scan operators
|
||||
tables := getTableScanOperators(ctx, a, n, scope)
|
||||
|
||||
//only do this if we have one TableScanOperator
|
||||
if len(tables) == 1 {
|
||||
return TransformPlanOp(n, func(node types.PlanOperator) (types.PlanOperator, bool, error) {
|
||||
switch n := node.(type) {
|
||||
case *PlanOpGroupBy:
|
||||
//only do this if there are no group by expressions
|
||||
if len(n.GroupByExprs) == 0 {
|
||||
|
||||
//table scan
|
||||
table := tables[0]
|
||||
ops := make([]*PlanOpPQLAggregate, 0)
|
||||
|
||||
for _, agg := range n.Aggregates {
|
||||
aggregable, ok := agg.(types.Aggregable)
|
||||
if !ok {
|
||||
return n, false, sql3.NewErrInternalf("unexpected aggregate function arg type '%T'", agg)
|
||||
}
|
||||
|
||||
ops = append(ops, NewPlanOpPQLAggregate(a, table.tableName, aggregable, table.filter))
|
||||
}
|
||||
newOp := NewPlanOpPQLMultiAggregate(a, ops)
|
||||
lenOps := len(ops)
|
||||
if lenOps > 1 {
|
||||
newOp.AddWarning(fmt.Sprintf("Multiple (%d) aggregates referenced in select list will result in multiple aggregate queries being executed.", lenOps))
|
||||
}
|
||||
return newOp, false, nil
|
||||
}
|
||||
return n, true, nil
|
||||
default:
|
||||
return n, true, nil
|
||||
}
|
||||
})
|
||||
}
|
||||
return n, true, nil
|
||||
}
|
||||
|
||||
func tryToReplaceGroupByWithPQLGroupBy(ctx context.Context, a *ExecutionPlanner, n types.PlanOperator, scope *OptimizerScope) (types.PlanOperator, bool, error) {
|
||||
//bail if there are any joins
|
||||
scans, err := hasOnlyTableScans(ctx, a, n, scope)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if !scans {
|
||||
return n, true, nil
|
||||
}
|
||||
//bail if there is a top
|
||||
top, err := hasTop(ctx, a, n, scope)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if top {
|
||||
return n, true, nil
|
||||
}
|
||||
|
||||
//go find the table scan operators
|
||||
tables := getTableScanOperators(ctx, a, n, scope)
|
||||
|
||||
//only do this if we have one TableScanOperator
|
||||
if len(tables) == 1 {
|
||||
return TransformPlanOp(n, func(node types.PlanOperator) (types.PlanOperator, bool, error) {
|
||||
switch n := node.(type) {
|
||||
case *PlanOpGroupBy:
|
||||
//table scan
|
||||
table := tables[0]
|
||||
//only do this if we have group by expressions
|
||||
if len(n.GroupByExprs) > 0 {
|
||||
//use a multi group by if more than 1 aggregate
|
||||
if len(n.Aggregates) > 1 {
|
||||
ops := make([]*PlanOpPQLGroupBy, 0)
|
||||
for _, agg := range n.Aggregates {
|
||||
|
||||
aggregable, ok := agg.(types.Aggregable)
|
||||
if !ok {
|
||||
return n, false, sql3.NewErrInternalf("unexpected aggregate function arg type '%T'", agg)
|
||||
}
|
||||
ops = append(ops, NewPlanOpPQLGroupBy(a, table.tableName, n.GroupByExprs, table.filter, aggregable))
|
||||
}
|
||||
newOp := NewPlanOpPQLMultiGroupBy(a, ops, n.GroupByExprs)
|
||||
newOp.AddWarning(fmt.Sprintf("Multiple (%d) aggregates referenced in select list will result in multiple group by aggregate queries being executed.", len(ops)))
|
||||
return newOp, false, nil
|
||||
}
|
||||
//only one aggregate
|
||||
aggregable, ok := n.Aggregates[0].(types.Aggregable)
|
||||
if !ok {
|
||||
return n, false, sql3.NewErrInternalf("unexpected aggregate function arg type '%T'", n.Aggregates[0])
|
||||
}
|
||||
newOp := NewPlanOpPQLGroupBy(a, table.tableName, n.GroupByExprs, table.filter, aggregable)
|
||||
return newOp, false, nil
|
||||
}
|
||||
return n, true, nil
|
||||
default:
|
||||
return n, true, nil
|
||||
}
|
||||
})
|
||||
}
|
||||
return n, true, nil
|
||||
}
|
||||
|
||||
func pushdownPQLTop(ctx context.Context, a *ExecutionPlanner, n types.PlanOperator, scope *OptimizerScope) (types.PlanOperator, bool, error) {
|
||||
//bail if there are any joins
|
||||
hasOnlyScans, err := hasOnlyTableScans(ctx, a, n, scope)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if !hasOnlyScans {
|
||||
return n, true, nil
|
||||
}
|
||||
|
||||
//go find the table scan operators
|
||||
tables := getTableScanOperators(ctx, a, n, scope)
|
||||
|
||||
//only do this if we have one TableScanOperator
|
||||
if len(tables) == 1 {
|
||||
return TransformPlanOp(n, func(node types.PlanOperator) (types.PlanOperator, bool, error) {
|
||||
switch n := node.(type) {
|
||||
case *PlanOpTop:
|
||||
table := tables[0]
|
||||
//set the topExpr for the PlanOpTableScan
|
||||
table.topExpr = n.expr
|
||||
//return the child of the top node to eliminate it
|
||||
return n.ChildOp, false, nil
|
||||
default:
|
||||
return n, true, nil
|
||||
}
|
||||
})
|
||||
}
|
||||
return n, true, nil
|
||||
}
|
||||
|
||||
func areAggregablesEqual(lhs types.Aggregable, rhs types.Aggregable) bool {
|
||||
if reflect.TypeOf(lhs) == reflect.TypeOf(rhs) {
|
||||
lhsRef, lhsok := lhs.AggExpression().(*qualifiedRefPlanExpression)
|
||||
rhsRef, rhsok := rhs.AggExpression().(*qualifiedRefPlanExpression)
|
||||
if lhsok && rhsok {
|
||||
return strings.EqualFold(lhsRef.columnName, rhsRef.columnName)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func fixGroupByProjections(ctx context.Context, a *ExecutionPlanner, n types.PlanOperator, scope *OptimizerScope) (types.PlanOperator, bool, error) {
|
||||
return TransformPlanOp(n, func(node types.PlanOperator) (types.PlanOperator, bool, error) {
|
||||
switch n := node.(type) {
|
||||
case *PlanOpProjection:
|
||||
switch childOp := n.ChildOp.(type) {
|
||||
case *PlanOpGroupBy:
|
||||
//PlanOpGroupBy's iterator returns group by exprs, then aggregates in the order they appear
|
||||
|
||||
for idx, pj := range n.Projections {
|
||||
expr, _, err := TransformExpr(pj, func(e types.PlanExpression) (types.PlanExpression, bool, error) {
|
||||
switch e.(type) {
|
||||
case types.Aggregable:
|
||||
// if we have a Aggregable, the AggExpression() will be a qualified ref
|
||||
// given we are in the context of a PlanOpProjection with a PlanOpGroupBy
|
||||
// we can use the ordinal position of the projection as the column index
|
||||
ae := newQualifiedRefPlanExpression("", "", idx, e.Type())
|
||||
return ae, false, nil
|
||||
default:
|
||||
return e, true, nil
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return n, true, err
|
||||
}
|
||||
n.Projections[idx] = expr
|
||||
}
|
||||
return n, false, nil
|
||||
|
||||
case *PlanOpPQLGroupBy:
|
||||
// PlanOpGroupBy's iterator returns group by exprs, then the single aggregate in the order they appear
|
||||
|
||||
// make a map of the names of the group by columns
|
||||
groupByColumnsNameMap := make(map[string]int)
|
||||
for gidx, gbe := range childOp.groupByExprs {
|
||||
gbeRef, ok := gbe.(*qualifiedRefPlanExpression)
|
||||
if !ok {
|
||||
return nil, false, sql3.NewErrInternalf("unexpected group by expression type '%T'", gbe)
|
||||
}
|
||||
gbeRef.columnIndex = gidx
|
||||
groupByColumnsNameMap[gbeRef.columnName] = gidx
|
||||
}
|
||||
//set the index for the aggregate to be the length of the group by list
|
||||
aggregateIndex := len(childOp.groupByExprs)
|
||||
|
||||
//loop projections:
|
||||
//1. looking for the Aggregable and replace it with a qualifiedRefPlanExpression pointing to
|
||||
// the offset in the child iterator
|
||||
//2. looking for the qualified refs replace it with a qualifiedRefPlanExpression pointing to
|
||||
// the offset in the child iterator
|
||||
for idx, pj := range n.Projections {
|
||||
expr, _, err := TransformExpr(pj, func(e types.PlanExpression) (types.PlanExpression, bool, error) {
|
||||
switch thisExpr := e.(type) {
|
||||
case types.Aggregable:
|
||||
ae := newQualifiedRefPlanExpression(fmt.Sprintf("$PlanOpPQLGroupBy:%d", aggregateIndex), "", aggregateIndex, e.Type())
|
||||
return ae, false, nil
|
||||
case *qualifiedRefPlanExpression:
|
||||
colIdx, ok := groupByColumnsNameMap[thisExpr.columnName]
|
||||
if ok {
|
||||
ae := newQualifiedRefPlanExpression(fmt.Sprintf("$PlanOpPQLGroupBy.%s:%d", thisExpr.columnName, colIdx), thisExpr.columnName, colIdx, e.Type())
|
||||
return ae, false, nil
|
||||
}
|
||||
return e, true, nil
|
||||
default:
|
||||
return e, true, nil
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return n, true, err
|
||||
}
|
||||
n.Projections[idx] = expr
|
||||
}
|
||||
return n, false, nil
|
||||
|
||||
case *PlanOpPQLMultiAggregate:
|
||||
//PlanOpGroupBy's iterator returns aggregates in the order they appear
|
||||
|
||||
// make a list of the aggregables
|
||||
aggregableList := make([]types.Aggregable, 0)
|
||||
for _, op := range childOp.operators {
|
||||
aggregableList = append(aggregableList, op.aggregate)
|
||||
}
|
||||
|
||||
//loop projections:
|
||||
//1. looking for the Aggregable and replace it with a qualifiedRefPlanExpression pointing to
|
||||
// the offset in the child iterator
|
||||
for idx, pj := range n.Projections {
|
||||
expr, _, err := TransformExpr(pj, func(e types.PlanExpression) (types.PlanExpression, bool, error) {
|
||||
switch thisExpr := e.(type) {
|
||||
case types.Aggregable:
|
||||
for idx, a := range aggregableList {
|
||||
if areAggregablesEqual(thisExpr, a) {
|
||||
ae := newQualifiedRefPlanExpression(fmt.Sprintf("$PlanOpPQLMultiAggregate:%d", idx), "", idx, e.Type())
|
||||
return ae, false, nil
|
||||
}
|
||||
}
|
||||
return e, true, nil
|
||||
default:
|
||||
return e, true, nil
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return n, true, err
|
||||
}
|
||||
n.Projections[idx] = expr
|
||||
}
|
||||
return n, false, nil
|
||||
|
||||
case *PlanOpPQLMultiGroupBy:
|
||||
//PlanOpPQLMultiGroupBy's iterator returns group by exprs, then the aggregates in the order they appear
|
||||
|
||||
// make a map of the names of the group by columns and update the indexes
|
||||
groupByColumnsNameMap := make(map[string]int)
|
||||
for gidx, gbe := range childOp.groupByExprs {
|
||||
gbeRef, ok := gbe.(*qualifiedRefPlanExpression)
|
||||
if !ok {
|
||||
return nil, false, sql3.NewErrInternalf("unexpected group by expression type '%T'", gbe)
|
||||
}
|
||||
gbeRef.columnIndex = gidx
|
||||
groupByColumnsNameMap[gbeRef.columnName] = gidx
|
||||
}
|
||||
//set the index for the start of the aggregates to be the length of the group by list
|
||||
aggregateStartIndex := len(childOp.groupByExprs)
|
||||
|
||||
// make a list of the aggregables
|
||||
aggregableList := make([]types.Aggregable, 0)
|
||||
for _, op := range childOp.operators {
|
||||
aggregableList = append(aggregableList, op.aggregate)
|
||||
}
|
||||
|
||||
//loop projections:
|
||||
//1. looking for the Aggregable and replace it with a qualifiedRefPlanExpression pointing to
|
||||
// the offset in the child iterator
|
||||
//2. looking for the qualified refs replace it with a qualifiedRefPlanExpression pointing to
|
||||
// the offset in the child iterator
|
||||
for idx, pj := range n.Projections {
|
||||
expr, _, err := TransformExpr(pj, func(e types.PlanExpression) (types.PlanExpression, bool, error) {
|
||||
switch thisExpr := e.(type) {
|
||||
case types.Aggregable:
|
||||
for idx, a := range aggregableList {
|
||||
if areAggregablesEqual(thisExpr, a) {
|
||||
ae := newQualifiedRefPlanExpression(fmt.Sprintf("$PlanOpPQLMultiGroupBy:%d", idx+aggregateStartIndex), "", idx+aggregateStartIndex, e.Type())
|
||||
return ae, false, nil
|
||||
}
|
||||
}
|
||||
return e, true, nil
|
||||
case *qualifiedRefPlanExpression:
|
||||
colIdx, ok := groupByColumnsNameMap[thisExpr.columnName]
|
||||
if ok {
|
||||
ae := newQualifiedRefPlanExpression(fmt.Sprintf("$PlanOpPQLMultiGroupBy.%s:%d", thisExpr.columnName, colIdx), thisExpr.columnName, colIdx, e.Type())
|
||||
return ae, false, nil
|
||||
}
|
||||
return e, true, nil
|
||||
|
||||
default:
|
||||
return e, true, nil
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return n, true, err
|
||||
}
|
||||
n.Projections[idx] = expr
|
||||
}
|
||||
return n, false, nil
|
||||
}
|
||||
return n, true, nil
|
||||
default:
|
||||
return n, true, nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func fixJoinProjections(ctx context.Context, a *ExecutionPlanner, n types.PlanOperator, scope *OptimizerScope) (types.PlanOperator, bool, error) {
|
||||
return TransformPlanOp(n, func(node types.PlanOperator) (types.PlanOperator, bool, error) {
|
||||
switch n := node.(type) {
|
||||
case *PlanOpProjection:
|
||||
switch childOp := n.ChildOp.(type) {
|
||||
case *PlanOpNestedLoops:
|
||||
//PlanOpNestedLoops iterator returns columns from top iterator and then columns from bottom iterator
|
||||
|
||||
//make a map of names from the schema
|
||||
schemaNameMap := make(map[string]int)
|
||||
schema := childOp.Schema()
|
||||
for idx, s := range schema {
|
||||
key := fmt.Sprintf("%s.%s", s.Table, s.Name)
|
||||
schemaNameMap[key] = idx
|
||||
}
|
||||
|
||||
for idx, pj := range n.Projections {
|
||||
expr, _, err := TransformExpr(pj, func(e types.PlanExpression) (types.PlanExpression, bool, error) {
|
||||
switch thisExpr := e.(type) {
|
||||
case *qualifiedRefPlanExpression:
|
||||
key := fmt.Sprintf("%s.%s", thisExpr.tableName, thisExpr.columnName)
|
||||
colIdx, ok := schemaNameMap[key]
|
||||
if ok {
|
||||
ae := newQualifiedRefPlanExpression(fmt.Sprintf("$PlanOpNestedLoops.%s.%s:%d", thisExpr.tableName, thisExpr.columnName, colIdx), thisExpr.columnName, colIdx, e.Type())
|
||||
return ae, false, nil
|
||||
}
|
||||
return e, true, nil
|
||||
|
||||
default:
|
||||
return e, true, nil
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return n, true, err
|
||||
}
|
||||
n.Projections[idx] = expr
|
||||
}
|
||||
return n, false, nil
|
||||
}
|
||||
return n, true, nil
|
||||
default:
|
||||
return n, true, nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// hasTop inspects a plan op tree and returns true (or error) if there are Top
|
||||
// operators.
|
||||
func hasTop(ctx context.Context, a *ExecutionPlanner, n types.PlanOperator, scope *OptimizerScope) (bool, error) {
|
||||
result := false
|
||||
InspectPlan(n, func(node types.PlanOperator) bool {
|
||||
switch node.(type) {
|
||||
case *PlanOpTop:
|
||||
result = true
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// hasTopN inspects a plan op tree and returns true (or error) if there are TopN
|
||||
// operators.
|
||||
func hasTopN(ctx context.Context, a *ExecutionPlanner, n types.PlanOperator, scope *OptimizerScope) (bool, error) {
|
||||
//TODO(pok) implement this
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// inspects a plan op tree and returns false (or error) if there are read operators other
|
||||
// than table scans
|
||||
func hasOnlyTableScans(ctx context.Context, a *ExecutionPlanner, n types.PlanOperator, scope *OptimizerScope) (bool, error) {
|
||||
//assume true
|
||||
result := true
|
||||
InspectPlan(n, func(node types.PlanOperator) bool {
|
||||
// if we find a nested loops, nope to only table scans
|
||||
switch node.(type) {
|
||||
case *PlanOpNestedLoops:
|
||||
result = false
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// inspects a plan op tree and returns a list (or error) of all the PlanOpTableScan operators
|
||||
func getTableScanOperators(ctx context.Context, a *ExecutionPlanner, n types.PlanOperator, scope *OptimizerScope) []*PlanOpPQLTableScan {
|
||||
var tables []*PlanOpPQLTableScan
|
||||
//go find the table scan operators
|
||||
InspectPlan(n, func(node types.PlanOperator) bool {
|
||||
switch nd := node.(type) {
|
||||
case *PlanOpPQLTableScan:
|
||||
tables = append(tables, nd)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return tables
|
||||
}
|
||||
385
sql3/planner/planwalker.go
Normal file
385
sql3/planner/planwalker.go
Normal file
|
|
@ -0,0 +1,385 @@
|
|||
package planner
|
||||
|
||||
import (
|
||||
"github.com/molecula/featurebase/v3/sql3/planner/types"
|
||||
)
|
||||
|
||||
// PlanVisitor visits nodes in the plan.
|
||||
type PlanVisitor interface {
|
||||
// VisitOperator method is invoked for each node during PlanWalk. If the
|
||||
// resulting PlanVisitor is not nil, PlanWalk visits each of the children of
|
||||
// the node with that visitor, followed by a call of VisitOperator(nil) to
|
||||
// the returned visitor.
|
||||
VisitOperator(op types.PlanOperator) PlanVisitor
|
||||
}
|
||||
|
||||
// PlanWalk traverses the plan depth-first. It starts by calling
|
||||
// v.VisitOperator; node must not be nil. If the result returned by
|
||||
// v.VisitOperator is not nil, PlanWalk is invoked recursively with the returned
|
||||
// result for each of the children of the plan operator, followed by a call of
|
||||
// v.VisitOperator(nil) to the returned result. If v.VisitOperator(op) returns
|
||||
// non-nil, then all children are walked, even if one of them returns nil.
|
||||
func PlanWalk(v PlanVisitor, op types.PlanOperator) {
|
||||
if v = v.VisitOperator(op); v == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, child := range op.Children() {
|
||||
PlanWalk(v, child)
|
||||
}
|
||||
|
||||
v.VisitOperator(nil)
|
||||
}
|
||||
|
||||
type planInspector func(types.PlanOperator) bool
|
||||
|
||||
func (f planInspector) VisitOperator(op types.PlanOperator) PlanVisitor {
|
||||
if f(op) {
|
||||
return f
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InspectPlan traverses the plan op graph depth-first order
|
||||
// if f(op) returns true, InspectPlan invokes f recursively for each of the children of op,
|
||||
// followed by a call of f(nil).
|
||||
func InspectPlan(op types.PlanOperator, f planInspector) {
|
||||
PlanWalk(f, op)
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// ExprVisitor visits expressions in an expression tree.
|
||||
type ExprVisitor interface {
|
||||
// VisitExpr method is invoked for each expr encountered by ExprWalk.
|
||||
// If the result is not nil, ExprWalk visits each of the children
|
||||
// of the expr, followed by a call of VisitExpr(nil) to the returned result.
|
||||
VisitExpr(expr types.PlanExpression) ExprVisitor
|
||||
}
|
||||
|
||||
func ExprWalk(v ExprVisitor, expr types.PlanExpression) {
|
||||
if v = v.VisitExpr(expr); v == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, child := range expr.Children() {
|
||||
ExprWalk(v, child)
|
||||
}
|
||||
|
||||
v.VisitExpr(nil)
|
||||
}
|
||||
|
||||
type exprInspector func(types.PlanExpression) bool
|
||||
|
||||
func (f exprInspector) VisitExpr(e types.PlanExpression) ExprVisitor {
|
||||
if f(e) {
|
||||
return f
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WalkExpressions traverses the plan and calls sql.Walk on any expression it finds.
|
||||
func WalkExpressions(v ExprVisitor, node types.PlanOperator) {
|
||||
InspectPlan(node, func(node types.PlanOperator) bool {
|
||||
if n, ok := node.(types.ContainsExpressions); ok {
|
||||
for _, e := range n.Expressions() {
|
||||
ExprWalk(v, e)
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// InspectExpressions traverses the plan and calls WalkExpressions on any
|
||||
// expression it finds.
|
||||
func InspectExpressions(node types.PlanOperator, f exprInspector) {
|
||||
WalkExpressions(f, node)
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// PlanOpExprVisitor visits expressions in an expression tree. Like ExprVisitor, but with the added context of the plan op in which
|
||||
// an expression is embedded.
|
||||
type PlanOpExprVisitor interface {
|
||||
// VisitPlanOpExpr method is invoked for each expr encountered by Walk. If the result Visitor is not nil, Walk visits each of
|
||||
// the children of the expr with that visitor, followed by a call of VisitPlanOpExpr(nil, nil) to the returned visitor.
|
||||
VisitPlanOpExpr(node types.PlanOperator, expression types.PlanExpression) PlanOpExprVisitor
|
||||
}
|
||||
|
||||
// ExprWithPlanOpWalk traverses the expression tree in depth-first order. It starts by calling v.VisitPlanOpExpr(op, expr); expr must
|
||||
// not be nil. If the visitor returned by v.VisitPlanOpExpr(op, expr) is not nil, Walk is invoked recursively with the returned
|
||||
// visitor for each children of the expr, followed by a call of v.VisitPlanOpExpr(nil, nil) to the returned visitor.
|
||||
func ExprWithPlanOpWalk(v PlanOpExprVisitor, n types.PlanOperator, expr types.PlanExpression) {
|
||||
if v = v.VisitPlanOpExpr(n, expr); v == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, child := range expr.Children() {
|
||||
ExprWithPlanOpWalk(v, n, child)
|
||||
}
|
||||
|
||||
v.VisitPlanOpExpr(nil, nil)
|
||||
}
|
||||
|
||||
type exprWithNodeInspector func(types.PlanOperator, types.PlanExpression) bool
|
||||
|
||||
func (f exprWithNodeInspector) VisitPlanOpExpr(n types.PlanOperator, e types.PlanExpression) PlanOpExprVisitor {
|
||||
if f(n, e) {
|
||||
return f
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WalkExpressionsWithPlanOp traverses the plan and calls ExprWithPlanOpWalk on any expression it finds.
|
||||
func WalkExpressionsWithPlanOp(v PlanOpExprVisitor, n types.PlanOperator) {
|
||||
InspectPlan(n, func(n types.PlanOperator) bool {
|
||||
if expressioner, ok := n.(types.ContainsExpressions); ok {
|
||||
for _, e := range expressioner.Expressions() {
|
||||
ExprWithPlanOpWalk(v, n, e)
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// InspectExpressionsWithPlanOp traverses the plan and calls f on any expression it finds.
|
||||
func InspectExpressionsWithPlanOp(node types.PlanOperator, f exprWithNodeInspector) {
|
||||
WalkExpressionsWithPlanOp(f, node)
|
||||
}
|
||||
|
||||
// PlanOpFunc is a function that given a plan op will return either a transformed plan op or the original plan op.
|
||||
// If there was a transformation, the bool will be true, and an error if there was an error
|
||||
type PlanOpFunc func(n types.PlanOperator) (types.PlanOperator, bool, error)
|
||||
|
||||
// TransformPlanOp applies a transformation function to the given plan op graph
|
||||
func TransformPlanOp(op types.PlanOperator, f PlanOpFunc) (types.PlanOperator, bool, error) {
|
||||
|
||||
children := op.Children()
|
||||
if len(children) == 0 {
|
||||
return f(op)
|
||||
}
|
||||
|
||||
var (
|
||||
newChildren []types.PlanOperator
|
||||
)
|
||||
|
||||
for i := range children {
|
||||
child := children[i]
|
||||
child, same, err := TransformPlanOp(child, f)
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
if !same {
|
||||
if newChildren == nil {
|
||||
newChildren = make([]types.PlanOperator, len(children))
|
||||
copy(newChildren, children)
|
||||
}
|
||||
newChildren[i] = child
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
sameC := true
|
||||
if len(newChildren) > 0 {
|
||||
sameC = false
|
||||
op, err = op.WithChildren(newChildren...)
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
}
|
||||
|
||||
op, sameN, err := f(op)
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
return op, sameC && sameN, nil
|
||||
}
|
||||
|
||||
// ExprWithPlanOpFunc is a function that given an expression and the node
|
||||
// that contains it, will return that expression as is or transformed
|
||||
// along with an error, if any.
|
||||
type ExprWithPlanOpFunc func(types.PlanOperator, types.PlanExpression) (types.PlanExpression, bool, error)
|
||||
|
||||
// ExprFunc is a function that given an expression will return that
|
||||
// expression as is or transformed, or bool to indicate
|
||||
// whether the expression was modified, and an error or nil.
|
||||
type ExprFunc func(e types.PlanExpression) (types.PlanExpression, bool, error)
|
||||
|
||||
// TransformPlanOpExprsWithPlanOp applies a transformation function to all expressions on the given plan operator from the bottom up in the context of the plan operator
|
||||
func TransformPlanOpExprsWithPlanOp(op types.PlanOperator, f ExprWithPlanOpFunc) (types.PlanOperator, bool, error) {
|
||||
return TransformPlanOp(op, func(n types.PlanOperator) (types.PlanOperator, bool, error) {
|
||||
return SinglePlanOpExprsWithPlanOp(n, f)
|
||||
})
|
||||
}
|
||||
|
||||
// TransformPlanOpExprs applies a transformation function to all expressions on the given plan operator from the bottom up
|
||||
func TransformPlanOpExprs(op types.PlanOperator, f ExprFunc) (types.PlanOperator, bool, error) {
|
||||
return TransformPlanOpExprsWithPlanOp(op, func(n types.PlanOperator, e types.PlanExpression) (types.PlanExpression, bool, error) {
|
||||
return f(e)
|
||||
})
|
||||
}
|
||||
|
||||
// SinglePlanOpExprsWithPlanOp applies a transformation function to all expressions on a given plan operator in the context of that plan operator
|
||||
func SinglePlanOpExprsWithPlanOp(n types.PlanOperator, f ExprWithPlanOpFunc) (types.PlanOperator, bool, error) {
|
||||
ne, ok := n.(types.ContainsExpressions)
|
||||
if !ok {
|
||||
return n, true, nil
|
||||
}
|
||||
|
||||
exprs := ne.Expressions()
|
||||
if len(exprs) == 0 {
|
||||
return n, true, nil
|
||||
}
|
||||
|
||||
var (
|
||||
newExprs []types.PlanExpression
|
||||
err error
|
||||
)
|
||||
|
||||
for i := range exprs {
|
||||
e := exprs[i]
|
||||
e, same, err := TransformExprWithPlanOp(n, e, f)
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
if !same {
|
||||
if newExprs == nil {
|
||||
newExprs = make([]types.PlanExpression, len(exprs))
|
||||
copy(newExprs, exprs)
|
||||
}
|
||||
newExprs[i] = e
|
||||
}
|
||||
}
|
||||
|
||||
if len(newExprs) > 0 {
|
||||
n, err = ne.WithExpressions(newExprs...)
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
return n, false, nil
|
||||
}
|
||||
return n, true, nil
|
||||
}
|
||||
|
||||
// TransformSinglePlanOpExpressions applies a transformation function to all expressions on the given plan operator
|
||||
func TransformSinglePlanOpExpressions(o types.PlanOperator, f ExprFunc) (types.PlanOperator, bool, error) {
|
||||
e, ok := o.(types.ContainsExpressions)
|
||||
if !ok {
|
||||
return o, true, nil
|
||||
}
|
||||
|
||||
exprs := e.Expressions()
|
||||
if len(exprs) == 0 {
|
||||
return o, true, nil
|
||||
}
|
||||
|
||||
var newExprs []types.PlanExpression
|
||||
for i := range exprs {
|
||||
expr := exprs[i]
|
||||
expr, same, err := TransformExpr(expr, f)
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
if !same {
|
||||
if newExprs == nil {
|
||||
newExprs = make([]types.PlanExpression, len(exprs))
|
||||
copy(newExprs, exprs)
|
||||
}
|
||||
newExprs[i] = expr
|
||||
}
|
||||
}
|
||||
if len(newExprs) > 0 {
|
||||
n, err := e.WithExpressions(newExprs...)
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
return n, false, nil
|
||||
}
|
||||
return o, true, nil
|
||||
}
|
||||
|
||||
// TransformExpr applies a transformation function to an expression
|
||||
func TransformExpr(e types.PlanExpression, f ExprFunc) (types.PlanExpression, bool, error) {
|
||||
children := e.Children()
|
||||
if len(children) == 0 {
|
||||
return f(e)
|
||||
}
|
||||
|
||||
var (
|
||||
newChildren []types.PlanExpression
|
||||
err error
|
||||
)
|
||||
|
||||
for i := 0; i < len(children); i++ {
|
||||
c := children[i]
|
||||
c, same, err := TransformExpr(c, f)
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
if !same {
|
||||
if newChildren == nil {
|
||||
newChildren = make([]types.PlanExpression, len(children))
|
||||
copy(newChildren, children)
|
||||
}
|
||||
newChildren[i] = c
|
||||
}
|
||||
}
|
||||
|
||||
sameC := true
|
||||
if len(newChildren) > 0 {
|
||||
sameC = false
|
||||
e, err = e.WithChildren(newChildren...)
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
}
|
||||
|
||||
e, sameN, err := f(e)
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
return e, sameC && sameN, nil
|
||||
}
|
||||
|
||||
// TransformExprWithPlanOp applies a transformation function to an expression in the context of a plan operator
|
||||
func TransformExprWithPlanOp(n types.PlanOperator, e types.PlanExpression, f ExprWithPlanOpFunc) (types.PlanExpression, bool, error) {
|
||||
children := e.Children()
|
||||
if len(children) == 0 {
|
||||
return f(n, e)
|
||||
}
|
||||
|
||||
var (
|
||||
newChildren []types.PlanExpression
|
||||
err error
|
||||
)
|
||||
|
||||
for i := 0; i < len(children); i++ {
|
||||
c := children[i]
|
||||
c, sameC, err := TransformExprWithPlanOp(n, c, f)
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
if !sameC {
|
||||
if newChildren == nil {
|
||||
newChildren = make([]types.PlanExpression, len(children))
|
||||
copy(newChildren, children)
|
||||
}
|
||||
newChildren[i] = c
|
||||
}
|
||||
}
|
||||
|
||||
sameC := true
|
||||
if len(newChildren) > 0 {
|
||||
sameC = false
|
||||
e, err = e.WithChildren(newChildren...)
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
}
|
||||
|
||||
e, sameN, err := f(n, e)
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
return e, sameC && sameN, nil
|
||||
}
|
||||
88
sql3/planner/types/operator.go
Normal file
88
sql3/planner/types/operator.go
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/molecula/featurebase/v3/sql3/parser"
|
||||
)
|
||||
|
||||
// PlanOperator is a node in an execution plan.
|
||||
type PlanOperator interface {
|
||||
fmt.Stringer
|
||||
|
||||
// Children of this operator.
|
||||
Children() []PlanOperator
|
||||
|
||||
// Schema of this operator.
|
||||
Schema() Schema
|
||||
|
||||
// Iterator produces an iterator from this node. The current row being
|
||||
// evaluated is provided, as well as the context of the query.
|
||||
Iterator(ctx context.Context, row Row) (RowIterator, error)
|
||||
|
||||
// WithChildren creates a new node with the children passed
|
||||
WithChildren(children ...PlanOperator) (PlanOperator, error)
|
||||
|
||||
// Plan returns a map containing a rich description of this operator;
|
||||
// intended to be marshalled into json.
|
||||
Plan() map[string]interface{}
|
||||
|
||||
// AddWarning adds a warning to the plan.
|
||||
AddWarning(warning string)
|
||||
|
||||
// Warnings returns a list of warnings as strings.
|
||||
Warnings() []string
|
||||
}
|
||||
|
||||
// ContainsExpressions exposes expressions in plan operators
|
||||
type ContainsExpressions interface {
|
||||
// returns the list of expressions contained by the plan operator
|
||||
Expressions() []PlanExpression
|
||||
|
||||
// WithExpressions returns a new operator with expressions replaced
|
||||
WithExpressions(...PlanExpression) (PlanOperator, error)
|
||||
}
|
||||
|
||||
// SchemaObject exposes an ObjectName() for operators that iterate on schema objects
|
||||
type SchemaObject interface {
|
||||
ObjectName() string
|
||||
}
|
||||
|
||||
// PlannerColumn is the definition of a column returned as a set from each operator
|
||||
type PlannerColumn struct {
|
||||
Name string
|
||||
Table string
|
||||
Type parser.ExprDataType
|
||||
}
|
||||
|
||||
// Schema is the definition a set of columns from each operator
|
||||
type Schema []*PlannerColumn
|
||||
|
||||
// Row is a tuple of values
|
||||
type Row []interface{}
|
||||
|
||||
// Append appends all the values in r2 to this row and returns the result
|
||||
func (r Row) Append(r2 Row) Row {
|
||||
row := make(Row, len(r)+len(r2))
|
||||
for i := range r {
|
||||
row[i] = r[i]
|
||||
}
|
||||
for i := range r2 {
|
||||
row[i+len(r)] = r2[i]
|
||||
}
|
||||
return row
|
||||
}
|
||||
|
||||
// ErrNoMoreRows is a 'special' error returned to signify no more rows
|
||||
var ErrNoMoreRows = errors.New("ErrNoMoreRows")
|
||||
|
||||
// RowIterator is an iterator that produces rows (or an error)
|
||||
type RowIterator interface {
|
||||
Next(ctx context.Context) (Row, error)
|
||||
}
|
||||
|
||||
type RowIterable interface {
|
||||
Iterator(ctx context.Context, row Row) (RowIterator, error)
|
||||
}
|
||||
62
sql3/planner/types/planexpression.go
Normal file
62
sql3/planner/types/planexpression.go
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/molecula/featurebase/v3/sql3/parser"
|
||||
)
|
||||
|
||||
//TODO(pok) we can get rid of this - we have expression types for all of these now...
|
||||
type AggregateFunctionType int
|
||||
|
||||
// The list of AggregateFunction.
|
||||
const (
|
||||
// Special tokens
|
||||
AGGREGATE_ILLEGAL AggregateFunctionType = iota
|
||||
AGGREGATE_COUNT
|
||||
AGGREGATE_COUNT_DISTINCT
|
||||
AGGREGATE_SUM
|
||||
AGGREGATE_AVG
|
||||
AGGREGATE_PERCENTILE
|
||||
AGGREGATE_MIN
|
||||
AGGREGATE_MAX
|
||||
)
|
||||
|
||||
// PlanExpression is an expression node for an execution plan
|
||||
type PlanExpression interface {
|
||||
// evaluates expression based on current row
|
||||
Evaluate(currentRow []interface{}) (interface{}, error)
|
||||
|
||||
// returns the type of the expression
|
||||
Type() parser.ExprDataType
|
||||
|
||||
// returns the child expressions for this expression
|
||||
Children() []PlanExpression
|
||||
|
||||
// creates a new expression node with the children replaced
|
||||
WithChildren(children ...PlanExpression) (PlanExpression, error)
|
||||
|
||||
// returns a map containing a rich description of this expression; intended to be
|
||||
// marshalled into json
|
||||
Plan() map[string]interface{}
|
||||
}
|
||||
|
||||
// Aggregattion buffer is an interface to something that maintains an aggregate during query
|
||||
// execution
|
||||
type AggregationBuffer interface {
|
||||
Eval(ctx context.Context) (interface{}, error)
|
||||
Update(ctx context.Context, row Row) error
|
||||
}
|
||||
|
||||
// Interface to an expression that is a an aggregate
|
||||
type Aggregable interface {
|
||||
NewBuffer() (AggregationBuffer, error)
|
||||
AggType() AggregateFunctionType
|
||||
AggExpression() PlanExpression
|
||||
AggAdditionalExpr() []PlanExpression
|
||||
}
|
||||
|
||||
// Interface to an expression that is a reference to a schema object
|
||||
type SchemaIdentifiable interface {
|
||||
Name() string
|
||||
}
|
||||
264
sql3/sql3.ebnf
Normal file
264
sql3/sql3.ebnf
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
(*
|
||||
|
||||
sql3
|
||||
==========================
|
||||
|
||||
Document the SQL language support in FeatureBase
|
||||
|
||||
*)
|
||||
|
||||
sql3 = statement, [ ";" ] ;
|
||||
|
||||
statement = show_tables
|
||||
| show_columns
|
||||
| drop_table
|
||||
| create_table_stmt
|
||||
| alter_table_stmt
|
||||
| select_stmt
|
||||
| insert_stmt
|
||||
| delete_stmt ;
|
||||
|
||||
(*
|
||||
|
||||
SHOW TABLES
|
||||
----------------
|
||||
Shows the tables within a FeatureBase instance.
|
||||
|
||||
*)
|
||||
show_tables = "SHOW", "TABLES" ;
|
||||
|
||||
(*
|
||||
|
||||
SHOW COLUMNS
|
||||
----------------
|
||||
Shows the columns on a FeatureBase table.
|
||||
|
||||
*)
|
||||
show_columns = "SHOW", "COLUMNS", "FROM", identifier ;
|
||||
|
||||
(*
|
||||
|
||||
DROP TABLE
|
||||
----------------
|
||||
Drops a FeatureBase table.
|
||||
|
||||
*)
|
||||
drop_table = "DROP", "TABLE", [ "IF", "EXISTS" ], identifier ;
|
||||
|
||||
|
||||
(*
|
||||
|
||||
CREATE TABLE
|
||||
----------------
|
||||
Creates a FeatureBase table.
|
||||
|
||||
*)
|
||||
create_table_stmt = "CREATE", "TABLE", [ "IF", "NOT", "EXISTS" ], identifier, "(", column_def, { ",", column_def }, ")", { table_options } ;
|
||||
|
||||
column_def = identifier, type_name, { column_constraint } ;
|
||||
|
||||
(*
|
||||
|
||||
Column Constraint
|
||||
----------------
|
||||
Constraints for a column:
|
||||
|
||||
MIN, MAX - min/max for int types
|
||||
|
||||
TIMEUNIT - 's', 'ms' etc.
|
||||
|
||||
TIMEQUANTUM - 'YMD' etc.
|
||||
|
||||
*)
|
||||
column_constraint =
|
||||
"MIN", integer_literal
|
||||
| "MAX", integer_literal
|
||||
| "TIMEUNIT", string_literal, [ "EPOCH", date_literal ]
|
||||
| "TIMEQUANTUM", string_literal, [ "TTL", string_literal ]
|
||||
| "CACHETYPE", ( "RANKED" | "LRU" ), [ "SIZE", integer_literal ] ;
|
||||
|
||||
table_options = "KEYPARTITIONS", integer_literal
|
||||
| "SHARDWIDTH", integer_literal ;
|
||||
|
||||
type_name = "INT"
|
||||
| "BOOL"
|
||||
| "TIMESTAMP"
|
||||
| "DECIMAL"
|
||||
| "STRING"
|
||||
| "STRINGSET"
|
||||
| "ID"
|
||||
| "IDSET" ;
|
||||
|
||||
(*
|
||||
|
||||
ALTER TABLE
|
||||
----------------
|
||||
Alters a FeatureBase table.
|
||||
|
||||
*)
|
||||
alter_table_stmt = "ALTER", "TABLE", ( add_column | drop_column | rename_column ) ;
|
||||
|
||||
add_column = "ADD", [ "COLUMN" ], column_def ;
|
||||
|
||||
drop_column = "DROP", [ "COLUMN" ], identifier ;
|
||||
|
||||
rename_column = "RENAME", [ "COLUMN" ], identifier, "TO", identifier ;
|
||||
|
||||
(*
|
||||
|
||||
INSERT
|
||||
----------------
|
||||
Inserts data into a FeatureBase table.
|
||||
|
||||
*)
|
||||
insert_stmt = "INSERT", "INTO", identifier, "(", identifier, { identifier, "," }, ")", "VALUES", "(", expr, { expr, "," }, ")" ;
|
||||
|
||||
(*
|
||||
|
||||
DELETE
|
||||
----------------
|
||||
Deletes data from a FeatureBase table.
|
||||
|
||||
*)
|
||||
delete_stmt = "DELETE" ;
|
||||
|
||||
(*
|
||||
|
||||
SELECT
|
||||
----------------
|
||||
Queries a FeatureBase table.
|
||||
|
||||
*)
|
||||
select_stmt = "SELECT", [ top_clause ], [ "DISTINCT" ], result_column, { ",", result_column }, [ from_clause ], [ where_clause ], [group_by_clause] ;
|
||||
|
||||
top_clause = ( "TOP" | "TOPN" ), "(", expr, ")" ;
|
||||
|
||||
result_column = expr, [ [ "AS" ], column_alias ]
|
||||
| "*"
|
||||
| identifier, ".", "*" ;
|
||||
|
||||
column_alias = identifier ;
|
||||
|
||||
from_clause = "FROM", table_or_subquery, { ",", table_or_subquery } ;
|
||||
|
||||
table_or_subquery = identifier, [ [ "AS" ], table_alias ], [ table_option ]
|
||||
| "(", select_stmt, ")", [ [ "AS" ], table_alias ] ;
|
||||
|
||||
table_alias = identifier ;
|
||||
|
||||
table_option = "SHARDS", "(", integer_literal, { ",", integer_literal }, ")";
|
||||
|
||||
where_clause = "WHERE", expr ;
|
||||
|
||||
group_by_clause = "GROUP", "BY", expr, { ",", expr }, [ "HAVING", expr ] ;
|
||||
|
||||
(*
|
||||
|
||||
Expressions
|
||||
----------------
|
||||
|
||||
timequantums - modelling
|
||||
|
||||
* let stringsetcolq refer to a set with a timequantum column
|
||||
* the expression stringsetcolq is an array/table? of tuple of (timestamp, set)
|
||||
* when you refer to stringsetcolq by name, this is shorthand for
|
||||
"the set value for the latest timestamp"
|
||||
* if we were to use dotted notation to refer to the components of the tuple,
|
||||
we could refer to the components of the tuple e.g.
|
||||
- stringsetcolq.timestamp
|
||||
- stringsetcolq.value
|
||||
* but stringsetcolq is an array of tuples, so assuming some array type notation
|
||||
- stringsetcolq[subscript].timestamp
|
||||
- stringsetcolq[subscript].value
|
||||
* this is dumb
|
||||
* what if, when you refer to stringsetcolq by name, this is shorthand for
|
||||
"the set value for the latest timestamp" - the tuple not the array
|
||||
* the underlying 'table' could be modelled as (_id, timestamp, set)
|
||||
* if you want access to that table, we could use a table valued function that would enable us to use
|
||||
cross apply etc.
|
||||
|
||||
|
||||
* you want to do range queries on the timestamp
|
||||
* you want to be able to see what timestamps you have
|
||||
* you want to be able to see what sets and set values you have
|
||||
* do we ever want to be able to do this with any arbitary range queryable type, right
|
||||
now we have one additional dimension, would we ever want more? would we want additional values?
|
||||
|
||||
*)
|
||||
expr = integer_literal
|
||||
| string_literal
|
||||
| decimal_literal
|
||||
| set_literal
|
||||
| date_literal
|
||||
| [ table_name, "." ], column_name
|
||||
| unary_op, expr
|
||||
| expr, binary_op, expr
|
||||
| function_call
|
||||
| "(", expr, ")"
|
||||
| "CAST", "(", expr, "AS", type_name, ")"
|
||||
| expr, [ "NOT" ], "LIKE", expr
|
||||
| expr, "IS", [ "NOT" ], "NULL"
|
||||
| expr, [ "NOT" ], "BETWEEN", expr, "AND", expr
|
||||
| expr, [ "NOT" ], "IN", "(", ( select_stmt | expr, { ",", expr } ), ")"
|
||||
| paren_select_stmt
|
||||
| case_expr ;
|
||||
|
||||
paren_select_stmt = "(", select_stmt, ")" ;
|
||||
|
||||
case_expr = "CASE", [ expr ], { "WHEN", expr, "THEN", expr }, [ "ELSE", expr ], "END" ;
|
||||
|
||||
unary_op = "-"
|
||||
| "+"
|
||||
| "!" ;
|
||||
|
||||
binary_op = "="
|
||||
| "!="
|
||||
| "<="
|
||||
| ">="
|
||||
| "&"
|
||||
| "|"
|
||||
| "<<"
|
||||
| ">>"
|
||||
| "+"
|
||||
| "-"
|
||||
| "*"
|
||||
| "/"
|
||||
| "%"
|
||||
| "||" ;
|
||||
|
||||
set_literal = "[", expr, { ",", expr }, "]" ;
|
||||
|
||||
date_literal = rfc_3339
|
||||
| "CURRENT_DATE"
|
||||
| "CURRENT_TIMESTAMP" ;
|
||||
|
||||
table_name = identifier ;
|
||||
|
||||
column_name = identifier ;
|
||||
|
||||
function_call = agg_function
|
||||
| non_agg_function ;
|
||||
|
||||
agg_function = ( "AVG" | "COUNT" | "MAX" | "MIN" | "SUM" ), "(", ( ( [ "DISTINCT" ], expr ) | "*" ), ")"
|
||||
| "PERCENTILE", "(", expr, ",", expr, ")" ;
|
||||
|
||||
non_agg_function =
|
||||
"SETCONTAINS" , "(", expr, ",", expr, ")"
|
||||
| "SETCONTAINSALL" , "(", expr, ",", expr, ")"
|
||||
| "SETCONTAINSANY" , "(", expr, ",", expr, ")"
|
||||
| "DATEPART" , "(", expr, ",", expr, ")" ;
|
||||
|
||||
identifier = letter , { letter | digit | "_" } ;
|
||||
|
||||
letter = "A" | "B" | "C" | "D" | "E" | "F" | "G"
|
||||
| "H" | "I" | "J" | "K" | "L" | "M" | "N"
|
||||
| "O" | "P" | "Q" | "R" | "S" | "T" | "U"
|
||||
| "V" | "W" | "X" | "Y" | "Z" | "a" | "b"
|
||||
| "c" | "d" | "e" | "f" | "g" | "h" | "i"
|
||||
| "j" | "k" | "l" | "m" | "n" | "o" | "p"
|
||||
| "q" | "r" | "s" | "t" | "u" | "v" | "w"
|
||||
| "x" | "y" | "z" ;
|
||||
|
||||
digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;
|
||||
|
||||
|
||||
517
sql3/sql_definitions_test.go
Normal file
517
sql3/sql_definitions_test.go
Normal file
|
|
@ -0,0 +1,517 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
package sql3_test
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/molecula/featurebase/v3/pql"
|
||||
)
|
||||
|
||||
// tableTests is the list of tests which get run by TestSQL_Execute in
|
||||
// sql_test.go. They're defined here just to keep the test definitions separate
|
||||
// from the test execution logic.
|
||||
var tableTests []tableTest = []tableTest{
|
||||
{
|
||||
name: "minmaxnegatives",
|
||||
table: tbl(
|
||||
"minmaxnegatives",
|
||||
srcHdrs(
|
||||
srcHdr("_id", fldTypeID),
|
||||
srcHdr("positive_int", fldTypeInt, "min 10", "max 100"),
|
||||
srcHdr("negative_int", fldTypeInt, "min -100", "max -10"),
|
||||
),
|
||||
srcRows(
|
||||
srcRow(int64(1), int64(11), int64(-11)),
|
||||
srcRow(int64(2), int64(22), int64(-22)),
|
||||
srcRow(int64(3), int64(33), int64(-33)),
|
||||
),
|
||||
),
|
||||
sqlTests: []sqlTest{},
|
||||
},
|
||||
{
|
||||
name: "unkeyed",
|
||||
table: tbl(
|
||||
"unkeyed",
|
||||
srcHdrs(
|
||||
srcHdr("_id", fldTypeID),
|
||||
srcHdr("an_int", fldTypeInt, "min 0", "max 100"),
|
||||
srcHdr("an_id_set", fldTypeIDSet),
|
||||
srcHdr("an_id", fldTypeID),
|
||||
srcHdr("a_string", fldTypeString),
|
||||
srcHdr("a_string_set", fldTypeStringSet),
|
||||
srcHdr("a_decimal", fldTypeDecimal2),
|
||||
),
|
||||
srcRows(
|
||||
srcRow(int64(1), int64(11), []int64{11, 12, 13}, int64(101), "str1", []string{"a1", "b1", "c1"}, float64(123.45)),
|
||||
srcRow(int64(2), int64(22), []int64{21, 22, 23}, int64(201), "str2", []string{"a2", "b2", "c2"}, float64(234.56)),
|
||||
srcRow(int64(3), int64(33), []int64{31, 32, 33}, int64(301), "str3", []string{"a3", "b3", "c3"}, float64(345.67)),
|
||||
srcRow(int64(4), int64(44), []int64{41, 42, 43}, int64(401), "str4", []string{"a4", "b4", "c4"}, float64(456.78)),
|
||||
),
|
||||
),
|
||||
sqlTests: []sqlTest{
|
||||
{
|
||||
// Select all.
|
||||
name: "select-all",
|
||||
sqls: sqls(
|
||||
"select * from unkeyed",
|
||||
"select _id, an_int, an_id_set, an_id, a_string, a_string_set, a_decimal from unkeyed",
|
||||
),
|
||||
expHdrs: hdrs(
|
||||
hdr("_id", fldTypeID),
|
||||
hdr("an_int", fldTypeInt),
|
||||
hdr("an_id_set", fldTypeIDSet),
|
||||
hdr("an_id", fldTypeID),
|
||||
hdr("a_string", fldTypeString),
|
||||
hdr("a_string_set", fldTypeStringSet),
|
||||
hdr("a_decimal", fldTypeDecimal2),
|
||||
),
|
||||
expRows: rows(
|
||||
row(int64(1), int64(11), []int64{11, 12, 13}, int64(101), "str1", []string{"a1", "b1", "c1"}, pql.NewDecimal(12345, 2)),
|
||||
row(int64(2), int64(22), []int64{21, 22, 23}, int64(201), "str2", []string{"a2", "b2", "c2"}, pql.NewDecimal(23456, 2)),
|
||||
row(int64(3), int64(33), []int64{31, 32, 33}, int64(301), "str3", []string{"a3", "b3", "c3"}, pql.NewDecimal(34567, 2)),
|
||||
row(int64(4), int64(44), []int64{41, 42, 43}, int64(401), "str4", []string{"a4", "b4", "c4"}, pql.NewDecimal(45678, 2)),
|
||||
),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
{
|
||||
// Select all with top.
|
||||
name: "select-all-with-top",
|
||||
sqls: sqls(
|
||||
"select top(2) * from unkeyed",
|
||||
),
|
||||
expHdrs: hdrs(
|
||||
hdr("_id", fldTypeID),
|
||||
hdr("an_int", fldTypeInt),
|
||||
hdr("an_id_set", fldTypeIDSet),
|
||||
hdr("an_id", fldTypeID),
|
||||
hdr("a_string", fldTypeString),
|
||||
hdr("a_string_set", fldTypeStringSet),
|
||||
hdr("a_decimal", fldTypeDecimal2),
|
||||
),
|
||||
expRows: rows(
|
||||
row(int64(1), int64(11), []int64{11, 12, 13}, int64(101), "str1", []string{"a1", "b1", "c1"}, pql.NewDecimal(12345, 2)),
|
||||
row(int64(2), int64(22), []int64{21, 22, 23}, int64(201), "str2", []string{"a2", "b2", "c2"}, pql.NewDecimal(23456, 2)),
|
||||
),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
{
|
||||
// Select all with where on each field.
|
||||
name: "select-all-with-where",
|
||||
sqls: sqls(
|
||||
"select * from unkeyed where an_int = 22",
|
||||
"select * from unkeyed where a_string = 'str2'",
|
||||
"select * from unkeyed where an_id = 201",
|
||||
),
|
||||
expHdrs: hdrs(
|
||||
hdr("_id", fldTypeID),
|
||||
hdr("an_int", fldTypeInt),
|
||||
hdr("an_id_set", fldTypeIDSet),
|
||||
hdr("an_id", fldTypeID),
|
||||
hdr("a_string", fldTypeString),
|
||||
hdr("a_string_set", fldTypeStringSet),
|
||||
hdr("a_decimal", fldTypeDecimal2),
|
||||
),
|
||||
expRows: rows(
|
||||
row(int64(2), int64(22), []int64{21, 22, 23}, int64(201), "str2", []string{"a2", "b2", "c2"}, pql.NewDecimal(23456, 2)),
|
||||
),
|
||||
compare: compareExactOrdered,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
table: tbl(
|
||||
"keyed",
|
||||
srcHdrs(
|
||||
srcHdr("_id", fldTypeString),
|
||||
srcHdr("an_int", fldTypeInt, "min 0", "max 100"),
|
||||
srcHdr("an_id_set", fldTypeIDSet),
|
||||
srcHdr("an_id", fldTypeID),
|
||||
srcHdr("a_string", fldTypeString),
|
||||
srcHdr("a_string_set", fldTypeStringSet),
|
||||
),
|
||||
srcRows(
|
||||
srcRow("one", int64(11), []int64{11, 12, 13}, int64(101), "str1", []string{"a1", "b1", "c1"}),
|
||||
srcRow("two", int64(22), []int64{21, 22, 23}, int64(201), "str2", []string{"a2", "b2", "c2"}),
|
||||
srcRow("three", int64(33), []int64{31, 32, 33}, int64(301), "str3", []string{"a3", "b3", "c3"}),
|
||||
srcRow("four", int64(44), []int64{41, 42, 43}, int64(401), "str4", []string{"a4", "b4", "c4"}),
|
||||
),
|
||||
),
|
||||
sqlTests: []sqlTest{
|
||||
{
|
||||
// Select all.
|
||||
name: "select-all",
|
||||
sqls: sqls(
|
||||
"select * from keyed",
|
||||
"select _id, an_int, an_id_set, an_id, a_string, a_string_set from keyed",
|
||||
),
|
||||
expHdrs: hdrs(
|
||||
hdr("_id", fldTypeString),
|
||||
hdr("an_int", fldTypeInt),
|
||||
hdr("an_id_set", fldTypeIDSet),
|
||||
hdr("an_id", fldTypeID),
|
||||
hdr("a_string", fldTypeString),
|
||||
hdr("a_string_set", fldTypeStringSet),
|
||||
),
|
||||
expRows: rows(
|
||||
row("one", int64(11), []int64{11, 12, 13}, int64(101), "str1", []string{"a1", "b1", "c1"}),
|
||||
row("two", int64(22), []int64{21, 22, 23}, int64(201), "str2", []string{"a2", "b2", "c2"}),
|
||||
row("three", int64(33), []int64{31, 32, 33}, int64(301), "str3", []string{"a3", "b3", "c3"}),
|
||||
row("four", int64(44), []int64{41, 42, 43}, int64(401), "str4", []string{"a4", "b4", "c4"}),
|
||||
),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
{
|
||||
// Select all with top.
|
||||
name: "select-all-with-top",
|
||||
sqls: sqls(
|
||||
"select top(2) * from keyed",
|
||||
),
|
||||
expHdrs: hdrs(
|
||||
hdr("_id", fldTypeString),
|
||||
hdr("an_int", fldTypeInt),
|
||||
hdr("an_id_set", fldTypeIDSet),
|
||||
hdr("an_id", fldTypeID),
|
||||
hdr("a_string", fldTypeString),
|
||||
hdr("a_string_set", fldTypeStringSet),
|
||||
),
|
||||
expRows: rows(
|
||||
row("one", int64(11), []int64{11, 12, 13}, int64(101), "str1", []string{"a1", "b1", "c1"}),
|
||||
row("two", int64(22), []int64{21, 22, 23}, int64(201), "str2", []string{"a2", "b2", "c2"}),
|
||||
row("three", int64(33), []int64{31, 32, 33}, int64(301), "str3", []string{"a3", "b3", "c3"}),
|
||||
row("four", int64(44), []int64{41, 42, 43}, int64(401), "str4", []string{"a4", "b4", "c4"}),
|
||||
),
|
||||
compare: compareIncludedIn,
|
||||
expRowCount: 2,
|
||||
},
|
||||
{
|
||||
// Select all with where on int field.
|
||||
name: "select-all-with-where",
|
||||
sqls: sqls(
|
||||
"select * from keyed where an_int = 22",
|
||||
"select * from keyed where a_string = 'str2'",
|
||||
"select * from keyed where an_id = 201",
|
||||
),
|
||||
expHdrs: hdrs(
|
||||
hdr("_id", fldTypeString),
|
||||
hdr("an_int", fldTypeInt),
|
||||
hdr("an_id_set", fldTypeIDSet),
|
||||
hdr("an_id", fldTypeID),
|
||||
hdr("a_string", fldTypeString),
|
||||
hdr("a_string_set", fldTypeStringSet),
|
||||
),
|
||||
expRows: rows(
|
||||
row("two", int64(22), []int64{21, 22, 23}, int64(201), "str2", []string{"a2", "b2", "c2"}),
|
||||
),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
setLiteralTests,
|
||||
setFunctionTests,
|
||||
setParameterTests,
|
||||
datePartTests,
|
||||
|
||||
insertTest,
|
||||
keyedInsertTest,
|
||||
timestampLiterals,
|
||||
unaryOpExprWithInt,
|
||||
unaryOpExprWithID,
|
||||
unaryOpExprWithBool,
|
||||
unaryOpExprWithDecimal,
|
||||
unaryOpExprWithTimestamp,
|
||||
unaryOpExprWithIDSet,
|
||||
unaryOpExprWithString,
|
||||
unaryOpExprWithStringSet,
|
||||
|
||||
binOpExprWithIntInt,
|
||||
binOpExprWithIntBool,
|
||||
binOpExprWithIntID,
|
||||
binOpExprWithIntDecimal,
|
||||
binOpExprWithIntTimestamp,
|
||||
binOpExprWithIntIDSet,
|
||||
binOpExprWithIntString,
|
||||
binOpExprWithIntStringSet,
|
||||
|
||||
binOpExprWithBoolInt,
|
||||
binOpExprWithBoolBool,
|
||||
binOpExprWithBoolID,
|
||||
binOpExprWithBoolDecimal,
|
||||
binOpExprWithBoolTimestamp,
|
||||
binOpExprWithBoolIDSet,
|
||||
binOpExprWithBoolString,
|
||||
binOpExprWithBoolStringSet,
|
||||
|
||||
binOpExprWithIDInt,
|
||||
binOpExprWithIDBool,
|
||||
binOpExprWithIDID,
|
||||
binOpExprWithIDDecimal,
|
||||
binOpExprWithIDTimestamp,
|
||||
binOpExprWithIDIDSet,
|
||||
binOpExprWithIDString,
|
||||
binOpExprWithIDStringSet,
|
||||
|
||||
binOpExprWithDecInt,
|
||||
binOpExprWithDecBool,
|
||||
binOpExprWithDecID,
|
||||
binOpExprWithDecDecimal,
|
||||
binOpExprWithDecTimestamp,
|
||||
binOpExprWithDecIDSet,
|
||||
binOpExprWithDecString,
|
||||
binOpExprWithDecStringSet,
|
||||
|
||||
binOpExprWithTSInt,
|
||||
binOpExprWithTSBool,
|
||||
binOpExprWithTSID,
|
||||
binOpExprWithTSDecimal,
|
||||
binOpExprWithTSTimestamp,
|
||||
binOpExprWithTSIDSet,
|
||||
binOpExprWithTSString,
|
||||
binOpExprWithTSStringSet,
|
||||
|
||||
binOpExprWithIDSetInt,
|
||||
binOpExprWithIDSetBool,
|
||||
binOpExprWithIDSetID,
|
||||
binOpExprWithIDSetDecimal,
|
||||
binOpExprWithIDSetTimestamp,
|
||||
binOpExprWithIDSetIDSet,
|
||||
binOpExprWithIDSetString,
|
||||
binOpExprWithIDSetStringSet,
|
||||
|
||||
binOpExprWithStringInt,
|
||||
binOpExprWithStringBool,
|
||||
binOpExprWithStringID,
|
||||
binOpExprWithStringDecimal,
|
||||
binOpExprWithStringTimestamp,
|
||||
binOpExprWithStringIDSet,
|
||||
binOpExprWithStringString,
|
||||
binOpExprWithStringStringSet,
|
||||
|
||||
binOpExprWithStringSetInt,
|
||||
binOpExprWithStringSetBool,
|
||||
binOpExprWithStringSetID,
|
||||
binOpExprWithStringSetDecimal,
|
||||
binOpExprWithStringSetTimestamp,
|
||||
binOpExprWithStringSetIDSet,
|
||||
binOpExprWithStringSetString,
|
||||
binOpExprWithStringSetStringSet,
|
||||
|
||||
//cast tests
|
||||
castIntLiteral,
|
||||
castInt,
|
||||
|
||||
castBool,
|
||||
castDecimal,
|
||||
castID,
|
||||
castIDSet,
|
||||
castString,
|
||||
castStringSet,
|
||||
castTimestamp,
|
||||
|
||||
//like tests
|
||||
likeTests,
|
||||
notLikeTests,
|
||||
|
||||
//null tests
|
||||
nullTests,
|
||||
notNullTests,
|
||||
|
||||
//between tests
|
||||
betweenTests,
|
||||
notBetweenTests,
|
||||
|
||||
//in tests
|
||||
inTests,
|
||||
notInTests,
|
||||
|
||||
//aggregate tests
|
||||
countTests,
|
||||
countDistinctTests,
|
||||
sumTests,
|
||||
avgTests,
|
||||
percentileTests,
|
||||
minmaxTests,
|
||||
|
||||
//groupby tests
|
||||
groupByTests,
|
||||
|
||||
//create table tests
|
||||
createTable,
|
||||
|
||||
//time quantums
|
||||
// Skip for now - timeQuantumInsertTest,
|
||||
}
|
||||
|
||||
var insertTest = tableTest{
|
||||
table: tbl(
|
||||
"testinsert",
|
||||
srcHdrs(
|
||||
srcHdr("_id", fldTypeID),
|
||||
srcHdr("a", fldTypeInt, "min 0", "max 1000"),
|
||||
srcHdr("b", fldTypeInt, "min 0", "max 1000"),
|
||||
srcHdr("s", fldTypeString),
|
||||
srcHdr("bl", fldTypeBool),
|
||||
srcHdr("d", fldTypeDecimal2),
|
||||
srcHdr("event", fldTypeStringSet),
|
||||
srcHdr("ievent", fldTypeIDSet),
|
||||
),
|
||||
nil,
|
||||
),
|
||||
sqlTests: []sqlTest{
|
||||
{
|
||||
// Insert
|
||||
sqls: sqls(
|
||||
"insert into testinsert (_id, a, b, s, bl, d, event, ievent) values (4, 40, 400, 'foo', false, 10.12, ['A', 'B', 'C'], [1, 2, 3])",
|
||||
),
|
||||
expHdrs: hdrs(),
|
||||
expRows: rows(),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
{
|
||||
// Insert with nulls
|
||||
sqls: sqls(
|
||||
"insert into testinsert (_id, a, b, s, bl, d, event, ievent) values (5, null, null, null, null, null, null, null)",
|
||||
"insert into testinsert (_id, a, b, s, bl, d, event, ievent) values (6, 1, null, null, null, null, null, null)",
|
||||
),
|
||||
expHdrs: hdrs(),
|
||||
expRows: rows(),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
{
|
||||
// InsertBadTable
|
||||
sqls: sqls(
|
||||
"insert into ifoo (a, b) values (1, 2)",
|
||||
),
|
||||
expErr: "table 'ifoo' not found",
|
||||
},
|
||||
{
|
||||
// InsertBadColumn
|
||||
sqls: sqls(
|
||||
"insert into testinsert (c, b) values (1, 2)",
|
||||
),
|
||||
expErr: "column 'c' not found",
|
||||
},
|
||||
{
|
||||
// InsertDupeColumn
|
||||
sqls: sqls(
|
||||
"insert into testinsert (a, a, b) values (1, 2)",
|
||||
),
|
||||
expErr: "duplicate column 'a'",
|
||||
},
|
||||
{
|
||||
// InsertMismatchColumnValues
|
||||
sqls: sqls(
|
||||
"insert into testinsert (_id, a, b) values (1)",
|
||||
),
|
||||
expErr: "mismatch in the count of expressions and target columns",
|
||||
},
|
||||
{
|
||||
// InsertHandleMissingColumns
|
||||
sqls: sqls(
|
||||
"insert into testinsert values (4, 40, 400)",
|
||||
),
|
||||
expErr: "mismatch in the count of expressions and target columns",
|
||||
},
|
||||
{
|
||||
// InsertHandleMissingId
|
||||
sqls: sqls(
|
||||
"insert into testinsert (a, b) values (1, 2)",
|
||||
),
|
||||
expErr: "insert column list must have '_id' column specified",
|
||||
},
|
||||
{
|
||||
// InsertHandleMissingIdPlusOneOther
|
||||
sqls: sqls(
|
||||
"insert into testinsert (_id) values (1)",
|
||||
),
|
||||
expErr: "insert column list must have at least one non '_id' column specified",
|
||||
},
|
||||
{
|
||||
// InsertSetsTypeError
|
||||
sqls: sqls(
|
||||
"insert into testinsert (_id, a, event) values (4, 40, [101, 150])",
|
||||
),
|
||||
expErr: "an expression of type 'IDSET' cannot be assigned to type 'STRINGSET'",
|
||||
},
|
||||
{
|
||||
// InsertSetsTypeError2
|
||||
sqls: sqls(
|
||||
"insert into testinsert (_id, a, ievent) values (4, 40, ['POST', 'GET'])",
|
||||
),
|
||||
expErr: "an expression of type 'STRINGSET' cannot be assigned to type 'IDSET'",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var keyedInsertTest = tableTest{
|
||||
name: "keyedinsert",
|
||||
table: tbl(
|
||||
"testkeyedinsert",
|
||||
srcHdrs(
|
||||
srcHdr("_id", fldTypeString),
|
||||
srcHdr("a", fldTypeInt, "min 0", "max 1000"),
|
||||
srcHdr("b", fldTypeInt, "min 0", "max 1000"),
|
||||
srcHdr("s", fldTypeString),
|
||||
srcHdr("bl", fldTypeBool),
|
||||
srcHdr("d", fldTypeDecimal2),
|
||||
srcHdr("event", fldTypeStringSet),
|
||||
srcHdr("ievent", fldTypeIDSet),
|
||||
),
|
||||
nil,
|
||||
),
|
||||
sqlTests: []sqlTest{
|
||||
{
|
||||
// Insert
|
||||
sqls: sqls(
|
||||
"insert into testkeyedinsert (_id, a, b, s, bl, d, event, ievent) values ('four', 40, 400, 'foo', false, 10.12, ['A', 'B', 'C'], [1, 2, 3])",
|
||||
),
|
||||
expHdrs: hdrs(),
|
||||
expRows: rows(),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func knownTimestamp() time.Time {
|
||||
tm, err := time.ParseInLocation(time.RFC3339, "2012-11-01T22:08:41+00:00", time.UTC)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
return tm
|
||||
}
|
||||
|
||||
var timestampLiterals = tableTest{
|
||||
table: tbl(
|
||||
"testtimestampliterals",
|
||||
srcHdrs(
|
||||
srcHdr("_id", fldTypeID),
|
||||
srcHdr("a", fldTypeInt, "min 0", "max 1000"),
|
||||
srcHdr("b", fldTypeInt, "min 0", "max 1000"),
|
||||
srcHdr("d", fldTypeDecimal2),
|
||||
srcHdr("ts", fldTypeTimestamp),
|
||||
srcHdr("event", fldTypeStringSet),
|
||||
srcHdr("ievent", fldTypeIDSet),
|
||||
),
|
||||
srcRows(),
|
||||
),
|
||||
sqlTests: []sqlTest{
|
||||
{
|
||||
// InsertWithCurrentTimestamp
|
||||
sqls: sqls(
|
||||
"insert into testtimestampliterals (_id, a, b, d, ts, event, ievent) values (4, 40, 400, 10.12, current_timestamp, ['A', 'B', 'C'], [1, 2, 3])",
|
||||
),
|
||||
expHdrs: hdrs(),
|
||||
expRows: rows(),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
{
|
||||
// InsertWithCurrentDate
|
||||
sqls: sqls(
|
||||
"insert into testtimestampliterals (_id, a, b, d, ts, event, ievent) values (4, 40, 400, 10.12, current_date, ['A', 'B', 'C'], [1, 2, 3])",
|
||||
),
|
||||
expHdrs: hdrs(),
|
||||
expRows: rows(),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
},
|
||||
}
|
||||
507
sql3/sql_defs_aggregate_test.go
Normal file
507
sql3/sql_defs_aggregate_test.go
Normal file
|
|
@ -0,0 +1,507 @@
|
|||
package sql3_test
|
||||
|
||||
import (
|
||||
"github.com/molecula/featurebase/v3/pql"
|
||||
"github.com/molecula/featurebase/v3/sql3/parser"
|
||||
)
|
||||
|
||||
//aggregate function tests
|
||||
var countTests = tableTest{
|
||||
table: tbl(
|
||||
"count_test",
|
||||
srcHdrs(
|
||||
srcHdr("_id", fldTypeID),
|
||||
srcHdr("i1", fldTypeInt, "min 0", "max 1000"),
|
||||
srcHdr("d1", fldTypeDecimal2),
|
||||
srcHdr("i2", fldTypeInt, "min 0", "max 1000"),
|
||||
),
|
||||
srcRows(
|
||||
srcRow(int64(1), int64(10), float64(10), int64(100)),
|
||||
srcRow(int64(2), int64(10), float64(10), int64(200)),
|
||||
srcRow(int64(3), int64(11), float64(11), nil),
|
||||
srcRow(int64(4), int64(12), float64(12), nil),
|
||||
srcRow(int64(5), int64(12), float64(12), nil),
|
||||
srcRow(int64(6), int64(13), float64(13), nil),
|
||||
),
|
||||
),
|
||||
sqlTests: []sqlTest{
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT COUNT(i1, d1) AS count_rows FROM count_test",
|
||||
),
|
||||
expErr: "count of formal parameters (1) does not match count of actual parameters (2)",
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT COUNT(1) AS count_rows FROM count_test",
|
||||
),
|
||||
expErr: "column reference expected",
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT COUNT(*) AS count_rows FROM count_test",
|
||||
"SELECT COUNT(_id) AS count_rows FROM count_test",
|
||||
),
|
||||
expHdrs: hdrs(
|
||||
hdr("count_rows", fldTypeInt),
|
||||
),
|
||||
expRows: rows(
|
||||
row(int64(6)),
|
||||
),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT COUNT(*) + 10 - 11 * 2 AS count_rows FROM count_test",
|
||||
),
|
||||
expHdrs: hdrs(
|
||||
hdr("count_rows", fldTypeInt),
|
||||
),
|
||||
expRows: rows(
|
||||
row(int64(-6)),
|
||||
),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT COUNT(*) AS count_rows FROM count_test WHERE i1 = 10",
|
||||
),
|
||||
expHdrs: hdrs(
|
||||
hdr("count_rows", fldTypeInt),
|
||||
),
|
||||
expRows: rows(
|
||||
row(int64(2)),
|
||||
),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT COUNT(*) AS count_rows FROM count_test WHERE i1 != 10",
|
||||
),
|
||||
expHdrs: hdrs(
|
||||
hdr("count_rows", fldTypeInt),
|
||||
),
|
||||
expRows: rows(
|
||||
row(int64(4)),
|
||||
),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT COUNT(*) AS count_rows FROM count_test WHERE i1 < 12",
|
||||
),
|
||||
expHdrs: hdrs(
|
||||
hdr("count_rows", fldTypeInt),
|
||||
),
|
||||
expRows: rows(
|
||||
row(int64(3)),
|
||||
),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT COUNT(*) AS count_rows FROM count_test WHERE i1 > 12",
|
||||
),
|
||||
expHdrs: hdrs(
|
||||
hdr("count_rows", fldTypeInt),
|
||||
),
|
||||
expRows: rows(
|
||||
row(int64(1)),
|
||||
),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT COUNT(*) AS count_rows FROM count_test WHERE i1 = 10 AND i2 = 100",
|
||||
),
|
||||
expHdrs: hdrs(
|
||||
hdr("count_rows", fldTypeInt),
|
||||
),
|
||||
expRows: rows(
|
||||
row(int64(1)),
|
||||
),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT COUNT(*) AS count_rows FROM count_test WHERE i1 = 10 OR i1 = 200 OR i1 = 12",
|
||||
),
|
||||
expHdrs: hdrs(
|
||||
hdr("count_rows", fldTypeInt),
|
||||
),
|
||||
expRows: rows(
|
||||
row(int64(4)),
|
||||
),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var countDistinctTests = tableTest{
|
||||
table: tbl(
|
||||
"count_d_test",
|
||||
srcHdrs(
|
||||
srcHdr("_id", fldTypeID),
|
||||
srcHdr("i1", fldTypeInt, "min 0", "max 1000"),
|
||||
srcHdr("i2", fldTypeInt, "min 0", "max 1000"),
|
||||
),
|
||||
srcRows(
|
||||
srcRow(int64(1), int64(10), int64(100)),
|
||||
srcRow(int64(2), int64(10), int64(200)),
|
||||
srcRow(int64(3), int64(11), nil),
|
||||
srcRow(int64(4), int64(12), nil),
|
||||
srcRow(int64(5), int64(12), nil),
|
||||
srcRow(int64(6), int64(13), nil),
|
||||
),
|
||||
),
|
||||
sqlTests: []sqlTest{
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT COUNT(distinct i1) AS count_rows FROM count_d_test",
|
||||
),
|
||||
expHdrs: hdrs(
|
||||
hdr("count_rows", fldTypeInt),
|
||||
),
|
||||
expRows: rows(
|
||||
row(int64(4)),
|
||||
),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT COUNT(distinct i1) AS count_rows FROM count_d_test where i1 > 11",
|
||||
),
|
||||
expHdrs: hdrs(
|
||||
hdr("count_rows", fldTypeInt),
|
||||
),
|
||||
expRows: rows(
|
||||
row(int64(2)),
|
||||
),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT COUNT(distinct i1) AS count_rows, sum(i1) as sum_rows FROM count_d_test where i1 > 11",
|
||||
),
|
||||
expHdrs: hdrs(
|
||||
hdr("count_rows", fldTypeInt),
|
||||
hdr("sum_rows", fldTypeInt),
|
||||
),
|
||||
expRows: rows(
|
||||
row(int64(2), int64(37)),
|
||||
),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var sumTests = tableTest{
|
||||
table: tbl(
|
||||
"sum_test",
|
||||
srcHdrs(
|
||||
srcHdr("_id", fldTypeID),
|
||||
srcHdr("i1", fldTypeInt, "min 0", "max 1000"),
|
||||
srcHdr("d1", fldTypeDecimal2),
|
||||
srcHdr("i2", fldTypeInt, "min 0", "max 1000"),
|
||||
srcHdr("s1", fldTypeString),
|
||||
),
|
||||
srcRows(
|
||||
srcRow(int64(1), int64(10), float64(10), int64(100), string("foo")),
|
||||
srcRow(int64(2), int64(10), float64(10), int64(200), string("foo")),
|
||||
srcRow(int64(3), int64(11), float64(11), nil, string("foo")),
|
||||
srcRow(int64(4), int64(12), float64(12), nil, string("foo")),
|
||||
srcRow(int64(5), int64(12), float64(12), nil, string("foo")),
|
||||
srcRow(int64(6), int64(13), float64(13), nil, string("foo")),
|
||||
),
|
||||
),
|
||||
sqlTests: []sqlTest{
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT sum(*) AS sum_rows FROM sum_test",
|
||||
),
|
||||
expErr: "column reference expected",
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT sum(_id) AS sum_rows FROM sum_test",
|
||||
),
|
||||
expErr: "_id column cannot be used in aggregate function 'sum'",
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT sum(1) AS sum_rows FROM sum_test",
|
||||
),
|
||||
expErr: "column reference expected",
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT sum(i1, d1) AS sum_rows FROM sum_test",
|
||||
),
|
||||
expErr: "count of formal parameters (1) does not match count of actual parameters (2)",
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT sum(i1) AS sum_rows FROM sum_test",
|
||||
),
|
||||
expHdrs: hdrs(
|
||||
hdr("sum_rows", fldTypeInt),
|
||||
),
|
||||
expRows: rows(
|
||||
row(int64(68)),
|
||||
),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT sum(d1) AS sum_rows FROM sum_test",
|
||||
),
|
||||
expHdrs: hdrs(
|
||||
hdr("sum_rows", fldTypeDecimal2),
|
||||
),
|
||||
expRows: rows(
|
||||
row(pql.NewDecimal(6800, 2)),
|
||||
),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var avgTests = tableTest{
|
||||
table: tbl(
|
||||
"avg_test",
|
||||
srcHdrs(
|
||||
srcHdr("_id", fldTypeID),
|
||||
srcHdr("i1", fldTypeInt, "min 0", "max 1000"),
|
||||
srcHdr("d1", fldTypeDecimal2),
|
||||
srcHdr("s1", fldTypeString),
|
||||
),
|
||||
srcRows(
|
||||
srcRow(int64(1), int64(10), float64(10), string("foo")),
|
||||
srcRow(int64(2), int64(10), float64(10), string("foo")),
|
||||
srcRow(int64(3), int64(11), float64(11), string("foo")),
|
||||
srcRow(int64(4), int64(12), float64(12), string("foo")),
|
||||
srcRow(int64(5), int64(12), float64(12), string("foo")),
|
||||
srcRow(int64(6), int64(13), float64(13), string("foo")),
|
||||
),
|
||||
),
|
||||
sqlTests: []sqlTest{
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT avg(*) AS avg_rows FROM avg_test",
|
||||
),
|
||||
expErr: "column reference expected",
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT avg(_id) AS avg_rows FROM avg_test",
|
||||
),
|
||||
expErr: "_id column cannot be used in aggregate function 'avg'",
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT avg(i1, d1) AS avg_rows FROM avg_test",
|
||||
),
|
||||
expErr: "count of formal parameters (1) does not match count of actual parameters (2)",
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT avg(s1) AS avg_rows FROM avg_test",
|
||||
),
|
||||
expErr: "integer or decimal expression expected",
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT avg(i1) AS avg_rows FROM avg_test",
|
||||
"SELECT avg(d1) AS avg_rows FROM avg_test",
|
||||
),
|
||||
expHdrs: hdrs(
|
||||
hdr("avg_rows", parser.NewDataTypeDecimal(4)),
|
||||
),
|
||||
expRows: rows(
|
||||
row(pql.NewDecimal(113333, 4)),
|
||||
),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var percentileTests = tableTest{
|
||||
table: tbl(
|
||||
"percentile_test",
|
||||
srcHdrs(
|
||||
srcHdr("_id", fldTypeID),
|
||||
srcHdr("i1", fldTypeInt, "min 0", "max 1000"),
|
||||
srcHdr("d1", fldTypeDecimal2),
|
||||
srcHdr("s1", fldTypeString),
|
||||
),
|
||||
srcRows(
|
||||
srcRow(int64(1), int64(10), float64(10), string("foo")),
|
||||
srcRow(int64(2), int64(10), float64(10), string("foo")),
|
||||
srcRow(int64(3), int64(11), float64(11), string("foo")),
|
||||
srcRow(int64(4), int64(12), float64(12), string("foo")),
|
||||
srcRow(int64(5), int64(12), float64(12), string("foo")),
|
||||
srcRow(int64(6), int64(13), float64(13), string("foo")),
|
||||
),
|
||||
),
|
||||
sqlTests: []sqlTest{
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT percentile(*) AS avg_rows FROM percentile_test",
|
||||
),
|
||||
expErr: "column reference expected",
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT percentile(10, i1) AS avg_rows FROM percentile_test",
|
||||
),
|
||||
expErr: "column reference expected",
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT percentile(_id, 50) AS avg_rows FROM percentile_test",
|
||||
),
|
||||
expErr: "_id column cannot be used in aggregate function 'percentile'",
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT percentile(i1, d1) AS avg_rows FROM percentile_test",
|
||||
),
|
||||
expErr: "literal expression expected",
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT percentile(s1, 50) AS avg_rows FROM percentile_test",
|
||||
),
|
||||
expErr: "integer, decimal or timestamp expression expected",
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT percentile(i1, 50) AS p_rows FROM percentile_test",
|
||||
),
|
||||
expHdrs: hdrs(
|
||||
hdr("p_rows", fldTypeInt),
|
||||
),
|
||||
expRows: rows(
|
||||
row(int64(12)),
|
||||
),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT percentile(d1, 50) AS p_rows FROM percentile_test",
|
||||
),
|
||||
expHdrs: hdrs(
|
||||
hdr("p_rows", fldTypeDecimal2),
|
||||
),
|
||||
expRows: rows(
|
||||
row(pql.NewDecimal(1000, 2)),
|
||||
),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var minmaxTests = tableTest{
|
||||
table: tbl(
|
||||
"minmax_test",
|
||||
srcHdrs(
|
||||
srcHdr("_id", fldTypeID),
|
||||
srcHdr("i1", fldTypeInt, "min 0", "max 1000"),
|
||||
srcHdr("d1", fldTypeDecimal2),
|
||||
srcHdr("s1", fldTypeString),
|
||||
),
|
||||
srcRows(
|
||||
srcRow(int64(1), int64(10), float64(10), string("foo")),
|
||||
srcRow(int64(2), int64(10), float64(10), string("foo")),
|
||||
srcRow(int64(3), int64(11), float64(11), string("foo")),
|
||||
srcRow(int64(4), int64(12), float64(12), string("foo")),
|
||||
srcRow(int64(5), int64(12), float64(12), string("foo")),
|
||||
srcRow(int64(6), int64(13), float64(13), string("foo")),
|
||||
),
|
||||
),
|
||||
sqlTests: []sqlTest{
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT min(*) AS p_rows FROM minmax_test",
|
||||
"SELECT max(*) AS p_rows FROM minmax_test",
|
||||
),
|
||||
expErr: "column reference expected",
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT min(i1, d1) AS p_rows FROM minmax_test",
|
||||
"SELECT max(i1, d1) AS p_rows FROM minmax_test",
|
||||
),
|
||||
expErr: "count of formal parameters (1) does not match count of actual parameters (2)",
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT min(1) AS p_rows FROM minmax_test",
|
||||
"SELECT max(1) AS p_rows FROM minmax_test",
|
||||
),
|
||||
expErr: "column reference expected",
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT min(_id) AS p_rows FROM minmax_test",
|
||||
"SELECT max(_id) AS p_rows FROM minmax_test",
|
||||
),
|
||||
expErr: "_id column cannot be used in aggregate function",
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT min(s1) AS p_rows FROM minmax_test",
|
||||
"SELECT max(s1) AS p_rows FROM minmax_test",
|
||||
),
|
||||
expErr: "integer, decimal or timestamp expression expected",
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT min(i1) AS p_rows FROM minmax_test",
|
||||
),
|
||||
expHdrs: hdrs(
|
||||
hdr("p_rows", fldTypeInt),
|
||||
),
|
||||
expRows: rows(
|
||||
row(int64(10)),
|
||||
),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT max(i1) AS p_rows FROM minmax_test",
|
||||
),
|
||||
expHdrs: hdrs(
|
||||
hdr("p_rows", fldTypeInt),
|
||||
),
|
||||
expRows: rows(
|
||||
row(int64(13)),
|
||||
),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT min(d1) AS p_rows FROM minmax_test",
|
||||
),
|
||||
expHdrs: hdrs(
|
||||
hdr("p_rows", fldTypeDecimal2),
|
||||
),
|
||||
expRows: rows(
|
||||
row(pql.NewDecimal(1000, 2)),
|
||||
),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"SELECT max(d1) AS p_rows FROM minmax_test",
|
||||
),
|
||||
expHdrs: hdrs(
|
||||
hdr("p_rows", fldTypeDecimal2),
|
||||
),
|
||||
expRows: rows(
|
||||
row(pql.NewDecimal(1300, 2)),
|
||||
),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
},
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue