athenadriver

repository·master·Indexed 41 days ago

https://github.com/uber/athenadriver

A specialized Go database driver for AWS Athena that integrates with the standard database/sql library. It extends the standard AWS Athena Go SDK with features such as cost monitoring (Moneywise mode), workgroup and tagging management, driver-level read-only mode, and improved resilience. The package includes the athenareader CLI tool for querying Athena data with support for multiple output formats and rendering styles.

Tokens
7.9K
Snippets
25
Records
44
Agent score
100%

What's inside athenadriver

  1. Overview of athenadriver

    master
    athenadriver is a fully-featured AWS Athena database driver for Go. It is designed to work with the Go standard library database/sql package, providing a hassle-free way to query AWS Athena. It extends the standard AWS Athena Go SDK by addressing limitations and adding advanced features like workgroup/tagging management, driver-level read-only mode, and cost tracking (Moneywise mode).
  2. How `athenadriver` handles Athena Go SDK limitations

    master

    The standard Athena Go SDK has several limitations regarding metadata and type information that athenadriver resolves:

    1. Column Mismatch in Metadata: For statements like DESCRIBE TABLE or SHOW SCHEMA, the Go SDK often fails to split tab-separated values in Rows[0].Data[0], leading to a mismatch between ColumnInfo and actual row data. athenadriver fixes this by splitting the string by tabs to ensure the number of data items matches the column count.

    2. Empty Rows for DDL/DML: For CTAS, CVAS, and INSERT INTO statements, the Go SDK may return an empty Rows set even when metadata exists. athenadriver maps the returned row value to UpdateCount to provide meaningful feedback.

    3. Type Information Loss: The Go SDK loses metadata for complex types (e.g., a map<string, boolean> only returns the type name map). athenadriver handles this by returning the string representation for complex types, allowing developers to manually deserialize them.

  3. Perform cross-database joins in Athena

    master

    Athena supports joining tables across different databases/schemas within the same account using standard SQL syntax. You can reference tables using the database.table notation.

    Example Join:

    SELECT a.column_name, b.column_name 
    FROM database_one.table_a a 
    LEFT JOIN database_two.table_b b ON a.id = b.id;
    -- Join tables from different databases
    SELECT a.elb_name, a.url 
    FROM default.elb_logs_new2 a 
    LEFT JOIN sampledb.elb_logs_new1 b ON a.request_timestamp = b.request_timestamp;
  4. Supported Athena Data Types in athenadriver

    master

    Beyond the standard types listed in official AWS documentation, athenadriver supports several additional types.

    When querying these types:

    • json, varbinary, and row are returned as their string representation.
    • interval year to month, interval day to second, time, time with time zone, and timestamp with time zone are returned as Go time.Time objects.

    This allows you to work with complex Athena types directly using standard Go types or string parsing.

    // Example of querying various types
    query := "SELECT JSON '\"Hello Athena\"', " +
    	"ST_POINT(-74.006801, 40.70522), " +
    	"ROW(1, 2.0),  INTERVAL '2' DAY, " +
    	"INTERVAL '3' MONTH, " +
    	"TIME '01:02:03.456', " +
    	"TIME '01:02:03.456 America/Los_Angeles', " +
    	"TIMESTAMP '2001-08-22 03:04:05.321 America/Los_Angeles';"
    
    // Results for json, varbinary, and row will be strings.
    // Results for time/timestamp types will be time.Time.
  5. Accessing Rows and Row Counts

    master

    Because athenadriver follows the Go database/sql design for streaming large datasets, you cannot perform random access on rows.

    • To get the total row count: You must iterate through all rows using rows.Next() and maintain a manual counter.
    • Random Access: There is no way to randomly access a row via the driver; you must traverse the result set sequentially.
  6. Limitations: Cross-account joins in Athena

    master

    Direct cross-account joins (e.g., account_1.db.table) are not supported because SQL cannot differentiate between tables with the same name in different accounts.

    Recommended Workaround: Create a metadata copy (database or table) in the local account that points to the S3 data in the remote account. If the S3 bucket owner is different from the account owner, you must configure Cross-account Access permissions.

  7. Database Reconnection and Batching in athenadriver

    master

    Reconnection

    athenadriver supports database reconnection. Because it implements the standard Go database/sql interface, the internal connection pool handles reconnection and retry logic automatically.

    Batching

    athenadriver does not support batched queries. It is a standard sql.driver implementation. To perform bulk inserts, you should use db.Exec with a single INSERT statement containing multiple VALUES clauses instead of multiple individual INSERT calls.

  8. Query with Athena Workgroups and Tags

    master

    You can specify an Athena workgroup and attach resource/cost tags to your queries. By default, if the specified workgroup does not exist, athenadriver will attempt to create it programmatically. To prevent this behavior and ensure the query fails if the workgroup is missing, use Config.SetWGRemoteCreationAllowed(false).

    Use drv.NewWGTags() to create a tag set and drv.NewWG(workgroupName, nil, wgTags) to create the workgroup object.

    wgTags := drv.NewWGTags()
    wgTags.AddTag("Uber User", "henry.wu")
    // Specify that workgroup `henry_wu` is used
    wg := drv.NewWG("henry_wu", nil, wgTags)
    conf.SetWorkGroup(wg)
    
    // Disable automatic creation of the workgroup if it doesn't exist
    conf.SetWGRemoteCreationAllowed(false)
  9. Get the number of rows affected by a query

    master

    To determine how many rows were affected by a query (such as CTAS, CVAS, or INSERT INTO), you have two primary methods:

    1. Recommended: Use DB.Exec().
    2. Alternative: Use DB.Query(). The returned ResultSet contains an UpdateCount member variable. For CTAS, CVAS, and INSERT INTO statements, the result will be a single row and single column where the column name is rows and the value is an int representing the UpdateCount.
    // Using QueryRow or QueryRowContext is suggested for single-row results
    // to retrieve the UpdateCount from a CTAS/INSERT INTO query.
    count, err := DB.QueryRowContext(ctx, "INSERT INTO ...").Scan(&rowsAffected)
  10. Configure AWS Authentication Methods

    master

    The driver supports three primary ways to handle AWS authentication:

    1. AWS CLI Config: Set the environment variable AWS_SDK_LOAD_CONFIG=1. The driver will then prioritize credentials from ~/.aws/credentials and region from ~/.aws/config. You must still provide a valid OutputBucket in the athenadriver.Config.
    2. Explicit Driver Config: When AWS_SDK_LOAD_CONFIG is NOT set, pass valid accessID, secretAccessKey, region, and outputBucket directly into drv.NewDefaultConfig().
    3. AWS SDK Default Resolution: If AWS_SDK_LOAD_CONFIG is NOT set and credentials are omitted from the config, the driver falls back to the standard AWS SDK default credential provider chain. You must still explicitly define Region and OutputBucket using conf.SetRegion() and conf.SetOutputBucket().
  11. Get Started with a Simple Athena Query

    master

    To use athenadriver, import the package and use the standard Go database/sql interface. You must first create a configuration using NewDefaultConfig specifying the S3 output bucket, AWS region, and credentials, then open a connection using sql.Open with drv.DriverName and the stringified configuration.

    package main
    
    import (
    	"database/sql"
    	drv "github.com/uber/athenadriver/go"
    )
    
    func main() {
    	// Step 1. Set AWS Credential in Driver Config.
    	conf, _ := drv.NewDefaultConfig("s3://myqueryresults/",
    		"us-east-2", "DummyAccessID", "DummySecretAccessKey")
    	// Step 2. Open Connection.
    	db, _ := sql.Open(drv.DriverName, conf.Stringify())
    	// Step 3. Query and print results
    	var url string
    	_ = db.QueryRow("SELECT url from sampledb.elb_logs limit 1").Scan(&url)
    	println(url)
    }