diff --git a/cmd/xdb/main.go b/cmd/xdb/main.go index 737cfc4f..866ca240 100644 --- a/cmd/xdb/main.go +++ b/cmd/xdb/main.go @@ -6,6 +6,7 @@ import ( "io" "os" "os/signal" + "path/filepath" "syscall" "github.com/rs/zerolog" @@ -167,7 +168,7 @@ func startNode(cmd *cobra.Command, args []string) { func createLogger(stdin io.Reader, stdout, stderr io.Writer) zerolog.Logger { // open the log file - file, err := os.OpenFile(logfile, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0600) + file, err := os.OpenFile(filepath.Clean(logfile), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0600) if err != nil { _, _ = fmt.Fprintln(stderr, fmt.Errorf("open logfile: %w", err).Error()) os.Exit(ExitAbnormal) diff --git a/internal/compiler/command/command.go b/internal/compiler/command/command.go index d4ae8734..c50af20a 100644 --- a/internal/compiler/command/command.go +++ b/internal/compiler/command/command.go @@ -237,8 +237,8 @@ type ( // this is a representation derived from the AST. If this is empty, the // executor has to interpolate the table from the execution context. Table string - // Name is the name of the column. - Name Expr + // Expr is the name of the column. + Expr Expr // Alias is the alias name for this table. May be empty. Alias string } @@ -422,9 +422,9 @@ func (u UpdateSetter) String() string { func (c Column) String() string { if c.Alias == "" { - return c.Name.String() + return c.Expr.String() } - return fmt.Sprintf("%v AS %v", c.Name, c.Alias) + return fmt.Sprintf("%v AS %v", c.Expr, c.Alias) } func (j Join) String() string { diff --git a/internal/compiler/command/expr.go b/internal/compiler/command/expr.go index 3bbd28f4..1c32aba4 100644 --- a/internal/compiler/command/expr.go +++ b/internal/compiler/command/expr.go @@ -15,11 +15,26 @@ type ( _expr() } - // LiteralExpr is a simple literal expression that has a single string - // value. - LiteralExpr struct { - // Value is the simple string value of this expression. - Value string + // ConstantLiteral is a constant literal expression. The Numeric flag determines whether + // or not this is a numeric literal. + ConstantLiteral struct { + Value string + Numeric bool + } + + // ConstantLiteralOrColumnReference is a string literal that represents either a constant + // string value or a column reference. If this literal is resolved, the column reference + // takes precedence over the string literal, meaning that if a column exists, whose name is + // equal to the value of this expression, the value in that column must be used. Only if there + // is no such column present, the string literal is to be used. + ConstantLiteralOrColumnReference struct { + ValueOrName string + } + + // ColumnReference is a string literal that represents a reference to a column. During resolving, + // if no column with such a name is present, an error must be risen. + ColumnReference struct { + Name string } // ConstantBooleanExpr is a simple expression that represents a boolean @@ -57,15 +72,46 @@ type ( } ) -func (LiteralExpr) _expr() {} func (ConstantBooleanExpr) _expr() {} func (RangeExpr) _expr() {} func (FunctionExpr) _expr() {} -func (l LiteralExpr) String() string { +func (ConstantLiteral) _expr() {} +func (ConstantLiteralOrColumnReference) _expr() {} +func (ColumnReference) _expr() {} + +// ConstantValue returns the constant value of this literal. +func (l ConstantLiteral) ConstantValue() string { return l.Value } +func (l ConstantLiteral) String() string { + return l.Value +} + +// ConstantValue returns the constant value of this literal. +func (l ConstantLiteralOrColumnReference) ConstantValue() string { + return l.ValueOrName +} + +// ReferencedColName returns the constant name of the referenced column name. +func (l ConstantLiteralOrColumnReference) ReferencedColName() string { + return l.ValueOrName +} + +func (l ConstantLiteralOrColumnReference) String() string { + return l.ValueOrName +} + +// ReferencedColName returns the constant name of the referenced column name. +func (l ColumnReference) ReferencedColName() string { + return l.Name +} + +func (l ColumnReference) String() string { + return l.Name +} + func (b ConstantBooleanExpr) String() string { return strconv.FormatBool(b.Value) } diff --git a/internal/compiler/numeric_parser.go b/internal/compiler/numeric_parser.go new file mode 100644 index 00000000..dd2360a5 --- /dev/null +++ b/internal/compiler/numeric_parser.go @@ -0,0 +1,156 @@ +package compiler + +import ( + "bytes" + "strings" +) + +type numericParserState func(*numericParser) numericParserState + +type numericParser struct { + candidate string + index int + + isReal bool + isHexadecimal bool + isErronous bool + hasDigitsBeforeExponent bool + + value *bytes.Buffer + + current numericParserState +} + +// isNumeric is an adapted copy of (engine.Engine).ToNumericValue. +func isNumeric(s string) bool { + if s == "" || s == "0x" || s == "." { + return false + } + p := numericParser{ + candidate: s, + index: 0, + + value: &bytes.Buffer{}, + + current: stateInitial, + } + p.parse() + return !p.isErronous +} + +func (p numericParser) done() bool { + return p.index >= len(p.candidate) +} + +func (p *numericParser) parse() { + for p.current != nil && !p.done() { + p.current = p.current(p) + } +} + +func (p *numericParser) get() byte { + return p.candidate[p.index] +} + +func (p *numericParser) step() { + _ = p.value.WriteByte(p.get()) + p.index++ +} + +func stateInitial(p *numericParser) numericParserState { + switch { + case strings.HasPrefix(p.candidate, "0x"): + p.index += 2 + p.isHexadecimal = true + return stateHex + case isDigit(p.get()): + return stateFirstDigits + case p.get() == '.': + return stateDecimalPoint + } + p.isErronous = true + return nil +} + +func stateHex(p *numericParser) numericParserState { + if isHexDigit(p.get()) { + p.step() + return stateHex + } + p.isErronous = true + return nil +} + +func stateFirstDigits(p *numericParser) numericParserState { + if isDigit(p.get()) { + p.hasDigitsBeforeExponent = true + p.step() + return stateFirstDigits + } else if p.get() == '.' { + return stateDecimalPoint + } + p.isErronous = true + return nil +} + +func stateDecimalPoint(p *numericParser) numericParserState { + if p.get() == '.' { + p.step() + p.isReal = true + return stateSecondDigits + } + p.isErronous = true + return nil +} + +func stateSecondDigits(p *numericParser) numericParserState { + if isDigit(p.get()) { + p.hasDigitsBeforeExponent = true + p.step() + return stateSecondDigits + } else if p.get() == 'E' { + if p.hasDigitsBeforeExponent { + return stateExponent + } + p.isErronous = true // if there were no first digits, + } + p.isErronous = true + return nil +} + +func stateExponent(p *numericParser) numericParserState { + if p.get() == 'E' { + p.step() + return stateOptionalSign + } + p.isErronous = true + return nil +} + +func stateOptionalSign(p *numericParser) numericParserState { + if p.get() == '+' || p.get() == '-' { + p.step() + return stateThirdDigits + } else if isDigit(p.get()) { + return stateThirdDigits + } + p.isErronous = true + return nil +} + +func stateThirdDigits(p *numericParser) numericParserState { + if isDigit(p.get()) { + p.step() + return stateThirdDigits + } + p.isErronous = true + return nil +} + +func isDigit(b byte) bool { + return b-'0' <= 9 +} + +func isHexDigit(b byte) bool { + return isDigit(b) || (b-'A' <= 15) || (b-'a' <= 15) +} diff --git a/internal/compiler/numeric_parser_bench_test.go b/internal/compiler/numeric_parser_bench_test.go new file mode 100644 index 00000000..d5b3fb88 --- /dev/null +++ b/internal/compiler/numeric_parser_bench_test.go @@ -0,0 +1,17 @@ +package compiler + +import ( + "testing" +) + +func BenchmarkToNumericValue(b *testing.B) { + str := "75610342.92389E-21423" + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + if !isNumeric(str) { + b.FailNow() + } + } +} diff --git a/internal/compiler/numeric_parser_test.go b/internal/compiler/numeric_parser_test.go new file mode 100644 index 00000000..5743d8c6 --- /dev/null +++ b/internal/compiler/numeric_parser_test.go @@ -0,0 +1,106 @@ +package compiler + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIsNumeric(t *testing.T) { + tests := []struct { + name string + s string + want bool + }{ + { + "empty", + "", + false, + }, + { + "string", + "hello", + false, + }, + { + "half hex", + "0x", + false, + }, + { + "hex", + "0x123ABC", + true, + }, + { + "hex", + "0xFF", + true, + }, + { + "full hex spectrum", + "0x0123456789ABCDEF", + true, + }, + { + "full hex spectrum", + "0xFEDCBA987654321", + true, + }, + { + "small integral", + "0", + true, + }, + { + "small integral", + "1", + true, + }, + { + "integral", + "1234567", + true, + }, + { + "integral", + "42", + true, + }, + { + "real", + "0.0", + true, + }, + { + "real", + ".0", + true, + }, + { + "only decimal point", + ".", + false, + }, + { + "real with exponent", + ".0E2", + true, + }, + { + "real with exponent", + "5.7E-242", + true, + }, + { + "invalid exponent", + ".0e2", // lowercase 'e' is not allowed + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, isNumeric(tt.s)) + }) + } +} diff --git a/internal/compiler/optimization/half_join_bench_test.go b/internal/compiler/optimization/half_join_bench_test.go index 6519ce46..c7b2d996 100644 --- a/internal/compiler/optimization/half_join_bench_test.go +++ b/internal/compiler/optimization/half_join_bench_test.go @@ -12,19 +12,21 @@ func Benchmark_OptHalfJoin(b *testing.B) { cmd := command.Project{ Cols: []command.Column{ { - Name: command.LiteralExpr{Value: "col1"}, + Expr: command.ColumnReference{Name: "col1"}, Alias: "myCol", }, - {Name: command.LiteralExpr{Value: "col2"}}, + { + Expr: command.ColumnReference{Name: "col2"}, + }, }, Input: command.Select{ Filter: command.EqualityExpr{ BinaryBase: command.BinaryBase{ - Left: command.LiteralExpr{ - Value: "foobar", + Left: command.ColumnReference{ + Name: "foobar", }, - Right: command.LiteralExpr{ - Value: "snafu", + Right: command.ColumnReference{ + Name: "snafu", }, }, Invert: true, diff --git a/internal/compiler/simple_compiler.go b/internal/compiler/simple_compiler.go index 7fbe9499..fffcdb37 100644 --- a/internal/compiler/simple_compiler.go +++ b/internal/compiler/simple_compiler.go @@ -9,6 +9,7 @@ import ( "github.com/xqueries/xdb/internal/compiler/optimization" "github.com/xqueries/xdb/internal/engine/types" "github.com/xqueries/xdb/internal/parser/ast" + "github.com/xqueries/xdb/internal/parser/scanner/token" ) type simpleCompiler struct { @@ -206,7 +207,7 @@ func (c *simpleCompiler) compileInsert(stmt *ast.InsertStmt) (command.Insert, er if len(stmt.ColumnName) != 0 { for _, col := range stmt.ColumnName { cols = append(cols, command.Column{ - Name: command.LiteralExpr{Value: col.Value()}, + Expr: command.ConstantLiteral{Value: col.Value()}, }) } } @@ -510,21 +511,9 @@ func (c *simpleCompiler) compileSelectCoreSelect(core *ast.SelectCore) (command. } } else if len(core.TableOrSubquery) == 0 { if core.JoinClause == nil { - // if there's no table to select from, use the projection columns as values, but keep the aliases - var values []command.Expr - for _, col := range cols { - values = append(values, col.Name) - } - var projectedCols []command.Column - for i, col := range cols { - projectedCols = append(projectedCols, command.Column{ - Name: command.LiteralExpr{Value: "column" + strconv.Itoa(i+1)}, - Alias: col.Alias, - }) - } return command.Project{ - Cols: projectedCols, - Input: command.Values{Values: [][]command.Expr{values}}, + Cols: cols, + Input: command.Values{Values: [][]command.Expr{}}, }, nil } @@ -581,7 +570,7 @@ func (c *simpleCompiler) compileResultColumn(col *ast.ResultColumn) (command.Col } return command.Column{ Table: tableName, - Name: command.LiteralExpr{Value: "*"}, + Expr: command.ColumnReference{Name: "*"}, }, nil } @@ -597,17 +586,33 @@ func (c *simpleCompiler) compileResultColumn(col *ast.ResultColumn) (command.Col return command.Column{ Alias: alias, - Name: expr, + Expr: expr, }, nil } func (c *simpleCompiler) compileExpr(expr *ast.Expr) (command.Expr, error) { switch { case expr.LiteralValue != nil: - if val := strings.ToLower(expr.LiteralValue.Value()); val == "true" || val == "false" { + literalValue := expr.LiteralValue.Value() + if val := strings.ToLower(literalValue); val == "true" || val == "false" { return command.ConstantBooleanExpr{Value: val == "true"}, nil } - return command.LiteralExpr{Value: expr.LiteralValue.Value()}, nil + if strings.HasPrefix(literalValue, "\"") { + unquoted, err := strconv.Unquote(literalValue) + if err != nil { + return nil, fmt.Errorf("unquote: %w", err) + } + return command.ConstantLiteralOrColumnReference{ValueOrName: unquoted}, nil + } else if strings.HasPrefix(literalValue, "'") { + unquoted, err := strconv.Unquote(literalValue) + if err != nil { + return nil, fmt.Errorf("unquote: %w", err) + } + return command.ConstantLiteral{Value: unquoted}, nil + } else if expr.LiteralValue.Type() == token.LiteralNumeric { + return command.ConstantLiteral{Value: literalValue, Numeric: true}, nil + } + return command.ColumnReference{Name: literalValue}, nil case expr.UnaryOperator != nil: val, err := c.compileExpr(expr.Expr1) if err != nil { diff --git a/internal/compiler/simple_compiler_test.go b/internal/compiler/simple_compiler_test.go index 8be40534..150613d4 100644 --- a/internal/compiler/simple_compiler_test.go +++ b/internal/compiler/simple_compiler_test.go @@ -33,9 +33,9 @@ func _TestSimpleCompilerCompileInsertNoOptimizations(t *testing.T) { Input: command.Values{ Values: [][]command.Expr{ { - command.LiteralExpr{Value: "1"}, - command.LiteralExpr{Value: "2"}, - command.LiteralExpr{Value: "3"}, + command.ConstantLiteral{Value: "1", Numeric: true}, + command.ConstantLiteral{Value: "2", Numeric: true}, + command.ConstantLiteral{Value: "3", Numeric: true}, }, }, }, @@ -53,9 +53,9 @@ func _TestSimpleCompilerCompileInsertNoOptimizations(t *testing.T) { Input: command.Values{ Values: [][]command.Expr{ { - command.LiteralExpr{Value: "1"}, - command.LiteralExpr{Value: "2"}, - command.LiteralExpr{Value: "3"}, + command.ConstantLiteral{Value: "1", Numeric: true}, + command.ConstantLiteral{Value: "2", Numeric: true}, + command.ConstantLiteral{Value: "3", Numeric: true}, }, }, }, @@ -73,14 +73,14 @@ func _TestSimpleCompilerCompileInsertNoOptimizations(t *testing.T) { Input: command.Values{ Values: [][]command.Expr{ { - command.LiteralExpr{Value: "1"}, - command.LiteralExpr{Value: "2"}, - command.LiteralExpr{Value: "3"}, + command.ConstantLiteral{Value: "1", Numeric: true}, + command.ConstantLiteral{Value: "2", Numeric: true}, + command.ConstantLiteral{Value: "3", Numeric: true}, }, { - command.LiteralExpr{Value: "4"}, - command.LiteralExpr{Value: "5"}, - command.LiteralExpr{Value: "6"}, + command.ConstantLiteral{Value: "4", Numeric: true}, + command.ConstantLiteral{Value: "5", Numeric: true}, + command.ConstantLiteral{Value: "6", Numeric: true}, }, }, }, @@ -98,7 +98,7 @@ func _TestSimpleCompilerCompileInsertNoOptimizations(t *testing.T) { Input: command.Project{ Cols: []command.Column{ { - Name: command.LiteralExpr{Value: "*"}, + Expr: command.ColumnReference{Name: "*"}, }, }, Input: command.Scan{ @@ -137,7 +137,7 @@ func _TestSimpleCompilerCompileUpdateNoOptimizations(t *testing.T) { Updates: []command.UpdateSetter{ { Cols: []string{"myCol"}, - Value: command.LiteralExpr{Value: "7"}, + Value: command.ConstantLiteral{Value: "7", Numeric: true}, }, }, }, @@ -153,14 +153,14 @@ func _TestSimpleCompilerCompileUpdateNoOptimizations(t *testing.T) { }, Filter: command.EqualityExpr{ BinaryBase: command.BinaryBase{ - Left: command.LiteralExpr{Value: "myOtherCol"}, - Right: command.LiteralExpr{Value: "9"}, + Left: command.ColumnReference{Name: "myOtherCol"}, + Right: command.ConstantLiteral{Value: "9", Numeric: true}, }, }, Updates: []command.UpdateSetter{ { Cols: []string{"myCol"}, - Value: command.LiteralExpr{Value: "7"}, + Value: command.ConstantLiteral{Value: "7", Numeric: true}, }, }, }, @@ -176,14 +176,14 @@ func _TestSimpleCompilerCompileUpdateNoOptimizations(t *testing.T) { }, Filter: command.EqualityExpr{ BinaryBase: command.BinaryBase{ - Left: command.LiteralExpr{Value: "myOtherCol"}, - Right: command.LiteralExpr{Value: "9"}, + Left: command.ColumnReference{Name: "myOtherCol"}, + Right: command.ConstantLiteral{Value: "9", Numeric: true}, }, }, Updates: []command.UpdateSetter{ { Cols: []string{"myCol"}, - Value: command.LiteralExpr{Value: "7"}, + Value: command.ConstantLiteral{Value: "7", Numeric: true}, }, }, }, @@ -385,8 +385,8 @@ func _TestSimpleCompilerCompileDeleteNoOptimizations(t *testing.T) { }, Filter: command.EqualityExpr{ BinaryBase: command.BinaryBase{ - Left: command.LiteralExpr{Value: "col1"}, - Right: command.LiteralExpr{Value: "col2"}, + Left: command.ColumnReference{Name: "col1"}, + Right: command.ColumnReference{Name: "col2"}, }, }, }, @@ -406,19 +406,19 @@ func _TestSimpleCompilerCompileSelectNoOptimizations(t *testing.T) { command.Values{ Values: [][]command.Expr{ { - command.LiteralExpr{Value: "1"}, - command.LiteralExpr{Value: "2"}, - command.LiteralExpr{Value: "3"}, + command.ConstantLiteral{Value: "1", Numeric: true}, + command.ConstantLiteral{Value: "2", Numeric: true}, + command.ConstantLiteral{Value: "3", Numeric: true}, }, { - command.LiteralExpr{Value: "4"}, - command.LiteralExpr{Value: "5"}, - command.LiteralExpr{Value: "6"}, + command.ConstantLiteral{Value: "4", Numeric: true}, + command.ConstantLiteral{Value: "5", Numeric: true}, + command.ConstantLiteral{Value: "6", Numeric: true}, }, { - command.LiteralExpr{Value: "7"}, - command.LiteralExpr{Value: "8"}, - command.LiteralExpr{Value: "9"}, + command.ConstantLiteral{Value: "7", Numeric: true}, + command.ConstantLiteral{Value: "8", Numeric: true}, + command.ConstantLiteral{Value: "9", Numeric: true}, }, }, }, @@ -430,7 +430,7 @@ func _TestSimpleCompilerCompileSelectNoOptimizations(t *testing.T) { command.Project{ Cols: []command.Column{ { - Name: command.LiteralExpr{Value: "*"}, + Expr: command.ColumnReference{Name: "*"}, }, }, Input: command.Scan{ @@ -447,7 +447,7 @@ func _TestSimpleCompilerCompileSelectNoOptimizations(t *testing.T) { command.Project{ Cols: []command.Column{ { - Name: command.LiteralExpr{Value: "*"}, + Expr: command.ColumnReference{Name: "*"}, }, }, Input: command.Select{ @@ -465,11 +465,11 @@ func _TestSimpleCompilerCompileSelectNoOptimizations(t *testing.T) { "simple select limit", "SELECT * FROM myTable LIMIT 5", command.Limit{ - Limit: command.LiteralExpr{Value: "5"}, + Limit: command.ConstantLiteral{Value: "5", Numeric: true}, Input: command.Project{ Cols: []command.Column{ { - Name: command.LiteralExpr{Value: "*"}, + Expr: command.ColumnReference{Name: "*"}, }, }, Input: command.Scan{ @@ -485,13 +485,13 @@ func _TestSimpleCompilerCompileSelectNoOptimizations(t *testing.T) { "simple select limit offset", "SELECT * FROM myTable LIMIT 5, 10", command.Limit{ - Limit: command.LiteralExpr{Value: "5"}, + Limit: command.ConstantLiteral{Value: "5", Numeric: true}, Input: command.Offset{ - Offset: command.LiteralExpr{Value: "10"}, + Offset: command.ConstantLiteral{Value: "10", Numeric: true}, Input: command.Project{ Cols: []command.Column{ { - Name: command.LiteralExpr{Value: "*"}, + Expr: command.ColumnReference{Name: "*"}, }, }, Input: command.Scan{ @@ -508,13 +508,13 @@ func _TestSimpleCompilerCompileSelectNoOptimizations(t *testing.T) { "simple select limit offset", "SELECT * FROM myTable LIMIT 5 OFFSET 10", command.Limit{ - Limit: command.LiteralExpr{Value: "5"}, + Limit: command.ConstantLiteral{Value: "5", Numeric: true}, Input: command.Offset{ - Offset: command.LiteralExpr{Value: "10"}, + Offset: command.ConstantLiteral{Value: "10", Numeric: true}, Input: command.Project{ Cols: []command.Column{ { - Name: command.LiteralExpr{Value: "*"}, + Expr: command.ColumnReference{Name: "*"}, }, }, Input: command.Scan{ @@ -534,7 +534,7 @@ func _TestSimpleCompilerCompileSelectNoOptimizations(t *testing.T) { Input: command.Project{ Cols: []command.Column{ { - Name: command.LiteralExpr{Value: "*"}, + Expr: command.ColumnReference{Name: "*"}, }, }, Input: command.Select{ @@ -555,7 +555,7 @@ func _TestSimpleCompilerCompileSelectNoOptimizations(t *testing.T) { command.Project{ Cols: []command.Column{ { - Name: command.LiteralExpr{Value: "*"}, + Expr: command.ColumnReference{Name: "*"}, }, }, Input: command.Select{ @@ -582,7 +582,7 @@ func _TestSimpleCompilerCompileSelectNoOptimizations(t *testing.T) { command.Project{ Cols: []command.Column{ { - Name: command.LiteralExpr{Value: "*"}, + Expr: command.ColumnReference{Name: "*"}, }, }, Input: command.Select{ @@ -609,7 +609,7 @@ func _TestSimpleCompilerCompileSelectNoOptimizations(t *testing.T) { command.Project{ Cols: []command.Column{ { - Name: command.LiteralExpr{Value: "*"}, + Expr: command.ColumnReference{Name: "*"}, }, }, Input: command.Select{ @@ -643,13 +643,13 @@ func _TestSimpleCompilerCompileSelectNoOptimizations(t *testing.T) { command.Project{ Cols: []command.Column{ { - Name: command.LiteralExpr{Value: "name"}, + Expr: command.ColumnReference{Name: "name"}, }, { - Name: command.MulExpression{ + Expr: command.MulExpression{ BinaryBase: command.BinaryBase{ - Left: command.LiteralExpr{Value: "amount"}, - Right: command.LiteralExpr{Value: "price"}, + Left: command.ColumnReference{Name: "amount"}, + Right: command.ColumnReference{Name: "price"}, }, }, Alias: "total_price", @@ -672,11 +672,11 @@ func _TestSimpleCompilerCompileSelectNoOptimizations(t *testing.T) { command.Project{ Cols: []command.Column{ { - Name: command.FunctionExpr{ + Expr: command.FunctionExpr{ Name: "AVG", Distinct: false, Args: []command.Expr{ - command.LiteralExpr{Value: "price"}, + command.ColumnReference{Name: "price"}, }, }, Alias: "avg_price", @@ -700,11 +700,11 @@ func _TestSimpleCompilerCompileSelectNoOptimizations(t *testing.T) { command.Project{ Cols: []command.Column{ { - Name: command.FunctionExpr{ + Expr: command.FunctionExpr{ Name: "AVG", Distinct: true, Args: []command.Expr{ - command.LiteralExpr{Value: "price"}, + command.ColumnReference{Name: "price"}, }, }, Alias: "avg_price", diff --git a/internal/compiler/testdata/TestCompileGolden/delete/#02.golden b/internal/compiler/testdata/TestCompileGolden/delete/#02.golden index 7fb8d5f3..4d50044e 100644 --- a/internal/compiler/testdata/TestCompileGolden/delete/#02.golden +++ b/internal/compiler/testdata/TestCompileGolden/delete/#02.golden @@ -1,4 +1,4 @@ -command.Delete{Table:command.SimpleTable{Schema:"", Table:"myTable", Alias:"", Indexed:false, Index:""}, Filter:command.EqualityExpr{BinaryBase:command.BinaryBase{Left:command.LiteralExpr{Value:"col1"}, Right:command.LiteralExpr{Value:"col2"}}, Invert:false}} +command.Delete{Table:command.SimpleTable{Schema:"", Table:"myTable", Alias:"", Indexed:false, Index:""}, Filter:command.EqualityExpr{BinaryBase:command.BinaryBase{Left:command.ColumnReference{Name:"col1"}, Right:command.ColumnReference{Name:"col2"}}, Invert:false}} String: Delete[filter=col1==col2](myTable) \ No newline at end of file diff --git a/internal/compiler/testdata/TestCompileGolden/expressions/#00.golden b/internal/compiler/testdata/TestCompileGolden/expressions/#00.golden index 65e5bd02..1880e311 100644 --- a/internal/compiler/testdata/TestCompileGolden/expressions/#00.golden +++ b/internal/compiler/testdata/TestCompileGolden/expressions/#00.golden @@ -1,4 +1,4 @@ -command.Values{Values:[][]command.Expr{[]command.Expr{command.LiteralExpr{Value:"7"}}}} +command.Values{Values:[][]command.Expr{[]command.Expr{command.ConstantLiteral{Value:"7", Numeric:true}}}} String: Values[]((7)) \ No newline at end of file diff --git a/internal/compiler/testdata/TestCompileGolden/expressions/#01.golden b/internal/compiler/testdata/TestCompileGolden/expressions/#01.golden index d71d2df5..d074ee17 100644 --- a/internal/compiler/testdata/TestCompileGolden/expressions/#01.golden +++ b/internal/compiler/testdata/TestCompileGolden/expressions/#01.golden @@ -1,4 +1,4 @@ -command.Values{Values:[][]command.Expr{[]command.Expr{command.UnaryNegativeExpr{UnaryBase:command.UnaryBase{Value:command.LiteralExpr{Value:"7"}}}}}} +command.Values{Values:[][]command.Expr{[]command.Expr{command.UnaryNegativeExpr{UnaryBase:command.UnaryBase{Value:command.ConstantLiteral{Value:"7", Numeric:true}}}}}} String: Values[]((- 7)) \ No newline at end of file diff --git a/internal/compiler/testdata/TestCompileGolden/select/#00.golden b/internal/compiler/testdata/TestCompileGolden/select/#00.golden index 30d96c92..bfdd5d06 100644 --- a/internal/compiler/testdata/TestCompileGolden/select/#00.golden +++ b/internal/compiler/testdata/TestCompileGolden/select/#00.golden @@ -1,4 +1,4 @@ -command.Project{Cols:[]command.Column{command.Column{Table:"", Name:command.LiteralExpr{Value:"*"}, Alias:""}}, Input:command.Scan{Table:command.SimpleTable{Schema:"", Table:"myTable", Alias:"", Indexed:false, Index:""}}} +command.Project{Cols:[]command.Column{command.Column{Table:"", Expr:command.ColumnReference{Name:"*"}, Alias:""}}, Input:command.Scan{Table:command.SimpleTable{Schema:"", Table:"myTable", Alias:"", Indexed:false, Index:""}}} String: Project[cols=*](Scan[table=myTable]()) \ No newline at end of file diff --git a/internal/compiler/testdata/TestCompileGolden/select/#01.golden b/internal/compiler/testdata/TestCompileGolden/select/#01.golden index b1333115..b69da328 100644 --- a/internal/compiler/testdata/TestCompileGolden/select/#01.golden +++ b/internal/compiler/testdata/TestCompileGolden/select/#01.golden @@ -1,4 +1,4 @@ -command.Project{Cols:[]command.Column{command.Column{Table:"", Name:command.LiteralExpr{Value:"*"}, Alias:""}}, Input:command.Select{Filter:command.ConstantBooleanExpr{Value:true}, Input:command.Scan{Table:command.SimpleTable{Schema:"", Table:"myTable", Alias:"", Indexed:false, Index:""}}}} +command.Project{Cols:[]command.Column{command.Column{Table:"", Expr:command.ColumnReference{Name:"*"}, Alias:""}}, Input:command.Select{Filter:command.ConstantBooleanExpr{Value:true}, Input:command.Scan{Table:command.SimpleTable{Schema:"", Table:"myTable", Alias:"", Indexed:false, Index:""}}}} String: Project[cols=*](Select[filter=true](Scan[table=myTable]())) \ No newline at end of file diff --git a/internal/compiler/testdata/TestCompileGolden/select/#02.golden b/internal/compiler/testdata/TestCompileGolden/select/#02.golden index c1b6b05e..238a4ff4 100644 --- a/internal/compiler/testdata/TestCompileGolden/select/#02.golden +++ b/internal/compiler/testdata/TestCompileGolden/select/#02.golden @@ -1,4 +1,4 @@ -command.Limit{Limit:command.LiteralExpr{Value:"5"}, Input:command.Project{Cols:[]command.Column{command.Column{Table:"", Name:command.LiteralExpr{Value:"*"}, Alias:""}}, Input:command.Scan{Table:command.SimpleTable{Schema:"", Table:"myTable", Alias:"", Indexed:false, Index:""}}}} +command.Limit{Limit:command.ConstantLiteral{Value:"5", Numeric:true}, Input:command.Project{Cols:[]command.Column{command.Column{Table:"", Expr:command.ColumnReference{Name:"*"}, Alias:""}}, Input:command.Scan{Table:command.SimpleTable{Schema:"", Table:"myTable", Alias:"", Indexed:false, Index:""}}}} String: Limit[limit=5](Project[cols=*](Scan[table=myTable]())) \ No newline at end of file diff --git a/internal/compiler/testdata/TestCompileGolden/select/#03.golden b/internal/compiler/testdata/TestCompileGolden/select/#03.golden index 3a46f1aa..6c28dd94 100644 --- a/internal/compiler/testdata/TestCompileGolden/select/#03.golden +++ b/internal/compiler/testdata/TestCompileGolden/select/#03.golden @@ -1,4 +1,4 @@ -command.Limit{Limit:command.LiteralExpr{Value:"5"}, Input:command.Offset{Offset:command.LiteralExpr{Value:"10"}, Input:command.Project{Cols:[]command.Column{command.Column{Table:"", Name:command.LiteralExpr{Value:"*"}, Alias:""}}, Input:command.Scan{Table:command.SimpleTable{Schema:"", Table:"myTable", Alias:"", Indexed:false, Index:""}}}}} +command.Limit{Limit:command.ConstantLiteral{Value:"5", Numeric:true}, Input:command.Offset{Offset:command.ConstantLiteral{Value:"10", Numeric:true}, Input:command.Project{Cols:[]command.Column{command.Column{Table:"", Expr:command.ColumnReference{Name:"*"}, Alias:""}}, Input:command.Scan{Table:command.SimpleTable{Schema:"", Table:"myTable", Alias:"", Indexed:false, Index:""}}}}} String: Limit[limit=5](Offset[offset=10](Project[cols=*](Scan[table=myTable]()))) \ No newline at end of file diff --git a/internal/compiler/testdata/TestCompileGolden/select/#04.golden b/internal/compiler/testdata/TestCompileGolden/select/#04.golden index 3a46f1aa..6c28dd94 100644 --- a/internal/compiler/testdata/TestCompileGolden/select/#04.golden +++ b/internal/compiler/testdata/TestCompileGolden/select/#04.golden @@ -1,4 +1,4 @@ -command.Limit{Limit:command.LiteralExpr{Value:"5"}, Input:command.Offset{Offset:command.LiteralExpr{Value:"10"}, Input:command.Project{Cols:[]command.Column{command.Column{Table:"", Name:command.LiteralExpr{Value:"*"}, Alias:""}}, Input:command.Scan{Table:command.SimpleTable{Schema:"", Table:"myTable", Alias:"", Indexed:false, Index:""}}}}} +command.Limit{Limit:command.ConstantLiteral{Value:"5", Numeric:true}, Input:command.Offset{Offset:command.ConstantLiteral{Value:"10", Numeric:true}, Input:command.Project{Cols:[]command.Column{command.Column{Table:"", Expr:command.ColumnReference{Name:"*"}, Alias:""}}, Input:command.Scan{Table:command.SimpleTable{Schema:"", Table:"myTable", Alias:"", Indexed:false, Index:""}}}}} String: Limit[limit=5](Offset[offset=10](Project[cols=*](Scan[table=myTable]()))) \ No newline at end of file diff --git a/internal/compiler/testdata/TestCompileGolden/select/#05.golden b/internal/compiler/testdata/TestCompileGolden/select/#05.golden index f3443f63..46a069bf 100644 --- a/internal/compiler/testdata/TestCompileGolden/select/#05.golden +++ b/internal/compiler/testdata/TestCompileGolden/select/#05.golden @@ -1,4 +1,4 @@ -command.Distinct{Input:command.Project{Cols:[]command.Column{command.Column{Table:"", Name:command.LiteralExpr{Value:"*"}, Alias:""}}, Input:command.Select{Filter:command.ConstantBooleanExpr{Value:true}, Input:command.Scan{Table:command.SimpleTable{Schema:"", Table:"myTable", Alias:"", Indexed:false, Index:""}}}}} +command.Distinct{Input:command.Project{Cols:[]command.Column{command.Column{Table:"", Expr:command.ColumnReference{Name:"*"}, Alias:""}}, Input:command.Select{Filter:command.ConstantBooleanExpr{Value:true}, Input:command.Scan{Table:command.SimpleTable{Schema:"", Table:"myTable", Alias:"", Indexed:false, Index:""}}}}} String: Distinct[](Project[cols=*](Select[filter=true](Scan[table=myTable]()))) \ No newline at end of file diff --git a/internal/compiler/testdata/TestCompileGolden/select/#06.golden b/internal/compiler/testdata/TestCompileGolden/select/#06.golden index c458e4f1..1283f797 100644 --- a/internal/compiler/testdata/TestCompileGolden/select/#06.golden +++ b/internal/compiler/testdata/TestCompileGolden/select/#06.golden @@ -1,4 +1,4 @@ -command.Project{Cols:[]command.Column{command.Column{Table:"", Name:command.LiteralExpr{Value:"*"}, Alias:""}}, Input:command.Select{Filter:command.ConstantBooleanExpr{Value:true}, Input:command.Join{Natural:false, Type:0x0, Filter:command.Expr(nil), Left:command.Scan{Table:command.SimpleTable{Schema:"", Table:"a", Alias:"", Indexed:false, Index:""}}, Right:command.Scan{Table:command.SimpleTable{Schema:"", Table:"b", Alias:"", Indexed:false, Index:""}}}}} +command.Project{Cols:[]command.Column{command.Column{Table:"", Expr:command.ColumnReference{Name:"*"}, Alias:""}}, Input:command.Select{Filter:command.ConstantBooleanExpr{Value:true}, Input:command.Join{Natural:false, Type:0x0, Filter:command.Expr(nil), Left:command.Scan{Table:command.SimpleTable{Schema:"", Table:"a", Alias:"", Indexed:false, Index:""}}, Right:command.Scan{Table:command.SimpleTable{Schema:"", Table:"b", Alias:"", Indexed:false, Index:""}}}}} String: Project[cols=*](Select[filter=true](Join[](Scan[table=a](),Scan[table=b]()))) \ No newline at end of file diff --git a/internal/compiler/testdata/TestCompileGolden/select/#07.golden b/internal/compiler/testdata/TestCompileGolden/select/#07.golden index c458e4f1..1283f797 100644 --- a/internal/compiler/testdata/TestCompileGolden/select/#07.golden +++ b/internal/compiler/testdata/TestCompileGolden/select/#07.golden @@ -1,4 +1,4 @@ -command.Project{Cols:[]command.Column{command.Column{Table:"", Name:command.LiteralExpr{Value:"*"}, Alias:""}}, Input:command.Select{Filter:command.ConstantBooleanExpr{Value:true}, Input:command.Join{Natural:false, Type:0x0, Filter:command.Expr(nil), Left:command.Scan{Table:command.SimpleTable{Schema:"", Table:"a", Alias:"", Indexed:false, Index:""}}, Right:command.Scan{Table:command.SimpleTable{Schema:"", Table:"b", Alias:"", Indexed:false, Index:""}}}}} +command.Project{Cols:[]command.Column{command.Column{Table:"", Expr:command.ColumnReference{Name:"*"}, Alias:""}}, Input:command.Select{Filter:command.ConstantBooleanExpr{Value:true}, Input:command.Join{Natural:false, Type:0x0, Filter:command.Expr(nil), Left:command.Scan{Table:command.SimpleTable{Schema:"", Table:"a", Alias:"", Indexed:false, Index:""}}, Right:command.Scan{Table:command.SimpleTable{Schema:"", Table:"b", Alias:"", Indexed:false, Index:""}}}}} String: Project[cols=*](Select[filter=true](Join[](Scan[table=a](),Scan[table=b]()))) \ No newline at end of file diff --git a/internal/compiler/testdata/TestCompileGolden/select/#08.golden b/internal/compiler/testdata/TestCompileGolden/select/#08.golden index 6da74ff1..8c35a5d2 100644 --- a/internal/compiler/testdata/TestCompileGolden/select/#08.golden +++ b/internal/compiler/testdata/TestCompileGolden/select/#08.golden @@ -1,4 +1,4 @@ -command.Project{Cols:[]command.Column{command.Column{Table:"", Name:command.LiteralExpr{Value:"*"}, Alias:""}}, Input:command.Select{Filter:command.ConstantBooleanExpr{Value:true}, Input:command.Join{Natural:false, Type:0x0, Filter:command.Expr(nil), Left:command.Join{Natural:false, Type:0x0, Filter:command.Expr(nil), Left:command.Scan{Table:command.SimpleTable{Schema:"", Table:"a", Alias:"", Indexed:false, Index:""}}, Right:command.Scan{Table:command.SimpleTable{Schema:"", Table:"b", Alias:"", Indexed:false, Index:""}}}, Right:command.Scan{Table:command.SimpleTable{Schema:"", Table:"c", Alias:"", Indexed:false, Index:""}}}}} +command.Project{Cols:[]command.Column{command.Column{Table:"", Expr:command.ColumnReference{Name:"*"}, Alias:""}}, Input:command.Select{Filter:command.ConstantBooleanExpr{Value:true}, Input:command.Join{Natural:false, Type:0x0, Filter:command.Expr(nil), Left:command.Join{Natural:false, Type:0x0, Filter:command.Expr(nil), Left:command.Scan{Table:command.SimpleTable{Schema:"", Table:"a", Alias:"", Indexed:false, Index:""}}, Right:command.Scan{Table:command.SimpleTable{Schema:"", Table:"b", Alias:"", Indexed:false, Index:""}}}, Right:command.Scan{Table:command.SimpleTable{Schema:"", Table:"c", Alias:"", Indexed:false, Index:""}}}}} String: Project[cols=*](Select[filter=true](Join[](Join[](Scan[table=a](),Scan[table=b]()),Scan[table=c]()))) \ No newline at end of file diff --git a/internal/compiler/testdata/TestCompileGolden/select/#09.golden b/internal/compiler/testdata/TestCompileGolden/select/#09.golden index ab61277a..724303df 100644 --- a/internal/compiler/testdata/TestCompileGolden/select/#09.golden +++ b/internal/compiler/testdata/TestCompileGolden/select/#09.golden @@ -1,4 +1,4 @@ -command.Project{Cols:[]command.Column{command.Column{Table:"", Name:command.LiteralExpr{Value:"name"}, Alias:""}, command.Column{Table:"", Name:command.MulExpression{BinaryBase:command.BinaryBase{Left:command.LiteralExpr{Value:"amount"}, Right:command.LiteralExpr{Value:"price"}}}, Alias:"total_price"}}, Input:command.Join{Natural:false, Type:0x0, Filter:command.Expr(nil), Left:command.Scan{Table:command.SimpleTable{Schema:"", Table:"items", Alias:"", Indexed:false, Index:""}}, Right:command.Scan{Table:command.SimpleTable{Schema:"", Table:"prices", Alias:"", Indexed:false, Index:""}}}} +command.Project{Cols:[]command.Column{command.Column{Table:"", Expr:command.ColumnReference{Name:"name"}, Alias:""}, command.Column{Table:"", Expr:command.MulExpression{BinaryBase:command.BinaryBase{Left:command.ColumnReference{Name:"amount"}, Right:command.ColumnReference{Name:"price"}}}, Alias:"total_price"}}, Input:command.Join{Natural:false, Type:0x0, Filter:command.Expr(nil), Left:command.Scan{Table:command.SimpleTable{Schema:"", Table:"items", Alias:"", Indexed:false, Index:""}}, Right:command.Scan{Table:command.SimpleTable{Schema:"", Table:"prices", Alias:"", Indexed:false, Index:""}}}} String: Project[cols=name,amount * price AS total_price](Join[](Scan[table=items](),Scan[table=prices]())) \ No newline at end of file diff --git a/internal/compiler/testdata/TestCompileGolden/select/#10.golden b/internal/compiler/testdata/TestCompileGolden/select/#10.golden index 808c7877..1a6d2bd6 100644 --- a/internal/compiler/testdata/TestCompileGolden/select/#10.golden +++ b/internal/compiler/testdata/TestCompileGolden/select/#10.golden @@ -1,4 +1,4 @@ -command.Project{Cols:[]command.Column{command.Column{Table:"", Name:command.FunctionExpr{Name:"AVG", Distinct:false, Args:[]command.Expr{command.LiteralExpr{Value:"price"}}}, Alias:"avg_price"}}, Input:command.Join{Natural:false, Type:0x1, Filter:command.Expr(nil), Left:command.Scan{Table:command.SimpleTable{Schema:"", Table:"items", Alias:"", Indexed:false, Index:""}}, Right:command.Scan{Table:command.SimpleTable{Schema:"", Table:"prices", Alias:"", Indexed:false, Index:""}}}} +command.Project{Cols:[]command.Column{command.Column{Table:"", Expr:command.FunctionExpr{Name:"AVG", Distinct:false, Args:[]command.Expr{command.ColumnReference{Name:"price"}}}, Alias:"avg_price"}}, Input:command.Join{Natural:false, Type:0x1, Filter:command.Expr(nil), Left:command.Scan{Table:command.SimpleTable{Schema:"", Table:"items", Alias:"", Indexed:false, Index:""}}, Right:command.Scan{Table:command.SimpleTable{Schema:"", Table:"prices", Alias:"", Indexed:false, Index:""}}}} String: Project[cols=AVG(price) AS avg_price](Join[type=JoinLeft](Scan[table=items](),Scan[table=prices]())) \ No newline at end of file diff --git a/internal/compiler/testdata/TestCompileGolden/select/#11.golden b/internal/compiler/testdata/TestCompileGolden/select/#11.golden index 16bc38d1..54173619 100644 --- a/internal/compiler/testdata/TestCompileGolden/select/#11.golden +++ b/internal/compiler/testdata/TestCompileGolden/select/#11.golden @@ -1,4 +1,4 @@ -command.Project{Cols:[]command.Column{command.Column{Table:"", Name:command.FunctionExpr{Name:"AVG", Distinct:true, Args:[]command.Expr{command.LiteralExpr{Value:"price"}}}, Alias:"avg_price"}}, Input:command.Join{Natural:false, Type:0x1, Filter:command.Expr(nil), Left:command.Scan{Table:command.SimpleTable{Schema:"", Table:"items", Alias:"", Indexed:false, Index:""}}, Right:command.Scan{Table:command.SimpleTable{Schema:"", Table:"prices", Alias:"", Indexed:false, Index:""}}}} +command.Project{Cols:[]command.Column{command.Column{Table:"", Expr:command.FunctionExpr{Name:"AVG", Distinct:true, Args:[]command.Expr{command.ColumnReference{Name:"price"}}}, Alias:"avg_price"}}, Input:command.Join{Natural:false, Type:0x1, Filter:command.Expr(nil), Left:command.Scan{Table:command.SimpleTable{Schema:"", Table:"items", Alias:"", Indexed:false, Index:""}}, Right:command.Scan{Table:command.SimpleTable{Schema:"", Table:"prices", Alias:"", Indexed:false, Index:""}}}} String: Project[cols=AVG(DISTINCT price) AS avg_price](Join[type=JoinLeft](Scan[table=items](),Scan[table=prices]())) \ No newline at end of file diff --git a/internal/compiler/testdata/TestCompileGolden/select/#12.golden b/internal/compiler/testdata/TestCompileGolden/select/#12.golden index c869a32e..6d2a5708 100644 --- a/internal/compiler/testdata/TestCompileGolden/select/#12.golden +++ b/internal/compiler/testdata/TestCompileGolden/select/#12.golden @@ -1,4 +1,4 @@ -command.Values{Values:[][]command.Expr{[]command.Expr{command.LiteralExpr{Value:"1"}, command.LiteralExpr{Value:"2"}, command.LiteralExpr{Value:"3"}}, []command.Expr{command.LiteralExpr{Value:"4"}, command.LiteralExpr{Value:"5"}, command.LiteralExpr{Value:"6"}}, []command.Expr{command.LiteralExpr{Value:"7"}, command.LiteralExpr{Value:"8"}, command.LiteralExpr{Value:"9"}}}} +command.Values{Values:[][]command.Expr{[]command.Expr{command.ConstantLiteral{Value:"1", Numeric:true}, command.ConstantLiteral{Value:"2", Numeric:true}, command.ConstantLiteral{Value:"3", Numeric:true}}, []command.Expr{command.ConstantLiteral{Value:"4", Numeric:true}, command.ConstantLiteral{Value:"5", Numeric:true}, command.ConstantLiteral{Value:"6", Numeric:true}}, []command.Expr{command.ConstantLiteral{Value:"7", Numeric:true}, command.ConstantLiteral{Value:"8", Numeric:true}, command.ConstantLiteral{Value:"9", Numeric:true}}}} String: Values[]((1,2,3),(4,5,6),(7,8,9)) \ No newline at end of file diff --git a/internal/compiler/testdata/TestCompileGolden/update/#00.golden b/internal/compiler/testdata/TestCompileGolden/update/#00.golden index 334d5c13..c14d770a 100644 --- a/internal/compiler/testdata/TestCompileGolden/update/#00.golden +++ b/internal/compiler/testdata/TestCompileGolden/update/#00.golden @@ -1,4 +1,4 @@ -command.Update{UpdateOr:0x5, Table:command.SimpleTable{Schema:"", Table:"myTable", Alias:"", Indexed:false, Index:""}, Updates:[]command.UpdateSetter{command.UpdateSetter{Cols:[]string{"myCol"}, Value:command.LiteralExpr{Value:"7"}}}, Filter:command.ConstantBooleanExpr{Value:true}} +command.Update{UpdateOr:0x5, Table:command.SimpleTable{Schema:"", Table:"myTable", Alias:"", Indexed:false, Index:""}, Updates:[]command.UpdateSetter{command.UpdateSetter{Cols:[]string{"myCol"}, Value:command.ConstantLiteral{Value:"7", Numeric:true}}}, Filter:command.ConstantBooleanExpr{Value:true}} String: Update[or=UpdateOrIgnore,table=myTable,sets=((myCol)=7),filter=true] \ No newline at end of file diff --git a/internal/compiler/testdata/TestCompileGolden/update/#01.golden b/internal/compiler/testdata/TestCompileGolden/update/#01.golden index 1672fb68..24f92e7c 100644 --- a/internal/compiler/testdata/TestCompileGolden/update/#01.golden +++ b/internal/compiler/testdata/TestCompileGolden/update/#01.golden @@ -1,4 +1,4 @@ -command.Update{UpdateOr:0x5, Table:command.SimpleTable{Schema:"", Table:"myTable", Alias:"", Indexed:false, Index:""}, Updates:[]command.UpdateSetter{command.UpdateSetter{Cols:[]string{"myCol"}, Value:command.LiteralExpr{Value:"7"}}}, Filter:command.EqualityExpr{BinaryBase:command.BinaryBase{Left:command.LiteralExpr{Value:"myOtherCol"}, Right:command.LiteralExpr{Value:"9"}}, Invert:false}} +command.Update{UpdateOr:0x5, Table:command.SimpleTable{Schema:"", Table:"myTable", Alias:"", Indexed:false, Index:""}, Updates:[]command.UpdateSetter{command.UpdateSetter{Cols:[]string{"myCol"}, Value:command.ConstantLiteral{Value:"7", Numeric:true}}}, Filter:command.EqualityExpr{BinaryBase:command.BinaryBase{Left:command.ColumnReference{Name:"myOtherCol"}, Right:command.ConstantLiteral{Value:"9", Numeric:true}}, Invert:false}} String: Update[or=UpdateOrIgnore,table=myTable,sets=((myCol)=7),filter=myOtherCol==9] \ No newline at end of file diff --git a/internal/compiler/testdata/TestCompileGolden/update/#02.golden b/internal/compiler/testdata/TestCompileGolden/update/#02.golden index dce58209..17d2de40 100644 --- a/internal/compiler/testdata/TestCompileGolden/update/#02.golden +++ b/internal/compiler/testdata/TestCompileGolden/update/#02.golden @@ -1,4 +1,4 @@ -command.Update{UpdateOr:0x4, Table:command.SimpleTable{Schema:"", Table:"myTable", Alias:"", Indexed:false, Index:""}, Updates:[]command.UpdateSetter{command.UpdateSetter{Cols:[]string{"myCol"}, Value:command.LiteralExpr{Value:"7"}}}, Filter:command.EqualityExpr{BinaryBase:command.BinaryBase{Left:command.LiteralExpr{Value:"myOtherCol"}, Right:command.LiteralExpr{Value:"9"}}, Invert:false}} +command.Update{UpdateOr:0x4, Table:command.SimpleTable{Schema:"", Table:"myTable", Alias:"", Indexed:false, Index:""}, Updates:[]command.UpdateSetter{command.UpdateSetter{Cols:[]string{"myCol"}, Value:command.ConstantLiteral{Value:"7", Numeric:true}}}, Filter:command.EqualityExpr{BinaryBase:command.BinaryBase{Left:command.ColumnReference{Name:"myOtherCol"}, Right:command.ConstantLiteral{Value:"9", Numeric:true}}, Invert:false}} String: Update[or=UpdateOrFail,table=myTable,sets=((myCol)=7),filter=myOtherCol==9] \ No newline at end of file diff --git a/internal/compiler/testdata/TestCompileGolden/update/#03.golden b/internal/compiler/testdata/TestCompileGolden/update/#03.golden index b7f4b736..b29ba3e0 100644 --- a/internal/compiler/testdata/TestCompileGolden/update/#03.golden +++ b/internal/compiler/testdata/TestCompileGolden/update/#03.golden @@ -1,4 +1,4 @@ -command.Update{UpdateOr:0x5, Table:command.SimpleTable{Schema:"", Table:"myTable", Alias:"", Indexed:false, Index:""}, Updates:[]command.UpdateSetter{command.UpdateSetter{Cols:[]string{"myCol1", "myCol2"}, Value:command.LiteralExpr{Value:"7"}}, command.UpdateSetter{Cols:[]string{"myOtherCol1", "myOtherCol2"}, Value:command.LiteralExpr{Value:"8"}}}, Filter:command.EqualityExpr{BinaryBase:command.BinaryBase{Left:command.LiteralExpr{Value:"myOtherCol"}, Right:command.LiteralExpr{Value:"9"}}, Invert:false}} +command.Update{UpdateOr:0x5, Table:command.SimpleTable{Schema:"", Table:"myTable", Alias:"", Indexed:false, Index:""}, Updates:[]command.UpdateSetter{command.UpdateSetter{Cols:[]string{"myCol1", "myCol2"}, Value:command.ConstantLiteral{Value:"7", Numeric:true}}, command.UpdateSetter{Cols:[]string{"myOtherCol1", "myOtherCol2"}, Value:command.ConstantLiteral{Value:"8", Numeric:true}}}, Filter:command.EqualityExpr{BinaryBase:command.BinaryBase{Left:command.ColumnReference{Name:"myOtherCol"}, Right:command.ConstantLiteral{Value:"9", Numeric:true}}, Invert:false}} String: Update[or=UpdateOrIgnore,table=myTable,sets=((myCol1,myCol2)=7,(myOtherCol1,myOtherCol2)=8),filter=myOtherCol==9] \ No newline at end of file diff --git a/internal/engine/engine.go b/internal/engine/engine.go index 44910168..1025633d 100644 --- a/internal/engine/engine.go +++ b/internal/engine/engine.go @@ -1,10 +1,11 @@ package engine import ( + "crypto/rand" "encoding/binary" "fmt" - "math/rand" "time" + "unsafe" "github.com/rs/zerolog" "github.com/xqueries/xdb/internal/compiler/command" @@ -41,8 +42,12 @@ func New(dbFile *storage.DBFile, opts ...Option) (Engine, error) { dbFile: dbFile, pageCache: dbFile.Cache(), - timeProvider: time.Now, - randomProvider: func() int64 { return int64(rand.Uint64()) }, + timeProvider: time.Now, + randomProvider: func() int64 { + buf := make([]byte, unsafe.Sizeof(int64(0))) // #nosec + _, _ = rand.Read(buf) + return int64(byteOrder.Uint64(buf)) + }, } e.tablesPageContainer = e.NewPageContainer(e.dbFile.TablesPageID()) for _, opt := range opts { diff --git a/internal/engine/engine_test.go b/internal/engine/engine_test.go index 598f6cb2..9afcc33e 100644 --- a/internal/engine/engine_test.go +++ b/internal/engine/engine_test.go @@ -53,11 +53,11 @@ func (suite *EngineSuite) EqualTables(expected, got table.Table) { gotIt, err := got.Rows() suite.NoError(err) for { - expectedNext, expectedOk := expectedIt.Next() - gotNext, gotOk := gotIt.Next() + expectedNext, expectedErr := expectedIt.Next() + gotNext, gotErr := gotIt.Next() + suite.EqualValues(expectedErr, gotErr) suite.Equal(expectedNext, gotNext) - suite.Equal(expectedOk, gotOk) - if !(expectedOk && gotOk) { + if !(expectedErr == nil && gotErr == nil) { break } } diff --git a/internal/engine/expression.go b/internal/engine/expression.go index 5ef25af3..920d4b51 100644 --- a/internal/engine/expression.go +++ b/internal/engine/expression.go @@ -2,7 +2,6 @@ package engine import ( "fmt" - "strconv" "github.com/xqueries/xdb/internal/compiler/command" "github.com/xqueries/xdb/internal/engine/types" @@ -21,8 +20,12 @@ func (e Engine) evaluateExpression(ctx ExecutionContext, expr command.Expr) (typ return e.evaluateBinaryExpr(ctx, ex) case command.ConstantBooleanExpr: return types.NewBool(ex.Value), nil - case command.LiteralExpr: - return e.evaluateLiteralExpr(ctx, ex) + case command.ConstantLiteral: + return e.evaluateConstantLiteral(ctx, ex) + case command.ColumnReference: + return e.evaluateColumnReference(ctx, ex) + case command.ConstantLiteralOrColumnReference: + return e.evaluateConstantLiteralOrColumnReference(ctx, ex) case command.FunctionExpr: return e.evaluateFunctionExpr(ctx, ex) } @@ -41,27 +44,38 @@ func (e Engine) evaluateMultipleExpressions(ctx ExecutionContext, exprs []comman return vals, nil } -// evaluateLiteralExpr evaluates the given literal expression based on the -// current execution context. The returned value will either be a numeric value -// (integer or real) or a string value. -func (e Engine) evaluateLiteralExpr(ctx ExecutionContext, expr command.LiteralExpr) (types.Value, error) { - // Check whether the expression value is a numeric literal. In the future, - // this evaluation might depend on the execution context. - if numVal, ok := ToNumericValue(expr.Value); ok { - return numVal, nil - } - value := expr.Value - // if not a numeric literal, remove quotes and resolve escapes - resolved, err := strconv.Unquote(expr.Value) - if err == nil { - value = resolved +func (e Engine) evaluateColumnReference(ctx ExecutionContext, expr command.ColumnReference) (types.Value, error) { + if val, ok := ctx.intermediateRow.ValueForColName(expr.Name); ok { + return val, nil } + return nil, ErrNoSuchColumn(expr.Name) +} + +func (e Engine) evaluateConstantLiteralOrColumnReference(ctx ExecutionContext, expr command.ConstantLiteralOrColumnReference) (types.Value, error) { + // ConstantLiteralOrColumnReference can't be numeric. For it to be a ConstantLiteralOrColumnReference, + // the value has to be enclosed in double quotes in the query. + value := expr.ValueOrName if val, ok := ctx.intermediateRow.ValueForColName(value); ok { return val, nil } return types.NewString(value), nil } +// evaluateConstantLiteral evaluates the given literal expression based on the +// current execution context. The returned value will either be a numeric value +// (integer or real) or a string value. +func (e Engine) evaluateConstantLiteral(ctx ExecutionContext, expr command.ConstantLiteral) (types.Value, error) { + if expr.Numeric { + // Check whether the expression value is a numeric literal. In the future, + // this evaluation might depend on the execution context. + if numVal, ok := ToNumericValue(expr.Value); ok { + return numVal, nil + } + return nil, fmt.Errorf("could not convert numeric literal to a number") + } + return types.NewString(expr.Value), nil +} + func (e Engine) evaluateFunctionExpr(ctx ExecutionContext, expr command.FunctionExpr) (types.Value, error) { exprs, err := e.evaluateMultipleExpressions(ctx, expr.Args) if err != nil { diff --git a/internal/engine/expression_test.go b/internal/engine/expression_test.go index 8862284b..edf41067 100644 --- a/internal/engine/expression_test.go +++ b/internal/engine/expression_test.go @@ -84,8 +84,8 @@ func TestEngine_evaluateExpression(t *testing.T) { newEmptyExecutionContext(), command.AddExpression{ BinaryBase: command.BinaryBase{ - Left: command.LiteralExpr{Value: "5"}, - Right: command.LiteralExpr{Value: "6"}, + Left: command.ConstantLiteral{Value: "5", Numeric: true}, + Right: command.ConstantLiteral{Value: "6", Numeric: true}, }, }, types.NewInteger(11), @@ -97,8 +97,8 @@ func TestEngine_evaluateExpression(t *testing.T) { newEmptyExecutionContext(), command.AddExpression{ BinaryBase: command.BinaryBase{ - Left: command.LiteralExpr{Value: "5.5"}, - Right: command.LiteralExpr{Value: "6.7"}, + Left: command.ConstantLiteral{Value: "5.5", Numeric: true}, + Right: command.ConstantLiteral{Value: "6.7", Numeric: true}, }, }, types.NewReal(12.2), @@ -110,8 +110,8 @@ func TestEngine_evaluateExpression(t *testing.T) { newEmptyExecutionContext(), command.AddExpression{ BinaryBase: command.BinaryBase{ - Left: command.LiteralExpr{Value: `"abc"`}, - Right: command.LiteralExpr{Value: `"def"`}, + Left: command.ConstantLiteral{Value: "abc"}, + Right: command.ConstantLiteral{Value: "def"}, }, }, types.NewString("abcdef"), @@ -127,8 +127,8 @@ func TestEngine_evaluateExpression(t *testing.T) { newEmptyExecutionContext(), command.SubExpression{ BinaryBase: command.BinaryBase{ - Left: command.LiteralExpr{Value: "6"}, - Right: command.LiteralExpr{Value: "5"}, + Left: command.ConstantLiteral{Value: "6", Numeric: true}, + Right: command.ConstantLiteral{Value: "5", Numeric: true}, }, }, types.NewInteger(1), @@ -140,8 +140,8 @@ func TestEngine_evaluateExpression(t *testing.T) { newEmptyExecutionContext(), command.SubExpression{ BinaryBase: command.BinaryBase{ - Left: command.LiteralExpr{Value: "12.2"}, - Right: command.LiteralExpr{Value: "7.6"}, + Left: command.ConstantLiteral{Value: "12.2", Numeric: true}, + Right: command.ConstantLiteral{Value: "7.6", Numeric: true}, }, }, types.NewReal(4.6), @@ -157,8 +157,8 @@ func TestEngine_evaluateExpression(t *testing.T) { newEmptyExecutionContext(), command.MulExpression{ BinaryBase: command.BinaryBase{ - Left: command.LiteralExpr{Value: "6"}, - Right: command.LiteralExpr{Value: "5"}, + Left: command.ConstantLiteral{Value: "6", Numeric: true}, + Right: command.ConstantLiteral{Value: "5", Numeric: true}, }, }, types.NewInteger(30), @@ -170,8 +170,8 @@ func TestEngine_evaluateExpression(t *testing.T) { newEmptyExecutionContext(), command.MulExpression{ BinaryBase: command.BinaryBase{ - Left: command.LiteralExpr{Value: "6.2"}, - Right: command.LiteralExpr{Value: "5.7"}, + Left: command.ConstantLiteral{Value: "6.2", Numeric: true}, + Right: command.ConstantLiteral{Value: "5.7", Numeric: true}, }, }, types.NewReal(35.34), @@ -187,8 +187,8 @@ func TestEngine_evaluateExpression(t *testing.T) { newEmptyExecutionContext(), command.DivExpression{ BinaryBase: command.BinaryBase{ - Left: command.LiteralExpr{Value: "15"}, - Right: command.LiteralExpr{Value: "5"}, + Left: command.ConstantLiteral{Value: "15", Numeric: true}, + Right: command.ConstantLiteral{Value: "5", Numeric: true}, }, }, types.NewReal(3), @@ -200,8 +200,8 @@ func TestEngine_evaluateExpression(t *testing.T) { newEmptyExecutionContext(), command.DivExpression{ BinaryBase: command.BinaryBase{ - Left: command.LiteralExpr{Value: "35.34"}, - Right: command.LiteralExpr{Value: "5.7"}, + Left: command.ConstantLiteral{Value: "35.34", Numeric: true}, + Right: command.ConstantLiteral{Value: "5.7", Numeric: true}, }, }, types.NewReal(6.2), @@ -217,8 +217,8 @@ func TestEngine_evaluateExpression(t *testing.T) { newEmptyExecutionContext(), command.ModExpression{ BinaryBase: command.BinaryBase{ - Left: command.LiteralExpr{Value: "7"}, - Right: command.LiteralExpr{Value: "5"}, + Left: command.ConstantLiteral{Value: "7", Numeric: true}, + Right: command.ConstantLiteral{Value: "5", Numeric: true}, }, }, types.NewInteger(2), @@ -230,8 +230,8 @@ func TestEngine_evaluateExpression(t *testing.T) { newEmptyExecutionContext(), command.ModExpression{ BinaryBase: command.BinaryBase{ - Left: command.LiteralExpr{Value: "7.2"}, - Right: command.LiteralExpr{Value: "5.2"}, + Left: command.ConstantLiteral{Value: "7.2", Numeric: true}, + Right: command.ConstantLiteral{Value: "5.2", Numeric: true}, }, }, nil, @@ -247,8 +247,8 @@ func TestEngine_evaluateExpression(t *testing.T) { newEmptyExecutionContext(), command.PowExpression{ BinaryBase: command.BinaryBase{ - Left: command.LiteralExpr{Value: "2"}, - Right: command.LiteralExpr{Value: "4"}, + Left: command.ConstantLiteral{Value: "2", Numeric: true}, + Right: command.ConstantLiteral{Value: "4", Numeric: true}, }, }, types.NewInteger(16), @@ -260,8 +260,8 @@ func TestEngine_evaluateExpression(t *testing.T) { newEmptyExecutionContext(), command.PowExpression{ BinaryBase: command.BinaryBase{ - Left: command.LiteralExpr{Value: "2.2"}, - Right: command.LiteralExpr{Value: "1.5"}, + Left: command.ConstantLiteral{Value: "2.2", Numeric: true}, + Right: command.ConstantLiteral{Value: "1.5", Numeric: true}, }, }, types.NewReal(3.2631273343220926), diff --git a/internal/engine/insert.go b/internal/engine/insert.go index f6d1675d..453bb552 100644 --- a/internal/engine/insert.go +++ b/internal/engine/insert.go @@ -18,7 +18,7 @@ func (e Engine) evaluateInsert(ctx ExecutionContext, c command.Insert) (table.Ta } if len(c.Cols) != 0 { - return nil, fmt.Errorf("explicit insert columns: %w", ErrUnsupported) + return nil, fmt.Errorf("explicit insert projectedColumns: %w", ErrUnsupported) } insertInput, err := e.evaluateList(ctx, c.Input) @@ -30,7 +30,13 @@ func (e Engine) evaluateInsert(ctx ExecutionContext, c command.Insert) (table.Ta if err != nil { return nil, fmt.Errorf("rows: %w", err) } - for next, ok := inputRows.Next(); ok; next, ok = inputRows.Next() { + for { + next, err := inputRows.Next() + if err == table.ErrEOT { + break + } else if err != nil { + return nil, err + } if err := inserter.Insert(next); err != nil { return nil, fmt.Errorf("insert: %w", err) } diff --git a/internal/engine/numeric_parser.go b/internal/engine/numeric_parser.go index c7d0fd01..61094c1a 100644 --- a/internal/engine/numeric_parser.go +++ b/internal/engine/numeric_parser.go @@ -16,7 +16,7 @@ type numericParser struct { isReal bool isHexadecimal bool - isErronous bool + isErroneous bool hasDigitsBeforeExponent bool value *bytes.Buffer @@ -39,7 +39,7 @@ func ToNumericValue(s string) (types.Value, bool) { current: stateInitial, } p.parse() - if p.isErronous { + if p.isErroneous { return nil, false } switch { @@ -94,6 +94,7 @@ func stateInitial(p *numericParser) numericParserState { case p.get() == '.': return stateDecimalPoint } + p.isErroneous = true return nil } @@ -102,7 +103,7 @@ func stateHex(p *numericParser) numericParserState { p.step() return stateHex } - p.isErronous = true + p.isErroneous = true return nil } @@ -114,7 +115,7 @@ func stateFirstDigits(p *numericParser) numericParserState { } else if p.get() == '.' { return stateDecimalPoint } - p.isErronous = true + p.isErroneous = true return nil } @@ -124,7 +125,7 @@ func stateDecimalPoint(p *numericParser) numericParserState { p.isReal = true return stateSecondDigits } - p.isErronous = true + p.isErroneous = true return nil } @@ -137,9 +138,9 @@ func stateSecondDigits(p *numericParser) numericParserState { if p.hasDigitsBeforeExponent { return stateExponent } - p.isErronous = true // if there were no first digits, + p.isErroneous = true // if there were no first digits, } - p.isErronous = true + p.isErroneous = true return nil } @@ -148,7 +149,7 @@ func stateExponent(p *numericParser) numericParserState { p.step() return stateOptionalSign } - p.isErronous = true + p.isErroneous = true return nil } @@ -159,7 +160,7 @@ func stateOptionalSign(p *numericParser) numericParserState { } else if isDigit(p.get()) { return stateThirdDigits } - p.isErronous = true + p.isErroneous = true return nil } @@ -168,7 +169,7 @@ func stateThirdDigits(p *numericParser) numericParserState { p.step() return stateThirdDigits } - p.isErronous = true + p.isErroneous = true return nil } diff --git a/internal/engine/numeric_parser_bench_test.go b/internal/engine/numeric_parser_bench_test.go index 5e4ab659..8accdc9c 100644 --- a/internal/engine/numeric_parser_bench_test.go +++ b/internal/engine/numeric_parser_bench_test.go @@ -8,7 +8,7 @@ import ( func BenchmarkToNumericValue(b *testing.B) { str := "75610342.92389E-21423" - expVal := 75610342.92389E-21423 + expVal := 75610342.92389e-21423 b.ResetTimer() diff --git a/internal/engine/numeric_parser_test.go b/internal/engine/numeric_parser_test.go index 8aa660ba..d9517545 100644 --- a/internal/engine/numeric_parser_test.go +++ b/internal/engine/numeric_parser_test.go @@ -101,7 +101,7 @@ func TestToNumericValue(t *testing.T) { { "real with exponent", "5.7E-242", - types.NewReal(5.7E-242), + types.NewReal(5.7e-242), true, }, { diff --git a/internal/engine/projected_table.go b/internal/engine/projected_table.go new file mode 100644 index 00000000..6d239886 --- /dev/null +++ b/internal/engine/projected_table.go @@ -0,0 +1,95 @@ +package engine + +import ( + "fmt" + + "github.com/xqueries/xdb/internal/compiler/command" + "github.com/xqueries/xdb/internal/engine/table" + "github.com/xqueries/xdb/internal/engine/types" +) + +// projectedTable is a table, which projects from an underlying table. +// This means, this table may reorder, rename, remove or add columns. +type projectedTable struct { + columns []table.Col + originalTable table.Table + projectedColumns []command.Column + ctx ExecutionContext + e Engine +} + +func (e Engine) newProjectedTable(ctx ExecutionContext, originalTable table.Table, columnExpressions []command.Column) (projectedTable, error) { + tbl := projectedTable{ + originalTable: originalTable, + projectedColumns: columnExpressions, + ctx: ctx, + e: e, + } + + // compute the column names + var cols []table.Col + for i, colNameExpr := range columnExpressions { + switch expr := colNameExpr.Expr.(type) { + case command.ColumnReference: + if expr.Name == "*" { + cols = append(cols, originalTable.Cols()...) + } else { + foundCol, ok := table.FindColumnForNameOrAlias(originalTable, expr.Name) + if !ok { + return projectedTable{}, ErrNoSuchColumn(expr.Name) + } + cols = append(cols, foundCol) + } + case command.ConstantLiteralOrColumnReference: + if foundCol, ok := table.FindColumnForNameOrAlias(originalTable, expr.ValueOrName); ok { + cols = append(cols, foundCol) + } else { + evaluatedName, err := e.evaluateExpression(ctx, colNameExpr.Expr) + if err != nil { + return projectedTable{}, fmt.Errorf("typeof colName: %w", err) + } + cols = append(cols, table.Col{ + QualifiedName: expr.ValueOrName, + Alias: colNameExpr.Alias, + Type: evaluatedName.Type(), + }) + } + default: + colName, err := e.evaluateExpression(ctx, colNameExpr.Expr) + if err != nil { + return projectedTable{}, fmt.Errorf("col name: %w", err) + } + if !colName.Is(types.String) { + colNameStr, err := types.String.Cast(colName) + if err != nil { + return projectedTable{}, fmt.Errorf("cast col name to string: %w", err) + } + cols = append(cols, table.Col{ + QualifiedName: colNameStr.(types.StringValue).Value, + Alias: colNameExpr.Alias, + Type: colName.Type(), + }) + } else { + cols = append(cols, table.Col{ + QualifiedName: colName.(types.StringValue).Value, + Alias: colNameExpr.Alias, + Type: colName.Type(), + }) + } + } + cols[i].Alias = colNameExpr.Alias + } + + tbl.columns = cols + return tbl, nil +} + +// Cols returns the columns of the projected table. +func (t projectedTable) Cols() []table.Col { + return t.columns +} + +// Rows returns a row iterator of the projected table. Use it to read rows one by one. +func (t projectedTable) Rows() (table.RowIterator, error) { + return t.createIterator() +} diff --git a/internal/engine/projected_table_iterator.go b/internal/engine/projected_table_iterator.go new file mode 100644 index 00000000..fc1f08ef --- /dev/null +++ b/internal/engine/projected_table_iterator.go @@ -0,0 +1,114 @@ +package engine + +import ( + "fmt" + "sync" + + "github.com/xqueries/xdb/internal/compiler/command" + "github.com/xqueries/xdb/internal/engine/table" + "github.com/xqueries/xdb/internal/engine/types" +) + +type projectedTableIterator struct { + underlying table.RowIterator + underlyingColumns []table.Col + projectedColumns []command.Column + ctx ExecutionContext + e Engine + rowCounter uint64 + isSingleRowTableOnce *sync.Once + isSingleRowTable bool +} + +func (t projectedTable) createIterator() (*projectedTableIterator, error) { + underlyingIterator, err := t.originalTable.Rows() + if err != nil { + return nil, err + } + return &projectedTableIterator{ + underlying: underlyingIterator, + projectedColumns: t.projectedColumns, + underlyingColumns: t.originalTable.Cols(), + ctx: t.ctx, + e: t.e, + isSingleRowTableOnce: &sync.Once{}, + }, nil +} + +// Next returns the next row from this table. It fetches an underlying row and performs the projection +// on it, meaning that columns might be renamed, reordered, added or removed. +func (i *projectedTableIterator) Next() (table.Row, error) { + if i.isSingleRowTable && i.rowCounter > 0 { + // we've already returned one row in a single row table, this is the + // end of this table + return table.Row{}, table.ErrEOT + } + + nextUnderlying, err := i.underlying.Next() + if err != nil { + nextUnderlying = table.Row{} // this is what we expect right here, but we do this to avoid a warning + if err != table.ErrEOT { + return table.Row{}, err + } else if err == table.ErrEOT && i.rowCounter > 0 { + // Only allow ErrEOT if there's already been a row returned. + // If we don't do this, something like `SELECT "a"` wouldn't + // return any rows, since the underlyingTable is empty. + return table.Row{}, err + } + } + + var vals []types.Value + for _, col := range i.projectedColumns { + newCtx := i.ctx.IntermediateRow(table.RowWithColInfo{ + Cols: i.underlyingColumns, + Row: nextUnderlying, + }) + // check if only a single row must be returned + i.isSingleRowTableOnce.Do(func() { + i.isSingleRowTable = projectedColumnsImplySingleRowTable(newCtx, i.projectedColumns) + }) + + if name, ok := col.Expr.(command.ColumnReference); ok && name.Name == "*" { + // add all underlying columns for an asterisk + vals = append(vals, nextUnderlying.Values...) + } else { + val, err := i.e.evaluateExpression(newCtx, col.Expr) + if err != nil { + return table.Row{}, fmt.Errorf("evaluate expression: %w", err) + } + vals = append(vals, val) + } + } + + i.rowCounter++ + return table.Row{Values: vals}, nil +} + +// Reset resets this table iterator, causing it to start from row 0 again. +func (i *projectedTableIterator) Reset() error { + i.isSingleRowTableOnce = &sync.Once{} + i.isSingleRowTable = false + return i.underlying.Reset() +} + +// projectedColumnsImplySingleRowTable checks whether the projected columns imply, that the resulting +// table should only consist of a single row. This would be the case if they contain constant columns, +// as in +// SELECT 'a'; +// or +// SELECT MIN(col1) FROM myTable; +// The resulting table then only consists of one row, instead of repeating the same value over and over. +func projectedColumnsImplySingleRowTable(ctx ExecutionContext, columns []command.Column) bool { + for _, column := range columns { + switch name := column.Expr.(type) { + case command.ConstantLiteral: + // if one projected column is a constant literal, the table is + // a single row table in any case + return true + case command.ConstantLiteralOrColumnReference: + _, ok := ctx.intermediateRow.ValueForColName(name.ValueOrName) + return !ok // column found -> not a single row table + } + } + return false +} diff --git a/internal/engine/projection.go b/internal/engine/projection.go index acc7b69f..6f42b2f3 100644 --- a/internal/engine/projection.go +++ b/internal/engine/projection.go @@ -5,7 +5,6 @@ import ( "github.com/xqueries/xdb/internal/compiler/command" "github.com/xqueries/xdb/internal/engine/table" - "github.com/xqueries/xdb/internal/engine/types" ) func (e Engine) evaluateProjection(ctx ExecutionContext, proj command.Project) (table.Table, error) { @@ -18,88 +17,13 @@ func (e Engine) evaluateProjection(ctx ExecutionContext, proj command.Project) ( if len(proj.Cols) == 0 { e.log.Debug(). - Str("ctx", ctx.String()). - Msg("projection filters all columns") + Msg("projection filters all projectedColumns") return table.Empty, nil } - var expectedColumnNames []string - aliases := make(map[string]string) - for _, col := range proj.Cols { - // evaluate the column name - colNameExpr, err := e.evaluateExpression(ctx, col.Name) - if err != nil { - return nil, fmt.Errorf("eval column name: %w", err) - } - var colName string - if colNameExpr.Is(types.String) { - colName = colNameExpr.(types.StringValue).Value - } else { - casted, err := types.String.Cast(colNameExpr) - if err != nil { - return nil, fmt.Errorf("cannot cast %v to %v: %w", colNameExpr.Type(), types.String, err) - } - colName = casted.(types.StringValue).Value - } - if col.Table != "" { - colName = col.Table + "." + colName - } - if col.Alias != "" { - aliases[colName] = col.Alias - } - - expectedColumnNames = append(expectedColumnNames, colName) - } - - // check if the table actually has all expected columns - for _, expectedCol := range expectedColumnNames { - if expectedCol == "*" { - continue - } - var found bool - for _, col := range origin.Cols() { - if col.QualifiedName == expectedCol || col.Alias == expectedCol { - found = true - break - } - } - if !found { - return nil, ErrNoSuchColumn(expectedCol) - } - } - - // apply aliases - for i, col := range origin.Cols() { - if alias, ok := aliases[col.QualifiedName]; ok { - origin.Cols()[i].Alias = alias - } - } - - var toRemove []string - for _, col := range origin.Cols() { - found := false - if len(expectedColumnNames) == 1 && expectedColumnNames[0] == "*" { - found = true - } else { - for _, expectedColumnName := range expectedColumnNames { - if expectedColumnName == col.QualifiedName { - found = true - break - } - } - } - if !found { - toRemove = append(toRemove, col.QualifiedName) - } + tbl, err := e.newProjectedTable(ctx, origin, proj.Cols) + if err != nil { + return nil, err } - - return table.NewFilteredCol(origin, func(index int, col table.Col) bool { - defer e.profiler.Enter("projection (lazy)").Exit() - for _, s := range toRemove { - if s == col.QualifiedName { - return false - } - } - return true - }), nil + return tbl, nil } diff --git a/internal/engine/projection_test.go b/internal/engine/projection_test.go index 316b5233..c7749e5d 100644 --- a/internal/engine/projection_test.go +++ b/internal/engine/projection_test.go @@ -22,8 +22,8 @@ func (suite *ProjectionSuite) TestEmptyProjection() { Cols: []command.Column{}, Input: command.Values{ Values: [][]command.Expr{ - {command.LiteralExpr{Value: "hello"}, command.LiteralExpr{Value: "world"}, command.ConstantBooleanExpr{Value: true}}, - {command.LiteralExpr{Value: "foo"}, command.LiteralExpr{Value: "bar"}, command.ConstantBooleanExpr{Value: false}}, + {command.ConstantLiteral{Value: "hello"}, command.ConstantLiteral{Value: "world"}, command.ConstantBooleanExpr{Value: true}}, + {command.ConstantLiteral{Value: "foo"}, command.ConstantLiteral{Value: "bar"}, command.ConstantBooleanExpr{Value: false}}, }, }, }) @@ -34,12 +34,12 @@ func (suite *ProjectionSuite) TestEmptyProjection() { func (suite *ProjectionSuite) TestSimpleProjection() { tbl, err := suite.engine.evaluateProjection(suite.ctx, command.Project{ Cols: []command.Column{ - {Name: command.LiteralExpr{Value: "column2"}}, + {Expr: command.ColumnReference{Name: "column2"}}, }, Input: command.Values{ Values: [][]command.Expr{ - {command.LiteralExpr{Value: "hello"}, command.LiteralExpr{Value: "world"}, command.ConstantBooleanExpr{Value: true}}, - {command.LiteralExpr{Value: "foo"}, command.LiteralExpr{Value: "bar"}, command.ConstantBooleanExpr{Value: false}}, + {command.ConstantLiteral{Value: "hello"}, command.ConstantLiteral{Value: "world"}, command.ConstantBooleanExpr{Value: true}}, + {command.ConstantLiteral{Value: "foo"}, command.ConstantLiteral{Value: "bar"}, command.ConstantBooleanExpr{Value: false}}, }, }, }) @@ -62,14 +62,14 @@ func (suite *ProjectionSuite) TestSimpleProjectionWithAlias() { tbl, err := suite.engine.evaluateProjection(suite.ctx, command.Project{ Cols: []command.Column{ { - Name: command.LiteralExpr{Value: "column2"}, + Expr: command.ColumnReference{Name: "column2"}, Alias: "foo", }, }, Input: command.Values{ Values: [][]command.Expr{ - {command.LiteralExpr{Value: "hello"}, command.LiteralExpr{Value: "world"}, command.ConstantBooleanExpr{Value: true}}, - {command.LiteralExpr{Value: "foo"}, command.LiteralExpr{Value: "bar"}, command.ConstantBooleanExpr{Value: false}}, + {command.ConstantLiteral{Value: "hello"}, command.ConstantLiteral{Value: "world"}, command.ConstantBooleanExpr{Value: true}}, + {command.ConstantLiteral{Value: "foo"}, command.ConstantLiteral{Value: "bar"}, command.ConstantBooleanExpr{Value: false}}, }, }, }) @@ -93,16 +93,51 @@ func (suite *ProjectionSuite) TestSimpleProjectionWithMissingColumn() { tbl, err := suite.engine.evaluateProjection(suite.ctx, command.Project{ Cols: []command.Column{ { - Name: command.LiteralExpr{Value: "foo"}, + Expr: command.ColumnReference{Name: "foo"}, }, }, Input: command.Values{ Values: [][]command.Expr{ - {command.LiteralExpr{Value: "hello"}, command.LiteralExpr{Value: "world"}, command.ConstantBooleanExpr{Value: true}}, - {command.LiteralExpr{Value: "foo"}, command.LiteralExpr{Value: "bar"}, command.ConstantBooleanExpr{Value: false}}, + {command.ConstantLiteral{Value: "hello"}, command.ConstantLiteral{Value: "world"}, command.ConstantBooleanExpr{Value: true}}, + {command.ConstantLiteral{Value: "foo"}, command.ConstantLiteral{Value: "bar"}, command.ConstantBooleanExpr{Value: false}}, }, }, }) suite.EqualError(err, "no column with name or alias 'foo'") suite.Nil(tbl) } + +func (suite *ProjectionSuite) TestSimpleProjectionWithColumnAndConstant() { + tbl, err := suite.engine.evaluateProjection(suite.ctx, command.Project{ + Cols: []command.Column{ + { + Expr: command.ColumnReference{Name: "column1"}, + }, + { + Expr: command.ConstantLiteralOrColumnReference{ValueOrName: "column4"}, + }, + }, + Input: command.Values{ + Values: [][]command.Expr{ + {command.ConstantLiteral{Value: "hello"}, command.ConstantLiteral{Value: "world"}, command.ConstantBooleanExpr{Value: true}}, + {command.ConstantLiteral{Value: "foo"}, command.ConstantLiteral{Value: "bar"}, command.ConstantBooleanExpr{Value: false}}, + }, + }, + }) + suite.NoError(err) + suite.EqualTables(table.NewInMemory( + []table.Col{ + { + QualifiedName: "column1", + Type: types.String, + }, + { + QualifiedName: "column4", + Type: types.String, + }, + }, + []table.Row{ + {Values: []types.Value{types.NewString("hello"), types.NewString("column4")}}, + }, + ), tbl) +} diff --git a/internal/engine/selection.go b/internal/engine/selection.go index 6ff38503..d5fceb22 100644 --- a/internal/engine/selection.go +++ b/internal/engine/selection.go @@ -27,19 +27,19 @@ func (e Engine) evaluateSelection(ctx ExecutionContext, sel command.Select) (tab case command.EqualityExpr, command.GreaterThanExpr, command.GreaterThanOrEqualToExpr, command.LessThanExpr, command.LessThanOrEqualToExpr: } - return table.NewFilteredRow(origin, func(r table.RowWithColInfo) bool { + return table.NewFilteredRow(origin, func(r table.RowWithColInfo) (bool, error) { defer e.profiler.Enter("selection (lazy)").Exit() switch filter := sel.Filter.(type) { case command.BinaryExpression: val, err := e.evaluateBinaryExpr(ctx.IntermediateRow(r), filter) if err != nil { - return false + return false, err } if !val.Is(types.Bool) { - return false + return false, fmt.Errorf("expression does not evaluate to bool") } - return val.(types.BoolValue).Value + return val.(types.BoolValue).Value, nil } - return false + return false, nil }), nil } diff --git a/internal/engine/selection_test.go b/internal/engine/selection_test.go index bffbc83e..de8116bb 100644 --- a/internal/engine/selection_test.go +++ b/internal/engine/selection_test.go @@ -22,8 +22,8 @@ func (suite *SelectionSuite) TestTrivialSelection() { Filter: command.ConstantBooleanExpr{Value: true}, Input: command.Values{ Values: [][]command.Expr{ - {command.LiteralExpr{Value: "hello"}, command.LiteralExpr{Value: "5"}, command.ConstantBooleanExpr{Value: true}}, - {command.LiteralExpr{Value: "foo"}, command.LiteralExpr{Value: "7"}, command.ConstantBooleanExpr{Value: false}}, + {command.ConstantLiteral{Value: "hello"}, command.ConstantLiteral{Value: "5", Numeric: true}, command.ConstantBooleanExpr{Value: true}}, + {command.ConstantLiteral{Value: "foo"}, command.ConstantLiteral{Value: "7", Numeric: true}, command.ConstantBooleanExpr{Value: false}}, }, }, }) @@ -54,14 +54,14 @@ func (suite *SelectionSuite) TestSimpleSelection() { tbl, err := suite.engine.evaluateSelection(suite.ctx, command.Select{ Filter: command.EqualityExpr{ BinaryBase: command.BinaryBase{ - Left: command.LiteralExpr{Value: "column2"}, - Right: command.LiteralExpr{Value: "7"}, + Left: command.ColumnReference{Name: "column2"}, + Right: command.ConstantLiteral{Value: "7", Numeric: true}, }, }, Input: command.Values{ Values: [][]command.Expr{ - {command.LiteralExpr{Value: "hello"}, command.LiteralExpr{Value: "5"}, command.ConstantBooleanExpr{Value: true}}, - {command.LiteralExpr{Value: "foo"}, command.LiteralExpr{Value: "7"}, command.ConstantBooleanExpr{Value: false}}, + {command.ConstantLiteral{Value: "hello"}, command.ConstantLiteral{Value: "5", Numeric: true}, command.ConstantBooleanExpr{Value: true}}, + {command.ConstantLiteral{Value: "foo"}, command.ConstantLiteral{Value: "7", Numeric: true}, command.ConstantBooleanExpr{Value: false}}, }, }, }) @@ -91,14 +91,14 @@ func (suite *SelectionSuite) TestComparingSelectionGreaterThan() { tbl, err := suite.engine.evaluateSelection(suite.ctx, command.Select{ Filter: command.GreaterThanExpr{ BinaryBase: command.BinaryBase{ - Left: command.LiteralExpr{Value: "column2"}, - Right: command.LiteralExpr{Value: "5"}, + Left: command.ColumnReference{Name: "column2"}, + Right: command.ConstantLiteral{Value: "5", Numeric: true}, }, }, Input: command.Values{ Values: [][]command.Expr{ - {command.LiteralExpr{Value: "hello"}, command.LiteralExpr{Value: "5"}, command.ConstantBooleanExpr{Value: true}}, - {command.LiteralExpr{Value: "foo"}, command.LiteralExpr{Value: "7"}, command.ConstantBooleanExpr{Value: false}}, + {command.ConstantLiteral{Value: "hello"}, command.ConstantLiteral{Value: "5", Numeric: true}, command.ConstantBooleanExpr{Value: true}}, + {command.ConstantLiteral{Value: "foo"}, command.ConstantLiteral{Value: "7", Numeric: true}, command.ConstantBooleanExpr{Value: false}}, }, }, }) @@ -128,14 +128,14 @@ func (suite *SelectionSuite) TestComparingSelectionLessThan() { tbl, err := suite.engine.evaluateSelection(suite.ctx, command.Select{ Filter: command.LessThanExpr{ BinaryBase: command.BinaryBase{ - Left: command.LiteralExpr{Value: "column2"}, - Right: command.LiteralExpr{Value: "7"}, + Left: command.ColumnReference{Name: "column2"}, + Right: command.ConstantLiteral{Value: "7", Numeric: true}, }, }, Input: command.Values{ Values: [][]command.Expr{ - {command.LiteralExpr{Value: "hello"}, command.LiteralExpr{Value: "5"}, command.ConstantBooleanExpr{Value: true}}, - {command.LiteralExpr{Value: "foo"}, command.LiteralExpr{Value: "7"}, command.ConstantBooleanExpr{Value: false}}, + {command.ConstantLiteral{Value: "hello"}, command.ConstantLiteral{Value: "5", Numeric: true}, command.ConstantBooleanExpr{Value: true}}, + {command.ConstantLiteral{Value: "foo"}, command.ConstantLiteral{Value: "7", Numeric: true}, command.ConstantBooleanExpr{Value: false}}, }, }, }) @@ -165,14 +165,14 @@ func (suite *SelectionSuite) TestComparingSelectionGreaterThanOrEqualTo() { tbl, err := suite.engine.evaluateSelection(suite.ctx, command.Select{ Filter: command.GreaterThanOrEqualToExpr{ BinaryBase: command.BinaryBase{ - Left: command.LiteralExpr{Value: "column2"}, - Right: command.LiteralExpr{Value: "5"}, + Left: command.ColumnReference{Name: "column2"}, + Right: command.ConstantLiteral{Value: "5", Numeric: true}, }, }, Input: command.Values{ Values: [][]command.Expr{ - {command.LiteralExpr{Value: "hello"}, command.LiteralExpr{Value: "5"}, command.ConstantBooleanExpr{Value: true}}, - {command.LiteralExpr{Value: "foo"}, command.LiteralExpr{Value: "7"}, command.ConstantBooleanExpr{Value: false}}, + {command.ConstantLiteral{Value: "hello"}, command.ConstantLiteral{Value: "5", Numeric: true}, command.ConstantBooleanExpr{Value: true}}, + {command.ConstantLiteral{Value: "foo"}, command.ConstantLiteral{Value: "7", Numeric: true}, command.ConstantBooleanExpr{Value: false}}, }, }, }) @@ -203,14 +203,14 @@ func (suite *SelectionSuite) TestComparingSelectionLessThanOrEqualTo() { tbl, err := suite.engine.evaluateSelection(suite.ctx, command.Select{ Filter: command.LessThanOrEqualToExpr{ BinaryBase: command.BinaryBase{ - Left: command.LiteralExpr{Value: "column2"}, - Right: command.LiteralExpr{Value: "7"}, + Left: command.ColumnReference{Name: "column2"}, + Right: command.ConstantLiteral{Value: "7", Numeric: true}, }, }, Input: command.Values{ Values: [][]command.Expr{ - {command.LiteralExpr{Value: "hello"}, command.LiteralExpr{Value: "5"}, command.ConstantBooleanExpr{Value: true}}, - {command.LiteralExpr{Value: "foo"}, command.LiteralExpr{Value: "7"}, command.ConstantBooleanExpr{Value: false}}, + {command.ConstantLiteral{Value: "hello"}, command.ConstantLiteral{Value: "5", Numeric: true}, command.ConstantBooleanExpr{Value: true}}, + {command.ConstantLiteral{Value: "foo"}, command.ConstantLiteral{Value: "7", Numeric: true}, command.ConstantBooleanExpr{Value: false}}, }, }, }) @@ -241,14 +241,14 @@ func (suite *SelectionSuite) TestComparingColumns() { tbl, err := suite.engine.evaluateSelection(suite.ctx, command.Select{ Filter: command.EqualityExpr{ BinaryBase: command.BinaryBase{ - Left: command.LiteralExpr{Value: "column1"}, - Right: command.LiteralExpr{Value: "column2"}, + Left: command.ColumnReference{Name: "column1"}, + Right: command.ColumnReference{Name: "column2"}, }, }, Input: command.Values{ Values: [][]command.Expr{ - {command.LiteralExpr{Value: "hello"}, command.LiteralExpr{Value: "world"}}, - {command.LiteralExpr{Value: "foo"}, command.LiteralExpr{Value: "foo"}}, + {command.ConstantLiteral{Value: "hello"}, command.ConstantLiteral{Value: "world"}}, + {command.ConstantLiteral{Value: "foo"}, command.ConstantLiteral{Value: "foo"}}, }, }, }) @@ -274,14 +274,14 @@ func (suite *SelectionSuite) TestComparingColumnAgainstString() { tbl, err := suite.engine.evaluateSelection(suite.ctx, command.Select{ Filter: command.EqualityExpr{ BinaryBase: command.BinaryBase{ - Left: command.LiteralExpr{Value: "column2"}, - Right: command.LiteralExpr{Value: "world"}, + Left: command.ColumnReference{Name: "column2"}, + Right: command.ConstantLiteral{Value: "world"}, }, }, Input: command.Values{ Values: [][]command.Expr{ - {command.LiteralExpr{Value: "hello"}, command.LiteralExpr{Value: "world"}}, - {command.LiteralExpr{Value: "foo"}, command.LiteralExpr{Value: "foo"}}, + {command.ConstantLiteral{Value: "hello"}, command.ConstantLiteral{Value: "world"}}, + {command.ConstantLiteral{Value: "foo"}, command.ConstantLiteral{Value: "foo"}}, }, }, }) @@ -306,29 +306,29 @@ func (suite *SelectionSuite) TestComparingColumnAgainstString() { func (suite *SelectionSuite) TestInvalidFilter() { suite.Run("invalid expression", func() { tbl, err := suite.engine.evaluateSelection(suite.ctx, command.Select{ - Filter: command.LiteralExpr{Value: "invalid"}, + Filter: command.ConstantLiteral{Value: "invalid"}, Input: command.Values{ Values: [][]command.Expr{ - {command.LiteralExpr{Value: "hello"}, command.LiteralExpr{Value: "5"}, command.ConstantBooleanExpr{Value: true}}, - {command.LiteralExpr{Value: "foo"}, command.LiteralExpr{Value: "7"}, command.ConstantBooleanExpr{Value: false}}, + {command.ConstantLiteral{Value: "hello"}, command.ConstantLiteral{Value: "5", Numeric: true}, command.ConstantBooleanExpr{Value: true}}, + {command.ConstantLiteral{Value: "foo"}, command.ConstantLiteral{Value: "7", Numeric: true}, command.ConstantBooleanExpr{Value: false}}, }, }, }) - suite.EqualError(err, "cannot use command.LiteralExpr as filter") + suite.EqualError(err, "cannot use command.ConstantLiteral as filter") suite.Zero(tbl) }) suite.Run("invalid binary expression", func() { tbl, err := suite.engine.evaluateSelection(suite.ctx, command.Select{ Filter: command.AddExpression{ BinaryBase: command.BinaryBase{ - Left: command.LiteralExpr{Value: "5"}, - Right: command.LiteralExpr{Value: "12"}, + Left: command.ConstantLiteral{Value: "5", Numeric: true}, + Right: command.ConstantLiteral{Value: "12", Numeric: true}, }, }, Input: command.Values{ Values: [][]command.Expr{ - {command.LiteralExpr{Value: "hello"}, command.LiteralExpr{Value: "5"}, command.ConstantBooleanExpr{Value: true}}, - {command.LiteralExpr{Value: "foo"}, command.LiteralExpr{Value: "7"}, command.ConstantBooleanExpr{Value: false}}, + {command.ConstantLiteral{Value: "hello"}, command.ConstantLiteral{Value: "5", Numeric: true}, command.ConstantBooleanExpr{Value: true}}, + {command.ConstantLiteral{Value: "foo"}, command.ConstantLiteral{Value: "7", Numeric: true}, command.ConstantBooleanExpr{Value: false}}, }, }, }) diff --git a/internal/engine/table.go b/internal/engine/table.go index d251c99f..1a09ae13 100644 --- a/internal/engine/table.go +++ b/internal/engine/table.go @@ -122,7 +122,7 @@ func (t Table) Name() string { return t.name } -// Cols returns the column information of this table as a slice of columns. +// Cols returns the column information of this table as a slice of projectedColumns. func (t Table) Cols() []table.Col { return t.cols } @@ -301,7 +301,7 @@ func deserializeColInfo(record []byte) (cols []table.Col, err error) { if n != len(buf) { return nil, fmt.Errorf("read col name: expected %v bytes, could only read %v", len(buf), n) } - // col read successfully, append to cols + // col read successfully, append to underlyingColumns cols = append(cols, table.Col{ QualifiedName: string(buf), Type: types.ByIndicator(types.TypeIndicator(typeIndicator)), diff --git a/internal/engine/table/error.go b/internal/engine/table/error.go new file mode 100644 index 00000000..1ac3d45d --- /dev/null +++ b/internal/engine/table/error.go @@ -0,0 +1,12 @@ +package table + +// Error is a sentinel error. +type Error string + +func (e Error) Error() string { return string(e) } + +const ( + // ErrEOT indicates, that a table iterator is fully drained and will not yield + // any more results. + ErrEOT Error = "end of table" +) diff --git a/internal/engine/table/filtered_col_iterator.go b/internal/engine/table/filtered_col_iterator.go index 268d3439..438b36c1 100644 --- a/internal/engine/table/filtered_col_iterator.go +++ b/internal/engine/table/filtered_col_iterator.go @@ -31,11 +31,11 @@ func newFilteredColIterator(origin Table, keep func(int, Col) bool) (*filteredCo } // Next returns the next row of this table iterator. -func (i filteredColIterator) Next() (Row, bool) { +func (i filteredColIterator) Next() (Row, error) { var vals []types.Value - next, ok := i.underlying.Next() - if !ok { - return Row{}, false + next, err := i.underlying.Next() + if err != nil { + return Row{}, err } for colIndex, value := range next.Values { result := sort.SearchInts(i.keepIndices, colIndex) @@ -45,7 +45,7 @@ func (i filteredColIterator) Next() (Row, bool) { } return Row{ Values: vals, - }, true + }, nil } // Reset makes this iterator start over from the first row. diff --git a/internal/engine/table/filtered_row.go b/internal/engine/table/filtered_row.go index 610f549b..c626c37f 100644 --- a/internal/engine/table/filtered_row.go +++ b/internal/engine/table/filtered_row.go @@ -2,12 +2,12 @@ package table type filteredRowTable struct { underlying Table - keep func(RowWithColInfo) bool + keep func(RowWithColInfo) (bool, error) } // NewFilteredRow returns a new table that can filter rows from the given // underlying table. -func NewFilteredRow(underlying Table, keep func(RowWithColInfo) bool) Table { +func NewFilteredRow(underlying Table, keep func(RowWithColInfo) (bool, error)) Table { return filteredRowTable{ underlying: underlying, keep: keep, diff --git a/internal/engine/table/filtered_row_iterator.go b/internal/engine/table/filtered_row_iterator.go index c78863ce..c865ace7 100644 --- a/internal/engine/table/filtered_row_iterator.go +++ b/internal/engine/table/filtered_row_iterator.go @@ -2,11 +2,11 @@ package table type filteredRowIterator struct { origin Table - keep func(RowWithColInfo) bool + keep func(RowWithColInfo) (bool, error) underlying RowIterator } -func newFilteredRowIterator(origin Table, keep func(RowWithColInfo) bool) (*filteredRowIterator, error) { +func newFilteredRowIterator(origin Table, keep func(RowWithColInfo) (bool, error)) (*filteredRowIterator, error) { rows, err := origin.Rows() if err != nil { return nil, err @@ -19,17 +19,19 @@ func newFilteredRowIterator(origin Table, keep func(RowWithColInfo) bool) (*filt } // Next returns the next not filtered row. -func (i filteredRowIterator) Next() (Row, bool) { +func (i filteredRowIterator) Next() (Row, error) { for { - next, ok := i.underlying.Next() - if !ok { - return Row{}, false + next, err := i.underlying.Next() + if err != nil { + return Row{}, err } - if i.keep(RowWithColInfo{ + if ok, err := i.keep(RowWithColInfo{ Cols: i.origin.Cols(), Row: next, - }) { - return next, true + }); err != nil { + return Row{}, err + } else if ok { + return next, nil } } } diff --git a/internal/engine/table/in_memory_iterator.go b/internal/engine/table/in_memory_iterator.go index df802d69..273534ac 100644 --- a/internal/engine/table/in_memory_iterator.go +++ b/internal/engine/table/in_memory_iterator.go @@ -6,13 +6,13 @@ type inMemoryRowIterator struct { } // Next returns the next row of this iterator. -func (i *inMemoryRowIterator) Next() (Row, bool) { +func (i *inMemoryRowIterator) Next() (Row, error) { if i.index == len(i.rows) { - return Row{}, false + return Row{}, ErrEOT } row := i.rows[i.index] i.index++ - return row, true + return row, nil } // Reset resets the row index to zero, causing Next to return diff --git a/internal/engine/table/string.go b/internal/engine/table/string.go index bbc27f0b..3b6661eb 100644 --- a/internal/engine/table/string.go +++ b/internal/engine/table/string.go @@ -16,6 +16,10 @@ import ( // 1 foobar // 2 snafu func ToString(tbl Table) (string, error) { + if tbl == nil { + return "", fmt.Errorf("table is nil") + } + if stringer, ok := tbl.(fmt.Stringer); ok { return stringer.String(), nil } @@ -35,9 +39,18 @@ func ToString(tbl Table) (string, error) { iterator, err := tbl.Rows() if err != nil { - return "", err + _ = w.Flush() + return buf.String(), err } - for next, ok := iterator.Next(); ok; next, ok = iterator.Next() { + for { + next, err := iterator.Next() + if err == ErrEOT { + break + } else if err != nil { + _ = w.Flush() + return buf.String(), fmt.Errorf("next: %w", err) + } + var strVals []string for i := 0; i < len(next.Values); i++ { strVals = append(strVals, next.Values[i].String()) diff --git a/internal/engine/table/table.go b/internal/engine/table/table.go index 5f7964c7..d0754cca 100644 --- a/internal/engine/table/table.go +++ b/internal/engine/table/table.go @@ -24,6 +24,21 @@ type Table interface { // RowIterator is an iterator that can be reset, which results in Next obtaining the rows // in the beginning again. type RowIterator interface { - Next() (Row, bool) + Next() (Row, error) Reset() error } + +// FindColumnForNameOrAlias checks the given table for a column that has the given nameOrAlias +// as name or as an alias. Every column is first checked for its name, then for its alias. +// A nameOrAlias "*" will NOT yield a column. +func FindColumnForNameOrAlias(tbl Table, nameOrAlias string) (foundColumn Col, found bool) { + cols := tbl.Cols() + for _, col := range cols { + if col.QualifiedName == nameOrAlias { + return col, true + } else if col.Alias == nameOrAlias { + return col, true + } + } + return Col{}, false +} diff --git a/internal/engine/table_iterator.go b/internal/engine/table_iterator.go index e737d52b..ce8c18a7 100644 --- a/internal/engine/table_iterator.go +++ b/internal/engine/table_iterator.go @@ -1,6 +1,8 @@ package engine import ( + "fmt" + "github.com/xqueries/xdb/internal/engine/profile" "github.com/xqueries/xdb/internal/engine/storage/page" "github.com/xqueries/xdb/internal/engine/table" @@ -25,23 +27,23 @@ func newTableRowIterator(profiler *profile.Profiler, cols []table.Col, dataPage } // Next returns the next row of this table iterator. -func (i *tableRowIterator) Next() (table.Row, bool) { +func (i *tableRowIterator) Next() (table.Row, error) { i.profiler.Enter("next row").Exit() if i.slots == nil { i.slots = i.dataPage.OccupiedSlots() } if i.index >= len(i.slots) { - return table.Row{}, false + return table.Row{}, table.ErrEOT } cell := i.dataPage.CellAt(i.slots[i.index]).(page.RecordCell) i.index++ row, err := deserializeRow(i.cols, cell.Record) if err != nil { - return table.Row{}, false + return table.Row{}, fmt.Errorf("deserialize: %w", err) } - return row, true + return row, nil } // Reset makes this iterator start over from the first row. diff --git a/internal/id/id.go b/internal/id/id.go index 5f2e9521..d7186a54 100644 --- a/internal/id/id.go +++ b/internal/id/id.go @@ -5,7 +5,6 @@ import ( "log" "math/rand" "sync" - "time" "github.com/oklog/ulid" ) @@ -23,7 +22,7 @@ type id ulid.ULID var ( lock sync.Mutex - randSource = rand.New(rand.NewSource(time.Now().UnixNano())) + randSource = rand.New(secureSource{}) // #nosec G404 false positive, we use crypto.Rand in the secureSource{} entropy = ulid.Monotonic(randSource, 0) ) diff --git a/internal/id/secure_rand.go b/internal/id/secure_rand.go new file mode 100644 index 00000000..e16ac7d6 --- /dev/null +++ b/internal/id/secure_rand.go @@ -0,0 +1,30 @@ +package id + +import ( + "crypto/rand" + "encoding/binary" + unsafeRand "math/rand" + "unsafe" +) + +var _ unsafeRand.Source = (*secureSource)(nil) + +type secureSource struct { +} + +// Uint64 creates a new, cryptographically secure random uint64. +func (s secureSource) Uint64() uint64 { + buf := make([]byte, unsafe.Sizeof(int64(0))) // #nosec + _, _ = rand.Read(buf) + return binary.BigEndian.Uint64(buf) +} + +// Int63 creates a new, cryptographically secure random int64. +func (s secureSource) Int63() int64 { + return int64(s.Uint64()) +} + +// Seed is a no-op. +func (s secureSource) Seed(_ int64) { + // no-op +} diff --git a/internal/parser/issue29_test.go b/internal/parser/issue29_test.go index 0ed35552..f7dee9e9 100644 --- a/internal/parser/issue29_test.go +++ b/internal/parser/issue29_test.go @@ -2,7 +2,7 @@ package parser import "testing" -func TestIssue29(t *testing.T) { +func TestIssue29(t *testing.T) { NegativeTest{ Name: "issue29", Query: "CREATE TABLE(n,FOREIGN KEY()REFERENCES n ON DELETE CASCADE)", @@ -16,14 +16,14 @@ func TestIssue29WithoutTableName(t *testing.T) { }.Run(t) } -func TestIssue29WithNumericColumn(t *testing.T) { +func TestIssue29WithNumericColumn(t *testing.T) { NegativeTest{ Name: "issue29 with numeric column", Query: "CREATE TABLE foo(1)", }.Run(t) } -func TestIssue29WithNumericTable(t *testing.T) { +func TestIssue29WithNumericTable(t *testing.T) { NegativeTest{ Name: "issue29 with numeric column", Query: "CREATE TABLE 1(foo)", diff --git a/internal/parser/negative_test.go b/internal/parser/negative_test.go index 9b1b0317..71d94199 100644 --- a/internal/parser/negative_test.go +++ b/internal/parser/negative_test.go @@ -1,8 +1,9 @@ package parser import ( - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) type NegativeTest struct { diff --git a/internal/parser/scanner/rule_based_scanner_test.go b/internal/parser/scanner/rule_based_scanner_test.go index c3bdcadf..74c75534 100644 --- a/internal/parser/scanner/rule_based_scanner_test.go +++ b/internal/parser/scanner/rule_based_scanner_test.go @@ -65,6 +65,16 @@ func TestRuleBasedScanner(t *testing.T) { token.New(1, 36, 35, 0, token.EOF, ""), }, }, + { + "quoted numeric literal", + `SELECT "7"`, + ruleset.Default, + []token.Token{ + token.New(1, 1, 0, 6, token.KeywordSelect, "SELECT"), + token.New(1, 8, 7, 3, token.Literal, `"7"`), + token.New(1, 11, 10, 0, token.EOF, ""), + }, + }, { "fractional numeric literal", "SELECT FROM || & +7 5.9 \"foobar\"", @@ -420,7 +430,7 @@ func _TestRuleBasedScannerWithRuleset(input string, ruleset ruleset.Ruleset, wan assert.Equal(want[i].Offset(), got[i].Offset(), "Offset doesn't match") assert.Equal(want[i].Length(), got[i].Length(), "Length doesn't match") assert.Equal(want[i].Type(), got[i].Type(), "Type doesn't match, want %s, but got %s", want[i].Type().String(), got[i].Type().String()) - assert.Equal(want[i].Value(), got[i].Value(), "Value doesn't match") + assert.Equal(want[i].Value(), got[i].Value(), "Expr doesn't match") } } } diff --git a/internal/parser/scanner/test/gen.go b/internal/parser/scanner/test/gen.go deleted file mode 100644 index 94a6059b..00000000 --- a/internal/parser/scanner/test/gen.go +++ /dev/null @@ -1,1467 +0,0 @@ -package scanner - -import ( - "bytes" - "math/rand" - "time" - "unicode" - - "github.com/xqueries/xdb/internal/parser/scanner/token" -) - -var _ token.Token = (*genTok)(nil) - -var rng = rand.New(rand.NewSource(time.Now().Unix())) - -type genTok struct { - offset int - value string - typ token.Type -} - -func (t genTok) Line() int { - return 1 -} - -func (t genTok) Col() int { - return t.offset + 1 -} - -func (t genTok) Offset() int { - return t.offset -} - -func (t genTok) Length() int { - return len(t.value) -} - -func (t genTok) Type() token.Type { - return t.typ -} - -func (t genTok) Value() string { - return t.value -} - -func generateEOF(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.EOF, - } -} -func generateStatementSeparator(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.StatementSeparator, - value: ";", - } -} -func generateKeywordAbort(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordAbort, - value: caseShuffle("Abort"), - } -} -func generateKeywordAction(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordAction, - value: caseShuffle("Action"), - } -} -func generateKeywordAdd(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordAdd, - value: caseShuffle("Add"), - } -} -func generateKeywordAfter(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordAfter, - value: caseShuffle("After"), - } -} -func generateKeywordAll(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordAll, - value: caseShuffle("All"), - } -} -func generateKeywordAlter(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordAlter, - value: caseShuffle("Alter"), - } -} -func generateKeywordAnalyze(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordAnalyze, - value: caseShuffle("Analyze"), - } -} -func generateKeywordAnd(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordAnd, - value: caseShuffle("And"), - } -} -func generateKeywordAs(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordAs, - value: caseShuffle("As"), - } -} -func generateKeywordAsc(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordAsc, - value: caseShuffle("Asc"), - } -} -func generateKeywordAttach(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordAttach, - value: caseShuffle("Attach"), - } -} -func generateKeywordAutoincrement(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordAutoincrement, - value: caseShuffle("Autoincrement"), - } -} -func generateKeywordBefore(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordBefore, - value: caseShuffle("Before"), - } -} -func generateKeywordBegin(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordBegin, - value: caseShuffle("Begin"), - } -} -func generateKeywordBetween(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordBetween, - value: caseShuffle("Between"), - } -} -func generateKeywordBy(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordBy, - value: caseShuffle("By"), - } -} -func generateKeywordCascade(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordCascade, - value: caseShuffle("Cascade"), - } -} -func generateKeywordCase(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordCase, - value: caseShuffle("Case"), - } -} -func generateKeywordCast(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordCast, - value: caseShuffle("Cast"), - } -} -func generateKeywordCheck(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordCheck, - value: caseShuffle("Check"), - } -} -func generateKeywordCollate(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordCollate, - value: caseShuffle("Collate"), - } -} -func generateKeywordColumn(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordColumn, - value: caseShuffle("Column"), - } -} -func generateKeywordCommit(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordCommit, - value: caseShuffle("Commit"), - } -} -func generateKeywordConflict(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordConflict, - value: caseShuffle("Conflict"), - } -} -func generateKeywordConstraint(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordConstraint, - value: caseShuffle("Constraint"), - } -} -func generateKeywordCreate(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordCreate, - value: caseShuffle("Create"), - } -} -func generateKeywordCross(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordCross, - value: caseShuffle("Cross"), - } -} -func generateKeywordCurrent(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordCurrent, - value: caseShuffle("Current"), - } -} -func generateKeywordCurrentDate(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordCurrentDate, - value: caseShuffle("CurrentDate"), - } -} -func generateKeywordCurrentTime(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordCurrentTime, - value: caseShuffle("CurrentTime"), - } -} -func generateKeywordCurrentTimestamp(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordCurrentTimestamp, - value: caseShuffle("CurrentTimestamp"), - } -} -func generateKeywordDatabase(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordDatabase, - value: caseShuffle("Database"), - } -} -func generateKeywordDefault(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordDefault, - value: caseShuffle("Default"), - } -} -func generateKeywordDeferrable(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordDeferrable, - value: caseShuffle("Deferrable"), - } -} -func generateKeywordDeferred(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordDeferred, - value: caseShuffle("Deferred"), - } -} -func generateKeywordDelete(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordDelete, - value: caseShuffle("Delete"), - } -} -func generateKeywordDesc(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordDesc, - value: caseShuffle("Desc"), - } -} -func generateKeywordDetach(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordDetach, - value: caseShuffle("Detach"), - } -} -func generateKeywordDistinct(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordDistinct, - value: caseShuffle("Distinct"), - } -} -func generateKeywordDo(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordDo, - value: caseShuffle("Do"), - } -} -func generateKeywordDrop(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordDrop, - value: caseShuffle("Drop"), - } -} -func generateKeywordEach(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordEach, - value: caseShuffle("Each"), - } -} -func generateKeywordElse(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordElse, - value: caseShuffle("Else"), - } -} -func generateKeywordEnd(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordEnd, - value: caseShuffle("End"), - } -} -func generateKeywordEscape(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordEscape, - value: caseShuffle("Escape"), - } -} -func generateKeywordExcept(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordExcept, - value: caseShuffle("Except"), - } -} -func generateKeywordExclude(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordExclude, - value: caseShuffle("Exclude"), - } -} -func generateKeywordExclusive(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordExclusive, - value: caseShuffle("Exclusive"), - } -} -func generateKeywordExists(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordExists, - value: caseShuffle("Exists"), - } -} -func generateKeywordExplain(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordExplain, - value: caseShuffle("Explain"), - } -} -func generateKeywordFail(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordFail, - value: caseShuffle("Fail"), - } -} -func generateKeywordFilter(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordFilter, - value: caseShuffle("Filter"), - } -} -func generateKeywordFirst(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordFirst, - value: caseShuffle("First"), - } -} -func generateKeywordFollowing(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordFollowing, - value: caseShuffle("Following"), - } -} -func generateKeywordFor(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordFor, - value: caseShuffle("For"), - } -} -func generateKeywordForeign(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordForeign, - value: caseShuffle("Foreign"), - } -} -func generateKeywordFrom(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordFrom, - value: caseShuffle("From"), - } -} -func generateKeywordFull(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordFull, - value: caseShuffle("Full"), - } -} -func generateKeywordGenerated(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordGenerated, - value: caseShuffle("Generated"), - } -} -func generateKeywordGlob(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordGlob, - value: caseShuffle("Glob"), - } -} -func generateKeywordGroup(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordGroup, - value: caseShuffle("Group"), - } -} -func generateKeywordGroups(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordGroups, - value: caseShuffle("Groups"), - } -} -func generateKeywordHaving(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordHaving, - value: caseShuffle("Having"), - } -} -func generateKeywordIf(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordIf, - value: caseShuffle("If"), - } -} -func generateKeywordIgnore(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordIgnore, - value: caseShuffle("Ignore"), - } -} -func generateKeywordImmediate(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordImmediate, - value: caseShuffle("Immediate"), - } -} -func generateKeywordIn(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordIn, - value: caseShuffle("In"), - } -} -func generateKeywordIndex(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordIndex, - value: caseShuffle("Index"), - } -} -func generateKeywordIndexed(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordIndexed, - value: caseShuffle("Indexed"), - } -} -func generateKeywordInitially(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordInitially, - value: caseShuffle("Initially"), - } -} -func generateKeywordInner(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordInner, - value: caseShuffle("Inner"), - } -} -func generateKeywordInsert(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordInsert, - value: caseShuffle("Insert"), - } -} -func generateKeywordInstead(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordInstead, - value: caseShuffle("Instead"), - } -} -func generateKeywordIntersect(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordIntersect, - value: caseShuffle("Intersect"), - } -} -func generateKeywordInto(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordInto, - value: caseShuffle("Into"), - } -} -func generateKeywordIs(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordIs, - value: caseShuffle("Is"), - } -} -func generateKeywordIsnull(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordIsnull, - value: caseShuffle("Isnull"), - } -} -func generateKeywordJoin(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordJoin, - value: caseShuffle("Join"), - } -} -func generateKeywordKey(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordKey, - value: caseShuffle("Key"), - } -} -func generateKeywordLast(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordLast, - value: caseShuffle("Last"), - } -} -func generateKeywordLeft(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordLeft, - value: caseShuffle("Left"), - } -} -func generateKeywordLike(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordLike, - value: caseShuffle("Like"), - } -} -func generateKeywordLimit(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordLimit, - value: caseShuffle("Limit"), - } -} -func generateKeywordMatch(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordMatch, - value: caseShuffle("Match"), - } -} -func generateKeywordNatural(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordNatural, - value: caseShuffle("Natural"), - } -} -func generateKeywordNo(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordNo, - value: caseShuffle("No"), - } -} -func generateKeywordNot(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordNot, - value: caseShuffle("Not"), - } -} -func generateKeywordNothing(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordNothing, - value: caseShuffle("Nothing"), - } -} -func generateKeywordNotnull(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordNotnull, - value: caseShuffle("Notnull"), - } -} -func generateKeywordNull(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordNull, - value: caseShuffle("Null"), - } -} -func generateKeywordNulls(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordNulls, - value: caseShuffle("Nulls"), - } -} -func generateKeywordOf(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordOf, - value: caseShuffle("Of"), - } -} -func generateKeywordOffset(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordOffset, - value: caseShuffle("Offset"), - } -} -func generateKeywordOn(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordOn, - value: caseShuffle("On"), - } -} -func generateKeywordOr(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordOr, - value: caseShuffle("Or"), - } -} -func generateKeywordOrder(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordOrder, - value: caseShuffle("Order"), - } -} -func generateKeywordOthers(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordOthers, - value: caseShuffle("Others"), - } -} -func generateKeywordOuter(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordOuter, - value: caseShuffle("Outer"), - } -} -func generateKeywordOver(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordOver, - value: caseShuffle("Over"), - } -} -func generateKeywordPartition(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordPartition, - value: caseShuffle("Partition"), - } -} -func generateKeywordPlan(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordPlan, - value: caseShuffle("Plan"), - } -} -func generateKeywordPragma(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordPragma, - value: caseShuffle("Pragma"), - } -} -func generateKeywordPreceding(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordPreceding, - value: caseShuffle("Preceding"), - } -} -func generateKeywordPrimary(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordPrimary, - value: caseShuffle("Primary"), - } -} -func generateKeywordQuery(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordQuery, - value: caseShuffle("Query"), - } -} -func generateKeywordRaise(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordRaise, - value: caseShuffle("Raise"), - } -} -func generateKeywordRange(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordRange, - value: caseShuffle("Range"), - } -} -func generateKeywordRecursive(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordRecursive, - value: caseShuffle("Recursive"), - } -} -func generateKeywordReferences(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordReferences, - value: caseShuffle("References"), - } -} -func generateKeywordRegexp(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordRegexp, - value: caseShuffle("Regexp"), - } -} -func generateKeywordReIndex(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordReIndex, - value: caseShuffle("ReIndex"), - } -} -func generateKeywordRelease(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordRelease, - value: caseShuffle("Release"), - } -} -func generateKeywordRename(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordRename, - value: caseShuffle("Rename"), - } -} -func generateKeywordReplace(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordReplace, - value: caseShuffle("Replace"), - } -} -func generateKeywordRestrict(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordRestrict, - value: caseShuffle("Restrict"), - } -} -func generateKeywordRight(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordRight, - value: caseShuffle("Right"), - } -} -func generateKeywordRollback(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordRollback, - value: caseShuffle("Rollback"), - } -} -func generateKeywordRow(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordRow, - value: caseShuffle("Row"), - } -} -func generateKeywordRows(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordRows, - value: caseShuffle("Rows"), - } -} -func generateKeywordSavepoint(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordSavepoint, - value: caseShuffle("Savepoint"), - } -} -func generateKeywordSelect(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordSelect, - value: caseShuffle("Select"), - } -} -func generateKeywordSet(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordSet, - value: caseShuffle("Set"), - } -} -func generateKeywordTable(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordTable, - value: caseShuffle("Table"), - } -} -func generateKeywordTemp(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordTemp, - value: caseShuffle("Temp"), - } -} -func generateKeywordTemporary(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordTemporary, - value: caseShuffle("Temporary"), - } -} -func generateKeywordThen(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordThen, - value: caseShuffle("Then"), - } -} -func generateKeywordTies(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordTies, - value: caseShuffle("Ties"), - } -} -func generateKeywordTo(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordTo, - value: caseShuffle("To"), - } -} -func generateKeywordTransaction(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordTransaction, - value: caseShuffle("Transaction"), - } -} -func generateKeywordTrigger(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordTrigger, - value: caseShuffle("Trigger"), - } -} -func generateKeywordUnbounded(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordUnbounded, - value: caseShuffle("Unbounded"), - } -} -func generateKeywordUnion(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordUnion, - value: caseShuffle("Union"), - } -} -func generateKeywordUnique(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordUnique, - value: caseShuffle("Unique"), - } -} -func generateKeywordUpdate(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordUpdate, - value: caseShuffle("Update"), - } -} -func generateKeywordUsing(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordUsing, - value: caseShuffle("Using"), - } -} -func generateKeywordVacuum(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordVacuum, - value: caseShuffle("Vacuum"), - } -} -func generateKeywordValues(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordValues, - value: caseShuffle("Values"), - } -} -func generateKeywordView(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordView, - value: caseShuffle("View"), - } -} -func generateKeywordVirtual(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordVirtual, - value: caseShuffle("Virtual"), - } -} -func generateKeywordWhen(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordWhen, - value: caseShuffle("When"), - } -} -func generateKeywordWhere(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordWhere, - value: caseShuffle("Where"), - } -} -func generateKeywordWindow(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordWindow, - value: caseShuffle("Window"), - } -} -func generateKeywordWith(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordWith, - value: caseShuffle("With"), - } -} -func generateKeywordWithout(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.KeywordWithout, - value: caseShuffle("Without"), - } -} - -var alphabet = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ -_=+[]{};:'\\|,<.>/?!@#$%^&*()§±`~¡™£¢∞§¶•ªº–≠§“‘…æ«≤≥÷`∑´®†¥¨ˆøπåß∂ƒ©˙∆˚¬≈ç√∫˜µ") - -func generateLiteral(offset int) token.Token { - var buf bytes.Buffer - limit := rng.Intn(40) - for i := 0; i < limit; i++ { - buf.WriteRune(alphabet[rng.Intn(len(alphabet))]) - } - - return genTok{ - offset: offset, - typ: token.Literal, - value: "\"" + buf.String() + "\"", - } -} - -var unaryOperators = []string{"-", "+", "~"} - -func generateUnaryOperator(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.UnaryOperator, - value: unaryOperators[rng.Intn(len(unaryOperators))], - } -} - -var binaryOperators = []string{"||", "*", "/", "%", "+", "-", "<<", ">>", "&", "|", "<", "<=", ">", ">=", "=", "==", "!=", "<>"} - -func generateBinaryOperator(offset int) token.Token { - return genTok{ - offset: offset, - typ: token.BinaryOperator, - value: binaryOperators[rng.Intn(len(binaryOperators))], - } -} - -func caseShuffle(s string) string { - - var buf bytes.Buffer - for _, x := range s { - n := rng.Intn(2) - r := unicode.ToLower(x) - if n == 0 { - r = unicode.ToUpper(x) - } - _, _ = buf.WriteRune(r) - } - - return buf.String() -} - -func generateTokenType() token.Type { - typ := token.Type(rng.Intn(int(token.Delimiter))) - if typ == token.Error || typ == token.Unknown || typ == token.EOF { - typ = token.StatementSeparator - } - return typ -} - -func generateTokenForType(offset int, typ token.Type) token.Token { - switch typ { - case token.StatementSeparator: - return generateStatementSeparator(offset) - case token.KeywordAbort: - return generateKeywordAbort(offset) - case token.KeywordAction: - return generateKeywordAction(offset) - case token.KeywordAdd: - return generateKeywordAdd(offset) - case token.KeywordAfter: - return generateKeywordAfter(offset) - case token.KeywordAll: - return generateKeywordAll(offset) - case token.KeywordAlter: - return generateKeywordAlter(offset) - case token.KeywordAnalyze: - return generateKeywordAnalyze(offset) - case token.KeywordAnd: - return generateKeywordAnd(offset) - case token.KeywordAs: - return generateKeywordAs(offset) - case token.KeywordAsc: - return generateKeywordAsc(offset) - case token.KeywordAttach: - return generateKeywordAttach(offset) - case token.KeywordAutoincrement: - return generateKeywordAutoincrement(offset) - case token.KeywordBefore: - return generateKeywordBefore(offset) - case token.KeywordBegin: - return generateKeywordBegin(offset) - case token.KeywordBetween: - return generateKeywordBetween(offset) - case token.KeywordBy: - return generateKeywordBy(offset) - case token.KeywordCascade: - return generateKeywordCascade(offset) - case token.KeywordCase: - return generateKeywordCase(offset) - case token.KeywordCast: - return generateKeywordCast(offset) - case token.KeywordCheck: - return generateKeywordCheck(offset) - case token.KeywordCollate: - return generateKeywordCollate(offset) - case token.KeywordColumn: - return generateKeywordColumn(offset) - case token.KeywordCommit: - return generateKeywordCommit(offset) - case token.KeywordConflict: - return generateKeywordConflict(offset) - case token.KeywordConstraint: - return generateKeywordConstraint(offset) - case token.KeywordCreate: - return generateKeywordCreate(offset) - case token.KeywordCross: - return generateKeywordCross(offset) - case token.KeywordCurrent: - return generateKeywordCurrent(offset) - case token.KeywordCurrentDate: - return generateKeywordCurrentDate(offset) - case token.KeywordCurrentTime: - return generateKeywordCurrentTime(offset) - case token.KeywordCurrentTimestamp: - return generateKeywordCurrentTimestamp(offset) - case token.KeywordDatabase: - return generateKeywordDatabase(offset) - case token.KeywordDefault: - return generateKeywordDefault(offset) - case token.KeywordDeferrable: - return generateKeywordDeferrable(offset) - case token.KeywordDeferred: - return generateKeywordDeferred(offset) - case token.KeywordDelete: - return generateKeywordDelete(offset) - case token.KeywordDesc: - return generateKeywordDesc(offset) - case token.KeywordDetach: - return generateKeywordDetach(offset) - case token.KeywordDistinct: - return generateKeywordDistinct(offset) - case token.KeywordDo: - return generateKeywordDo(offset) - case token.KeywordDrop: - return generateKeywordDrop(offset) - case token.KeywordEach: - return generateKeywordEach(offset) - case token.KeywordElse: - return generateKeywordElse(offset) - case token.KeywordEnd: - return generateKeywordEnd(offset) - case token.KeywordEscape: - return generateKeywordEscape(offset) - case token.KeywordExcept: - return generateKeywordExcept(offset) - case token.KeywordExclude: - return generateKeywordExclude(offset) - case token.KeywordExclusive: - return generateKeywordExclusive(offset) - case token.KeywordExists: - return generateKeywordExists(offset) - case token.KeywordExplain: - return generateKeywordExplain(offset) - case token.KeywordFail: - return generateKeywordFail(offset) - case token.KeywordFilter: - return generateKeywordFilter(offset) - case token.KeywordFirst: - return generateKeywordFirst(offset) - case token.KeywordFollowing: - return generateKeywordFollowing(offset) - case token.KeywordFor: - return generateKeywordFor(offset) - case token.KeywordForeign: - return generateKeywordForeign(offset) - case token.KeywordFrom: - return generateKeywordFrom(offset) - case token.KeywordFull: - return generateKeywordFull(offset) - case token.KeywordGenerated: - return generateKeywordGenerated(offset) - case token.KeywordGlob: - return generateKeywordGlob(offset) - case token.KeywordGroup: - return generateKeywordGroup(offset) - case token.KeywordGroups: - return generateKeywordGroups(offset) - case token.KeywordHaving: - return generateKeywordHaving(offset) - case token.KeywordIf: - return generateKeywordIf(offset) - case token.KeywordIgnore: - return generateKeywordIgnore(offset) - case token.KeywordImmediate: - return generateKeywordImmediate(offset) - case token.KeywordIn: - return generateKeywordIn(offset) - case token.KeywordIndex: - return generateKeywordIndex(offset) - case token.KeywordIndexed: - return generateKeywordIndexed(offset) - case token.KeywordInitially: - return generateKeywordInitially(offset) - case token.KeywordInner: - return generateKeywordInner(offset) - case token.KeywordInsert: - return generateKeywordInsert(offset) - case token.KeywordInstead: - return generateKeywordInstead(offset) - case token.KeywordIntersect: - return generateKeywordIntersect(offset) - case token.KeywordInto: - return generateKeywordInto(offset) - case token.KeywordIs: - return generateKeywordIs(offset) - case token.KeywordIsnull: - return generateKeywordIsnull(offset) - case token.KeywordJoin: - return generateKeywordJoin(offset) - case token.KeywordKey: - return generateKeywordKey(offset) - case token.KeywordLast: - return generateKeywordLast(offset) - case token.KeywordLeft: - return generateKeywordLeft(offset) - case token.KeywordLike: - return generateKeywordLike(offset) - case token.KeywordLimit: - return generateKeywordLimit(offset) - case token.KeywordMatch: - return generateKeywordMatch(offset) - case token.KeywordNatural: - return generateKeywordNatural(offset) - case token.KeywordNo: - return generateKeywordNo(offset) - case token.KeywordNot: - return generateKeywordNot(offset) - case token.KeywordNothing: - return generateKeywordNothing(offset) - case token.KeywordNotnull: - return generateKeywordNotnull(offset) - case token.KeywordNull: - return generateKeywordNull(offset) - case token.KeywordNulls: - return generateKeywordNulls(offset) - case token.KeywordOf: - return generateKeywordOf(offset) - case token.KeywordOffset: - return generateKeywordOffset(offset) - case token.KeywordOn: - return generateKeywordOn(offset) - case token.KeywordOr: - return generateKeywordOr(offset) - case token.KeywordOrder: - return generateKeywordOrder(offset) - case token.KeywordOthers: - return generateKeywordOthers(offset) - case token.KeywordOuter: - return generateKeywordOuter(offset) - case token.KeywordOver: - return generateKeywordOver(offset) - case token.KeywordPartition: - return generateKeywordPartition(offset) - case token.KeywordPlan: - return generateKeywordPlan(offset) - case token.KeywordPragma: - return generateKeywordPragma(offset) - case token.KeywordPreceding: - return generateKeywordPreceding(offset) - case token.KeywordPrimary: - return generateKeywordPrimary(offset) - case token.KeywordQuery: - return generateKeywordQuery(offset) - case token.KeywordRaise: - return generateKeywordRaise(offset) - case token.KeywordRange: - return generateKeywordRange(offset) - case token.KeywordRecursive: - return generateKeywordRecursive(offset) - case token.KeywordReferences: - return generateKeywordReferences(offset) - case token.KeywordRegexp: - return generateKeywordRegexp(offset) - case token.KeywordReIndex: - return generateKeywordReIndex(offset) - case token.KeywordRelease: - return generateKeywordRelease(offset) - case token.KeywordRename: - return generateKeywordRename(offset) - case token.KeywordReplace: - return generateKeywordReplace(offset) - case token.KeywordRestrict: - return generateKeywordRestrict(offset) - case token.KeywordRight: - return generateKeywordRight(offset) - case token.KeywordRollback: - return generateKeywordRollback(offset) - case token.KeywordRow: - return generateKeywordRow(offset) - case token.KeywordRows: - return generateKeywordRows(offset) - case token.KeywordSavepoint: - return generateKeywordSavepoint(offset) - case token.KeywordSelect: - return generateKeywordSelect(offset) - case token.KeywordSet: - return generateKeywordSet(offset) - case token.KeywordTable: - return generateKeywordTable(offset) - case token.KeywordTemp: - return generateKeywordTemp(offset) - case token.KeywordTemporary: - return generateKeywordTemporary(offset) - case token.KeywordThen: - return generateKeywordThen(offset) - case token.KeywordTies: - return generateKeywordTies(offset) - case token.KeywordTo: - return generateKeywordTo(offset) - case token.KeywordTransaction: - return generateKeywordTransaction(offset) - case token.KeywordTrigger: - return generateKeywordTrigger(offset) - case token.KeywordUnbounded: - return generateKeywordUnbounded(offset) - case token.KeywordUnion: - return generateKeywordUnion(offset) - case token.KeywordUnique: - return generateKeywordUnique(offset) - case token.KeywordUpdate: - return generateKeywordUpdate(offset) - case token.KeywordUsing: - return generateKeywordUsing(offset) - case token.KeywordVacuum: - return generateKeywordVacuum(offset) - case token.KeywordValues: - return generateKeywordValues(offset) - case token.KeywordView: - return generateKeywordView(offset) - case token.KeywordVirtual: - return generateKeywordVirtual(offset) - case token.KeywordWhen: - return generateKeywordWhen(offset) - case token.KeywordWhere: - return generateKeywordWhere(offset) - case token.KeywordWindow: - return generateKeywordWindow(offset) - case token.KeywordWith: - return generateKeywordWith(offset) - case token.KeywordWithout: - return generateKeywordWithout(offset) - case token.Literal: - return generateLiteral(offset) - case token.UnaryOperator: - return generateUnaryOperator(offset) - case token.BinaryOperator: - return generateBinaryOperator(offset) - default: - } - return generateStatementSeparator(offset) -} - -func generateToken(offset int) token.Token { - if rng.Intn(2) == 0 { - return generateTokenForType(offset, token.Literal) - } - - typ := generateTokenType() - return generateTokenForType(offset, typ) -} - -func generateScannerInputAndExpectedOutput() (scannerInput string, scannerOutput []token.Token) { - - var buf bytes.Buffer - - amountOfTokens := rng.Intn(200) - currentOffset := 0 - for i := 0; i < amountOfTokens; i++ { - // generate token - tok := generateToken(currentOffset) - currentOffset += tok.Length() - 1 - - // append to results - buf.WriteString(tok.Value()) - scannerOutput = append(scannerOutput, tok) - - // generate whitespace - whitespaces := rng.Intn(5) + 1 - currentOffset += whitespaces - for i := 0; i < whitespaces; i++ { - _, _ = buf.WriteRune(' ') - } - } - - // EOF token - scannerOutput = append(scannerOutput, generateEOF(currentOffset)) - - scannerInput = buf.String() - return -} diff --git a/internal/parser/scanner/test/gen_test.go b/internal/parser/scanner/test/gen_test.go deleted file mode 100644 index b7a502e0..00000000 --- a/internal/parser/scanner/test/gen_test.go +++ /dev/null @@ -1,16 +0,0 @@ -package scanner - -import ( - "fmt" - "testing" - "time" -) - -func Test_generateScannerInputAndExpectedOutput(t *testing.T) { - start := time.Now() - scIn, _ := generateScannerInputAndExpectedOutput() - fmt.Printf("took %v\n", time.Since(start).Round(time.Millisecond)) - - t.Log(scIn) - // t.Fail() -} diff --git a/internal/test/examples_test.go b/internal/test/examples_test.go index 8a58492c..2796f94c 100644 --- a/internal/test/examples_test.go +++ b/internal/test/examples_test.go @@ -88,6 +88,7 @@ func TestExample06(t *testing.T) { } func TestExample07(t *testing.T) { + t.Skip("see issue #70") p := profile.NewProfiler() RunAndCompare(t, Test{