mirror of
https://github.com/neilotoole/sq.git
synced 2024-12-25 01:04:55 +03:00
* Fixed space issues with expressions * Alias can now be an arbitrary string * Alias can now be an arbitrary string (fixed) * Alias now automatically applied to expressions * Ignore .run * Fixed issue with TestRun not logging correctly to testing.T * Fiddling with sqlite3 temp file closing * Re-enable tests
This commit is contained in:
parent
44d27207f8
commit
2ba633fc2a
2
.gitignore
vendored
2
.gitignore
vendored
@ -24,7 +24,7 @@ _testmain.go
|
||||
*.prof
|
||||
|
||||
.DS_Store
|
||||
|
||||
/.run
|
||||
/.idea
|
||||
/grammar/build
|
||||
*/~test$*
|
||||
|
23
CHANGELOG.md
23
CHANGELOG.md
@ -25,7 +25,7 @@ to SLQ (`sq`'s query language).
|
||||
# Now
|
||||
$ sq '.actor | where(.actor_id <= 2)'
|
||||
```
|
||||
- Column-only queries are now permissible. This has the neat side effect
|
||||
- [#256]: Column-only queries are now possible. This has the neat side effect
|
||||
that `sq` can now be used as a calculator.
|
||||
|
||||
```shell
|
||||
@ -33,11 +33,13 @@ to SLQ (`sq`'s query language).
|
||||
1+2
|
||||
3
|
||||
```
|
||||
You probably want to use `--no-header` (`-H`):
|
||||
You may want to use `--no-header` (`-H`) when using `sq` as a calculator.
|
||||
|
||||
```shell
|
||||
$ sq -H 1+2
|
||||
3
|
||||
$ sq -H '(1+2)*3'
|
||||
9
|
||||
```
|
||||
|
||||
|
||||
@ -59,7 +61,22 @@ to SLQ (`sq`'s query language).
|
||||
PENELOPE 3
|
||||
NICK 3
|
||||
```
|
||||
- [#258]: Column aliases can now be arbitrary strings, instead of only a
|
||||
valid identifier.
|
||||
|
||||
```shell
|
||||
# Previously only valid identifier allowed
|
||||
$ sq '.actor | .first_name:given_name | .[0:2]'
|
||||
given_name
|
||||
PENELOPE
|
||||
NICK
|
||||
|
||||
# Now, any arbitrary string can be used
|
||||
$ sq '.actor | .first_name:"Given Name" | .[0:2]'
|
||||
Given Name
|
||||
PENELOPE
|
||||
NICK
|
||||
```
|
||||
|
||||
## [v0.37.1] - 2023-06-15
|
||||
|
||||
@ -580,6 +597,8 @@ make working with lots of sources much easier.
|
||||
[#244]: https://github.com/neilotoole/sq/issues/244
|
||||
[#252]: https://github.com/neilotoole/sq/issues/252
|
||||
[#254]: https://github.com/neilotoole/sq/issues/254
|
||||
[#256]: https://github.com/neilotoole/sq/issues/256
|
||||
[#258]: https://github.com/neilotoole/sq/issues/258
|
||||
|
||||
[v0.15.2]: https://github.com/neilotoole/sq/releases/tag/v0.15.2
|
||||
[v0.15.3]: https://github.com/neilotoole/sq/compare/v0.15.2...v0.15.3
|
||||
|
@ -50,6 +50,10 @@ func New(ctx context.Context, t testing.TB, from *TestRun) *TestRun {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
if !lg.InContext(ctx) {
|
||||
ctx = lg.NewContext(ctx, slogt.New(t))
|
||||
}
|
||||
|
||||
tr := &TestRun{T: t, Context: ctx}
|
||||
|
||||
var cfgStore config.Store
|
||||
@ -68,10 +72,6 @@ func New(ctx context.Context, t testing.TB, from *TestRun) *TestRun {
|
||||
//
|
||||
// If cfgStore is nil, a new one is created in a temp dir.
|
||||
func newRun(ctx context.Context, t testing.TB, cfgStore config.Store) (ru *run.Run, out, errOut *bytes.Buffer) {
|
||||
if !lg.InContext(ctx) {
|
||||
ctx = lg.NewContext(ctx, slogt.New(t))
|
||||
}
|
||||
|
||||
out = &bytes.Buffer{}
|
||||
errOut = &bytes.Buffer{}
|
||||
|
||||
|
@ -876,6 +876,10 @@ func NewScratchSource(ctx context.Context, name string) (src *source.Source, cln
|
||||
return nil, cleanFn, err
|
||||
}
|
||||
|
||||
// REVISIT: This mechanism is janky: should we be keeping the file open?
|
||||
// Probably not.
|
||||
lg.WarnIfCloseError(log, "Close scratch file", f)
|
||||
|
||||
log.Debug("Created sqlite3 scratchdb data file", lga.Path, f.Name())
|
||||
|
||||
src = &source.Source{
|
||||
@ -887,7 +891,10 @@ func NewScratchSource(ctx context.Context, name string) (src *source.Source, cln
|
||||
fn := func() error {
|
||||
log.Debug("Deleting sqlite3 scratchdb data file", lga.Src, src, lga.Path, f.Name())
|
||||
if cleanFn != nil {
|
||||
return cleanFn()
|
||||
cleanErr := cleanFn()
|
||||
if cleanErr != nil {
|
||||
log.Warn("Error cleaning scratch source", lga.Err, cleanErr)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -168,7 +168,7 @@ selector: NAME (NAME)?;
|
||||
// - ."actor".first_name
|
||||
selectorElement: (selector) (alias)?;
|
||||
|
||||
alias: ALIAS_RESERVED | ':' (ARG | ID);
|
||||
alias: ALIAS_RESERVED | ':' (ARG | ID | STRING);
|
||||
// The grammar has problems dealing with "reserved" lexer tokens.
|
||||
// Basically, there's a problem with using "column:KEYWORD".
|
||||
// ALIAS_RESERVED is a hack to deal with those cases.
|
||||
|
@ -3,6 +3,8 @@ package ast
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/neilotoole/sq/libsq/core/stringz"
|
||||
|
||||
"github.com/neilotoole/sq/libsq/ast/internal/slq"
|
||||
)
|
||||
|
||||
@ -15,6 +17,8 @@ func (v *parseTreeVisitor) VisitAlias(ctx *slq.AliasContext) any {
|
||||
var alias string
|
||||
if ctx.ID() != nil {
|
||||
alias = ctx.ID().GetText()
|
||||
} else if ctx.STRING() != nil {
|
||||
alias = stringz.StripDoubleQuote(ctx.STRING().GetText())
|
||||
}
|
||||
|
||||
switch node := v.cur.(type) {
|
||||
|
@ -1,6 +1,12 @@
|
||||
package ast
|
||||
|
||||
import "github.com/neilotoole/sq/libsq/ast/internal/slq"
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/neilotoole/sq/libsq/core/stringz"
|
||||
|
||||
"github.com/neilotoole/sq/libsq/ast/internal/slq"
|
||||
)
|
||||
|
||||
var (
|
||||
_ Node = (*ExprElementNode)(nil)
|
||||
@ -103,6 +109,13 @@ func (v *parseTreeVisitor) VisitExprElement(ctx *slq.ExprElementContext) interfa
|
||||
return e
|
||||
}
|
||||
}
|
||||
|
||||
if node.alias == "" {
|
||||
node.alias = ctx.GetText()
|
||||
node.alias = stringz.StripDoubleQuote(node.alias)
|
||||
node.alias = strings.TrimPrefix(node.alias, "_")
|
||||
}
|
||||
|
||||
return v.cur.AddChild(node)
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/neilotoole/sq/libsq/ast/internal/slq"
|
||||
)
|
||||
|
||||
@ -132,9 +134,7 @@ func (v *parseTreeVisitor) VisitFunc(ctx *slq.FuncContext) any {
|
||||
|
||||
if node.alias == "" {
|
||||
node.alias = ctx.GetText()
|
||||
if node.alias[0] == '_' {
|
||||
node.alias = node.alias[1:]
|
||||
}
|
||||
node.alias = strings.TrimPrefix(node.alias, "_")
|
||||
}
|
||||
|
||||
return v.cur.AddChild(node)
|
||||
|
File diff suppressed because one or more lines are too long
@ -86,65 +86,65 @@ func slqParserInit() {
|
||||
12, 25, 277, 9, 25, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 0, 1, 50, 28, 0,
|
||||
2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38,
|
||||
40, 42, 44, 46, 48, 50, 52, 54, 0, 9, 1, 0, 40, 45, 2, 0, 3, 6, 20, 20,
|
||||
1, 0, 23, 24, 2, 0, 27, 27, 29, 29, 2, 0, 2, 2, 12, 13, 1, 0, 14, 16, 1,
|
||||
0, 40, 43, 3, 0, 28, 28, 38, 39, 48, 48, 2, 0, 18, 19, 23, 24, 306, 0,
|
||||
59, 1, 0, 0, 0, 2, 80, 1, 0, 0, 0, 4, 88, 1, 0, 0, 0, 6, 108, 1, 0, 0,
|
||||
0, 8, 110, 1, 0, 0, 0, 10, 112, 1, 0, 0, 0, 12, 116, 1, 0, 0, 0, 14, 131,
|
||||
1, 0, 0, 0, 16, 133, 1, 0, 0, 0, 18, 143, 1, 0, 0, 0, 20, 145, 1, 0, 0,
|
||||
0, 22, 147, 1, 0, 0, 0, 24, 158, 1, 0, 0, 0, 26, 167, 1, 0, 0, 0, 28, 169,
|
||||
1, 0, 0, 0, 30, 181, 1, 0, 0, 0, 32, 185, 1, 0, 0, 0, 34, 197, 1, 0, 0,
|
||||
0, 36, 201, 1, 0, 0, 0, 38, 208, 1, 0, 0, 0, 40, 210, 1, 0, 0, 0, 42, 212,
|
||||
1, 0, 0, 0, 44, 215, 1, 0, 0, 0, 46, 217, 1, 0, 0, 0, 48, 230, 1, 0, 0,
|
||||
0, 50, 246, 1, 0, 0, 0, 52, 278, 1, 0, 0, 0, 54, 280, 1, 0, 0, 0, 56, 58,
|
||||
5, 1, 0, 0, 57, 56, 1, 0, 0, 0, 58, 61, 1, 0, 0, 0, 59, 57, 1, 0, 0, 0,
|
||||
59, 60, 1, 0, 0, 0, 60, 62, 1, 0, 0, 0, 61, 59, 1, 0, 0, 0, 62, 71, 3,
|
||||
2, 1, 0, 63, 65, 5, 1, 0, 0, 64, 63, 1, 0, 0, 0, 65, 66, 1, 0, 0, 0, 66,
|
||||
64, 1, 0, 0, 0, 66, 67, 1, 0, 0, 0, 67, 68, 1, 0, 0, 0, 68, 70, 3, 2, 1,
|
||||
0, 69, 64, 1, 0, 0, 0, 70, 73, 1, 0, 0, 0, 71, 69, 1, 0, 0, 0, 71, 72,
|
||||
1, 0, 0, 0, 72, 77, 1, 0, 0, 0, 73, 71, 1, 0, 0, 0, 74, 76, 5, 1, 0, 0,
|
||||
75, 74, 1, 0, 0, 0, 76, 79, 1, 0, 0, 0, 77, 75, 1, 0, 0, 0, 77, 78, 1,
|
||||
0, 0, 0, 78, 1, 1, 0, 0, 0, 79, 77, 1, 0, 0, 0, 80, 85, 3, 4, 2, 0, 81,
|
||||
82, 5, 36, 0, 0, 82, 84, 3, 4, 2, 0, 83, 81, 1, 0, 0, 0, 84, 87, 1, 0,
|
||||
0, 0, 85, 83, 1, 0, 0, 0, 85, 86, 1, 0, 0, 0, 86, 3, 1, 0, 0, 0, 87, 85,
|
||||
1, 0, 0, 0, 88, 93, 3, 6, 3, 0, 89, 90, 5, 35, 0, 0, 90, 92, 3, 6, 3, 0,
|
||||
91, 89, 1, 0, 0, 0, 92, 95, 1, 0, 0, 0, 93, 91, 1, 0, 0, 0, 93, 94, 1,
|
||||
0, 0, 0, 94, 5, 1, 0, 0, 0, 95, 93, 1, 0, 0, 0, 96, 109, 3, 42, 21, 0,
|
||||
97, 109, 3, 44, 22, 0, 98, 109, 3, 36, 18, 0, 99, 109, 3, 16, 8, 0, 100,
|
||||
109, 3, 28, 14, 0, 101, 109, 3, 32, 16, 0, 102, 109, 3, 46, 23, 0, 103,
|
||||
109, 3, 20, 10, 0, 104, 109, 3, 22, 11, 0, 105, 109, 3, 24, 12, 0, 106,
|
||||
109, 3, 10, 5, 0, 107, 109, 3, 48, 24, 0, 108, 96, 1, 0, 0, 0, 108, 97,
|
||||
1, 0, 0, 0, 108, 98, 1, 0, 0, 0, 108, 99, 1, 0, 0, 0, 108, 100, 1, 0, 0,
|
||||
0, 108, 101, 1, 0, 0, 0, 108, 102, 1, 0, 0, 0, 108, 103, 1, 0, 0, 0, 108,
|
||||
104, 1, 0, 0, 0, 108, 105, 1, 0, 0, 0, 108, 106, 1, 0, 0, 0, 108, 107,
|
||||
1, 0, 0, 0, 109, 7, 1, 0, 0, 0, 110, 111, 7, 0, 0, 0, 111, 9, 1, 0, 0,
|
||||
0, 112, 114, 3, 12, 6, 0, 113, 115, 3, 38, 19, 0, 114, 113, 1, 0, 0, 0,
|
||||
114, 115, 1, 0, 0, 0, 115, 11, 1, 0, 0, 0, 116, 117, 3, 14, 7, 0, 117,
|
||||
127, 5, 31, 0, 0, 118, 123, 3, 50, 25, 0, 119, 120, 5, 35, 0, 0, 120, 122,
|
||||
3, 50, 25, 0, 121, 119, 1, 0, 0, 0, 122, 125, 1, 0, 0, 0, 123, 121, 1,
|
||||
0, 0, 0, 123, 124, 1, 0, 0, 0, 124, 128, 1, 0, 0, 0, 125, 123, 1, 0, 0,
|
||||
0, 126, 128, 5, 2, 0, 0, 127, 118, 1, 0, 0, 0, 127, 126, 1, 0, 0, 0, 127,
|
||||
128, 1, 0, 0, 0, 128, 129, 1, 0, 0, 0, 129, 130, 5, 32, 0, 0, 130, 13,
|
||||
1, 0, 0, 0, 131, 132, 7, 1, 0, 0, 132, 15, 1, 0, 0, 0, 133, 134, 5, 7,
|
||||
0, 0, 134, 135, 5, 31, 0, 0, 135, 136, 3, 18, 9, 0, 136, 137, 5, 32, 0,
|
||||
0, 137, 17, 1, 0, 0, 0, 138, 139, 3, 34, 17, 0, 139, 140, 3, 8, 4, 0, 140,
|
||||
141, 3, 34, 17, 0, 141, 144, 1, 0, 0, 0, 142, 144, 3, 34, 17, 0, 143, 138,
|
||||
1, 0, 0, 0, 143, 142, 1, 0, 0, 0, 144, 19, 1, 0, 0, 0, 145, 146, 5, 8,
|
||||
0, 0, 146, 21, 1, 0, 0, 0, 147, 153, 5, 9, 0, 0, 148, 150, 5, 31, 0, 0,
|
||||
149, 151, 3, 34, 17, 0, 150, 149, 1, 0, 0, 0, 150, 151, 1, 0, 0, 0, 151,
|
||||
152, 1, 0, 0, 0, 152, 154, 5, 32, 0, 0, 153, 148, 1, 0, 0, 0, 153, 154,
|
||||
1, 0, 0, 0, 154, 156, 1, 0, 0, 0, 155, 157, 3, 38, 19, 0, 156, 155, 1,
|
||||
0, 0, 0, 156, 157, 1, 0, 0, 0, 157, 23, 1, 0, 0, 0, 158, 159, 5, 21, 0,
|
||||
0, 159, 161, 5, 31, 0, 0, 160, 162, 3, 50, 25, 0, 161, 160, 1, 0, 0, 0,
|
||||
161, 162, 1, 0, 0, 0, 162, 163, 1, 0, 0, 0, 163, 164, 5, 32, 0, 0, 164,
|
||||
25, 1, 0, 0, 0, 165, 168, 3, 34, 17, 0, 166, 168, 3, 12, 6, 0, 167, 165,
|
||||
1, 0, 0, 0, 167, 166, 1, 0, 0, 0, 168, 27, 1, 0, 0, 0, 169, 170, 5, 22,
|
||||
0, 0, 170, 171, 5, 31, 0, 0, 171, 176, 3, 26, 13, 0, 172, 173, 5, 35, 0,
|
||||
0, 173, 175, 3, 26, 13, 0, 174, 172, 1, 0, 0, 0, 175, 178, 1, 0, 0, 0,
|
||||
176, 174, 1, 0, 0, 0, 176, 177, 1, 0, 0, 0, 177, 179, 1, 0, 0, 0, 178,
|
||||
176, 1, 0, 0, 0, 179, 180, 5, 32, 0, 0, 180, 29, 1, 0, 0, 0, 181, 183,
|
||||
3, 34, 17, 0, 182, 184, 7, 2, 0, 0, 183, 182, 1, 0, 0, 0, 183, 184, 1,
|
||||
0, 0, 0, 184, 31, 1, 0, 0, 0, 185, 186, 5, 25, 0, 0, 186, 187, 5, 31, 0,
|
||||
0, 187, 192, 3, 30, 15, 0, 188, 189, 5, 35, 0, 0, 189, 191, 3, 30, 15,
|
||||
1, 0, 23, 24, 3, 0, 27, 27, 29, 29, 48, 48, 2, 0, 2, 2, 12, 13, 1, 0, 14,
|
||||
16, 1, 0, 40, 43, 3, 0, 28, 28, 38, 39, 48, 48, 2, 0, 18, 19, 23, 24, 306,
|
||||
0, 59, 1, 0, 0, 0, 2, 80, 1, 0, 0, 0, 4, 88, 1, 0, 0, 0, 6, 108, 1, 0,
|
||||
0, 0, 8, 110, 1, 0, 0, 0, 10, 112, 1, 0, 0, 0, 12, 116, 1, 0, 0, 0, 14,
|
||||
131, 1, 0, 0, 0, 16, 133, 1, 0, 0, 0, 18, 143, 1, 0, 0, 0, 20, 145, 1,
|
||||
0, 0, 0, 22, 147, 1, 0, 0, 0, 24, 158, 1, 0, 0, 0, 26, 167, 1, 0, 0, 0,
|
||||
28, 169, 1, 0, 0, 0, 30, 181, 1, 0, 0, 0, 32, 185, 1, 0, 0, 0, 34, 197,
|
||||
1, 0, 0, 0, 36, 201, 1, 0, 0, 0, 38, 208, 1, 0, 0, 0, 40, 210, 1, 0, 0,
|
||||
0, 42, 212, 1, 0, 0, 0, 44, 215, 1, 0, 0, 0, 46, 217, 1, 0, 0, 0, 48, 230,
|
||||
1, 0, 0, 0, 50, 246, 1, 0, 0, 0, 52, 278, 1, 0, 0, 0, 54, 280, 1, 0, 0,
|
||||
0, 56, 58, 5, 1, 0, 0, 57, 56, 1, 0, 0, 0, 58, 61, 1, 0, 0, 0, 59, 57,
|
||||
1, 0, 0, 0, 59, 60, 1, 0, 0, 0, 60, 62, 1, 0, 0, 0, 61, 59, 1, 0, 0, 0,
|
||||
62, 71, 3, 2, 1, 0, 63, 65, 5, 1, 0, 0, 64, 63, 1, 0, 0, 0, 65, 66, 1,
|
||||
0, 0, 0, 66, 64, 1, 0, 0, 0, 66, 67, 1, 0, 0, 0, 67, 68, 1, 0, 0, 0, 68,
|
||||
70, 3, 2, 1, 0, 69, 64, 1, 0, 0, 0, 70, 73, 1, 0, 0, 0, 71, 69, 1, 0, 0,
|
||||
0, 71, 72, 1, 0, 0, 0, 72, 77, 1, 0, 0, 0, 73, 71, 1, 0, 0, 0, 74, 76,
|
||||
5, 1, 0, 0, 75, 74, 1, 0, 0, 0, 76, 79, 1, 0, 0, 0, 77, 75, 1, 0, 0, 0,
|
||||
77, 78, 1, 0, 0, 0, 78, 1, 1, 0, 0, 0, 79, 77, 1, 0, 0, 0, 80, 85, 3, 4,
|
||||
2, 0, 81, 82, 5, 36, 0, 0, 82, 84, 3, 4, 2, 0, 83, 81, 1, 0, 0, 0, 84,
|
||||
87, 1, 0, 0, 0, 85, 83, 1, 0, 0, 0, 85, 86, 1, 0, 0, 0, 86, 3, 1, 0, 0,
|
||||
0, 87, 85, 1, 0, 0, 0, 88, 93, 3, 6, 3, 0, 89, 90, 5, 35, 0, 0, 90, 92,
|
||||
3, 6, 3, 0, 91, 89, 1, 0, 0, 0, 92, 95, 1, 0, 0, 0, 93, 91, 1, 0, 0, 0,
|
||||
93, 94, 1, 0, 0, 0, 94, 5, 1, 0, 0, 0, 95, 93, 1, 0, 0, 0, 96, 109, 3,
|
||||
42, 21, 0, 97, 109, 3, 44, 22, 0, 98, 109, 3, 36, 18, 0, 99, 109, 3, 16,
|
||||
8, 0, 100, 109, 3, 28, 14, 0, 101, 109, 3, 32, 16, 0, 102, 109, 3, 46,
|
||||
23, 0, 103, 109, 3, 20, 10, 0, 104, 109, 3, 22, 11, 0, 105, 109, 3, 24,
|
||||
12, 0, 106, 109, 3, 10, 5, 0, 107, 109, 3, 48, 24, 0, 108, 96, 1, 0, 0,
|
||||
0, 108, 97, 1, 0, 0, 0, 108, 98, 1, 0, 0, 0, 108, 99, 1, 0, 0, 0, 108,
|
||||
100, 1, 0, 0, 0, 108, 101, 1, 0, 0, 0, 108, 102, 1, 0, 0, 0, 108, 103,
|
||||
1, 0, 0, 0, 108, 104, 1, 0, 0, 0, 108, 105, 1, 0, 0, 0, 108, 106, 1, 0,
|
||||
0, 0, 108, 107, 1, 0, 0, 0, 109, 7, 1, 0, 0, 0, 110, 111, 7, 0, 0, 0, 111,
|
||||
9, 1, 0, 0, 0, 112, 114, 3, 12, 6, 0, 113, 115, 3, 38, 19, 0, 114, 113,
|
||||
1, 0, 0, 0, 114, 115, 1, 0, 0, 0, 115, 11, 1, 0, 0, 0, 116, 117, 3, 14,
|
||||
7, 0, 117, 127, 5, 31, 0, 0, 118, 123, 3, 50, 25, 0, 119, 120, 5, 35, 0,
|
||||
0, 120, 122, 3, 50, 25, 0, 121, 119, 1, 0, 0, 0, 122, 125, 1, 0, 0, 0,
|
||||
123, 121, 1, 0, 0, 0, 123, 124, 1, 0, 0, 0, 124, 128, 1, 0, 0, 0, 125,
|
||||
123, 1, 0, 0, 0, 126, 128, 5, 2, 0, 0, 127, 118, 1, 0, 0, 0, 127, 126,
|
||||
1, 0, 0, 0, 127, 128, 1, 0, 0, 0, 128, 129, 1, 0, 0, 0, 129, 130, 5, 32,
|
||||
0, 0, 130, 13, 1, 0, 0, 0, 131, 132, 7, 1, 0, 0, 132, 15, 1, 0, 0, 0, 133,
|
||||
134, 5, 7, 0, 0, 134, 135, 5, 31, 0, 0, 135, 136, 3, 18, 9, 0, 136, 137,
|
||||
5, 32, 0, 0, 137, 17, 1, 0, 0, 0, 138, 139, 3, 34, 17, 0, 139, 140, 3,
|
||||
8, 4, 0, 140, 141, 3, 34, 17, 0, 141, 144, 1, 0, 0, 0, 142, 144, 3, 34,
|
||||
17, 0, 143, 138, 1, 0, 0, 0, 143, 142, 1, 0, 0, 0, 144, 19, 1, 0, 0, 0,
|
||||
145, 146, 5, 8, 0, 0, 146, 21, 1, 0, 0, 0, 147, 153, 5, 9, 0, 0, 148, 150,
|
||||
5, 31, 0, 0, 149, 151, 3, 34, 17, 0, 150, 149, 1, 0, 0, 0, 150, 151, 1,
|
||||
0, 0, 0, 151, 152, 1, 0, 0, 0, 152, 154, 5, 32, 0, 0, 153, 148, 1, 0, 0,
|
||||
0, 153, 154, 1, 0, 0, 0, 154, 156, 1, 0, 0, 0, 155, 157, 3, 38, 19, 0,
|
||||
156, 155, 1, 0, 0, 0, 156, 157, 1, 0, 0, 0, 157, 23, 1, 0, 0, 0, 158, 159,
|
||||
5, 21, 0, 0, 159, 161, 5, 31, 0, 0, 160, 162, 3, 50, 25, 0, 161, 160, 1,
|
||||
0, 0, 0, 161, 162, 1, 0, 0, 0, 162, 163, 1, 0, 0, 0, 163, 164, 5, 32, 0,
|
||||
0, 164, 25, 1, 0, 0, 0, 165, 168, 3, 34, 17, 0, 166, 168, 3, 12, 6, 0,
|
||||
167, 165, 1, 0, 0, 0, 167, 166, 1, 0, 0, 0, 168, 27, 1, 0, 0, 0, 169, 170,
|
||||
5, 22, 0, 0, 170, 171, 5, 31, 0, 0, 171, 176, 3, 26, 13, 0, 172, 173, 5,
|
||||
35, 0, 0, 173, 175, 3, 26, 13, 0, 174, 172, 1, 0, 0, 0, 175, 178, 1, 0,
|
||||
0, 0, 176, 174, 1, 0, 0, 0, 176, 177, 1, 0, 0, 0, 177, 179, 1, 0, 0, 0,
|
||||
178, 176, 1, 0, 0, 0, 179, 180, 5, 32, 0, 0, 180, 29, 1, 0, 0, 0, 181,
|
||||
183, 3, 34, 17, 0, 182, 184, 7, 2, 0, 0, 183, 182, 1, 0, 0, 0, 183, 184,
|
||||
1, 0, 0, 0, 184, 31, 1, 0, 0, 0, 185, 186, 5, 25, 0, 0, 186, 187, 5, 31,
|
||||
0, 0, 187, 192, 3, 30, 15, 0, 188, 189, 5, 35, 0, 0, 189, 191, 3, 30, 15,
|
||||
0, 190, 188, 1, 0, 0, 0, 191, 194, 1, 0, 0, 0, 192, 190, 1, 0, 0, 0, 192,
|
||||
193, 1, 0, 0, 0, 193, 195, 1, 0, 0, 0, 194, 192, 1, 0, 0, 0, 195, 196,
|
||||
5, 32, 0, 0, 196, 33, 1, 0, 0, 0, 197, 199, 5, 46, 0, 0, 198, 200, 5, 46,
|
||||
@ -3626,6 +3626,7 @@ type IAliasContext interface {
|
||||
COLON() antlr.TerminalNode
|
||||
ARG() antlr.TerminalNode
|
||||
ID() antlr.TerminalNode
|
||||
STRING() antlr.TerminalNode
|
||||
|
||||
// IsAliasContext differentiates from other interfaces.
|
||||
IsAliasContext()
|
||||
@ -3674,6 +3675,10 @@ func (s *AliasContext) ID() antlr.TerminalNode {
|
||||
return s.GetToken(SLQParserID, 0)
|
||||
}
|
||||
|
||||
func (s *AliasContext) STRING() antlr.TerminalNode {
|
||||
return s.GetToken(SLQParserSTRING, 0)
|
||||
}
|
||||
|
||||
func (s *AliasContext) GetRuleContext() antlr.RuleContext {
|
||||
return s
|
||||
}
|
||||
@ -3749,7 +3754,7 @@ func (p *SLQParser) Alias() (localctx IAliasContext) {
|
||||
p.SetState(207)
|
||||
_la = p.GetTokenStream().LA(1)
|
||||
|
||||
if !(_la == SLQParserARG || _la == SLQParserID) {
|
||||
if !((int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&281475647799296) != 0) {
|
||||
p.GetErrorHandler().RecoverInline(p)
|
||||
} else {
|
||||
p.GetErrorHandler().ReportMatch(p)
|
||||
|
@ -18,11 +18,8 @@ func doExpr(rc *Context, expr *ast.ExprNode) (string, error) {
|
||||
if expr.HasParens() {
|
||||
sb.WriteRune('(')
|
||||
}
|
||||
for i, child := range expr.Children() {
|
||||
if i > 0 {
|
||||
sb.WriteRune(sp)
|
||||
}
|
||||
|
||||
for _, child := range expr.Children() {
|
||||
switch child := child.(type) {
|
||||
case *ast.TblColSelectorNode, *ast.ColSelectorNode:
|
||||
val, err := renderSelectorNode(rc.Dialect, child)
|
||||
|
@ -27,5 +27,17 @@ func doOperator(rc *Context, op *ast.OperatorNode) (string, error) {
|
||||
}
|
||||
|
||||
// By default, just return the operator unchanged.
|
||||
if operatorHasSpace(val) {
|
||||
val = " " + val + " "
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func operatorHasSpace(op string) bool {
|
||||
switch op {
|
||||
case "-", "+", "*", "/":
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
@ -4,6 +4,8 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/neilotoole/sq/libsq/core/stringz"
|
||||
|
||||
"github.com/antlr/antlr4/runtime/Go/antlr/v4"
|
||||
|
||||
"github.com/neilotoole/sq/libsq/ast/internal/slq"
|
||||
@ -17,7 +19,12 @@ func (v *parseTreeVisitor) VisitSelectorElement(ctx *slq.SelectorElementContext)
|
||||
}
|
||||
|
||||
if aliasCtx := ctx.Alias(); aliasCtx != nil {
|
||||
node.alias = ctx.Alias().ID().GetText()
|
||||
if aliasCtx.ID() != nil {
|
||||
node.alias = aliasCtx.ID().GetText()
|
||||
}
|
||||
if aliasCtx.STRING() != nil {
|
||||
node.alias = stringz.StripDoubleQuote(aliasCtx.STRING().GetText())
|
||||
}
|
||||
}
|
||||
|
||||
if err := v.cur.AddChild(node); err != nil {
|
||||
|
@ -438,6 +438,19 @@ func DoubleQuote(s string) string {
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// StripDoubleQuote strips double quotes from s,
|
||||
// or returns s unchanged if it is not correctly double-quoted.
|
||||
func StripDoubleQuote(s string) string {
|
||||
if len(s) < 2 {
|
||||
return s
|
||||
}
|
||||
|
||||
if s[0] == '"' && s[len(s)-1] == '"' {
|
||||
return s[1 : len(s)-1]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// BacktickQuote backtick-quotes (and escapes) s.
|
||||
//
|
||||
// hello `world` --> `hello ``world```
|
||||
|
@ -325,6 +325,29 @@ func TestDoubleQuote(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStripDoubleQuote(t *testing.T) {
|
||||
testCases := []struct {
|
||||
in string
|
||||
want string
|
||||
}{
|
||||
{in: ``, want: ``},
|
||||
{in: `"`, want: `"`},
|
||||
{in: `""`, want: ``},
|
||||
{in: `"a`, want: `"a`},
|
||||
{in: `"a"`, want: `a`},
|
||||
{in: `"abc"`, want: `abc`},
|
||||
{in: `"hello "" world"`, want: `hello "" world`},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tutil.Name(i, tc.in), func(t *testing.T) {
|
||||
got := stringz.StripDoubleQuote(tc.in)
|
||||
require.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBacktickQuote(t *testing.T) {
|
||||
testCases := []struct {
|
||||
in string
|
||||
|
@ -40,8 +40,7 @@ func (ng *engine) prepare(ctx context.Context, qm *queryModel) error {
|
||||
return errz.Errorf("unknown ast.Tabler %T: %s", node, node)
|
||||
}
|
||||
|
||||
rndr := ng.targetDB.SQLDriver().Renderer()
|
||||
// rndr := ng.rc.Renderer
|
||||
rndr := ng.rc.Renderer
|
||||
|
||||
if frags.Columns, err = rndr.SelectCols(ng.rc, qm.Cols); err != nil {
|
||||
return err
|
||||
|
@ -5,12 +5,10 @@ import (
|
||||
|
||||
"github.com/neilotoole/sq/drivers/mysql"
|
||||
|
||||
"github.com/neilotoole/sq/libsq/source"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
//nolint:exhaustive,lll
|
||||
//nolint:exhaustive
|
||||
func TestQuery_args(t *testing.T) {
|
||||
testCases := []queryTestCase{
|
||||
{
|
||||
@ -18,7 +16,7 @@ func TestQuery_args(t *testing.T) {
|
||||
in: `@sakila | .actor | where(.first_name == $first)`,
|
||||
args: map[string]string{"first": "TOM"},
|
||||
wantSQL: `SELECT * FROM "actor" WHERE "first_name" = 'TOM'`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `first_name` = 'TOM'"},
|
||||
override: driverMap{mysql.Type: "SELECT * FROM `actor` WHERE `first_name` = 'TOM'"},
|
||||
wantRecCount: 2,
|
||||
},
|
||||
{
|
||||
@ -26,7 +24,7 @@ func TestQuery_args(t *testing.T) {
|
||||
in: `@sakila | .actor | where(.first_name == $first && .last_name == $last)`,
|
||||
args: map[string]string{"first": "TOM", "last": "MIRANDA"},
|
||||
wantSQL: `SELECT * FROM "actor" WHERE "first_name" = 'TOM' AND "last_name" = 'MIRANDA'`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `first_name` = 'TOM' AND `last_name` = 'MIRANDA'"},
|
||||
override: driverMap{mysql.Type: "SELECT * FROM `actor` WHERE `first_name` = 'TOM' AND `last_name` = 'MIRANDA'"},
|
||||
wantRecCount: 1,
|
||||
},
|
||||
{
|
||||
@ -34,7 +32,7 @@ func TestQuery_args(t *testing.T) {
|
||||
in: `@sakila | .actor | where(.actor_id == int($id))`,
|
||||
args: map[string]string{"id": "1"},
|
||||
wantSQL: `SELECT * FROM "actor" WHERE "actor_id" = 1`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` = 1"},
|
||||
override: driverMap{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` = 1"},
|
||||
skip: true, // Skip until we implement casting, e.g. .actor_id == int($id)
|
||||
wantRecCount: 1,
|
||||
},
|
||||
|
@ -7,8 +7,6 @@ import (
|
||||
|
||||
"github.com/neilotoole/sq/drivers/mysql"
|
||||
|
||||
"github.com/neilotoole/sq/libsq/source"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
@ -19,14 +17,14 @@ func TestQuery_cols(t *testing.T) {
|
||||
name: "cols",
|
||||
in: `@sakila | .actor | .first_name, .last_name`,
|
||||
wantSQL: `SELECT "first_name", "last_name" FROM "actor"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT `first_name`, `last_name` FROM `actor`"},
|
||||
override: driverMap{mysql.Type: "SELECT `first_name`, `last_name` FROM `actor`"},
|
||||
wantRecCount: sakila.TblActorCount,
|
||||
},
|
||||
{
|
||||
name: "cols-whitespace-single-col",
|
||||
in: `@sakila | .actor | ."first name"`,
|
||||
wantSQL: `SELECT "first name" FROM "actor"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT `first name` FROM `actor`"},
|
||||
override: driverMap{mysql.Type: "SELECT `first name` FROM `actor`"},
|
||||
wantRecCount: sakila.TblActorCount,
|
||||
skipExec: true,
|
||||
},
|
||||
@ -34,44 +32,50 @@ func TestQuery_cols(t *testing.T) {
|
||||
name: "cols-whitespace-multiple-cols",
|
||||
in: `@sakila | .actor | .actor_id, ."first name", ."last name"`,
|
||||
wantSQL: `SELECT "actor_id", "first name", "last name" FROM "actor"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT `actor_id`, `first name`, `last name` FROM `actor`"},
|
||||
override: driverMap{mysql.Type: "SELECT `actor_id`, `first name`, `last name` FROM `actor`"},
|
||||
wantRecCount: sakila.TblActorCount,
|
||||
skipExec: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: "table-whitespace",
|
||||
in: `@sakila | ."film actor"`,
|
||||
wantSQL: `SELECT * FROM "film actor"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `film actor`"},
|
||||
override: driverMap{mysql.Type: "SELECT * FROM `film actor`"},
|
||||
skipExec: true,
|
||||
},
|
||||
{
|
||||
name: "cols-aliases",
|
||||
in: `@sakila | .actor | .first_name:given_name, .last_name:family_name`,
|
||||
wantSQL: `SELECT "first_name" AS "given_name", "last_name" AS "family_name" FROM "actor"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT `first_name` AS `given_name`, `last_name` AS `family_name` FROM `actor`"},
|
||||
override: driverMap{mysql.Type: "SELECT `first_name` AS `given_name`, `last_name` AS `family_name` FROM `actor`"},
|
||||
wantRecCount: sakila.TblActorCount,
|
||||
},
|
||||
{
|
||||
name: "cols-aliases-whitespace",
|
||||
in: `@sakila | .actor | .first_name:"Given Name", .last_name:family_name`,
|
||||
wantSQL: `SELECT "first_name" AS "Given Name", "last_name" AS "family_name" FROM "actor"`,
|
||||
override: driverMap{mysql.Type: "SELECT `first_name` AS `Given Name`, `last_name` AS `family_name` FROM `actor`"},
|
||||
wantRecCount: sakila.TblActorCount,
|
||||
},
|
||||
{
|
||||
name: "handle-table/cols",
|
||||
in: `@sakila.actor | .first_name, .last_name`,
|
||||
wantSQL: `SELECT "first_name", "last_name" FROM "actor"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT `first_name`, `last_name` FROM `actor`"},
|
||||
override: driverMap{mysql.Type: "SELECT `first_name`, `last_name` FROM `actor`"},
|
||||
wantRecCount: sakila.TblActorCount,
|
||||
},
|
||||
{
|
||||
name: "cols-select-literal-value",
|
||||
in: `@sakila.actor | .first_name, "xxx", .last_name`,
|
||||
wantSQL: `SELECT "first_name", 'xxx', "last_name" FROM "actor"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT `first_name`, 'xxx', `last_name` FROM `actor`"},
|
||||
wantSQL: `SELECT "first_name", 'xxx' AS "xxx", "last_name" FROM "actor"`,
|
||||
override: driverMap{mysql.Type: "SELECT `first_name`, 'xxx' AS `xxx`, `last_name` FROM `actor`"},
|
||||
wantRecCount: sakila.TblActorCount,
|
||||
},
|
||||
{
|
||||
name: "select/literal",
|
||||
in: `@sakila.actor | .first_name, 5`,
|
||||
wantSQL: `SELECT "first_name", 5 FROM "actor"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT `first_name`, 5 FROM `actor`"},
|
||||
wantSQL: `SELECT "first_name", 5 AS "5" FROM "actor"`,
|
||||
override: driverMap{mysql.Type: "SELECT `first_name`, 5 AS `5` FROM `actor`"},
|
||||
wantRecCount: sakila.TblActorCount,
|
||||
},
|
||||
}
|
||||
|
@ -5,8 +5,6 @@ import (
|
||||
|
||||
"github.com/neilotoole/sq/drivers/mysql"
|
||||
|
||||
"github.com/neilotoole/sq/libsq/source"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
@ -17,35 +15,35 @@ func TestQuery_count(t *testing.T) {
|
||||
name: "alias",
|
||||
in: `@sakila | .actor | count:quantity`,
|
||||
wantSQL: `SELECT count(*) AS "quantity" FROM "actor"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT count(*) AS `quantity` FROM `actor`"},
|
||||
override: driverMap{mysql.Type: "SELECT count(*) AS `quantity` FROM `actor`"},
|
||||
wantRecCount: 1,
|
||||
},
|
||||
{
|
||||
name: "count-same-alias",
|
||||
in: `@sakila | .actor | count:count`,
|
||||
wantSQL: `SELECT count(*) AS "count" FROM "actor"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT count(*) AS `count` FROM `actor`"},
|
||||
override: driverMap{mysql.Type: "SELECT count(*) AS `count` FROM `actor`"},
|
||||
wantRecCount: 1,
|
||||
},
|
||||
{
|
||||
name: "whitespace-col",
|
||||
in: `@sakila | .actor | count(."first name")`,
|
||||
wantSQL: `SELECT count("first name") FROM "actor"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT count(`first name`) FROM `actor`"},
|
||||
override: driverMap{mysql.Type: "SELECT count(`first name`) FROM `actor`"},
|
||||
skipExec: true,
|
||||
},
|
||||
{
|
||||
name: "select-handle-table",
|
||||
in: `@sakila.actor | count`,
|
||||
wantSQL: `SELECT count(*) AS "count" FROM "actor"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT count(*) AS `count` FROM `actor`"},
|
||||
override: driverMap{mysql.Type: "SELECT count(*) AS `count` FROM `actor`"},
|
||||
wantRecCount: 1,
|
||||
},
|
||||
{
|
||||
name: "select-handle-table-ws-selector",
|
||||
in: `@sakila.actor | count(."first name")`,
|
||||
wantSQL: `SELECT count("first name") FROM "actor"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT count(`first name`) FROM `actor`"},
|
||||
override: driverMap{mysql.Type: "SELECT count(`first name`) FROM `actor`"},
|
||||
skipExec: true,
|
||||
},
|
||||
{
|
||||
@ -54,21 +52,21 @@ func TestQuery_count(t *testing.T) {
|
||||
// being a reserved word (unique).
|
||||
in: `@sakila | .actor | count:unique`,
|
||||
wantSQL: `SELECT count(*) AS "unique" FROM "actor"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT count(*) AS `unique` FROM `actor`"},
|
||||
override: driverMap{mysql.Type: "SELECT count(*) AS `unique` FROM `actor`"},
|
||||
wantRecCount: 1,
|
||||
},
|
||||
{
|
||||
name: "no-parens-no-args-with-alias-arbitrary",
|
||||
in: `@sakila | .actor | count:something_123`,
|
||||
wantSQL: `SELECT count(*) AS "something_123" FROM "actor"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT count(*) AS `something_123` FROM `actor`"},
|
||||
override: driverMap{mysql.Type: "SELECT count(*) AS `something_123` FROM `actor`"},
|
||||
wantRecCount: 1,
|
||||
},
|
||||
{
|
||||
name: "parens-no-args",
|
||||
in: `@sakila | .actor | count()`,
|
||||
wantSQL: `SELECT count(*) AS "count" FROM "actor"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT count(*) AS `count` FROM `actor`"},
|
||||
override: driverMap{mysql.Type: "SELECT count(*) AS `count` FROM `actor`"},
|
||||
wantRecCount: 1,
|
||||
},
|
||||
{
|
||||
@ -80,7 +78,7 @@ func TestQuery_count(t *testing.T) {
|
||||
name: "single-selector",
|
||||
in: `@sakila | .actor | count(.first_name)`,
|
||||
wantSQL: `SELECT count("first_name") FROM "actor"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT count(`first_name`) FROM `actor`"},
|
||||
override: driverMap{mysql.Type: "SELECT count(`first_name`) FROM `actor`"},
|
||||
wantRecCount: 1,
|
||||
},
|
||||
{
|
||||
@ -92,7 +90,7 @@ func TestQuery_count(t *testing.T) {
|
||||
name: "count/no-parens-no-args",
|
||||
in: `@sakila | .actor | count`,
|
||||
wantSQL: `SELECT count(*) AS "count" FROM "actor"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT count(*) AS `count` FROM `actor`"},
|
||||
override: driverMap{mysql.Type: "SELECT count(*) AS `count` FROM `actor`"},
|
||||
wantRecCount: 1,
|
||||
},
|
||||
{
|
||||
@ -101,7 +99,7 @@ func TestQuery_count(t *testing.T) {
|
||||
// being a reserved word (count).
|
||||
in: `@sakila | .actor | count:count`,
|
||||
wantSQL: `SELECT count(*) AS "count" FROM "actor"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT count(*) AS `count` FROM `actor`"},
|
||||
override: driverMap{mysql.Type: "SELECT count(*) AS `count` FROM `actor`"},
|
||||
wantRecCount: 1,
|
||||
},
|
||||
}
|
||||
|
@ -3,44 +3,44 @@ package libsq_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/neilotoole/sq/testh/tutil"
|
||||
|
||||
"github.com/neilotoole/sq/testh/sakila"
|
||||
|
||||
"github.com/neilotoole/sq/drivers/mysql"
|
||||
|
||||
"github.com/neilotoole/sq/libsq/source"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
//nolint:exhaustive,lll
|
||||
//nolint:exhaustive
|
||||
func TestQuery_expr_where(t *testing.T) {
|
||||
testCases := []queryTestCase{
|
||||
{
|
||||
name: "literal/string",
|
||||
in: `@sakila | .actor | where(.first_name == "TOM")`,
|
||||
wantSQL: `SELECT * FROM "actor" WHERE "first_name" = 'TOM'`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `first_name` = 'TOM'"},
|
||||
override: driverMap{mysql.Type: "SELECT * FROM `actor` WHERE `first_name` = 'TOM'"},
|
||||
wantRecCount: 2,
|
||||
},
|
||||
{
|
||||
name: "literal/two-strings",
|
||||
in: `@sakila | .actor | where(.first_name == "TOM" && .last_name == "MIRANDA")`,
|
||||
wantSQL: `SELECT * FROM "actor" WHERE "first_name" = 'TOM' AND "last_name" = 'MIRANDA'`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `first_name` = 'TOM' AND `last_name` = 'MIRANDA'"},
|
||||
override: driverMap{mysql.Type: "SELECT * FROM `actor` WHERE `first_name` = 'TOM' AND `last_name` = 'MIRANDA'"},
|
||||
wantRecCount: 1,
|
||||
},
|
||||
{
|
||||
name: "literal/integer",
|
||||
in: `@sakila | .actor | where(.actor_id == 1)`,
|
||||
wantSQL: `SELECT * FROM "actor" WHERE "actor_id" = 1`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` = 1"},
|
||||
override: driverMap{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` = 1"},
|
||||
wantRecCount: 1,
|
||||
},
|
||||
{
|
||||
name: "is_null",
|
||||
in: `@sakila | .address | where(.postal_code == null)`,
|
||||
wantSQL: `SELECT * FROM "address" WHERE "postal_code" IS NULL`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `address` WHERE `postal_code` IS NULL"},
|
||||
override: driverMap{mysql.Type: "SELECT * FROM `address` WHERE `postal_code` IS NULL"},
|
||||
wantRecCount: 4,
|
||||
// skipExec because mysql sakila db doesn't have the same null values.
|
||||
// This is a bug in the dataset.
|
||||
@ -50,7 +50,7 @@ func TestQuery_expr_where(t *testing.T) {
|
||||
name: "is_not_null",
|
||||
in: `@sakila | .address | where(.postal_code != null)`,
|
||||
wantSQL: `SELECT * FROM "address" WHERE "postal_code" IS NOT NULL`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `address` WHERE `postal_code` IS NOT NULL"},
|
||||
override: driverMap{mysql.Type: "SELECT * FROM `address` WHERE `postal_code` IS NOT NULL"},
|
||||
wantRecCount: 599,
|
||||
// skipExec because mysql sakila db doesn't have the same null values.
|
||||
// This is a bug in the dataset.
|
||||
@ -72,63 +72,72 @@ func TestQuery_expr_literal(t *testing.T) {
|
||||
{
|
||||
name: "table/col_and_literal",
|
||||
in: `@sakila | .actor | .first_name, 1`,
|
||||
wantSQL: `SELECT "first_name", 1 FROM "actor"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT `first_name`, 1 FROM `actor`"},
|
||||
wantSQL: `SELECT "first_name", 1 AS "1" FROM "actor"`,
|
||||
override: driverMap{mysql.Type: "SELECT `first_name`, 1 AS `1` FROM `actor`"},
|
||||
wantRecCount: sakila.TblActorCount,
|
||||
sinkFns: []SinkTestFunc{assertSinkColValue(1, int64(1))},
|
||||
},
|
||||
{
|
||||
name: "table/literal",
|
||||
in: `@sakila | .actor | 1`,
|
||||
wantSQL: `SELECT 1 FROM "actor"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT 1 FROM `actor`"},
|
||||
wantSQL: `SELECT 1 AS "1" FROM "actor"`,
|
||||
override: driverMap{mysql.Type: "SELECT 1 AS `1` FROM `actor`"},
|
||||
wantRecCount: sakila.TblActorCount,
|
||||
sinkFns: []SinkTestFunc{assertSinkColValue(0, int64(1))},
|
||||
},
|
||||
{
|
||||
name: "no-table/literal",
|
||||
in: `@sakila | 1`,
|
||||
wantSQL: `SELECT 1`,
|
||||
wantSQL: `SELECT 1 AS "1"`,
|
||||
override: driverMap{mysql.Type: "SELECT 1 AS `1`"},
|
||||
wantRecCount: 1,
|
||||
sinkFns: []SinkTestFunc{assertSinkColValue(0, int64(1))},
|
||||
},
|
||||
{
|
||||
name: "no-table/literal_addition",
|
||||
in: `@sakila | 1+1`,
|
||||
wantSQL: `SELECT 1+1 AS "1+1"`,
|
||||
override: driverMap{mysql.Type: "SELECT 1+1 AS `1+1`"},
|
||||
wantRecCount: 1,
|
||||
sinkFns: []SinkTestFunc{assertSinkColValue(0, int64(2))},
|
||||
},
|
||||
{
|
||||
name: "table/literal_parens",
|
||||
in: `@sakila | .actor | (1)`,
|
||||
wantSQL: `SELECT (1) FROM "actor"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT (1) FROM `actor`"},
|
||||
wantSQL: `SELECT (1) AS "(1)" FROM "actor"`,
|
||||
override: driverMap{mysql.Type: "SELECT (1) AS `(1)` FROM `actor`"},
|
||||
wantRecCount: sakila.TblActorCount,
|
||||
sinkFns: []SinkTestFunc{assertSinkColValue(0, int64(1))},
|
||||
},
|
||||
{
|
||||
name: "table/addition",
|
||||
in: `@sakila | .actor | 1+2`,
|
||||
wantSQL: `SELECT 1 + 2 FROM "actor"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT 1 + 2 FROM `actor`"},
|
||||
wantSQL: `SELECT 1+2 AS "1+2" FROM "actor"`,
|
||||
override: driverMap{mysql.Type: "SELECT 1+2 AS `1+2` FROM `actor`"},
|
||||
wantRecCount: sakila.TblActorCount,
|
||||
sinkFns: []SinkTestFunc{assertSinkColValue(0, int64(3))},
|
||||
},
|
||||
{
|
||||
name: "table/addition_whitespace",
|
||||
in: `@sakila | .actor | 1+ 2 + 3`,
|
||||
wantSQL: `SELECT 1 + 2 + 3 FROM "actor"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT 1 + 2 + 3 FROM `actor`"},
|
||||
wantSQL: `SELECT 1+2+3 AS "1+2+3" FROM "actor"`,
|
||||
override: driverMap{mysql.Type: "SELECT 1+2+3 AS `1+2+3` FROM `actor`"},
|
||||
wantRecCount: sakila.TblActorCount,
|
||||
sinkFns: []SinkTestFunc{assertSinkColValue(0, int64(6))},
|
||||
},
|
||||
{
|
||||
name: "table/math_parens",
|
||||
in: `@sakila | .actor | (1+ 2) * 3`,
|
||||
wantSQL: `SELECT (1 + 2) * 3 FROM "actor"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT (1 + 2) * 3 FROM `actor`"},
|
||||
in: `@sakila | .actor | ((2+2) * 3)`,
|
||||
wantSQL: `SELECT ((2+2)*3) AS "((2+2)*3)" FROM "actor"`,
|
||||
override: driverMap{mysql.Type: "SELECT ((2+2)*3) AS `((2+2)*3)` FROM `actor`"},
|
||||
wantRecCount: sakila.TblActorCount,
|
||||
sinkFns: []SinkTestFunc{assertSinkColValue(0, int64(9))},
|
||||
sinkFns: []SinkTestFunc{assertSinkColValue(0, int64(12))},
|
||||
},
|
||||
{
|
||||
name: "table/literal_alias",
|
||||
in: `@sakila | .actor | 1:total`,
|
||||
wantSQL: `SELECT 1 AS "total" FROM "actor"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT 1 AS `total` FROM `actor`"},
|
||||
override: driverMap{mysql.Type: "SELECT 1 AS `total` FROM `actor`"},
|
||||
wantRecCount: sakila.TblActorCount,
|
||||
sinkFns: []SinkTestFunc{
|
||||
assertSinkColName(0, "total"),
|
||||
@ -138,8 +147,8 @@ func TestQuery_expr_literal(t *testing.T) {
|
||||
{
|
||||
name: "table/addition_alias",
|
||||
in: `@sakila | .actor | (1+2):total`,
|
||||
wantSQL: `SELECT (1 + 2) AS "total" FROM "actor"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT (1 + 2) AS `total` FROM `actor`"},
|
||||
wantSQL: `SELECT (1+2) AS "total" FROM "actor"`,
|
||||
override: driverMap{mysql.Type: "SELECT (1+2) AS `total` FROM `actor`"},
|
||||
sinkFns: []SinkTestFunc{
|
||||
assertSinkColValue(0, int64(3)),
|
||||
assertSinkColName(0, "total"),
|
||||
@ -148,9 +157,9 @@ func TestQuery_expr_literal(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
for i, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Run(tutil.Name(i, tc.name), func(t *testing.T) {
|
||||
execQueryTestCase(t, tc)
|
||||
})
|
||||
}
|
||||
|
33
libsq/query_func_test.go
Normal file
33
libsq/query_func_test.go
Normal file
@ -0,0 +1,33 @@
|
||||
package libsq_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/neilotoole/sq/drivers/mysql"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
//nolint:exhaustive
|
||||
func TestQuery_func(t *testing.T) {
|
||||
testCases := []queryTestCase{
|
||||
{
|
||||
name: "max",
|
||||
in: `@sakila | .actor | max(.actor_id)`,
|
||||
wantSQL: `SELECT max("actor_id") AS "max(.actor_id)" FROM "actor"`,
|
||||
override: driverMap{mysql.Type: "SELECT max(`actor_id`) AS `max(.actor_id)` FROM `actor`"},
|
||||
wantRecCount: 1,
|
||||
sinkFns: []SinkTestFunc{
|
||||
assertSinkColName(0, "max(.actor_id)"),
|
||||
assertSinkColValue(0, int64(200)),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
execQueryTestCase(t, tc)
|
||||
})
|
||||
}
|
||||
}
|
@ -19,14 +19,14 @@ func TestQuery_groupby(t *testing.T) {
|
||||
name: "group_by/single-term",
|
||||
in: `@sakila | .payment | .customer_id, sum(.amount) | group_by(.customer_id)`,
|
||||
wantSQL: `SELECT "customer_id", sum("amount") AS "sum(.amount)" FROM "payment" GROUP BY "customer_id"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT `customer_id`, sum(`amount`) AS `sum(.amount)` FROM `payment` GROUP BY `customer_id`"},
|
||||
override: driverMap{mysql.Type: "SELECT `customer_id`, sum(`amount`) AS `sum(.amount)` FROM `payment` GROUP BY `customer_id`"},
|
||||
wantRecCount: 599,
|
||||
},
|
||||
{
|
||||
name: "group_by/multiple_terms",
|
||||
in: `@sakila | .payment | .customer_id, .staff_id, sum(.amount) | group_by(.customer_id, .staff_id)`,
|
||||
wantSQL: `SELECT "customer_id", "staff_id", sum("amount") AS "sum(.amount)" FROM "payment" GROUP BY "customer_id", "staff_id"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT `customer_id`, `staff_id`, sum(`amount`) AS `sum(.amount)` FROM `payment` GROUP BY `customer_id`, `staff_id`"},
|
||||
override: driverMap{mysql.Type: "SELECT `customer_id`, `staff_id`, sum(`amount`) AS `sum(.amount)` FROM `payment` GROUP BY `customer_id`, `staff_id`"},
|
||||
wantRecCount: 1198,
|
||||
},
|
||||
{
|
||||
|
@ -7,8 +7,6 @@ import (
|
||||
|
||||
"github.com/neilotoole/sq/drivers/mysql"
|
||||
|
||||
"github.com/neilotoole/sq/libsq/source"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
@ -19,21 +17,21 @@ func TestQuery_join(t *testing.T) {
|
||||
name: "join/single-selector",
|
||||
in: `@sakila | .actor, .film_actor | join(.actor_id)`,
|
||||
wantSQL: `SELECT * FROM "actor" INNER JOIN "film_actor" ON "actor"."actor_id" = "film_actor"."actor_id"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` INNER JOIN `film_actor` ON `actor`.`actor_id` = `film_actor`.`actor_id`"},
|
||||
override: driverMap{mysql.Type: "SELECT * FROM `actor` INNER JOIN `film_actor` ON `actor`.`actor_id` = `film_actor`.`actor_id`"},
|
||||
wantRecCount: sakila.TblFilmActorCount,
|
||||
},
|
||||
{
|
||||
name: "join/fq-table-cols-equal",
|
||||
in: `@sakila | .actor, .film_actor | join(.film_actor.actor_id == .actor.actor_id)`,
|
||||
wantSQL: `SELECT * FROM "actor" INNER JOIN "film_actor" ON "film_actor"."actor_id" = "actor"."actor_id"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` INNER JOIN `film_actor` ON `film_actor`.`actor_id` = `actor`.`actor_id`"},
|
||||
override: driverMap{mysql.Type: "SELECT * FROM `actor` INNER JOIN `film_actor` ON `film_actor`.`actor_id` = `actor`.`actor_id`"},
|
||||
wantRecCount: sakila.TblFilmActorCount,
|
||||
},
|
||||
{
|
||||
name: "join/fq-table-cols-equal-whitespace",
|
||||
in: `@sakila | .actor, ."film actor" | join(."film actor".actor_id == .actor.actor_id)`,
|
||||
wantSQL: `SELECT * FROM "actor" INNER JOIN "film actor" ON "film actor"."actor_id" = "actor"."actor_id"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` INNER JOIN `film actor` ON `film actor`.`actor_id` = `actor`.`actor_id`"},
|
||||
override: driverMap{mysql.Type: "SELECT * FROM `actor` INNER JOIN `film actor` ON `film actor`.`actor_id` = `actor`.`actor_id`"},
|
||||
skipExec: true,
|
||||
},
|
||||
}
|
||||
|
@ -18,7 +18,11 @@ func TestQuery_no_source(t *testing.T) {
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{"1+2", "SELECT 1 + 2", false},
|
||||
{"1+2", `SELECT 1+2 AS "1+2"`, false},
|
||||
{"(1+ 2) * 3", `SELECT (1+2)*3 AS "(1+2)*3"`, false},
|
||||
{"(1+ 2) * 3", `SELECT (1+2)*3 AS "(1+2)*3"`, false},
|
||||
{`1:"the number"`, `SELECT 1 AS "the number"`, false},
|
||||
{`1:thenumber`, `SELECT 1 AS "thenumber"`, false},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
@ -36,7 +40,7 @@ func TestQuery_no_source(t *testing.T) {
|
||||
ScratchDBOpener: dbases,
|
||||
}
|
||||
|
||||
gotSQL, gotErr := libsq.SLQ2SQL(th.Context, qc, "1+2")
|
||||
gotSQL, gotErr := libsq.SLQ2SQL(th.Context, qc, tc.in)
|
||||
if tc.wantErr {
|
||||
require.Error(t, gotErr)
|
||||
return
|
||||
|
@ -7,54 +7,52 @@ import (
|
||||
|
||||
"github.com/neilotoole/sq/drivers/mysql"
|
||||
|
||||
"github.com/neilotoole/sq/libsq/source"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
//nolint:exhaustive,lll
|
||||
//nolint:exhaustive
|
||||
func TestQuery_orderby(t *testing.T) {
|
||||
testCases := []queryTestCase{
|
||||
{
|
||||
name: "order_by/single-element",
|
||||
in: `@sakila | .actor | order_by(.first_name)`,
|
||||
wantSQL: `SELECT * FROM "actor" ORDER BY "first_name"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` ORDER BY `first_name`"},
|
||||
override: driverMap{mysql.Type: "SELECT * FROM `actor` ORDER BY `first_name`"},
|
||||
wantRecCount: sakila.TblActorCount,
|
||||
},
|
||||
{
|
||||
name: "order_by/single-element-table-selector",
|
||||
in: `@sakila | .actor | order_by(.actor.first_name)`,
|
||||
wantSQL: `SELECT * FROM "actor" ORDER BY "actor"."first_name"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` ORDER BY `actor`.`first_name`"},
|
||||
override: driverMap{mysql.Type: "SELECT * FROM `actor` ORDER BY `actor`.`first_name`"},
|
||||
wantRecCount: sakila.TblActorCount,
|
||||
},
|
||||
{
|
||||
name: "order_by/single-element-asc",
|
||||
in: `@sakila | .actor | order_by(.first_name+)`,
|
||||
wantSQL: `SELECT * FROM "actor" ORDER BY "first_name" ASC`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` ORDER BY `first_name` ASC"},
|
||||
override: driverMap{mysql.Type: "SELECT * FROM `actor` ORDER BY `first_name` ASC"},
|
||||
wantRecCount: sakila.TblActorCount,
|
||||
},
|
||||
{
|
||||
name: "order_by/single-element-desc",
|
||||
in: `@sakila | .actor | order_by(.first_name-)`,
|
||||
wantSQL: `SELECT * FROM "actor" ORDER BY "first_name" DESC`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` ORDER BY `first_name` DESC"},
|
||||
override: driverMap{mysql.Type: "SELECT * FROM `actor` ORDER BY `first_name` DESC"},
|
||||
wantRecCount: sakila.TblActorCount,
|
||||
},
|
||||
{
|
||||
name: "order_by/multiple-elements",
|
||||
in: `@sakila | .actor | order_by(.first_name+, .last_name-)`,
|
||||
wantSQL: `SELECT * FROM "actor" ORDER BY "first_name" ASC, "last_name" DESC`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` ORDER BY `first_name` ASC, `last_name` DESC"},
|
||||
override: driverMap{mysql.Type: "SELECT * FROM `actor` ORDER BY `first_name` ASC, `last_name` DESC"},
|
||||
wantRecCount: sakila.TblActorCount,
|
||||
},
|
||||
{
|
||||
name: "order_by/synonym-sort-by",
|
||||
in: `@sakila | .actor | sort_by(.first_name)`,
|
||||
wantSQL: `SELECT * FROM "actor" ORDER BY "first_name"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` ORDER BY `first_name`"},
|
||||
override: driverMap{mysql.Type: "SELECT * FROM `actor` ORDER BY `first_name`"},
|
||||
wantRecCount: sakila.TblActorCount,
|
||||
},
|
||||
{
|
||||
|
@ -19,6 +19,10 @@ import (
|
||||
"github.com/neilotoole/sq/testh/sakila"
|
||||
)
|
||||
|
||||
// driverMap is a map of source.DriverType to a string.
|
||||
// It is used to specify a string for a specific driver.
|
||||
type driverMap map[source.DriverType]string
|
||||
|
||||
// queryTestCase is used to test libsq's rendering of SLQ into SQL.
|
||||
// It is probably the most important test struct in the codebase.
|
||||
type queryTestCase struct {
|
||||
@ -46,7 +50,7 @@ type queryTestCase struct {
|
||||
// override allows an alternative "wantSQL" for a specific driver type.
|
||||
// For example, MySQL uses backtick as the quote char, so it needs
|
||||
// a separate wantSQL string.
|
||||
override map[source.DriverType]string
|
||||
override driverMap
|
||||
|
||||
// onlyFor indicates that this test should only run on sources of
|
||||
// the specified types. When empty, the test is executed on all types.
|
||||
|
@ -7,8 +7,6 @@ import (
|
||||
|
||||
"github.com/neilotoole/sq/drivers/mysql"
|
||||
|
||||
"github.com/neilotoole/sq/libsq/source"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
@ -19,21 +17,21 @@ func TestQuery_unique(t *testing.T) {
|
||||
name: "unique/single-col",
|
||||
in: `@sakila | .actor | .first_name | unique`,
|
||||
wantSQL: `SELECT DISTINCT "first_name" FROM "actor"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT DISTINCT `first_name` FROM `actor`"},
|
||||
override: driverMap{mysql.Type: "SELECT DISTINCT `first_name` FROM `actor`"},
|
||||
wantRecCount: 128,
|
||||
},
|
||||
{
|
||||
name: "unique/no-col",
|
||||
in: `@sakila | .actor | unique`,
|
||||
wantSQL: `SELECT DISTINCT * FROM "actor"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT DISTINCT * FROM `actor`"},
|
||||
override: driverMap{mysql.Type: "SELECT DISTINCT * FROM `actor`"},
|
||||
wantRecCount: sakila.TblActorCount,
|
||||
},
|
||||
{
|
||||
name: "unique/no-col",
|
||||
in: `@sakila | .actor | unique`,
|
||||
wantSQL: `SELECT DISTINCT * FROM "actor"`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT DISTINCT * FROM `actor`"},
|
||||
override: driverMap{mysql.Type: "SELECT DISTINCT * FROM `actor`"},
|
||||
wantRecCount: sakila.TblActorCount,
|
||||
},
|
||||
}
|
||||
|
@ -5,8 +5,6 @@ import (
|
||||
|
||||
"github.com/neilotoole/sq/drivers/mysql"
|
||||
|
||||
"github.com/neilotoole/sq/libsq/source"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
@ -17,42 +15,42 @@ func TestQuery_where(t *testing.T) {
|
||||
name: "operator/eq",
|
||||
in: `@sakila | .actor | where(.actor_id == 100)`,
|
||||
wantSQL: `SELECT * FROM "actor" WHERE "actor_id" = 100`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` = 100"},
|
||||
override: driverMap{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` = 100"},
|
||||
wantRecCount: 1,
|
||||
},
|
||||
{
|
||||
name: "operator/ne",
|
||||
in: `@sakila | .actor | where(.actor_id != 100)`,
|
||||
wantSQL: `SELECT * FROM "actor" WHERE "actor_id" != 100`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` != 100"},
|
||||
override: driverMap{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` != 100"},
|
||||
wantRecCount: 199,
|
||||
},
|
||||
{
|
||||
name: "operator/lt",
|
||||
in: `@sakila | .actor | where(.actor_id < 100)`,
|
||||
wantSQL: `SELECT * FROM "actor" WHERE "actor_id" < 100`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` < 100"},
|
||||
override: driverMap{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` < 100"},
|
||||
wantRecCount: 99,
|
||||
},
|
||||
{
|
||||
name: "operator/lte",
|
||||
in: `@sakila | .actor | where(.actor_id <= 100)`,
|
||||
wantSQL: `SELECT * FROM "actor" WHERE "actor_id" <= 100`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` <= 100"},
|
||||
override: driverMap{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` <= 100"},
|
||||
wantRecCount: 100,
|
||||
},
|
||||
{
|
||||
name: "operator/gt",
|
||||
in: `@sakila | .actor | where(.actor_id > 100)`,
|
||||
wantSQL: `SELECT * FROM "actor" WHERE "actor_id" > 100`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` > 100"},
|
||||
override: driverMap{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` > 100"},
|
||||
wantRecCount: 100,
|
||||
},
|
||||
{
|
||||
name: "operator/gte",
|
||||
in: `@sakila | .actor | where(.actor_id >= 100)`,
|
||||
wantSQL: `SELECT * FROM "actor" WHERE "actor_id" >= 100`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` >= 100"},
|
||||
override: driverMap{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` >= 100"},
|
||||
wantRecCount: 101,
|
||||
},
|
||||
{
|
||||
@ -65,28 +63,28 @@ func TestQuery_where(t *testing.T) {
|
||||
name: "select_alias",
|
||||
in: `@sakila | .actor | select(.actor_id == 1)`,
|
||||
wantSQL: `SELECT * FROM "actor" WHERE "actor_id" = 1`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` = 1"},
|
||||
override: driverMap{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` = 1"},
|
||||
wantRecCount: 1,
|
||||
},
|
||||
{
|
||||
name: "where_compound_1",
|
||||
in: `@sakila | .actor | where(.actor_id >= 100 && .actor_id < 150)`,
|
||||
wantSQL: `SELECT * FROM "actor" WHERE "actor_id" >= 100 AND "actor_id" < 150`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` >= 100 AND `actor_id` < 150"},
|
||||
override: driverMap{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` >= 100 AND `actor_id` < 150"},
|
||||
wantRecCount: 50,
|
||||
},
|
||||
{
|
||||
name: "where_compound_2",
|
||||
in: `@sakila | .actor | where(.actor_id >= 100 || (.actor_id < 150 && .first_name == "TOM"))`,
|
||||
wantSQL: `SELECT * FROM "actor" WHERE "actor_id" >= 100 OR ("actor_id" < 150 AND "first_name" = 'TOM')`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` >= 100 OR (`actor_id` < 150 AND `first_name` = 'TOM')"},
|
||||
override: driverMap{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` >= 100 OR (`actor_id` < 150 AND `first_name` = 'TOM')"},
|
||||
wantRecCount: 103,
|
||||
},
|
||||
{
|
||||
name: "where_using_col_alias",
|
||||
in: `@sakila | .actor | .first_name:given_name | where(.given_name == "TOM")`,
|
||||
wantSQL: `SELECT "first_name" AS "given_name" FROM "actor" WHERE "given_name" = 'TOM'`,
|
||||
override: map[source.DriverType]string{mysql.Type: "SELECT `first_name` AS `given_name` FROM `actor` WHERE `given_name` = 'TOM'"},
|
||||
override: driverMap{mysql.Type: "SELECT `first_name` AS `given_name` FROM `actor` WHERE `given_name` = 'TOM'"},
|
||||
wantRecCount: 2,
|
||||
// Skip because this only works on SQLite, not the other SQL databases.
|
||||
// I'm not sure if this will ever be implemented. Perhaps sq could look at
|
||||
|
@ -535,7 +535,10 @@ func TempDirFile(filename string) (dir string, f *os.File, cleanFn func() error,
|
||||
}
|
||||
|
||||
cleanFn = func() error {
|
||||
return errz.Append(f.Close(), os.RemoveAll(dir))
|
||||
closeErr := f.Close()
|
||||
removeErr := os.RemoveAll(dir)
|
||||
|
||||
return errz.Append(closeErr, removeErr)
|
||||
}
|
||||
|
||||
return dir, f, cleanFn, nil
|
||||
|
Loading…
Reference in New Issue
Block a user