mirror of
https://github.com/neilotoole/sq.git
synced 2024-12-24 16:51:34 +03:00
sq inspect now shows catalog (when outputting in JSON and YAML format) (#329)
This commit is contained in:
parent
30b7d6421e
commit
d7fc315028
16
CHANGELOG.md
16
CHANGELOG.md
@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
Breaking changes are annotated with ☢️, and alpha/beta features with 🐥.
|
||||
|
||||
## [v0.43.1] - 2023-11-19
|
||||
|
||||
### Added
|
||||
|
||||
- Related to [#270], the output of `sq inspect` now includes the
|
||||
source's catalog (in JSON and YAML output formats).
|
||||
|
||||
### Fixed
|
||||
|
||||
- MySQL driver didn't populate all expected values for `sq inspect --overview`.
|
||||
|
||||
### Changed
|
||||
|
||||
- ☢️ Removed unused `--exec` and `--query` flags from `sq sql` command.
|
||||
|
||||
## [v0.43.0] - 2023-11-18
|
||||
|
||||
### Added
|
||||
@ -871,3 +886,4 @@ make working with lots of sources much easier.
|
||||
[v0.42.0]: https://github.com/neilotoole/sq/compare/v0.41.1...v0.42.0
|
||||
[v0.42.1]: https://github.com/neilotoole/sq/compare/v0.42.0...v0.42.1
|
||||
[v0.43.0]: https://github.com/neilotoole/sq/compare/v0.42.1...v0.43.0
|
||||
[v0.43.1]: https://github.com/neilotoole/sq/compare/v0.43.0...v0.43.1
|
||||
|
@ -124,6 +124,13 @@ func TestCmdInspect_json_yaml(t *testing.T) {
|
||||
require.Nil(t, srcMeta.Tables)
|
||||
require.Zero(t, srcMeta.TableCount)
|
||||
require.Zero(t, srcMeta.ViewCount)
|
||||
require.NotEmpty(t, srcMeta.Name)
|
||||
require.NotEmpty(t, srcMeta.Schema)
|
||||
require.NotEmpty(t, srcMeta.FQName)
|
||||
require.NotEmpty(t, srcMeta.DBDriver)
|
||||
require.NotEmpty(t, srcMeta.DBProduct)
|
||||
require.NotEmpty(t, srcMeta.DBVersion)
|
||||
require.NotZero(t, srcMeta.Size)
|
||||
})
|
||||
|
||||
t.Run("inspect_dbprops", func(t *testing.T) {
|
||||
|
@ -31,12 +31,7 @@ func newSQLCmd() *cobra.Command {
|
||||
Short: "Execute DB-native SQL query or statement",
|
||||
Long: `Execute a SQL query or statement against the active source using the
|
||||
source's SQL dialect. Use flag --src=@HANDLE to specify an alternative
|
||||
source.
|
||||
|
||||
If flag --query is set, sq will run the input as a query
|
||||
(SELECT) and return the query rows. If flag --exec is set,
|
||||
sq will execute the input and return the result. If neither
|
||||
flag is set, sq attempts to determine the appropriate mode.`,
|
||||
source.`,
|
||||
RunE: execSQL,
|
||||
Example: ` # Select from active source
|
||||
$ sq sql 'SELECT * FROM actor'
|
||||
@ -45,26 +40,19 @@ flag is set, sq attempts to determine the appropriate mode.`,
|
||||
$ sq sql --src=@sakila_pg12 'SELECT * FROM actor'
|
||||
|
||||
# Drop table @sakila_pg12.actor
|
||||
$ sq sql --exec --src=@sakila_pg12 'DROP TABLE actor'
|
||||
$ sq sql --src=@sakila_pg12 'DROP TABLE actor'
|
||||
|
||||
# Select from active source and write results to @sakila_ms17.actor
|
||||
$ sq sql 'SELECT * FROM actor' --insert=@sakila_ms17.actor`,
|
||||
}
|
||||
|
||||
addQueryCmdFlags(cmd)
|
||||
|
||||
// TODO: These flags aren't actually implemented yet.
|
||||
// This entire --exec mechanism needs to be revisited.
|
||||
// User explicitly wants to execute the SQL using sql.DB.Query
|
||||
// cmd.Flags().Bool(flag.SQLQuery, false, flag.SQLQueryUsage)
|
||||
// User explicitly wants to execute the SQL using sql.DB.Exec
|
||||
// cmd.Flags().Bool(flag.SQLExec, false, flag.SQLExecUsage)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func execSQL(cmd *cobra.Command, args []string) error {
|
||||
ru := run.FromContext(cmd.Context())
|
||||
ctx := cmd.Context()
|
||||
ru := run.FromContext(ctx)
|
||||
switch len(args) {
|
||||
default:
|
||||
return errz.New("a single query string is required")
|
||||
@ -76,7 +64,7 @@ func execSQL(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
err := determineSources(cmd.Context(), ru, true)
|
||||
err := determineSources(ctx, ru, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -93,7 +81,7 @@ func execSQL(cmd *cobra.Command, args []string) error {
|
||||
if !cmdFlagChanged(cmd, flag.Insert) {
|
||||
// The user didn't specify the --insert=@src.tbl flag,
|
||||
// so we just want to print the records.
|
||||
return execSQLPrint(cmd.Context(), ru, activeSrc)
|
||||
return execSQLPrint(ctx, ru, activeSrc)
|
||||
}
|
||||
|
||||
// Instead of printing the records, they will be
|
||||
@ -113,7 +101,7 @@ func execSQL(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return execSQLInsert(cmd.Context(), ru, activeSrc, destSrc, destTbl)
|
||||
return execSQLInsert(ctx, ru, activeSrc, destSrc, destTbl)
|
||||
}
|
||||
|
||||
// execSQLPrint executes the SQL and prints resulting records
|
||||
@ -144,7 +132,7 @@ func execSQLInsert(ctx context.Context, ru *run.Run,
|
||||
ctx, cancelFn := context.WithCancel(ctx)
|
||||
defer cancelFn()
|
||||
|
||||
fromDB, err := pools.Open(ctx, fromSrc)
|
||||
fromPool, err := pools.Open(ctx, fromSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -154,18 +142,17 @@ func execSQLInsert(ctx context.Context, ru *run.Run,
|
||||
return err
|
||||
}
|
||||
|
||||
// Note: We don't need to worry about closing fromDB and
|
||||
// Note: We don't need to worry about closing fromPool and
|
||||
// destPool because they are closed by pools.Close, which
|
||||
// is invoked by ru.Close, and ru is closed further up the
|
||||
// stack.
|
||||
|
||||
inserter := libsq.NewDBWriter(
|
||||
destPool,
|
||||
destTbl,
|
||||
driver.OptTuningRecChanSize.Get(destSrc.Options),
|
||||
libsq.DBWriterCreateTableIfNotExistsHook(destTbl),
|
||||
)
|
||||
err = libsq.QuerySQL(ctx, fromDB, nil, inserter, args[0])
|
||||
err = libsq.QuerySQL(ctx, fromPool, nil, inserter, args[0])
|
||||
if err != nil {
|
||||
return errz.Wrapf(err, "insert to {%s} failed", source.Target(destSrc, destTbl))
|
||||
}
|
||||
@ -178,7 +165,7 @@ func execSQLInsert(ctx context.Context, ru *run.Run,
|
||||
lg.FromContext(ctx).Debug(lgm.RowsAffected, lga.Count, affected)
|
||||
|
||||
// TODO: Should really use a Printer here
|
||||
fmt.Fprintf(ru.Out, stringz.Plu("Inserted %d row(s) into %s\n",
|
||||
_, _ = fmt.Fprintf(ru.Out, stringz.Plu("Inserted %d row(s) into %s\n",
|
||||
int(affected)), affected, source.Target(destSrc, destTbl))
|
||||
return nil
|
||||
}
|
||||
|
@ -291,20 +291,17 @@ func getSourceMetadata(ctx context.Context, src *source.Source, db sqlz.DB, noSc
|
||||
})
|
||||
})
|
||||
|
||||
if noSchema {
|
||||
return md, nil
|
||||
if !noSchema {
|
||||
g.Go(func() error {
|
||||
return doRetry(gCtx, func() error {
|
||||
var err error
|
||||
md.Tables, err = getAllTblMetas(gCtx, db)
|
||||
return err
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
g.Go(func() error {
|
||||
return doRetry(gCtx, func() error {
|
||||
var err error
|
||||
md.Tables, err = getAllTblMetas(gCtx, db)
|
||||
return err
|
||||
})
|
||||
})
|
||||
|
||||
err := g.Wait()
|
||||
if err != nil {
|
||||
if err := g.Wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -322,20 +319,29 @@ func getSourceMetadata(ctx context.Context, src *source.Source, db sqlz.DB, noSc
|
||||
func setSourceSummaryMeta(ctx context.Context, db sqlz.DB, md *source.Metadata) error {
|
||||
const summaryQuery = `SELECT @@GLOBAL.version, @@GLOBAL.version_comment, @@GLOBAL.version_compile_os,
|
||||
@@GLOBAL.version_compile_machine, DATABASE(), CURRENT_USER(),
|
||||
(SELECT CATALOG_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = DATABASE() LIMIT 1),
|
||||
(SELECT SUM( data_length + index_length )
|
||||
FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE()) AS size`
|
||||
|
||||
var version, versionComment, versionOS, versionArch, schema string
|
||||
var size sql.NullInt64
|
||||
err := db.QueryRowContext(ctx, summaryQuery).Scan(&version, &versionComment, &versionOS, &versionArch, &schema,
|
||||
&md.User, &size)
|
||||
err := db.QueryRowContext(ctx, summaryQuery).Scan(
|
||||
&version,
|
||||
&versionComment,
|
||||
&versionOS,
|
||||
&versionArch,
|
||||
&schema,
|
||||
&md.User,
|
||||
&md.Catalog,
|
||||
&size,
|
||||
)
|
||||
if err != nil {
|
||||
return errw(err)
|
||||
}
|
||||
|
||||
md.Name = schema
|
||||
md.Schema = schema
|
||||
md.FQName = schema
|
||||
md.FQName = md.Catalog + "." + schema
|
||||
if size.Valid {
|
||||
md.Size = size.Int64
|
||||
}
|
||||
|
@ -229,16 +229,28 @@ func (d *driveri) ListSchemas(ctx context.Context, db sqlz.DB) ([]string, error)
|
||||
return schemas, nil
|
||||
}
|
||||
|
||||
// CurrentCatalog implements driver.SQLDriver. MySQL does not support catalogs,
|
||||
// so this method returns an error.
|
||||
func (d *driveri) CurrentCatalog(_ context.Context, _ sqlz.DB) (string, error) {
|
||||
return "", errz.New("mysql: catalog mechanism not supported")
|
||||
// CurrentCatalog implements driver.SQLDriver. Although MySQL doesn't really
|
||||
// support catalogs, we return the value found in INFORMATION_SCHEMA.SCHEMATA,
|
||||
// i.e. "def".
|
||||
func (d *driveri) CurrentCatalog(ctx context.Context, db sqlz.DB) (string, error) {
|
||||
var catalog string
|
||||
|
||||
if err := db.QueryRowContext(ctx, selectCatalog).Scan(&catalog); err != nil {
|
||||
return "", errw(err)
|
||||
}
|
||||
return catalog, nil
|
||||
}
|
||||
|
||||
// ListCatalogs implements driver.SQLDriver. MySQL does not support catalogs,
|
||||
// so this method returns an error.
|
||||
func (d *driveri) ListCatalogs(_ context.Context, _ sqlz.DB) ([]string, error) {
|
||||
return nil, errz.New("mysql: catalog mechanism not supported")
|
||||
// ListCatalogs implements driver.SQLDriver. MySQL does not really support catalogs,
|
||||
// but this method simply delegates to CurrentCatalog, which returns the value
|
||||
// found in INFORMATION_SCHEMA.SCHEMATA, i.e. "def".
|
||||
func (d *driveri) ListCatalogs(ctx context.Context, db sqlz.DB) ([]string, error) {
|
||||
catalog, err := d.CurrentCatalog(ctx, db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []string{catalog}, nil
|
||||
}
|
||||
|
||||
// AlterTableRename implements driver.SQLDriver.
|
||||
@ -600,12 +612,14 @@ func tblfmt[T string | tablefq.T](tbl T) string {
|
||||
return tfq.Render(stringz.BacktickQuote)
|
||||
}
|
||||
|
||||
const selectCatalog = `SELECT CATALOG_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = DATABASE() LIMIT 1`
|
||||
|
||||
func doRenderFuncCatalog(_ *render.Context, fn *ast.FuncNode) (string, error) {
|
||||
if fn.FuncName() != ast.FuncNameCatalog {
|
||||
// Shouldn't happen
|
||||
return "", errz.Errorf("expected %s function, got %q", ast.FuncNameCatalog, fn.FuncName())
|
||||
}
|
||||
|
||||
const frag = `(SELECT CATALOG_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = DATABASE() LIMIT 1)`
|
||||
const frag = `(` + selectCatalog + `)`
|
||||
return frag, nil
|
||||
}
|
||||
|
@ -206,6 +206,7 @@ current_setting('server_version'), version(), "current_user"()`
|
||||
return nil, errz.New("NULL value for current_schema(): check privileges and search_path")
|
||||
}
|
||||
|
||||
md.Catalog = md.Name
|
||||
md.Schema = schema.String
|
||||
md.FQName = md.Name + "." + schema.String
|
||||
|
||||
|
@ -960,7 +960,9 @@ func (p *pool) SourceMetadata(ctx context.Context, noSchema bool) (*source.Metad
|
||||
|
||||
md.Size = fi.Size()
|
||||
md.Name = fi.Name()
|
||||
md.FQName = fi.Name() + "/" + md.Schema
|
||||
md.FQName = fi.Name() + "." + md.Schema
|
||||
// SQLite doesn't support catalog, but we conventionally set it to "default"
|
||||
md.Catalog = "default"
|
||||
md.Location = p.src.Location
|
||||
|
||||
md.DBProperties, err = getDBProperties(ctx, p.db)
|
||||
|
@ -138,6 +138,7 @@ GROUP BY database_id) AS total_size_bytes`
|
||||
|
||||
md.Name = catalog
|
||||
md.FQName = catalog + "." + schema
|
||||
md.Catalog = catalog
|
||||
md.Schema = schema
|
||||
|
||||
if md.DBProperties, err = getDBProperties(ctx, db); err != nil {
|
||||
|
@ -608,15 +608,16 @@ func TestSQLDriver_CurrentCatalog(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSQLDriver_CurrentSchema(t *testing.T) {
|
||||
func TestSQLDriver_CurrentSchemaCatalog(t *testing.T) {
|
||||
testCases := []struct {
|
||||
handle string
|
||||
want string
|
||||
handle string
|
||||
wantSchema string
|
||||
wantCatalog string
|
||||
}{
|
||||
{sakila.SL3, "main"},
|
||||
{sakila.Pg, "public"},
|
||||
{sakila.My, "sakila"},
|
||||
{sakila.MS, "dbo"},
|
||||
{sakila.SL3, "main", "default"},
|
||||
{sakila.Pg, "public", "sakila"},
|
||||
{sakila.My, "sakila", "def"},
|
||||
{sakila.MS, "dbo", "sakila"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@ -627,16 +628,26 @@ func TestSQLDriver_CurrentSchema(t *testing.T) {
|
||||
|
||||
gotSchema, err := drvr.CurrentSchema(th.Context, db)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.want, gotSchema)
|
||||
require.Equal(t, tc.wantSchema, gotSchema)
|
||||
|
||||
md, err := pool.SourceMetadata(th.Context, false)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, md)
|
||||
require.Equal(t, md.Schema, gotSchema)
|
||||
require.Equal(t, md.Schema, tc.wantSchema)
|
||||
require.Equal(t, md.Catalog, tc.wantCatalog)
|
||||
|
||||
gotSchemas, err := drvr.ListSchemas(th.Context, db)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, gotSchemas, gotSchema)
|
||||
|
||||
if drvr.Dialect().Catalog {
|
||||
gotCatalog, err := drvr.CurrentCatalog(th.Context, db)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.wantCatalog, gotCatalog)
|
||||
gotCatalogs, err := drvr.ListCatalogs(th.Context, db)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, gotCatalogs, gotCatalog)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,9 @@ type Metadata struct {
|
||||
// This may be empty for some sources.
|
||||
Schema string `json:"schema,omitempty" yaml:"schema,omitempty"`
|
||||
|
||||
// Catalog is the catalog name, for example "sakila".
|
||||
Catalog string `json:"catalog,omitempty" yaml:"catalog,omitempty"`
|
||||
|
||||
// Driver is the source driver type.
|
||||
Driver DriverType `json:"driver" yaml:"driver"`
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user