Skip to content

Commit ef6bdea

Browse files
authored
Merge pull request #11 from muroon/result-mode
Result Mode
2 parents f0c3a10 + 1778740 commit ef6bdea

File tree

13 files changed

+1128
-155
lines changed

13 files changed

+1128
-155
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,20 @@ If a method must be supplied to satisfy a standard library interface but is unsu
4242
the driver will **panic** indicating so. If there are new offerings in Athena and/or
4343
helpful additions, feel free to PR.
4444

45+
## Result Mode
46+
47+
go-athena has the following modes to get the result of the query.
48+
49+
- API (default)
50+
- DL
51+
- GZIP DL
52+
53+
Note
54+
55+
- DL and GZIP DL Mode are used only in the Select statement.
56+
- Other statements automatically use API mode under DL or GZIP DL Mode.
57+
- Detailed explanation is described [here](doc/result_mode.md).
58+
- [Usages of Result Mode](doc/result_mode.md#usages).
4559

4660
## Testing
4761

conn.go

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@ import (
44
"context"
55
"database/sql/driver"
66
"errors"
7+
"fmt"
78
"regexp"
9+
"strings"
810
"time"
911

12+
uuid "github.com/satori/go.uuid"
13+
1014
"github.com/aws/aws-sdk-go/aws"
15+
"github.com/aws/aws-sdk-go/aws/session"
1116
"github.com/aws/aws-sdk-go/service/athena"
1217
"github.com/aws/aws-sdk-go/service/athena/athenaiface"
1318
)
@@ -19,6 +24,11 @@ type conn struct {
1924
workgroup string
2025

2126
pollFrequency time.Duration
27+
28+
resultMode ResultMode
29+
session *session.Session
30+
timeout uint
31+
catalog string
2232
}
2333

2434
func (c *conn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
@@ -40,6 +50,38 @@ func (c *conn) ExecContext(ctx context.Context, query string, args []driver.Name
4050
}
4151

4252
func (c *conn) runQuery(ctx context.Context, query string) (driver.Rows, error) {
53+
// result mode
54+
isSelect := isSelectQuery(query)
55+
resultMode := c.resultMode
56+
if rmode, ok := getResultMode(ctx); ok {
57+
resultMode = rmode
58+
}
59+
if !isSelect {
60+
resultMode = ResultModeAPI
61+
}
62+
63+
// timeout
64+
timeout := c.timeout
65+
if to, ok := getTimeout(ctx); ok {
66+
timeout = to
67+
}
68+
69+
// catalog
70+
catalog := c.catalog
71+
if cat, ok := getCatalog(ctx); ok {
72+
catalog = cat
73+
}
74+
75+
// mode ctas
76+
var ctasTable string
77+
var afterDownload func() error
78+
if isSelect && resultMode == ResultModeGzipDL {
79+
// Create AS Select
80+
ctasTable = fmt.Sprintf("tmp_ctas_%v", strings.Replace(uuid.NewV4().String(), "-", "", -1))
81+
query = fmt.Sprintf("CREATE TABLE %s WITH (format='TEXTFILE') AS %s", ctasTable, query)
82+
afterDownload = c.dropCTASTable(ctx, ctasTable)
83+
}
84+
4385
queryID, err := c.startQuery(query)
4486
if err != nil {
4587
return nil, err
@@ -50,12 +92,33 @@ func (c *conn) runQuery(ctx context.Context, query string) (driver.Rows, error)
5092
}
5193

5294
return newRows(rowsConfig{
53-
Athena: c.athena,
54-
QueryID: queryID,
55-
SkipHeader: !isDDLQuery(query),
95+
Athena: c.athena,
96+
QueryID: queryID,
97+
SkipHeader: !isDDLQuery(query),
98+
ResultMode: resultMode,
99+
Session: c.session,
100+
OutputLocation: c.OutputLocation,
101+
Timeout: timeout,
102+
AfterDownload: afterDownload,
103+
CTASTable: ctasTable,
104+
DB: c.db,
105+
Catalog: catalog,
56106
})
57107
}
58108

109+
func (c *conn) dropCTASTable(ctx context.Context, table string) func() error {
110+
return func() error {
111+
query := fmt.Sprintf("DROP TABLE %s", table)
112+
113+
queryID, err := c.startQuery(query)
114+
if err != nil {
115+
return err
116+
}
117+
118+
return c.waitOnQuery(ctx, queryID)
119+
}
120+
}
121+
59122
// startQuery starts an Athena query and returns its ID.
60123
func (c *conn) startQuery(query string) (string, error) {
61124
resp, err := c.athena.StartQueryExecution(&athena.StartQueryExecutionInput{
@@ -146,3 +209,11 @@ var ddlQueryRegex = regexp.MustCompile(`(?i)^(ALTER|CREATE|DESCRIBE|DROP|MSCK|SH
146209
func isDDLQuery(query string) bool {
147210
return ddlQueryRegex.Match([]byte(query))
148211
}
212+
213+
func isSelectQuery(query string) bool {
214+
return regexp.MustCompile(`(?i)^SELECT`).Match([]byte(query))
215+
}
216+
217+
func isCTASQuery(query string) bool {
218+
return regexp.MustCompile(`(?i)^CREATE.+AS\s+SELECT`).Match([]byte(query))
219+
}

context.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package athena
2+
3+
import "context"
4+
5+
const contextPrefix string = "go-athena"
6+
7+
/*
8+
* Result Mode
9+
*/
10+
11+
const resultModeContextKey string = "result_mode_key"
12+
13+
// ResultModeContextKey context key of setting result mode
14+
var ResultModeContextKey string = contextPrefix + resultModeContextKey
15+
16+
// SetAPIMode set APIMode to ResultMode from context
17+
func SetAPIMode(ctx context.Context) context.Context {
18+
return context.WithValue(ctx, ResultModeContextKey, ResultModeAPI)
19+
}
20+
21+
// SetDLMode set DownloadMode to ResultMode from context
22+
func SetDLMode(ctx context.Context) context.Context {
23+
return context.WithValue(ctx, ResultModeContextKey, ResultModeDL)
24+
}
25+
26+
// SetGzipDLMode set CTASMode to ResultMode from context
27+
func SetGzipDLMode(ctx context.Context) context.Context {
28+
return context.WithValue(ctx, ResultModeContextKey, ResultModeGzipDL)
29+
}
30+
31+
func getResultMode(ctx context.Context) (ResultMode, bool) {
32+
val, ok := ctx.Value(ResultModeContextKey).(ResultMode)
33+
return val, ok
34+
}
35+
36+
/*
37+
* timeout
38+
*/
39+
40+
const timeoutContextKey string = "timeout_key"
41+
42+
// TimeoutContextKey context key of setting timeout
43+
var TimeoutContextKey string = contextPrefix + timeoutContextKey
44+
45+
// SetTimeout set timeout from context
46+
func SetTimeout(ctx context.Context, timeout uint) context.Context {
47+
return context.WithValue(ctx, TimeoutContextKey, timeout)
48+
}
49+
50+
func getTimeout(ctx context.Context) (uint, bool) {
51+
val, ok := ctx.Value(TimeoutContextKey).(uint)
52+
return val, ok
53+
}
54+
55+
/*
56+
* catalog
57+
*/
58+
59+
const catalogContextKey string = "catalog_key"
60+
61+
// CatalogContextKey context key of setting catalog
62+
var CatalogContextKey string = contextPrefix + catalogContextKey
63+
64+
// SetCatalog set catalog from context
65+
func SetTimout(ctx context.Context, catalog string) context.Context {
66+
return context.WithValue(ctx, CatalogContextKey, catalog)
67+
}
68+
69+
func getCatalog(ctx context.Context) (string, bool) {
70+
val, ok := ctx.Value(CatalogContextKey).(string)
71+
return val, ok
72+
}

0 commit comments

Comments
 (0)