From 1e733c6a58aab0f983c0b4c8dfd885e097db00de Mon Sep 17 00:00:00 2001 From: Jan Was Date: Sun, 7 Feb 2021 15:46:20 +0000 Subject: [PATCH] Make all readers logging readers --- drivers/drivers.go | 14 +-- drivers/godror/godror.go | 19 +--- .../metadata/informationschema/metadata.go | 96 +++++++------------ drivers/metadata/metadata_test.go | 4 +- drivers/metadata/oracle/metadata.go | 62 +++--------- drivers/metadata/reader.go | 63 ++++++++++++ drivers/metadata/writer.go | 10 +- drivers/mssql/mssql.go | 12 +-- drivers/mssql/reader.go | 9 +- drivers/mysql/mysql.go | 26 ++--- drivers/oracle/oracle.go | 19 +--- drivers/postgres/postgres.go | 51 ++++------ drivers/postgres/reader.go | 16 ++-- drivers/sqlite3/reader.go | 15 ++- drivers/sqlite3/sqlite3.go | 4 +- drivers/trino/trino.go | 25 ++--- metacmd/cmds.go | 13 ++- 17 files changed, 199 insertions(+), 259 deletions(-) diff --git a/drivers/drivers.go b/drivers/drivers.go index 48d1aece4b7..a4f813f058a 100644 --- a/drivers/drivers.go +++ b/drivers/drivers.go @@ -85,9 +85,9 @@ type Driver struct { // BatchQueryPrefixes will be used by BatchQueryPrefixes if defined. BatchQueryPrefixes map[string]string // NewMetadataReader returns a db metadata introspector. - NewMetadataReader func(db DB) metadata.Reader + NewMetadataReader func(db DB, opts ...metadata.ReaderOption) metadata.Reader // NewMetadataWriter returns a db metadata printer. - NewMetadataWriter func(db DB, w io.Writer) metadata.Writer + NewMetadataWriter func(db DB, w io.Writer, opts ...metadata.ReaderOption) metadata.Writer } // drivers is the map of drivers funcs. @@ -427,26 +427,26 @@ func NextResultSet(q *sql.Rows) bool { } // NewMetadataReader wraps creating a new database introspector for the specified driver. -func NewMetadataReader(u *dburl.URL, db DB, w io.Writer) (metadata.Reader, error) { +func NewMetadataReader(u *dburl.URL, db DB, w io.Writer, opts ...metadata.ReaderOption) (metadata.Reader, error) { d, ok := drivers[u.Driver] if !ok || d.NewMetadataReader == nil { return nil, fmt.Errorf(text.NotSupportedByDriver, `describe commands`) } - return d.NewMetadataReader(db), nil + return d.NewMetadataReader(db, opts...), nil } // NewMetadataWriter wraps creating a new database metadata printer for the specified driver. -func NewMetadataWriter(u *dburl.URL, db DB, w io.Writer) (metadata.Writer, error) { +func NewMetadataWriter(u *dburl.URL, db DB, w io.Writer, opts ...metadata.ReaderOption) (metadata.Writer, error) { d, ok := drivers[u.Driver] if !ok { return nil, fmt.Errorf(text.NotSupportedByDriver, `describe commands`) } if d.NewMetadataWriter != nil { - return d.NewMetadataWriter(db, w), nil + return d.NewMetadataWriter(db, w, opts...), nil } if d.NewMetadataReader == nil { return nil, fmt.Errorf(text.NotSupportedByDriver, `describe commands`) } - newMetadataWriter := metadata.NewDefaultWriter(d.NewMetadataReader(db)) + newMetadataWriter := metadata.NewDefaultWriter(d.NewMetadataReader(db, opts...)) return newMetadataWriter(db, w), nil } diff --git a/drivers/godror/godror.go b/drivers/godror/godror.go index 6c36fcf3679..2a5313854c9 100644 --- a/drivers/godror/godror.go +++ b/drivers/godror/godror.go @@ -8,8 +8,6 @@ import ( "database/sql" "fmt" "io" - "log" - "os" "regexp" "strings" @@ -111,22 +109,11 @@ func init() { return typ, sqlstr, q, nil }, NewMetadataReader: orameta.NewReader(), - NewMetadataWriter: func(db drivers.DB, w io.Writer) metadata.Writer { - // TODO if options would be common to all readers, this could be moved - // to the caller and passed in an argument - envs := env.All() - opts := []orameta.Option{} - if envs["ECHO_HIDDEN"] == "on" || envs["ECHO_HIDDEN"] == "noexec" { - if envs["ECHO_HIDDEN"] == "noexec" { - opts = append(opts, orameta.WithDryRun(true)) - } - opts = append(opts, orameta.WithLogger(log.New(os.Stdout, "DEBUG: ", log.LstdFlags))) - } - newReader := orameta.NewReader(opts...) - writerOpts := []metadata.Option{ + NewMetadataWriter: func(db drivers.DB, w io.Writer, opts ...metadata.ReaderOption) metadata.Writer { + writerOpts := []metadata.WriterOption{ metadata.WithSystemSchemas([]string{"ctxsys", "flows_files", "mdsys", "outln", "sys", "system", "xdb", "xs$null"}), } - return metadata.NewDefaultWriter(newReader(db), writerOpts...)(db, w) + return metadata.NewDefaultWriter(orameta.NewReader()(db, opts...), writerOpts...)(db, w) }, }) } diff --git a/drivers/metadata/informationschema/metadata.go b/drivers/metadata/informationschema/metadata.go index 1414eb51650..3a1fd57b4cd 100644 --- a/drivers/metadata/informationschema/metadata.go +++ b/drivers/metadata/informationschema/metadata.go @@ -14,15 +14,12 @@ import ( // InformationSchema metadata reader type InformationSchema struct { - db drivers.DB - logger Logger - dryRun bool - pf func(int) string - hasTypeDetails bool - hasFunctions bool - hasSequences bool - hasIndexes bool - colExpr map[ColumnName]string + metadata.LoggingReader + pf func(int) string + hasFunctions bool + hasSequences bool + hasIndexes bool + colExpr map[ColumnName]string } var _ metadata.BasicReader = &InformationSchema{} @@ -48,7 +45,7 @@ const ( ) // New InformationSchema reader -func New(opts ...Option) func(db drivers.DB) metadata.Reader { +func New(opts ...metadata.ReaderOption) func(drivers.DB, ...metadata.ReaderOption) metadata.Reader { s := &InformationSchema{ pf: func(n int) string { return fmt.Sprintf("$%d", n) }, hasFunctions: true, @@ -66,68 +63,52 @@ func New(opts ...Option) func(db drivers.DB) metadata.Reader { FunctionsSecurityType: "security_type", }, } + // aply InformationSchema specific options for _, o := range opts { o(s) } - return func(db drivers.DB) metadata.Reader { - s.db = db + return func(db drivers.DB, opts ...metadata.ReaderOption) metadata.Reader { + s.LoggingReader = metadata.NewLoggingReader(db, opts...) return s } } -// Option to configure the InformationSchema reader -type Option func(*InformationSchema) - -// WithLogger used to log queries before executing them -func WithLogger(l Logger) Option { - return func(s *InformationSchema) { - s.logger = l - } -} - -// WithDryRun allows to avoid running any queries -func WithDryRun(d bool) Option { - return func(s *InformationSchema) { - s.dryRun = d - } -} - // WithPlaceholder generator function, that usually returns either `?` or `$n`, // where `n` is the argument. -func WithPlaceholder(pf func(int) string) Option { - return func(s *InformationSchema) { - s.pf = pf +func WithPlaceholder(pf func(int) string) metadata.ReaderOption { + return func(r metadata.Reader) { + r.(*InformationSchema).pf = pf } } // WithCustomColumns to use different expressions for some columns -func WithCustomColumns(cols map[ColumnName]string) Option { - return func(s *InformationSchema) { +func WithCustomColumns(cols map[ColumnName]string) metadata.ReaderOption { + return func(r metadata.Reader) { for k, v := range cols { - s.colExpr[k] = v + r.(*InformationSchema).colExpr[k] = v } } } // WithFunctions when the `routines` and `parameters` tables exists -func WithFunctions(fun bool) Option { - return func(s *InformationSchema) { - s.hasFunctions = fun +func WithFunctions(fun bool) metadata.ReaderOption { + return func(r metadata.Reader) { + r.(*InformationSchema).hasFunctions = fun } } // WithIndexes when the `statistics` table exists -func WithIndexes(ind bool) Option { - return func(s *InformationSchema) { - s.hasIndexes = ind +func WithIndexes(ind bool) metadata.ReaderOption { + return func(r metadata.Reader) { + r.(*InformationSchema).hasIndexes = ind } } // WithSequences when the `sequences` table exists -func WithSequences(seq bool) Option { - return func(s *InformationSchema) { - s.hasSequences = seq +func WithSequences(seq bool) metadata.ReaderOption { + return func(r metadata.Reader) { + r.(*InformationSchema).hasSequences = seq } } @@ -168,7 +149,7 @@ func (s InformationSchema) Columns(catalog, schemaPattern, tablePattern string) } qstr += ` ORDER BY table_catalog, table_schema, table_name, ordinal_position` - rows, err := s.query(qstr, vals...) + rows, err := s.Query(qstr, vals...) if err != nil { if err == sql.ErrNoRows { return metadata.NewColumnSet([]metadata.Column{}), nil @@ -274,7 +255,7 @@ FROM information_schema.sequences } qstr += ` ORDER BY table_catalog, table_schema, table_type, table_name` - rows, err := s.query(qstr, vals...) + rows, err := s.Query(qstr, vals...) if err != nil { if err == sql.ErrNoRows { return metadata.NewTableSet([]metadata.Table{}), nil @@ -320,7 +301,7 @@ FROM information_schema.schemata } qstr += ` ORDER BY catalog_name, schema_name` - rows, err := s.query(qstr, vals...) + rows, err := s.Query(qstr, vals...) if err != nil { if err == sql.ErrNoRows { return metadata.NewSchemaSet([]metadata.Schema{}), nil @@ -393,7 +374,7 @@ func (s InformationSchema) Functions(catalog, schemaPattern, namePattern string, } qstr += ` ORDER BY routine_catalog, routine_schema, routine_name, COALESCE(routine_type, '')` - rows, err := s.query(qstr, vals...) + rows, err := s.Query(qstr, vals...) if err != nil { if err == sql.ErrNoRows { return metadata.NewFunctionSet([]metadata.Function{}), nil @@ -469,7 +450,7 @@ func (s InformationSchema) FunctionColumns(catalog, schemaPattern, functionPatte } qstr += ` ORDER BY specific_catalog, specific_schema, specific_name, ordinal_position, COALESCE(parameter_name, '')` - rows, err := s.query(qstr, vals...) + rows, err := s.Query(qstr, vals...) if err != nil { if err == sql.ErrNoRows { return metadata.NewFunctionColumnSet([]metadata.FunctionColumn{}), nil @@ -549,7 +530,7 @@ GROUP BY table_catalog, index_schema, table_name, index_name, index_type ORDER BY table_catalog, index_schema, table_name, index_name ` - rows, err := s.query(qstr, vals...) + rows, err := s.Query(qstr, vals...) if err != nil { if err == sql.ErrNoRows { return metadata.NewIndexSet([]metadata.Index{}), nil @@ -618,7 +599,7 @@ JOIN information_schema.columns c ON } qstr += ` ORDER BY i.table_catalog, index_schema, table_name, index_name, seq_in_index` - rows, err := s.query(qstr, vals...) + rows, err := s.Query(qstr, vals...) if err != nil { if err == sql.ErrNoRows { return metadata.NewIndexColumnSet([]metadata.IndexColumn{}), nil @@ -679,7 +660,7 @@ FROM information_schema.sequences } qstr += ` ORDER BY sequence_catalog, sequence_schema, sequence_name` - rows, err := s.query(qstr, vals...) + rows, err := s.Query(qstr, vals...) if err != nil { if err == sql.ErrNoRows { return metadata.NewSequenceSet([]metadata.Sequence{}), nil @@ -702,14 +683,3 @@ ORDER BY sequence_catalog, sequence_schema, sequence_name` } return metadata.NewSequenceSet(results), nil } - -func (s InformationSchema) query(q string, v ...interface{}) (*sql.Rows, error) { - if s.logger != nil { - s.logger.Println(q) - s.logger.Println(v) - } - if s.dryRun { - return nil, sql.ErrNoRows - } - return s.db.Query(q, v...) -} diff --git a/drivers/metadata/metadata_test.go b/drivers/metadata/metadata_test.go index 866c8a76148..c26af96724a 100644 --- a/drivers/metadata/metadata_test.go +++ b/drivers/metadata/metadata_test.go @@ -59,7 +59,7 @@ var ( infos.FunctionColumnsColumnSize: "COALESCE(character_maximum_length, numeric_precision, datetime_precision, interval_precision, 0)", }), }, - WriterOpts: []metadata.Option{ + WriterOpts: []metadata.WriterOption{ metadata.WithSystemSchemas([]string{"pg_catalog", "pg_toast", "information_schema"}), }, }, @@ -86,7 +86,7 @@ var ( infos.FunctionColumnsNumericPrecRadix: "10", }), }, - WriterOpts: []metadata.Option{ + WriterOpts: []metadata.WriterOption{ metadata.WithSystemSchemas([]string{"mysql", "performance_schema", "information_schema"}), }, }, diff --git a/drivers/metadata/oracle/metadata.go b/drivers/metadata/oracle/metadata.go index 94598dc8366..501cd7f85f7 100644 --- a/drivers/metadata/oracle/metadata.go +++ b/drivers/metadata/oracle/metadata.go @@ -11,41 +11,18 @@ import ( ) type metaReader struct { - db drivers.DB - logger Logger - dryRun bool + metadata.LoggingReader } -type Logger interface { - Println(...interface{}) -} - -func NewReader(opts ...Option) func(db drivers.DB) metadata.Reader { - r := &metaReader{} - for _, o := range opts { - o(r) - } - return func(db drivers.DB) metadata.Reader { - r.db = db +func NewReader() func(drivers.DB, ...metadata.ReaderOption) metadata.Reader { + return func(db drivers.DB, opts ...metadata.ReaderOption) metadata.Reader { + r := &metaReader{ + LoggingReader: metadata.NewLoggingReader(db, opts...), + } return r } } -// Option to configure the reader -type Option func(*metaReader) - -func WithLogger(l Logger) Option { - return func(r *metaReader) { - r.logger = l - } -} - -func WithDryRun(d bool) Option { - return func(r *metaReader) { - r.dryRun = d - } -} - func (r metaReader) Catalogs() (*metadata.CatalogSet, error) { qstr := `SELECT UPPER(Value) AS catalog @@ -58,7 +35,7 @@ FROM dba_db_links ORDER BY catalog ` - rows, err := r.query(qstr) + rows, err := r.Query(qstr) if err != nil { if err == sql.ErrNoRows { return metadata.NewCatalogSet([]metadata.Catalog{}), nil @@ -98,7 +75,7 @@ FROM all_users } qstr += ` ORDER BY username` - rows, err := r.query(qstr, vals...) + rows, err := r.Query(qstr, vals...) if err != nil { if err == sql.ErrNoRows { return metadata.NewSchemaSet([]metadata.Schema{}), nil @@ -181,7 +158,7 @@ FROM all_synonyms s } qstr += ` ORDER BY table_schem, table_name, table_type` - rows, err := r.query(qstr, vals...) + rows, err := r.Query(qstr, vals...) if err != nil { if err == sql.ErrNoRows { return metadata.NewTableSet([]metadata.Table{}), nil @@ -239,7 +216,7 @@ FROM all_tab_columns c } qstr += ` ORDER BY c.owner, c.table_name, c.column_id` - rows, err := r.query(qstr, vals...) + rows, err := r.Query(qstr, vals...) if err != nil { if err == sql.ErrNoRows { return metadata.NewColumnSet([]metadata.Column{}), nil @@ -312,7 +289,7 @@ JOIN all_objects b ON b.object_id = a.object_id AND a.sequence = 1 } qstr += ` ORDER BY procedure_schem, procedure_name, procedure_type` - rows, err := r.query(qstr, vals...) + rows, err := r.Query(qstr, vals...) if err != nil { if err == sql.ErrNoRows { return metadata.NewFunctionSet([]metadata.Function{}), nil @@ -371,7 +348,7 @@ JOIN all_arguments a ON b.object_id = a.object_id AND a.data_level = 0 } qstr += ` ORDER BY procedure_schem, procedure_name, ordinal_position` - rows, err := r.query(qstr, vals...) + rows, err := r.Query(qstr, vals...) if err != nil { if err == sql.ErrNoRows { return metadata.NewFunctionColumnSet([]metadata.FunctionColumn{}), nil @@ -433,7 +410,7 @@ FROM all_indexes o qstr += ` ORDER BY o.owner, o.table_name, o.index_name` - rows, err := r.query(qstr, vals...) + rows, err := r.Query(qstr, vals...) if err != nil { if err == sql.ErrNoRows { return metadata.NewIndexSet([]metadata.Index{}), nil @@ -486,7 +463,7 @@ JOIN all_ind_columns b ON o.owner = b.index_owner AND o.index_name = b.index_nam } qstr += ` ORDER BY o.owner, o.table_name, o.index_name, b.column_position` - rows, err := r.query(qstr, vals...) + rows, err := r.Query(qstr, vals...) if err != nil { if err == sql.ErrNoRows { return metadata.NewIndexColumnSet([]metadata.IndexColumn{}), nil @@ -509,14 +486,3 @@ ORDER BY o.owner, o.table_name, o.index_name, b.column_position` } return metadata.NewIndexColumnSet(results), nil } - -func (r metaReader) query(q string, v ...interface{}) (*sql.Rows, error) { - if r.logger != nil { - r.logger.Println(q) - r.logger.Println(v) - } - if r.dryRun { - return nil, sql.ErrNoRows - } - return r.db.Query(q, v...) -} diff --git a/drivers/metadata/reader.go b/drivers/metadata/reader.go index 2bb27783964..1666319d9c2 100644 --- a/drivers/metadata/reader.go +++ b/drivers/metadata/reader.go @@ -1,5 +1,7 @@ package metadata +import "database/sql" + // PluginReader allows to be easily composed from other readers type PluginReader struct { catalogs func() (*CatalogSet, error) @@ -112,3 +114,64 @@ func (p PluginReader) Sequences(catalog, schemaPattern, namePattern string) (*Se } return p.sequences(catalog, schemaPattern, namePattern) } + +type LoggingReader struct { + db DB + logger logger + dryRun bool +} + +type logger interface { + Println(...interface{}) +} + +func NewLoggingReader(db DB, opts ...ReaderOption) LoggingReader { + r := LoggingReader{ + db: db, + } + for _, o := range opts { + o(&r) + } + return r +} + +// ReaderOption to configure the reader +type ReaderOption func(Reader) + +// WithLogger used to log queries before executing them +func WithLogger(l logger) ReaderOption { + return func(r Reader) { + r.(loggerSetter).setLogger(l) + } +} + +// WithDryRun allows to avoid running any queries +func WithDryRun(d bool) ReaderOption { + return func(r Reader) { + r.(loggerSetter).setDryRun(d) + } +} + +type loggerSetter interface { + setLogger(logger) + setDryRun(bool) +} + +func (r *LoggingReader) setLogger(l logger) { + r.logger = l +} + +func (r *LoggingReader) setDryRun(d bool) { + r.dryRun = d +} + +func (r LoggingReader) Query(q string, v ...interface{}) (*sql.Rows, error) { + if r.logger != nil { + r.logger.Println(q) + r.logger.Println(v) + } + if r.dryRun { + return nil, sql.ErrNoRows + } + return r.db.Query(q, v...) +} diff --git a/drivers/metadata/writer.go b/drivers/metadata/writer.go index 41b93aa0e14..781e23eb525 100644 --- a/drivers/metadata/writer.go +++ b/drivers/metadata/writer.go @@ -35,7 +35,7 @@ type DefaultWriter struct { var _ Writer = &DefaultWriter{} -func NewDefaultWriter(r Reader, opts ...Option) func(db DB, w io.Writer) Writer { +func NewDefaultWriter(r Reader, opts ...WriterOption) func(db DB, w io.Writer) Writer { defaultWriter := &DefaultWriter{ r: r, tableTypes: map[rune][]string{ @@ -65,11 +65,11 @@ func NewDefaultWriter(r Reader, opts ...Option) func(db DB, w io.Writer) Writer } } -// Option to configure the DefaultWriter -type Option func(*DefaultWriter) +// WriterOption to configure the DefaultWriter +type WriterOption func(*DefaultWriter) // WithSystemSchemas that are ignored unless showSystem is true -func WithSystemSchemas(schemas []string) Option { +func WithSystemSchemas(schemas []string) WriterOption { return func(w *DefaultWriter) { w.systemSchemas = make(map[string]struct{}, len(schemas)) for _, s := range schemas { @@ -79,7 +79,7 @@ func WithSystemSchemas(schemas []string) Option { } // WithListAllDbs that lists all catalogs -func WithListAllDbs(f func(string, bool) error) Option { +func WithListAllDbs(f func(string, bool) error) WriterOption { return func(w *DefaultWriter) { w.listAllDbs = f } diff --git a/drivers/mssql/mssql.go b/drivers/mssql/mssql.go index 0b726032c06..254b3b9ab04 100644 --- a/drivers/mssql/mssql.go +++ b/drivers/mssql/mssql.go @@ -15,7 +15,7 @@ import ( ) func init() { - newReader := func(db drivers.DB) metadata.Reader { + newReader := func(db drivers.DB, opts ...metadata.ReaderOption) metadata.Reader { ir := infos.New( infos.WithIndexes(false), infos.WithSequences(false), @@ -24,7 +24,7 @@ func init() { }), )(db) mr := &metaReader{ - db: db, + LoggingReader: metadata.NewLoggingReader(db, opts...), } return metadata.NewPluginReader(ir, mr) } @@ -60,12 +60,12 @@ func init() { return strings.Contains(err.Error(), "Login failed for") }, NewMetadataReader: newReader, - NewMetadataWriter: func(db drivers.DB, w io.Writer) metadata.Writer { - reader := newReader(db) - opts := []metadata.Option{ + NewMetadataWriter: func(db drivers.DB, w io.Writer, opts ...metadata.ReaderOption) metadata.Writer { + reader := newReader(db, opts...) + writerOpts := []metadata.WriterOption{ metadata.WithSystemSchemas([]string{"db_accessadmin", "db_backupoperator", "db_datareader", "db_datawriter", "db_ddladmin", "db_denydatareader", "db_denydatawriter", "db_owner", "db_securityadmin", "INFORMATION_SCHEMA", "sys"}), } - return metadata.NewDefaultWriter(reader, opts...)(db, w) + return metadata.NewDefaultWriter(reader, writerOpts...)(db, w) }, }) } diff --git a/drivers/mssql/reader.go b/drivers/mssql/reader.go index 934007573c1..233a90cd0b7 100644 --- a/drivers/mssql/reader.go +++ b/drivers/mssql/reader.go @@ -3,12 +3,11 @@ package mssql import ( "strings" - "github.com/xo/usql/drivers" "github.com/xo/usql/drivers/metadata" ) type metaReader struct { - db drivers.DB + metadata.LoggingReader } func (r metaReader) Catalogs() (*metadata.CatalogSet, error) { @@ -18,7 +17,7 @@ FROM sys.databases ORDER BY name ` - rows, err := r.db.Query(qstr) + rows, err := r.Query(qstr) if err != nil { return nil, err } @@ -73,7 +72,7 @@ JOIN sys.indexes i ON i.object_id = t.object_id qstr += ` ORDER BY s.name, t.name, i.name` - rows, err := r.db.Query(qstr, vals...) + rows, err := r.Query(qstr, vals...) if err != nil { return nil, err } @@ -130,7 +129,7 @@ JOIN sys.types ty ON ty.user_type_id = c.user_type_id } qstr += ` ORDER BY s.name, t.name, i.name, ic.index_column_id` - rows, err := r.db.Query(qstr, vals...) + rows, err := r.Query(qstr, vals...) if err != nil { return nil, err } diff --git a/drivers/mysql/mysql.go b/drivers/mysql/mysql.go index d9000e35ee3..403d2aaf1b7 100644 --- a/drivers/mysql/mysql.go +++ b/drivers/mysql/mysql.go @@ -5,26 +5,23 @@ package mysql import ( "io" - "log" - "os" "strconv" "github.com/go-sql-driver/mysql" // DRIVER: mysql "github.com/xo/usql/drivers" "github.com/xo/usql/drivers/metadata" infos "github.com/xo/usql/drivers/metadata/informationschema" - "github.com/xo/usql/env" ) func init() { - readerOpts := []infos.Option{ + newReader := infos.New( infos.WithPlaceholder(func(int) string { return "?" }), infos.WithSequences(false), infos.WithCustomColumns(map[infos.ColumnName]string{ infos.ColumnsNumericPrecRadix: "10", infos.FunctionColumnsNumericPrecRadix: "10", }), - } + ) drivers.Register("mysql", drivers.Driver{ AllowMultilineComments: true, AllowHashComments: true, @@ -46,23 +43,12 @@ func init() { } return false }, - NewMetadataReader: infos.New(readerOpts...), - NewMetadataWriter: func(db drivers.DB, w io.Writer) metadata.Writer { - opts := append([]infos.Option{}, readerOpts...) - // TODO if options would be common to all readers, this could be moved - // to the caller and passed in an argument - envs := env.All() - if envs["ECHO_HIDDEN"] == "on" || envs["ECHO_HIDDEN"] == "noexec" { - if envs["ECHO_HIDDEN"] == "noexec" { - opts = append(opts, infos.WithDryRun(true)) - } - opts = append(opts, infos.WithLogger(log.New(os.Stdout, "DEBUG: ", log.LstdFlags))) - } - reader := infos.New(opts...)(db) - writerOpts := []metadata.Option{ + NewMetadataReader: newReader, + NewMetadataWriter: func(db drivers.DB, w io.Writer, opts ...metadata.ReaderOption) metadata.Writer { + writerOpts := []metadata.WriterOption{ metadata.WithSystemSchemas([]string{"mysql", "information_schema", "performance_schema"}), } - return metadata.NewDefaultWriter(reader, writerOpts...)(db, w) + return metadata.NewDefaultWriter(newReader(db, opts...), writerOpts...)(db, w) }, }, "memsql", "vitess", "tidb") } diff --git a/drivers/oracle/oracle.go b/drivers/oracle/oracle.go index 03dcebbeb91..e2cef979b1d 100644 --- a/drivers/oracle/oracle.go +++ b/drivers/oracle/oracle.go @@ -7,8 +7,6 @@ import ( "database/sql" "fmt" "io" - "log" - "os" "regexp" "strings" @@ -110,22 +108,11 @@ func init() { return typ, sqlstr, q, nil }, NewMetadataReader: orameta.NewReader(), - NewMetadataWriter: func(db drivers.DB, w io.Writer) metadata.Writer { - // TODO if options would be common to all readers, this could be moved - // to the caller and passed in an argument - envs := env.All() - opts := []orameta.Option{} - if envs["ECHO_HIDDEN"] == "on" || envs["ECHO_HIDDEN"] == "noexec" { - if envs["ECHO_HIDDEN"] == "noexec" { - opts = append(opts, orameta.WithDryRun(true)) - } - opts = append(opts, orameta.WithLogger(log.New(os.Stdout, "DEBUG: ", log.LstdFlags))) - } - newReader := orameta.NewReader(opts...) - writerOpts := []metadata.Option{ + NewMetadataWriter: func(db drivers.DB, w io.Writer, opts ...metadata.ReaderOption) metadata.Writer { + writerOpts := []metadata.WriterOption{ metadata.WithSystemSchemas([]string{"ctxsys", "flows_files", "mdsys", "outln", "sys", "system", "xdb", "xs$null"}), } - return metadata.NewDefaultWriter(newReader(db), writerOpts...)(db, w) + return metadata.NewDefaultWriter(orameta.NewReader()(db, opts...), writerOpts...)(db, w) }, }) } diff --git a/drivers/postgres/postgres.go b/drivers/postgres/postgres.go index f309b002894..14ed2b6fb57 100644 --- a/drivers/postgres/postgres.go +++ b/drivers/postgres/postgres.go @@ -5,24 +5,29 @@ package postgres import ( "io" - "log" - "os" "github.com/lib/pq" // DRIVER: postgres "github.com/xo/dburl" "github.com/xo/usql/drivers" "github.com/xo/usql/drivers/metadata" infos "github.com/xo/usql/drivers/metadata/informationschema" - "github.com/xo/usql/env" ) func init() { - readerOpts := []infos.Option{ - infos.WithIndexes(false), - infos.WithCustomColumns(map[infos.ColumnName]string{ - infos.ColumnsColumnSize: "COALESCE(character_maximum_length, numeric_precision, datetime_precision, interval_precision, 0)", - infos.FunctionColumnsColumnSize: "COALESCE(character_maximum_length, numeric_precision, datetime_precision, interval_precision, 0)", - }), + newReader := func(db drivers.DB, opts ...metadata.ReaderOption) metadata.Reader { + newIS := infos.New( + infos.WithIndexes(false), + infos.WithCustomColumns(map[infos.ColumnName]string{ + infos.ColumnsColumnSize: "COALESCE(character_maximum_length, numeric_precision, datetime_precision, interval_precision, 0)", + infos.FunctionColumnsColumnSize: "COALESCE(character_maximum_length, numeric_precision, datetime_precision, interval_precision, 0)", + }), + ) + return metadata.NewPluginReader( + newIS(db, opts...), + &metaReader{ + LoggingReader: metadata.NewLoggingReader(db, opts...), + }, + ) } drivers.Register("postgres", drivers.Driver{ Name: "pq", @@ -60,32 +65,12 @@ func init() { } return false }, - NewMetadataReader: func(db drivers.DB) metadata.Reader { - return metadata.NewPluginReader( - infos.New(readerOpts...)(db), - &metaReader{db: db}, - ) - }, - NewMetadataWriter: func(db drivers.DB, w io.Writer) metadata.Writer { - opts := append([]infos.Option{}, readerOpts...) - // TODO if options would be common to all readers, this could be moved - // to the caller and passed in an argument - envs := env.All() - if envs["ECHO_HIDDEN"] == "on" || envs["ECHO_HIDDEN"] == "noexec" { - if envs["ECHO_HIDDEN"] == "noexec" { - opts = append(opts, infos.WithDryRun(true)) - } - opts = append(opts, infos.WithLogger(log.New(os.Stdout, "DEBUG: ", log.LstdFlags))) - } - reader := metadata.NewPluginReader( - infos.New(opts...)(db), - // TODO this reader doesn't get logger options applied - &metaReader{db: db}, - ) - writerOpts := []metadata.Option{ + NewMetadataReader: newReader, + NewMetadataWriter: func(db drivers.DB, w io.Writer, opts ...metadata.ReaderOption) metadata.Writer { + writerOpts := []metadata.WriterOption{ metadata.WithSystemSchemas([]string{"pg_catalog", "pg_toast", "information_schema"}), } - return metadata.NewDefaultWriter(reader, writerOpts...)(db, w) + return metadata.NewDefaultWriter(newReader(db, opts...), writerOpts...)(db, w) }, }, "cockroachdb", "redshift") } diff --git a/drivers/postgres/reader.go b/drivers/postgres/reader.go index 4dba18c10ca..6c0bd06e40b 100644 --- a/drivers/postgres/reader.go +++ b/drivers/postgres/reader.go @@ -4,12 +4,11 @@ import ( "fmt" "strings" - "github.com/xo/usql/drivers" "github.com/xo/usql/drivers/metadata" ) type metaReader struct { - db drivers.DB + metadata.LoggingReader } func (r metaReader) Catalogs() (*metadata.CatalogSet, error) { @@ -19,7 +18,7 @@ FROM pg_catalog.pg_database d ORDER BY 1; ` - rows, err := r.db.Query(qstr) + rows, err := r.Query(qstr) if err != nil { return nil, err } @@ -54,11 +53,12 @@ FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace LEFT JOIN pg_catalog.pg_index i ON i.indexrelid = c.oid LEFT JOIN pg_catalog.pg_class c2 ON i.indrelid = c2.oid -WHERE c.relkind IN ('i','I','') - AND n.nspname !~ '^pg_toast' - AND pg_catalog.pg_table_is_visible(c.oid) ` - conds := []string{} + conds := []string{"c.relkind IN ('i','I','')", + "n.nspname <> 'pg_catalog'", + "n.nspname <> 'information_schema'", + "n.nspname !~ '^pg_toast'", + "pg_catalog.pg_table_is_visible(c.oid)"} vals := []interface{}{} if schemaPattern != "" { vals = append(vals, schemaPattern) @@ -78,7 +78,7 @@ WHERE c.relkind IN ('i','I','') qstr += ` ORDER BY 1,2` - rows, err := r.db.Query(qstr, vals...) + rows, err := r.Query(qstr, vals...) if err != nil { return nil, err } diff --git a/drivers/sqlite3/reader.go b/drivers/sqlite3/reader.go index a1ce93475b9..0f98f41ff92 100644 --- a/drivers/sqlite3/reader.go +++ b/drivers/sqlite3/reader.go @@ -3,12 +3,11 @@ package sqlite3 import ( "strings" - "github.com/xo/usql/drivers" "github.com/xo/usql/drivers/metadata" ) type metaReader struct { - db drivers.DB + metadata.LoggingReader } // Columns from selected catalog (or all, if empty), matching schemas and tables @@ -27,7 +26,7 @@ func (r metaReader) Columns(catalog, schemaPattern, tablePattern string) (*metad CASE WHEN "notnull" = 1 THEN 'NO' ELSE 'YES' END, COALESCE(dflt_value, '') FROM pragma_table_info(?)` - rows, err := r.db.Query(qstr, table.Name) + rows, err := r.Query(qstr, table.Name) if err != nil { return nil, err } @@ -118,7 +117,7 @@ FROM ( } qstr += ` ORDER BY table_type, table_name` - rows, err := r.db.Query(qstr, vals...) + rows, err := r.Query(qstr, vals...) if err != nil { return nil, err } @@ -156,7 +155,7 @@ FROM pragma_database_list } qstr += ` ORDER BY seq` - rows, err := r.db.Query(qstr, vals...) + rows, err := r.Query(qstr, vals...) if err != nil { return nil, err } @@ -205,7 +204,7 @@ FROM pragma_function_list } qstr += ` ORDER BY name, type` - rows, err := r.db.Query(qstr, vals...) + rows, err := r.Query(qstr, vals...) if err != nil { return nil, err } @@ -259,7 +258,7 @@ JOIN pragma_index_list(m.name) i qstr += ` ORDER BY m.name, i.seq` - rows, err := r.db.Query(qstr, vals...) + rows, err := r.Query(qstr, vals...) if err != nil { return nil, err } @@ -306,7 +305,7 @@ JOIN pragma_index_xinfo(i.name) ic qstr += ` ORDER BY m.name, i.seq, ic.seqno` - rows, err := r.db.Query(qstr, vals...) + rows, err := r.Query(qstr, vals...) if err != nil { return nil, err } diff --git a/drivers/sqlite3/sqlite3.go b/drivers/sqlite3/sqlite3.go index 94e35129bdc..7d689c07d27 100644 --- a/drivers/sqlite3/sqlite3.go +++ b/drivers/sqlite3/sqlite3.go @@ -14,9 +14,9 @@ import ( ) func init() { - newReader := func(db drivers.DB) metadata.Reader { + newReader := func(db drivers.DB, opts ...metadata.ReaderOption) metadata.Reader { return &metaReader{ - db: db, + LoggingReader: metadata.NewLoggingReader(db, opts...), } } drivers.Register("sqlite3", drivers.Driver{ diff --git a/drivers/trino/trino.go b/drivers/trino/trino.go index 36d1e6600c9..51d8eb8357e 100644 --- a/drivers/trino/trino.go +++ b/drivers/trino/trino.go @@ -5,8 +5,6 @@ package trino import ( "io" - "log" - "os" "regexp" _ "github.com/trinodb/trino-go-client/trino" // DRIVER: trino @@ -19,7 +17,7 @@ import ( func init() { endRE := regexp.MustCompile(`;?\s*$`) - readerOpts := []infos.Option{ + newReader := infos.New( infos.WithPlaceholder(func(int) string { return "?" }), infos.WithCustomColumns(map[infos.ColumnName]string{ infos.ColumnsColumnSize: "0", @@ -30,7 +28,7 @@ func init() { infos.WithFunctions(false), infos.WithSequences(false), infos.WithIndexes(false), - } + ) drivers.Register("trino", drivers.Driver{ AllowMultilineComments: true, Process: func(prefix string, sqlstr string) (string, string, bool, error) { @@ -48,25 +46,14 @@ func init() { } return "Trino " + ver, nil }, - NewMetadataReader: infos.New(readerOpts...), - NewMetadataWriter: func(db drivers.DB, w io.Writer) metadata.Writer { - opts := append([]infos.Option{}, readerOpts...) - // TODO if options would be common to all readers, this could be moved - // to the caller and passed in an argument - envs := env.All() - if envs["ECHO_HIDDEN"] == "on" || envs["ECHO_HIDDEN"] == "noexec" { - if envs["ECHO_HIDDEN"] == "noexec" { - opts = append(opts, infos.WithDryRun(true)) - } - opts = append(opts, infos.WithLogger(log.New(os.Stdout, "DEBUG: ", log.LstdFlags))) - } - reader := infos.New(opts...)(db) - writerOpts := []metadata.Option{ + NewMetadataReader: newReader, + NewMetadataWriter: func(db drivers.DB, w io.Writer, opts ...metadata.ReaderOption) metadata.Writer { + writerOpts := []metadata.WriterOption{ metadata.WithListAllDbs(func(pattern string, verbose bool) error { return listAllDbs(db, w, pattern, verbose) }), } - return metadata.NewDefaultWriter(reader, writerOpts...)(db, w) + return metadata.NewDefaultWriter(newReader(db, opts...), writerOpts...)(db, w) }, }) } diff --git a/metacmd/cmds.go b/metacmd/cmds.go index ce365476310..7b5988d37cc 100644 --- a/metacmd/cmds.go +++ b/metacmd/cmds.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "io/ioutil" + "log" "os" "sort" "strconv" @@ -11,6 +12,7 @@ import ( "github.com/xo/dburl" "github.com/xo/usql/drivers" + "github.com/xo/usql/drivers/metadata" "github.com/xo/usql/env" "github.com/xo/usql/text" ) @@ -596,7 +598,16 @@ func init() { "l[+]": "list databases", }, Process: func(p *Params) error { - m, err := drivers.NewMetadataWriter(p.Handler.URL(), p.Handler.DB(), p.Handler.IO().Stdout()) + opts := []metadata.ReaderOption{} + envs := env.All() + if envs["ECHO_HIDDEN"] == "on" || envs["ECHO_HIDDEN"] == "noexec" { + if envs["ECHO_HIDDEN"] == "noexec" { + opts = append(opts, metadata.WithDryRun(true)) + } + opts = append(opts, metadata.WithLogger(log.New(os.Stdout, "DEBUG: ", log.LstdFlags))) + } + + m, err := drivers.NewMetadataWriter(p.Handler.URL(), p.Handler.DB(), p.Handler.IO().Stdout(), opts...) if err != nil { return err }