diff --git a/driver.go b/driver.go new file mode 100644 index 0000000..5c04887 --- /dev/null +++ b/driver.go @@ -0,0 +1,39 @@ +// Package airtablesql implements a Go SQL driver for Airtable +// Import package github.com/sclgo/airtable-sql/register to register +// a driver with default configuration automatically or use CreateDriver from this +// package and register it explicitly with sql.Register. +package airtablesql // import github.com/sclgo/airtable-sql + +import ( + "net/url" + + drivere "github.com/dolthub/go-mysql-server/driver" + "github.com/dolthub/go-mysql-server/sql" + "github.com/mehanizm/airtable" + "github.com/sclgo/airtable-sql/internal/errhelp" + "github.com/sclgo/airtable-sql/provider" + + "database/sql/driver" +) + +func CreateDriver() interface { + driver.Driver + driver.DriverContext +} { + return drivere.New(factory{}, nil) +} + +type factory struct{} + +// Resolve implements driver.Provider +func (factory) Resolve(dsn string, _ *drivere.Options) (string, sql.DatabaseProvider, error) { + dsnUri, err := url.Parse(dsn) + if err != nil { + return "", nil, errhelp.Errorf("could not parse DSN %s: %w", dsn, err) + } + key := dsnUri.Query().Get("key") + if key == "" { + return "", nil, errhelp.Errorf("could not find key in DSN %s", dsn) + } + return dsn, provider.New(airtable.NewClient(key)), nil +} diff --git a/driver_integ_test.go b/driver_integ_test.go new file mode 100644 index 0000000..aa0b0a9 --- /dev/null +++ b/driver_integ_test.go @@ -0,0 +1,26 @@ +package airtablesql_test + +import ( + "database/sql" + "os" + "testing" + + airtablesql "github.com/sclgo/airtable-sql" + "github.com/stretchr/testify/require" +) + +func TestCreateDriver(t *testing.T) { + t.Run("happy", func(t *testing.T) { + if os.Getenv("AIRTABLE_API_KEY") == "" { + t.Skip("skipping integration test; AIRTABLE_API_KEY not set") + } + drv := airtablesql.CreateDriver() + cnct, err := drv.OpenConnector("airtable:?key=" + os.Getenv("AIRTABLE_API_KEY")) + require.NoError(t, err) + db := sql.OpenDB(cnct) + row := db.QueryRow("SELECT `Part Name` FROM `Order Assembly`.Parts") + var part string + require.NoError(t, row.Scan(&part)) + require.Equal(t, "Power Supply Unit", part) + }) +} diff --git a/provider/airdb.go b/provider/airdb.go index 7106989..8a823ec 100644 --- a/provider/airdb.go +++ b/provider/airdb.go @@ -9,8 +9,9 @@ import ( "github.com/mehanizm/airtable" ) -var _ sql.Database = (*AirDB)(nil) -var _ sql.Table = (*AirSqlTable)(nil) +var ( + _ sql.Database = (*AirDB)(nil) +) type AirDB struct { client *airtable.Client @@ -31,7 +32,7 @@ func (a *AirDB) GetTableInsensitive(ctx *sql.Context, tblName string) (sql.Table for _, tbl := range tbls.Tables { if strings.EqualFold(tblName, tbl.Name) { return AirSqlTable{ - tableClient: a.client.GetTable(a.dbId, tbl.Name), + tableClient: a.client.GetTable(a.dbId, tbl.Name), airSchema: tbl, schema: toSqlSchema(tbl), }, true, nil diff --git a/provider/airsqltable.go b/provider/airsqltable.go index 749191e..50a85d4 100644 --- a/provider/airsqltable.go +++ b/provider/airsqltable.go @@ -9,6 +9,12 @@ import ( "github.com/mehanizm/airtable" ) +var ( + _ sql.RowIter = (*basicRowIter)(nil) + _ sql.Disposable = (*basicRowIter)(nil) + _ sql.Table = (*AirSqlTable)(nil) +) + type AirSqlTable struct { tableClient *airtable.Table airSchema *airtable.TableSchema @@ -100,8 +106,3 @@ func (b *basicRowIter) Close(*sql.Context) error { b.recs = nil return nil } - -var ( - _ sql.RowIter = (*basicRowIter)(nil) - _ sql.Disposable = (*basicRowIter)(nil) -) diff --git a/register/register.go b/register/register.go new file mode 100644 index 0000000..4bab430 --- /dev/null +++ b/register/register.go @@ -0,0 +1,11 @@ +package register + +import ( + "database/sql" + + airtablesql "github.com/sclgo/airtable-sql" +) + +func init() { + sql.Register("airtable", airtablesql.CreateDriver()) +}