Skip to content

Commit 181e128

Browse files
authored
Release wrap up v0.47.0 (#377)
* Misc cleanup pre-release
1 parent 1b57138 commit 181e128

28 files changed

+154
-156
lines changed

.images/sq_inspect_remote_s3.png

236 KB
Loading

CHANGELOG.md

+14-11
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ Breaking changes are annotated with ☢️, and alpha/beta features with 🐥.
1010
## Upcoming
1111

1212
This is a significant release, focused on improving i/o, responsiveness,
13-
and performance. The headline features are caching of ingested data for
14-
document sources such as CSV or Excel, and download caching for remote
15-
document sources.
13+
and performance. The headline features are [caching](https://sq.io/docs/source#cache)
14+
of [ingested](https://sq.io/docs/source#ingest) data for [document sources](https://sq.io/docs/source#document-source)
15+
such as CSV or Excel, and [download](https://sq.io/docs/source#download) caching for remote document sources.
1616

1717
### Added
1818

@@ -21,13 +21,16 @@ document sources.
2121
by the new config options [`progress`](https://sq.io/docs/config#progress)
2222
and [`progress.delay`](https://sq.io/docs/config#progressdelay). You can also use
2323
the `--no-progress` flag to disable the progress bar.
24-
- Ingested [document sources](https://sq.io/docs/concepts#document-source) (such as
24+
- Note: the progress bar is rendered
25+
on `stderr` and is always zapped from the terminal when command output begins. It won't corrupt the output.
26+
- Ingested [document sources](https://sq.io/docs/source#document-source) (such as
2527
[CSV](https://sq.io/docs/drivers/csv) or [Excel](https://sq.io/docs/drivers/xlsx))
26-
now make use of an ingest cache DB. Previously, ingestion of document source data occurred
27-
on each `sq` command. It is now a one-time cost; subsequent use of the document source utilizes
28-
the cache DB. If the source document changes, the ingest cache DB is invalidated and
28+
now make use of an [ingest](https://sq.io/docs/source#ingest) cache DB. Previously, ingestion
29+
of document source data occurred on each `sq` command. It is now a one-time cost; subsequent
30+
use of the document source utilizes
31+
the cache DB. Until, that is, the source document changes: then the ingest cache DB is invalidated and
2932
ingested again. This is a significantly improved experience for large document sources.
30-
- There's several new commands to interact with the cache.
33+
- There are several new commands to interact with the cache (although you shouldn't need to):
3134
- [`sq cache enable`](https://sq.io/docs/cmd/cache_enable) and
3235
[`sq cache disable`](https://sq.io/docs/cmd/cache_disable) control cache usage.
3336
You can also instead use the new [`ingest.cache`](https://sq.io/docs/config#ingestcache)
@@ -36,11 +39,11 @@ document sources.
3639
- [`sq cache location`](https://sq.io/docs/cmd/cache_location) prints the cache location on disk.
3740
- [`sq cache stat`](https://sq.io/docs/cmd/cache_stat) shows stats about the cache.
3841
- [`sq cache tree`](https://sq.io/docs/cmd/cache_location) shows a tree view of the cache.
39-
- Downloading of remote document sources (e.g. a CSV file at
42+
- The [download](https://sq.io/docs/source#download) mechanism for remote document sources (e.g. a CSV file at
4043
[`https://sq.io/testdata/actor.csv`](https://sq.io/testdata/actor.csv)) has been completely
4144
overhauled. Previously, `sq` would re-download the remote file on every command. Now, the
42-
remote file is downloaded and cached locally. Subsequent commands check for staleness of
43-
the cached download, and re-download if necessary.
45+
remote file is downloaded and [cached](https://sq.io/docs/source#cache) locally.
46+
Subsequent `sq` invocations check for staleness of the cached download, and re-download if necessary.
4447
- As part of the download revamp, new config options have been introduced:
4548
- [`http.request.timeout`](https://sq.io/docs/config#httprequesttimeout) is the timeout for the initial response from the server, and
4649
[`http.response.timeout`](https://sq.io/docs/config#httpresponsetimeout) is the timeout for reading the entire response body. We separate

README.md

+17-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
`sq` is a command line tool that provides jq-style access to
1010
structured data sources: SQL databases, or document formats like CSV or Excel.
11+
It is the lovechild of sql+jq.
1112

1213
![sq](.images/splash.png)
1314

@@ -84,7 +85,9 @@ $ sq ls
8485

8586
Let's [add](https://sq.io/docs/cmd/add) a source. First we'll add a [SQLite](https://sq.io/docs/drivers/sqlite)
8687
database, but this could also be [Postgres](https://sq.io/docs/drivers/postgres),
87-
[SQL Server](https://sq.io/docs/drivers/sqlserver), [Excel](https://sq.io/docs/drivers/xlsx), etc.
88+
[SQL Server](https://sq.io/docs/drivers/sqlserver) etc., or a document source such [Excel](https://sq.io/docs/drivers/xlsx) or
89+
[CSV](https://sq.io/docs/drivers/csv).
90+
8891
Download the sample DB, and `sq add` the source.
8992

9093
```shell
@@ -118,6 +121,19 @@ $ sq ping
118121
@sakila 1ms pong
119122
```
120123

124+
> [!TIP]
125+
> Document sources such as CSV or Excel can be added from the local filesystem, or
126+
> from a remote HTTP URL.
127+
>
128+
> ```shell
129+
> $ sq add https://acme.s3.amazonaws.com/sales.csv
130+
> ```
131+
>
132+
> See the [sources](https://sq.io/docs/source#download) docs for more.
133+
>
134+
> ![sq inspect remote](./.images/sq_inspect_remote_s3.png)
135+
136+
121137
### Query
122138
123139
Fundamentally, `sq` is for querying data. The jq-style syntax is covered in

cli/cli.go

-1
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,6 @@ func newCommandTree(ru *run.Run) (rootCmd *cobra.Command) {
262262
addCmd(ru, xCmd, newXLockSrcCmd())
263263
addCmd(ru, xCmd, newXLockConfigCmd())
264264
addCmd(ru, xCmd, newXProgressCmd())
265-
addCmd(ru, xCmd, newXDownloadCmd())
266265

267266
return rootCmd
268267
}

cli/cmd_cache.go

+4-5
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,10 @@ func newCacheCmd() *cobra.Command {
4545

4646
func newCacheLocationCmd() *cobra.Command {
4747
cmd := &cobra.Command{
48-
Use: "location",
49-
Aliases: []string{"loc"},
50-
Short: "Print cache location",
51-
Long: "Print cache location.",
52-
Args: cobra.ExactArgs(0),
48+
Use: "location",
49+
Short: "Print cache location",
50+
Long: "Print cache location.",
51+
Args: cobra.ExactArgs(0),
5352
RunE: func(cmd *cobra.Command, args []string) error {
5453
ru := run.FromContext(cmd.Context())
5554
return ru.Writers.Config.CacheLocation(ru.Files.CacheDir())

cli/cmd_config.go

+5-6
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,11 @@ func newConfigCmd() *cobra.Command {
5959

6060
func newConfigLocationCmd() *cobra.Command {
6161
cmd := &cobra.Command{
62-
Use: "location",
63-
Aliases: []string{"loc"},
64-
Short: "Print config location",
65-
Long: "Print config location. Use --verbose for more detail.",
66-
Args: cobra.ExactArgs(0),
67-
RunE: execConfigLocation,
62+
Use: "location",
63+
Short: "Print config location",
64+
Long: "Print config location. Use --verbose for more detail.",
65+
Args: cobra.ExactArgs(0),
66+
RunE: execConfigLocation,
6867
Example: ` # Print config location
6968
$ sq config location
7069
/Users/neilotoole/.config/sq

cli/cmd_inspect.go

+3
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@ formats both show extensive detail.`,
104104
panicOn(cmd.RegisterFlagCompletionFunc(flag.ActiveSchema,
105105
activeSchemaCompleter{getActiveSourceViaArgs}.complete))
106106
addOptionFlag(cmd.Flags(), driver.OptIngestCache)
107+
108+
cmd.Flags().StringP(flag.Input, flag.InputShort, "", flag.InputUsage)
109+
panicOn(cmd.Flags().MarkHidden(flag.Input)) // Hide for now; this is mostly used for testing.
107110
return cmd
108111
}
109112

cli/cmd_slq.go

+3
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,9 @@ func addQueryCmdFlags(cmd *cobra.Command) {
327327

328328
cmd.Flags().StringP(flag.Output, flag.OutputShort, "", flag.OutputUsage)
329329

330+
cmd.Flags().StringP(flag.Input, flag.InputShort, "", flag.InputUsage)
331+
panicOn(cmd.Flags().MarkHidden(flag.Input)) // Hide for now; this is mostly used for testing.
332+
330333
cmd.Flags().String(flag.Insert, "", flag.InsertUsage)
331334
panicOn(cmd.RegisterFlagCompletionFunc(flag.Insert,
332335
(&handleTableCompleter{onlySQL: true, handleRequired: true}).complete))

cli/cmd_x.go

-72
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,15 @@ package cli
33
import (
44
"bufio"
55
"fmt"
6-
"net/url"
76
"os"
87
"time"
98

109
"github.com/spf13/cobra"
1110

12-
"github.com/neilotoole/sq/cli/flag"
1311
"github.com/neilotoole/sq/cli/run"
14-
"github.com/neilotoole/sq/libsq/core/errz"
15-
"github.com/neilotoole/sq/libsq/core/ioz/checksum"
16-
"github.com/neilotoole/sq/libsq/core/ioz/httpz"
1712
"github.com/neilotoole/sq/libsq/core/lg"
1813
"github.com/neilotoole/sq/libsq/core/progress"
1914
"github.com/neilotoole/sq/libsq/files"
20-
"github.com/neilotoole/sq/libsq/files/downloader"
21-
"github.com/neilotoole/sq/libsq/source"
2215
)
2316

2417
// newXCmd returns the root "x" command, which is the container
@@ -122,71 +115,6 @@ func execXProgress(cmd *cobra.Command, _ []string) error {
122115
return ctx.Err()
123116
}
124117

125-
func newXDownloadCmd() *cobra.Command {
126-
cmd := &cobra.Command{
127-
Use: "download URL",
128-
Short: "Download a file",
129-
Hidden: true,
130-
Args: cobra.ExactArgs(1),
131-
RunE: execXDownloadCmd,
132-
Example: ` $ sq x download https://sq.io/testdata/actor.csv
133-
134-
# Download a big-ass file
135-
$ sq x download https://sqio-public.s3.amazonaws.com/testdata/payment-large.gen.csv
136-
`,
137-
}
138-
cmd.Flags().BoolP(flag.JSON, flag.JSONShort, false, flag.JSONUsage)
139-
return cmd
140-
}
141-
142-
func execXDownloadCmd(cmd *cobra.Command, args []string) error {
143-
ctx := cmd.Context()
144-
log := lg.FromContext(ctx)
145-
ru := run.FromContext(ctx)
146-
147-
u, err := url.ParseRequestURI(args[0])
148-
if err != nil {
149-
return err
150-
}
151-
152-
sum := checksum.Sum([]byte(u.String()))
153-
fakeSrc := &source.Source{Handle: "@download_" + sum}
154-
cacheDir, err := ru.Files.CacheDirFor(fakeSrc)
155-
if err != nil {
156-
return err
157-
}
158-
159-
c := httpz.NewClient(
160-
httpz.DefaultUserAgent,
161-
httpz.OptResponseTimeout(time.Second*15),
162-
httpz.OptRequestDelay(time.Second*5),
163-
)
164-
dl, err := downloader.New(fakeSrc.Handle, c, u.String(), cacheDir)
165-
if err != nil {
166-
return err
167-
}
168-
169-
h := downloader.NewSinkHandler(log.With("origin", "handler"))
170-
if cacheFile := dl.Get(ctx, h.Handler); cacheFile == "" {
171-
fmt.Fprintf(ru.Out, "No cache file: %s\n", err)
172-
} else {
173-
fmt.Fprintf(ru.Out, "Returned cache file: %s\n", cacheFile)
174-
}
175-
176-
switch {
177-
case len(h.Errors) > 0:
178-
err1 := errz.Err(h.Errors[0])
179-
return err1
180-
case len(h.Downloaded) > 0:
181-
fmt.Fprintf(ru.Out, "Cached: %s\n", h.Downloaded[0])
182-
return nil
183-
case len(h.Streams) > 0:
184-
fmt.Fprintf(ru.Out, "Uncached: %d bytes\n", h.Streams[0].Size())
185-
}
186-
187-
return nil
188-
}
189-
190118
func pressEnter() <-chan struct{} {
191119
done := make(chan struct{})
192120
go func() {

cli/complete.go

+6-5
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,12 @@ var OptShellCompletionLog = options.NewBool(
4040
0,
4141
false,
4242
"Enable logging of shell completion activity",
43-
`Enable logging of shell completion activity. It is frequently the case
44-
that shell completion handlers will trigger work (such as inspecting
45-
the schema) that doesn't complete by the shell completion timeout. This can result
46-
in the logs being filled with uninteresting junk when the timeout triggers
47-
logging of errors due to the cancellation.`,
43+
`Enable logging of shell completion activity. This is really only useful
44+
for debugging shell completion functionality. It's disabled by default,
45+
because it's frequently the case that shell completion handlers will trigger
46+
work (such as inspecting the schema) that doesn't complete by the shell
47+
completion timeout. This can result in the logs being filled with uninteresting
48+
junk when the timeout triggers logging of errors.`,
4849
)
4950

5051
// completionFunc is a shell completion function.

cli/config/store.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ var OptConfigLockTimeout = options.NewDuration(
7171
0,
7272
time.Second*5,
7373
"Wait timeout to acquire config lock",
74-
`Wait timeout to acquire the config lock. During this period, retry will occur
74+
`Wait timeout to acquire the config lock (which prevents multiple sq instances
75+
stepping on each other's config changes). During this period, retry will occur
7576
if the lock is already held by another process. If zero, no retry occurs.`,
7677
)

cli/flag/flag.go

+9
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,15 @@ const (
7474
OutputShort = "o"
7575
OutputUsage = "Write output to <file> instead of stdout"
7676

77+
// Input sets Run.Stdin to the named file. At this time, this is used
78+
// mainly for debugging, so it's marked hidden by the CLI. I'm not
79+
// sure if this will ever be generally useful. Also, there's been no
80+
// testing done to see how this flag would interact with, say,
81+
// flag.PasswordPrompt, which also reads from stdin.
82+
Input = "input"
83+
InputShort = "i"
84+
InputUsage = "Read input from <file> instead of stdin"
85+
7786
InspectOverview = "overview"
7887
InspectOverviewShort = "O"
7988
InspectOverviewUsage = "Show metadata only (no schema)"

cli/logging.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ func getLogEnabled(ctx context.Context, osArgs []string, cfg *config.Config) boo
219219

220220
val, ok, err := getBootstrapFlagValue(flag.LogEnabled, "", flag.LogEnabledUsage, osArgs)
221221
if err != nil {
222-
bootLog.Error("Reading log 'enabled' from flag", lga.Flag, flag.LogEnabled, lga.Err, err)
222+
bootLog.Warn("Reading log 'enabled' from flag", lga.Flag, flag.LogEnabled, lga.Err, err)
223223
}
224224
if ok {
225225
bootLog.Debug("Using log 'enabled' specified via flag", lga.Flag, flag.LogEnabled, lga.Val, val)
@@ -277,7 +277,7 @@ func getLogLevel(ctx context.Context, osArgs []string, cfg *config.Config) slog.
277277

278278
val, ok, err := getBootstrapFlagValue(flag.LogLevel, "", flag.LogLevelUsage, osArgs)
279279
if err != nil {
280-
bootLog.Error("Reading log level from flag", lga.Flag, flag.LogLevel, lga.Err, err)
280+
bootLog.Warn("Reading log level from flag", lga.Flag, flag.LogLevel, lga.Err, err)
281281
}
282282
if ok {
283283
bootLog.Debug("Using log level specified via flag", lga.Flag, flag.LogLevel, lga.Val, val)
@@ -321,7 +321,7 @@ func getLogFormat(ctx context.Context, osArgs []string, cfg *config.Config) form
321321

322322
val, ok, err := getBootstrapFlagValue(flag.LogFormat, "", flag.LogFormatUsage, osArgs)
323323
if err != nil {
324-
bootLog.Error("Error reading log format from flag", lga.Flag, flag.LogFormat, lga.Err, err)
324+
bootLog.Warn("Error reading log format from flag", lga.Flag, flag.LogFormat, lga.Err, err)
325325
}
326326
if ok {
327327
bootLog.Debug("Using log format specified via flag", lga.Flag, flag.LogFormat, lga.Val, val)
@@ -373,7 +373,7 @@ func getLogFilePath(ctx context.Context, osArgs []string, cfg *config.Config) st
373373

374374
fp, ok, err := getBootstrapFlagValue(flag.LogFile, "", flag.LogFileUsage, osArgs)
375375
if err != nil {
376-
bootLog.Error("Reading log file from flag", lga.Flag, flag.LogFile, lga.Err, err)
376+
bootLog.Warn("Reading log file from flag", lga.Flag, flag.LogFile, lga.Err, err)
377377
}
378378
if ok {
379379
bootLog.Debug("Log file specified via flag", lga.Flag, flag.LogFile, lga.Path, fp)

cli/options.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import (
1818
"github.com/neilotoole/sq/libsq/core/timez"
1919
"github.com/neilotoole/sq/libsq/driver"
2020
"github.com/neilotoole/sq/libsq/files"
21-
"github.com/neilotoole/sq/libsq/files/downloader"
2221
"github.com/neilotoole/sq/libsq/source"
2322
"github.com/neilotoole/sq/libsq/source/drivertype"
2423
)
@@ -184,8 +183,8 @@ func RegisterDefaultOpts(reg *options.Registry) {
184183
files.OptHTTPRequestTimeout,
185184
files.OptHTTPResponseTimeout,
186185
files.OptHTTPSInsecureSkipVerify,
187-
downloader.OptCache,
188-
downloader.OptContinueOnError,
186+
files.OptDownloadCache,
187+
files.OptDownloadContinueOnError,
189188
driver.OptConnMaxOpen,
190189
driver.OptConnMaxIdle,
191190
driver.OptConnMaxIdleTime,

cli/run.go

+17
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,23 @@ func preRun(cmd *cobra.Command, ru *run.Run) error {
140140
ru.Cleanup = cleanup.New()
141141
}
142142

143+
// If the --input=some/file flag is set, then we need
144+
// to override ru.Stdin (which is typically stdin) to point
145+
// it at the input source file.
146+
if cmdFlagChanged(ru.Cmd, flag.Input) {
147+
fpath, _ := ru.Cmd.Flags().GetString(flag.Input)
148+
fpath, err := filepath.Abs(fpath)
149+
if err != nil {
150+
return errz.Wrapf(err, "failed to get absolute path for --%s", flag.Input)
151+
}
152+
153+
f, err := os.Open(fpath)
154+
if err != nil {
155+
return errz.Wrapf(err, "failed to open file specified by flag --%s", flag.Input)
156+
}
157+
ru.Stdin = f
158+
}
159+
143160
// If the --output=/some/file flag is set, then we need to
144161
// override ru.Out (which is typically stdout) to point it at
145162
// the output destination file.

drivers/sqlite3/db_type_test.go

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/neilotoole/sq/testh"
1717
"github.com/neilotoole/sq/testh/fixt"
1818
"github.com/neilotoole/sq/testh/sakila"
19+
"github.com/neilotoole/sq/testh/tu"
1920
)
2021

2122
// typeTestTableDDLPath is the location of the SQL CREATE statement
@@ -165,6 +166,8 @@ func createTypeTestTbls(th *testh.Helper, src *source.Source, nTimes int, withDa
165166
// the returned data matches the inserted data, including verifying
166167
// that NULL is handled correctly.
167168
func TestDatabaseTypes(t *testing.T) {
169+
tu.SkipIssueWindows(t, tu.GH355SQLiteDecimalWin)
170+
168171
th := testh.New(t)
169172
src := th.Source(sakila.SL3)
170173
actualTblName := createTypeTestTbls(th, src, 1, true)[0]

libsq/core/ioz/ioz.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func CopyFile(dst, src string, mkdir bool) error {
6767
return errz.Err(err)
6868
}
6969
defer in.Close()
70-
tmp, err := os.CreateTemp(filepath.Dir(dst), "")
70+
tmp, err := os.CreateTemp(filepath.Dir(dst), "*_"+filepath.Base(src))
7171
if err != nil {
7272
return errz.Err(err)
7373
}

0 commit comments

Comments
 (0)