diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ec11f15..1adbad5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,4 +19,4 @@ jobs: go-version: stable - name: test run: | - go test -v -failfast -race -coverpkg=./... -covermode=atomic -coverprofile=coverage.txt ./... + ./script/test diff --git a/ari.goal b/ari.goal index 587badd..2c9388f 100644 --- a/ari.goal +++ b/ari.goal @@ -1,7 +1,9 @@ +/ Shape istbl:{and["d"=@x;&/"s"=@'!x;&/{(@'x)¿"ANSI"}x;&/(*ls)=ls:#'x]} / is x a dictionary-as-table reshape:{((*/x)#y){(-y)$x}/|1_x} / Implementation by anaseto, shared on Matrix shape:{-1_#:'*:\x} / Implementation by John Earnest, shared on k-tree depths:{[ind;l]?[(@l)~"A";,/o[ind+1]'l; (@l)¿"NSI";(#l)#ind; ind-1]} / list depths +/ Output formats md.tbl:{[t;fmt] / helper k:!t; v:(..?[(@x)¿"nN";p.fmt$x;$'x])'.t; w:(-1+""#k)|(|/-1+""#)'v (k;v):(-w)!'´(k;v); "|"+("|\n|"/,/"|"/(k;"-"*w;+v))+"|"} @@ -31,4 +33,43 @@ ltx.lst:{[l;fmt] / helper sprintf.ltx:{[x;fmt]?[istbl x;ltx.tbl[x;fmt]; (@x)¿"ANSI";ltx.lst[x;fmt]; "n"=@x;fmt$x; $x]} / LaTeX output csv.tbl:{(*'x)!(1_'x)} / table from csv parsing, assumes header json.tbl:{ks:!*x; vs:@[;ks]'x; ks!+vs} / table from parsing json array of like objects +/ Test Framework +tt.suite:"global"; tt.suitestate:..[es:();fs:();ps:();ss:()]; tt.state:..[global:tt.suitestate] +tt.st:tt.state; tt.ffast:0; tt.clear:{tt.st::tt.state} +tt.record:{[k;f;r] / key in test state; function tested; return value + suite:@[.;"FILE";:[;tt.suite]] + or[suite¿!tt.st;tt.st[suite]:tt.suitestate] / ensure starting suite state + ?["ps"~k;(m:"\n"/" "+=r"msg" + d:..[f:p.f;p:p.r;msg:"function panicked with:\n$p.m"] + tt.st[suite]:@[tt.st[suite];"ps";..x,p.d]) + "es"~k;(d:..[f:p.f;e:p.r;msg:"function returned error: $p.r\n $p.f"] + tt.st[suite]:@[tt.st[suite];"es";..x,p.d]) + "fs"~k;(d:..[f:p.f;r:p.r;msg:"function returned $p.r instead of 1\n $p.f"] + tt.st[suite]:@[tt.st[suite];"fs";..x,p.d]) + "ss"~k;(d:..[f:p.f]; tt.st[suite]:@[tt.st[suite];"ss";..x,p.d]) + :error"tt.record k must be one of ps, es, fs, ss, but received $k"]} +tt.tf:{[f] + r:rt.try[f;0;{pmsg:x"msg";f:y; error[..[f:p.f;msg:p.pmsg;pnc:1]]}[;f]] + ?["e"~@r;?[("d"=@.r)and(.r)..pnc;tt.record["ps";f;r];tt.record["es";f;r]] + 1~r;tt.record["ss";f;r] + tt.record["fs";f;r]] + r} +tt.t:{?[("f"~@x);:tt.tf@x;error"tt.t expects f, received %s: %v"$(@x;x)]} +tt.fs:{sfx:"test.ari""test.goal"; pfx:"**/*""*"; fs:,/glob',/pfx+`sfx} +tt.file:{orig:@[.;"FILE";0]; ::["FILE";x]; ?[tt.ffast; 'eval 'read x;eval 'read x]; ::["FILE";or[orig;tt.suite]]; x} +tt.chkall:{ + fs:tt.fs 0; rs:tt.file'fs; es:"e"=@'rs + ?[|/es + [errors:(..p.es)#rs; files:(..p.es)#fs; error[(,"msg")!,"\n"/(files+": ")+(..msg)'errors]] + rs]} +tt.saymsg:{say " "+x"msg"} +tt.repsut:{[suite] + (ss;fs;es;ps):tt.st[suite][!"ss fs es ps"];(css;cfs;ces;cps):#'(ss;fs;es;ps) + bad:~+/(cfs;ces;cps); or[bad;say qq/Suite "$suite" has problems:/] + and[cfs;tt.saymsg'fs]; and[ces;tt.saymsg'es]; and[cps;tt.saymsg'ps] + s:qq/Suite "$suite" $css succeeded, $cfs failed, $ces errored, $cps panicked/ + ?[and[tt.suite~suite;+/#'tt.st[tt.suite]];say s; ~tt.suite~suite;say s;0]} +tt.report:{ + tt.repsut'!tt.st; (tes;tfs;tps;tss):+/#''. .'tt.st; say "Total $tss succeeded, $tfs failed, $tes errored, $tps panicked" + and[|//@[;!"es fs ps"]'#''tt.st;error["Tests failed."]]} 1 diff --git a/cmd/ari/root.go b/cmd/ari/root.go index 67af7cf..49f12b7 100644 --- a/cmd/ari/root.go +++ b/cmd/ari/root.go @@ -49,9 +49,9 @@ const ( const ( cliModeGoalPrompt = " " cliModeGoalNextPrompt = " " - cliModeSQLReadOnlyPrompt = "sql> " + cliModeSQLReadOnlyPrompt = "sql) " cliModeSQLReadOnlyNextPrompt = " > " - cliModeSQLReadWritePrompt = "sql!> " + cliModeSQLReadWritePrompt = "sql!) " cliModeSQLReadWriteNextPrompt = " > " ) @@ -81,6 +81,7 @@ func (cliSystem *CliSystem) switchMode(cliMode cliMode, args []string) error { func (cliSystem *CliSystem) switchModeToGoal() error { cliSystem.cliMode = cliModeGoal + cliSystem.prompt = cliModeGoalPrompt if !cliSystem.rawREPL { cliSystem.cliEditor.Prompt = cliModeGoalPrompt cliSystem.cliEditor.NextPrompt = cliModeGoalNextPrompt @@ -114,6 +115,7 @@ func (cliSystem *CliSystem) switchModeToSQLReadOnly(args []string) error { } } cliSystem.cliMode = cliModeSQLReadOnly + cliSystem.prompt = cliModeSQLReadOnlyPrompt if !cliSystem.rawREPL { cliSystem.cliEditor.CheckInputComplete = modeSQLCheckInputComplete cliSystem.cliEditor.AutoComplete = cliSystem.autoCompleter.sqlAutoCompleteFn() @@ -141,6 +143,7 @@ func (cliSystem *CliSystem) switchModeToSQLReadWrite(args []string) error { } } cliSystem.cliMode = cliModeSQLReadOnly + cliSystem.prompt = cliModeSQLReadWritePrompt if !cliSystem.rawREPL { cliSystem.cliEditor.CheckInputComplete = modeSQLCheckInputComplete cliSystem.cliEditor.AutoComplete = cliSystem.autoCompleter.sqlAutoCompleteFn() @@ -243,6 +246,12 @@ func ariMain(cmd *cobra.Command, args []string) int { // MUST PRECEDE EXECUTE/REPL goalFilesToLoad := viper.GetStringSlice("load") for _, f := range goalFilesToLoad { + var path string + path, err = filepath.Abs(f) + if err != nil { + fmt.Fprintf(os.Stderr, "File to load %s is not recognized as a path on your system: %v", f, err) + } + ariContext.GoalContext.AssignGlobal("FILE", goal.NewS(path)) _, err = runScript(&mainCliSystem, f) if err != nil { fmt.Fprintf(os.Stderr, "Failed to load file %q with error: %v", f, err) @@ -261,10 +270,17 @@ func ariMain(cmd *cobra.Command, args []string) int { fmt.Fprintf(os.Stderr, "Error: %v\n", err) return 1 } + //nolint:nestif // separate returns in each if if programToExecute != "" { - goalV, errr := runCommand(&mainCliSystem, programToExecute) - if errr != nil { - fmt.Fprintf(os.Stderr, "Failed to execute program:\n%q\n with error:\n%v\n", programToExecute, err) + var goalV goal.V + goalV, err = runCommand(&mainCliSystem, programToExecute) + if goalV.IsError() { + ee := newExitError(ariContext.GoalContext, goalV.Error()) + formatREPLError(ee) + return ee.Code + } + if err != nil { + fmt.Fprintf(os.Stderr, "Program panicked with: %v\n \n%q\n", err, programToExecute) return 1 } // Support -e/--execute along with a file argument. @@ -284,8 +300,9 @@ func ariMain(cmd *cobra.Command, args []string) int { fmt.Fprintf(os.Stderr, "File %q is not recognized as a path on your system: %v", f, err) } ariContext.GoalContext.AssignGlobal("FILE", goal.NewS(path)) - goalV, errr := runScript(&mainCliSystem, f) - if errr != nil { + var goalV goal.V + goalV, err = runScript(&mainCliSystem, f) + if err != nil { fmt.Fprintf(os.Stderr, "Failed to run file %q with error: %v", f, err) return 1 } @@ -493,7 +510,7 @@ func (e *ExitError) Error() string { // newExitError produces an *ExitError from a Goal error value. // // Adapted from Goal's implementation. -func newExitError(ctx *goal.Context, e *goal.Error) error { +func newExitError(ctx *goal.Context, e *goal.Error) *ExitError { ee := &ExitError{Msg: e.Msg(ctx)} if d, ok := e.Value().BV().(*goal.D); ok { if v, ok := d.Get(goal.NewS("code")); ok { @@ -576,27 +593,34 @@ func detectAriPrint(goalContext *goal.Context) func(goal.V) { } func (cliSystem *CliSystem) replEvalSQLReadOnly(line string) { - _, err := cliSystem.sqlQuery(line, nil) + goalV, err := cliSystem.sqlQuery(line, nil) if err != nil { fmt.Fprintf(os.Stderr, "Failed to run SQL query %q\nDatabase Error:%s\n", line, err) - } else { + return + } + if cliSystem.outputFormat == outputFormatGoal { _, err := cliSystem.ariContext.GoalContext.Eval(`fmt.tbl[sql.p;*#'sql.p;#sql.p;"%.1f"]`) if err != nil { fmt.Fprintf(os.Stderr, "Failed to print SQL query results via Goal evaluation: %v\n", err) } + return } + printInOutputFormat(cliSystem.ariContext.GoalContext, cliSystem.outputFormat, goalV) } func (cliSystem *CliSystem) replEvalSQLReadWrite(line string) { - _, err := cliSystem.sqlExec(line, nil) + goalV, err := cliSystem.sqlExec(line, nil) if err != nil { fmt.Fprintf(os.Stderr, "Failed to run SQL query %q\nDatabase Error:%s\n", line, err) - } else { + return + } + if cliSystem.outputFormat == outputFormatGoal { _, err := cliSystem.ariContext.GoalContext.Eval(`fmt.tbl[sql.p;*#'sql.p;#sql.p;"%.1f"]`) if err != nil { fmt.Fprintf(os.Stderr, "Failed to print SQL exec results via Goal evaluation: %v\n", err) } } + printInOutputFormat(cliSystem.ariContext.GoalContext, cliSystem.outputFormat, goalV) } func (cliSystem *CliSystem) replEvalSystemCommand(line string) error { diff --git a/go.mod b/go.mod index 6fe37d2..ef4d79c 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.21 toolchain go1.22.0 require ( - codeberg.org/anaseto/goal v0.43.1-0.20240904143145-df99d8e0051b + codeberg.org/anaseto/goal v0.43.1-0.20240910104443-7553fb1f7fe3 github.com/charmbracelet/lipgloss v0.13.0 github.com/go-resty/resty/v2 v2.14.0 github.com/jarcoal/httpmock v1.3.1 diff --git a/go.sum b/go.sum index 460a142..564e602 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -codeberg.org/anaseto/goal v0.43.1-0.20240904143145-df99d8e0051b h1:P3prjSjmz8wYHT4iTg3ZYivbcpaHmL7G5KrW37Xqx04= -codeberg.org/anaseto/goal v0.43.1-0.20240904143145-df99d8e0051b/go.mod h1:oipi4mkQiwXW9Td2IxNhuMV0Ewq4obs6EvkqpyZ6qMs= +codeberg.org/anaseto/goal v0.43.1-0.20240910104443-7553fb1f7fe3 h1:BSx4JTolD3SLv8kzBxNlg3qWbikchp99xsj6H6tYH7c= +codeberg.org/anaseto/goal v0.43.1-0.20240910104443-7553fb1f7fe3/go.mod h1:oipi4mkQiwXW9Td2IxNhuMV0Ewq4obs6EvkqpyZ6qMs= github.com/apache/arrow/go/v17 v17.0.0 h1:RRR2bdqKcdbss9Gxy2NS/hK8i4LDMh23L6BbkN5+F54= github.com/apache/arrow/go/v17 v17.0.0/go.mod h1:jR7QHkODl15PfYyjM2nU+yTLScZ/qfj7OSUZmJ8putc= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= diff --git a/goal.go b/goal.go index aa1089b..fb902f4 100644 --- a/goal.go +++ b/goal.go @@ -5,6 +5,7 @@ import ( _ "embed" "fmt" "os" + "path/filepath" "regexp" "strings" @@ -99,6 +100,7 @@ func printV(ctx *goal.Context, x goal.V) error { } } +// Implements help dyad. func VFHelpFn(help Help) func(goalContext *goal.Context, args []goal.V) goal.V { return func(goalContext *goal.Context, args []goal.V) goal.V { x := args[len(args)-1] @@ -220,6 +222,34 @@ func helpTriadic(help Help, x goal.V, args []goal.V) goal.V { return goal.NewI(1) } +// Implements glob monad. +func VFGlob(_ *goal.Context, args []goal.V) goal.V { + x := args[len(args)-1] + globPatternS, ok := x.BV().(goal.S) + if !ok { + return panicType("glob s", "s", x) + } + match, err := filepath.Glob(string(globPatternS)) + if err != nil { + return goal.NewPanicError(err) + } + return goal.NewAS(match) +} + +// Implements abspath monad. +func VFAbsPath(_ *goal.Context, args []goal.V) goal.V { + x := args[len(args)-1] + globPatternS, ok := x.BV().(goal.S) + if !ok { + return panicType("abspath s", "s", x) + } + path, err := filepath.Abs(string(globPatternS)) + if err != nil { + return goal.NewPanicError(err) + } + return goal.NewS(path) +} + // Go <> Goal helpers func stringMapFromGoalDict(d *goal.D) (map[string]string, error) { @@ -273,6 +303,8 @@ func goalRegisterVariadics(ariContext *Context, goalContext *goal.Context, help gos.Import(goalContext, "") // Ari // Monads + goalContext.RegisterMonad("abspath", VFAbsPath) + goalContext.RegisterMonad("glob", VFGlob) goalContext.RegisterMonad("sql.close", VFSqlClose) goalContext.RegisterMonad("sql.open", VFSqlOpen) goalContext.RegisterMonad("time.now", VFTimeNow) diff --git a/goal_test.go b/goal_test.go index 5f229f8..bb3606f 100644 --- a/goal_test.go +++ b/goal_test.go @@ -15,7 +15,7 @@ import ( ) func TestGoalOk(t *testing.T) { - t.Parallel() + // t.Parallel() // go test reports data race tests := map[string]struct { input string result string @@ -57,7 +57,7 @@ func TestGoalOk(t *testing.T) { t.Fatalf("error creating ari Context: %v", err) } t.Run(name, func(t *testing.T) { - t.Parallel() + // t.Parallel() // go test reports data race goalV, err := goalCtx.Eval(test.input) if err != nil { t.Fatalf("Context.GoalContext.Eval(%q) returned an error: %v", test.input, err) @@ -71,7 +71,7 @@ func TestGoalOk(t *testing.T) { } func TestGoalError(t *testing.T) { - t.Parallel() + // t.Parallel() // go test reports data race tests := map[string]struct { input string errMsg string @@ -89,7 +89,7 @@ func TestGoalError(t *testing.T) { t.Fatalf("error creating ari Context: %v", err) } t.Run(name, func(t *testing.T) { - t.Parallel() + // t.Parallel() // go test reports data race goalV, err := goalCtx.Eval(test.input) if err == nil { t.Fatalf("Context.GoalContext.Eval(%q) should return an error, but instead returned: %v", @@ -118,7 +118,7 @@ type matchTest struct { // Adapted from Goal implementation. func getMatchTests(glob string) ([]matchTest, error) { - d := os.DirFS("testing/") + d := os.DirFS("testing/via-go/") fnames, err := fs.Glob(d, glob) if err != nil { return nil, err @@ -192,15 +192,11 @@ func TestEval(t *testing.T) { if err != nil { t.Fatalf("getMatchTests: %v", err) } - smts, err := getScriptMatchTests("*.goal") - if err != nil { - t.Fatalf("getScriptMatchTests: %v", err) - } cwd, err := os.Getwd() if err != nil { t.Fatalf("failed to get current working directory: %v", err) } - for _, mt := range append(mts, smts...) { + for _, mt := range mts { if mt.Fname == "errors.goal" { continue } @@ -229,17 +225,9 @@ func TestEval(t *testing.T) { defer httpmock.DeactivateAndReset() registerHTTPMocks() - if mt.IsScript { - ariContextLeft.GoalContext.AssignGlobal("ARGS", goal.NewAS([]string{mt.Fname})) - err = os.Chdir(cwd + "/testing/scripts") - if err != nil { - t.Fatalf("failed to chdir to 'testing/scripts': %v", err) - } - } else { - err = os.Chdir(cwd + "/testing") - if err != nil { - t.Fatalf("failed to chdir to 'testing': %v", err) - } + err = os.Chdir(cwd + "/testing/via-go/") + if err != nil { + t.Fatalf("failed to chdir to 'testing/via-go': %v", err) } err = ariContextLeft.GoalContext.Compile(mt.Left, "", "") ps := ariContextLeft.GoalContext.String() diff --git a/script/ari-test b/script/ari-test new file mode 100755 index 0000000..d44446d --- /dev/null +++ b/script/ari-test @@ -0,0 +1,5 @@ +#!/usr/bin/env sh + +set -e + +ari -e " 'tt.chkall 0; tt.report 0" diff --git a/script/install b/script/install new file mode 100755 index 0000000..d5aca29 --- /dev/null +++ b/script/install @@ -0,0 +1,5 @@ +#!/usr/bin/env sh + +set -e + +go install ./cmd/ari diff --git a/script/test b/script/test index 684893e..a967712 100755 --- a/script/test +++ b/script/test @@ -1,3 +1,7 @@ #!/usr/bin/env sh +set -e + go test -v -failfast -race -coverpkg=./... -covermode=atomic -coverprofile=coverage.txt ./... +go install ./cmd/ari +./script/ari-test diff --git a/sql.go b/sql.go index af82db6..5ae3de4 100644 --- a/sql.go +++ b/sql.go @@ -254,7 +254,7 @@ func sqlQMonadic(x goal.V, sqlDatabase *SQLDatabase, goalContext *goal.Context) } var err error if sqlDatabase.DB == nil || !sqlDatabase.IsOpen { - fmt.Fprintf(os.Stdout, "Opening database %q\n", sqlDatabase.DataSource) + // fmt.Fprintf(os.Stdout, "Opening database %q\n", sqlDatabase.DataSource) err = sqlDatabase.Open() if err != nil { return goal.NewPanicError(err) @@ -307,7 +307,7 @@ func sqlExecMonadic(x goal.V, sqlDatabase *SQLDatabase, goalContext *goal.Contex } var err error if sqlDatabase.DB == nil || !sqlDatabase.IsOpen { - fmt.Fprintf(os.Stdout, "Opening database %q\n", sqlDatabase.DataSource) + // fmt.Fprintf(os.Stdout, "Opening database %q\n", sqlDatabase.DataSource) err = sqlDatabase.Open() if err != nil { return goal.NewPanicError(err) diff --git a/testing/ari-test.goal b/testing/ari-test.goal new file mode 100644 index 0000000..dd07ab0 --- /dev/null +++ b/testing/ari-test.goal @@ -0,0 +1 @@ +tt.t{1<#glob"*.md"} diff --git a/testing/error-test.goal b/testing/error-test.goal new file mode 100644 index 0000000..11065f2 --- /dev/null +++ b/testing/error-test.goal @@ -0,0 +1 @@ +tt.t {42=@[{http.xyz};0;{42}]} diff --git a/testing/errors.goal b/testing/errors.goal deleted file mode 100644 index 9d2e868..0000000 --- a/testing/errors.goal +++ /dev/null @@ -1 +0,0 @@ -http.wonky / undefined global diff --git a/testing/scripts/sql.goal b/testing/scripts/sql.goal deleted file mode 100644 index c2b07ff..0000000 --- a/testing/scripts/sql.goal +++ /dev/null @@ -1,35 +0,0 @@ -stmt:rq` -CREATE TABLE starwars - AS SELECT * - FROM read_csv( - '../data/starwars.csv', - header = true, - nullstr = 'NA', - columns = { - 'name': 'VARCHAR', - 'height': 'INT', - 'mass': 'INT', - 'haircolor': 'VARCHAR', - 'skin_color': 'VARCHAR', - 'eye_color': 'VARCHAR', - 'birth_year': 'INT', - 'sex': 'VARCHAR', - 'gender': 'VARCHAR', - 'homeworld': 'VARCHAR', - 'species': 'VARCHAR', - 'films': 'VARCHAR', - 'vehicles': 'VARCHAR', - 'starships': 'VARCHAR' - }); -` -q:rq` - SELECT species, sex, avg(height) AS avgheight, avg(mass) AS avgmass - FROM starwars -GROUP BY (species, sex) -ORDER BY species; -` -sql.exec stmt -/ Formatting as CSV to use ~ in the test runner (doesn't return truthy for 0n) -","csv sql.q q -/RESULT: -"species,sex,avgheight,avgmass\nAleena,male,79.0,15.0\nBesalisk,male,198.0,102.0\nCerean,male,198.0,82.0\nChagrian,male,196.0,0n\nClawdite,female,168.0,55.0\nDroid,none,131.2,69.75\nDug,male,112.0,40.0\nEwok,male,88.0,20.0\nGeonosian,male,183.0,80.0\nGungan,male,208.66666666666666,74.0\nHuman,male,182.3913043478261,85.70588235294117\nHuman,female,163.57142857142858,56.333333333333336\nHutt,hermaphroditic,175.0,1358.0\nIktotchi,male,188.0,0n\nKaleesh,male,216.0,159.0\nKaminoan,male,229.0,88.0\nKaminoan,female,213.0,0n\nKel Dor,male,188.0,80.0\nMirialan,female,168.0,53.0\nMon Calamari,male,180.0,83.0\nMuun,male,191.0,0n\nNautolan,male,196.0,87.0\nNeimodian,male,191.0,90.0\nPau'an,male,206.0,80.0\nQuermian,male,264.0,0n\nRodian,male,173.0,74.0\nSkakoan,male,193.0,48.0\nSullustan,male,160.0,68.0\nTholothian,female,184.0,50.0\nTogruta,female,178.0,57.0\nToong,male,163.0,65.0\nToydarian,male,137.0,0n\nTrandoshan,male,190.0,113.0\nTwi'lek,male,180.0,0n\nTwi'lek,female,178.0,55.0\nVulptereen,male,94.0,45.0\nWookiee,male,231.0,124.0\nXexto,male,122.0,0n\nYoda's species,male,66.0,17.0\nZabrak,male,173.0,80.0\n0n,0n,175.0,81.0\n" diff --git a/testing/sql-test.goal b/testing/sql-test.goal new file mode 100644 index 0000000..150d4b7 --- /dev/null +++ b/testing/sql-test.goal @@ -0,0 +1,36 @@ +f:(path.dir abspath FILE)+"/data/starwars.csv" +stmt:qq` +CREATE TABLE starwars + AS SELECT * + FROM read_csv( + '$f', + header = true, + nullstr = 'NA', + columns = { + 'name': 'VARCHAR', + 'height': 'INT', + 'mass': 'INT', + 'haircolor': 'VARCHAR', + 'skin_color': 'VARCHAR', + 'eye_color': 'VARCHAR', + 'birth_year': 'INT', + 'sex': 'VARCHAR', + 'gender': 'VARCHAR', + 'homeworld': 'VARCHAR', + 'species': 'VARCHAR', + 'films': 'VARCHAR', + 'vehicles': 'VARCHAR', + 'starships': 'VARCHAR' + }); +` +q:rq` + SELECT species, sex, avg(height) AS avgheight, avg(mass) AS avgmass + FROM starwars +GROUP BY (species, sex) +ORDER BY species; +` +sql.exec stmt +t:sql.q q +tt.t{41=#t"avgheight"} +tt.t{173=_math.avg t"avgheight"} +sql.exec "DROP TABLE starwars;" / idempotency diff --git a/testing/table-test.goal b/testing/table-test.goal new file mode 100644 index 0000000..be38845 --- /dev/null +++ b/testing/table-test.goal @@ -0,0 +1,5 @@ +f:(path.dir abspath FILE)+"/data/starwars.csv" +t:csv.tbl ","csv 'read f +tt.t{87=#*t}; tt.t{14=#t} +t:json.tbl@json rq/[{"a":1,"b":2},{"a":10,"b":20},{"a":100,"b":200}]/ +tt.t{3=#*t}; tt.t{(1 10 100)~t"a"} diff --git a/testing/table.goal b/testing/table.goal deleted file mode 100644 index feffc46..0000000 --- a/testing/table.goal +++ /dev/null @@ -1,2 +0,0 @@ -t:csv.tbl ","csv 'read"data/starwars.csv";(#*t;!t) / (87;"name""height""mass""hair_color""skin_color""eye_color""birth_year""sex""gender""homeworld""species""films""vehicles""starships") -t:json.tbl@json rq/[{"a":1,"b":2},{"a":10,"b":20},{"a":100,"b":200}]/;(#*t;!t) / (3;"a""b") diff --git a/testing/time-test.goal b/testing/time-test.goal new file mode 100644 index 0000000..7762051 --- /dev/null +++ b/testing/time-test.goal @@ -0,0 +1,3 @@ +tt.t{"Mon Jan _2 15:04:05 MST 2006"~time.UnixDate}; ft:time.RFC3339 +t1:time.parse[ft;"2019-12-31T23:59:59Z"];t2:time.parse[ft;"2020-01-01T00:00:00Z"] +tt.t{1000000000~time.sub[t2;t1]} diff --git a/testing/time.goal b/testing/time.goal deleted file mode 100644 index d1b2db3..0000000 --- a/testing/time.goal +++ /dev/null @@ -1,2 +0,0 @@ -time.UnixDate / "Mon Jan _2 15:04:05 MST 2006" -f:time.parse;ft:time.RFC3339;t1:f[ft;"2019-12-31T23:59:59Z"];t2:f[ft;"2020-01-01T00:00:00Z"];time.sub[t2;t1] / 1000000000 diff --git a/testing/http.goal b/testing/via-go/http.goal similarity index 100% rename from testing/http.goal rename to testing/via-go/http.goal