sq/drivers/csv/csv.go

183 lines
4.4 KiB
Go
Raw Normal View History

2020-08-06 20:58:47 +03:00
// Package csv implements the sq driver for CSV/TSV et al.
package csv
import (
"context"
"database/sql"
"log/slog"
2020-08-06 20:58:47 +03:00
"github.com/neilotoole/sq/libsq/core/errz"
"github.com/neilotoole/sq/libsq/core/lg"
"github.com/neilotoole/sq/libsq/core/lg/lga"
"github.com/neilotoole/sq/libsq/core/lg/lgm"
2020-08-06 20:58:47 +03:00
"github.com/neilotoole/sq/libsq/driver"
"github.com/neilotoole/sq/libsq/source"
)
const (
// TypeCSV is the CSV driver type.
TypeCSV = source.DriverType("csv")
2020-08-06 20:58:47 +03:00
// TypeTSV is the TSV driver type.
TypeTSV = source.DriverType("tsv")
2020-08-06 20:58:47 +03:00
)
// Provider implements driver.Provider.
type Provider struct {
Log *slog.Logger
Scratcher driver.ScratchPoolOpener
2020-08-06 20:58:47 +03:00
Files *source.Files
}
// DriverFor implements driver.Provider.
func (d *Provider) DriverFor(typ source.DriverType) (driver.Driver, error) {
switch typ { //nolint:exhaustive
2020-08-06 20:58:47 +03:00
case TypeCSV:
return &driveri{log: d.Log, typ: TypeCSV, scratcher: d.Scratcher, files: d.Files}, nil
2020-08-06 20:58:47 +03:00
case TypeTSV:
return &driveri{log: d.Log, typ: TypeTSV, scratcher: d.Scratcher, files: d.Files}, nil
2020-08-06 20:58:47 +03:00
}
return nil, errz.Errorf("unsupported driver type {%s}", typ)
2020-08-06 20:58:47 +03:00
}
// Driver implements driver.Driver.
type driveri struct {
log *slog.Logger
typ source.DriverType
scratcher driver.ScratchPoolOpener
2020-08-06 20:58:47 +03:00
files *source.Files
}
// DriverMetadata implements driver.Driver.
func (d *driveri) DriverMetadata() driver.Metadata {
2020-08-06 20:58:47 +03:00
md := driver.Metadata{Type: d.typ, Monotable: true}
if d.typ == TypeCSV {
md.Description = "Comma-Separated Values"
md.Doc = "https://en.wikipedia.org/wiki/Comma-separated_values"
} else {
md.Description = "Tab-Separated Values"
md.Doc = "https://en.wikipedia.org/wiki/Tab-separated_values"
}
return md
}
// Open implements driver.PoolOpener.
func (d *driveri) Open(ctx context.Context, src *source.Source) (driver.Pool, error) {
lg.FromContext(ctx).Debug(lgm.OpenSrc, lga.Src, src)
pool := &pool{
log: d.log,
src: src,
files: d.files,
2020-08-06 20:58:47 +03:00
}
var err error
pool.impl, err = d.scratcher.OpenScratch(ctx, src.Handle)
2020-08-06 20:58:47 +03:00
if err != nil {
return nil, err
}
if err = ingestCSV(ctx, src, d.files.OpenFunc(src), pool.impl); err != nil {
2020-08-06 20:58:47 +03:00
return nil, err
}
return pool, nil
2020-08-06 20:58:47 +03:00
}
// Truncate implements driver.Driver.
2023-04-01 11:38:32 +03:00
func (d *driveri) Truncate(_ context.Context, _ *source.Source, _ string, _ bool) (int64, error) {
2020-08-06 20:58:47 +03:00
return 0, errz.Errorf("truncate not supported for %s", d.DriverMetadata().Type)
}
// ValidateSource implements driver.Driver.
func (d *driveri) ValidateSource(src *source.Source) (*source.Source, error) {
2020-08-06 20:58:47 +03:00
if src.Type != d.typ {
return nil, errz.Errorf("expected driver type {%s} but got {%s}", d.typ, src.Type)
2020-08-06 20:58:47 +03:00
}
return src, nil
}
// Ping implements driver.Driver.
2023-04-01 11:38:32 +03:00
func (d *driveri) Ping(_ context.Context, src *source.Source) error {
r, err := d.files.Open(src)
2020-08-06 20:58:47 +03:00
if err != nil {
return err
}
defer lg.WarnIfCloseError(d.log, lgm.CloseFileReader, r)
2020-08-06 20:58:47 +03:00
return nil
}
// pool implements driver.Pool.
type pool struct {
log *slog.Logger
src *source.Source
impl driver.Pool
files *source.Files
}
// DB implements driver.Pool.
func (p *pool) DB(ctx context.Context) (*sql.DB, error) {
return p.impl.DB(ctx)
}
// SQLDriver implements driver.Pool.
func (p *pool) SQLDriver() driver.SQLDriver {
return p.impl.SQLDriver()
}
// Source implements driver.Pool.
func (p *pool) Source() *source.Source {
return p.src
}
// TableMetadata implements driver.Pool.
func (p *pool) TableMetadata(ctx context.Context, tblName string) (*source.TableMetadata, error) {
if tblName != source.MonotableName {
return nil, errz.Errorf("table name should be %s for CSV/TSV etc., but got: %s",
source.MonotableName, tblName)
}
srcMeta, err := p.SourceMetadata(ctx, false)
2020-08-06 20:58:47 +03:00
if err != nil {
return nil, err
2020-08-06 20:58:47 +03:00
}
// There will only ever be one table for CSV.
return srcMeta.Tables[0], nil
}
// SourceMetadata implements driver.Pool.
func (p *pool) SourceMetadata(ctx context.Context, noSchema bool) (*source.Metadata, error) {
md, err := p.impl.SourceMetadata(ctx, noSchema)
if err != nil {
return nil, err
}
md.Handle = p.src.Handle
md.Location = p.src.Location
md.Driver = p.src.Type
md.Name, err = source.LocationFileName(p.src)
if err != nil {
return nil, err
}
md.Size, err = p.files.Size(p.src)
if err != nil {
return nil, err
}
md.FQName = md.Name
return md, nil
}
// Close implements driver.Pool.
func (p *pool) Close() error {
p.log.Debug(lgm.CloseDB, lga.Handle, p.src.Handle)
return errz.Err(p.impl.Close())
}