datus-athena 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. datus_athena-0.1.0/.gitignore +1 -0
  2. datus_athena-0.1.0/AWS_TESTING.md +290 -0
  3. datus_athena-0.1.0/PKG-INFO +266 -0
  4. datus_athena-0.1.0/README.md +246 -0
  5. datus_athena-0.1.0/datus_athena/__init__.py +34 -0
  6. datus_athena-0.1.0/datus_athena/config.py +92 -0
  7. datus_athena-0.1.0/datus_athena/connector.py +607 -0
  8. datus_athena-0.1.0/datus_athena/handlers.py +97 -0
  9. datus_athena-0.1.0/datus_athena/models.py +15 -0
  10. datus_athena-0.1.0/datus_athena/results.py +63 -0
  11. datus_athena-0.1.0/datus_athena/skills/db-athena-sql/SKILL.md +105 -0
  12. datus_athena-0.1.0/datus_athena/skills/db-athena-sql/references/table-design-and-writes.md +65 -0
  13. datus_athena-0.1.0/datus_athena/skills.py +27 -0
  14. datus_athena-0.1.0/datus_athena/tpch_data.py +52 -0
  15. datus_athena-0.1.0/pyproject.toml +62 -0
  16. datus_athena-0.1.0/scripts/aws-test-environment.yaml +81 -0
  17. datus_athena-0.1.0/scripts/init_tpch_data.py +50 -0
  18. datus_athena-0.1.0/tests/__init__.py +0 -0
  19. datus_athena-0.1.0/tests/conftest.py +1 -0
  20. datus_athena-0.1.0/tests/integration/__init__.py +0 -0
  21. datus_athena-0.1.0/tests/integration/conftest.py +116 -0
  22. datus_athena-0.1.0/tests/integration/http_faults.py +168 -0
  23. datus_athena-0.1.0/tests/integration/test_aws_authentication.py +86 -0
  24. datus_athena-0.1.0/tests/integration/test_connection.py +35 -0
  25. datus_athena-0.1.0/tests/integration/test_contract.py +113 -0
  26. datus_athena-0.1.0/tests/integration/test_metadata_retrieval.py +45 -0
  27. datus_athena-0.1.0/tests/integration/test_resilience.py +102 -0
  28. datus_athena-0.1.0/tests/integration/test_sql_execution.py +53 -0
  29. datus_athena-0.1.0/tests/integration/test_tpch.py +35 -0
  30. datus_athena-0.1.0/tests/unit/__init__.py +0 -0
  31. datus_athena-0.1.0/tests/unit/conftest.py +84 -0
  32. datus_athena-0.1.0/tests/unit/test_config.py +90 -0
  33. datus_athena-0.1.0/tests/unit/test_connector_unit.py +527 -0
  34. datus_athena-0.1.0/tests/unit/test_handlers.py +113 -0
  35. datus_athena-0.1.0/tests/unit/test_metadata.py +367 -0
  36. datus_athena-0.1.0/tests/unit/test_migration_mixin.py +11 -0
  37. datus_athena-0.1.0/tests/unit/test_registration.py +38 -0
  38. datus_athena-0.1.0/tests/unit/test_skills.py +59 -0
  39. datus_athena-0.1.0/tests/unit/test_tpch_data.py +21 -0
  40. datus_athena-0.1.0/tests/unit/test_writable_contract.py +133 -0
@@ -0,0 +1 @@
1
+ .aws-test/
@@ -0,0 +1,290 @@
1
+ # Minimal AWS test environment
2
+
3
+ Use the local AWS CLI in `us-east-1`. The CloudFormation template creates only
4
+ one S3 bucket, one Glue database and one Athena SQL engine v3 Workgroup. SQL uses
5
+ on-demand scanned-data billing. The scan cutoff is 10 MiB per query. There is no
6
+ capacity reservation, Spark session, Glue ETL job, crawler, customer KMS key or
7
+ database server. S3 requests/storage and Glue catalog usage are billed separately.
8
+ See [Athena pricing](https://aws.amazon.com/athena/pricing/) and
9
+ [scan controls](https://docs.aws.amazon.com/athena/latest/ug/workgroups-setting-control-limits-cloudwatch.html).
10
+
11
+ The private bucket uses SSE-S3 and blocks public access. Query results expire
12
+ after one day; fixture data expires after seven days. Lifecycle expiration does
13
+ not delete the stack or Glue definitions. Recreate fixtures under a fresh empty
14
+ prefix after expiration. There is no automatic account-wide spending cutoff.
15
+
16
+ ## Create
17
+
18
+ Run from `datus-athena/` after installing this checkout's packages. The existing
19
+ repository-root virtual environment is referenced as `../.venv`; substitute the
20
+ package-local `.venv` if following the README's installation commands.
21
+
22
+ ```bash
23
+ export AWS_PROFILE=default
24
+ export AWS_REGION=us-east-1
25
+ export AWS_DEFAULT_REGION=us-east-1
26
+ aws sts get-caller-identity
27
+
28
+ export ATHENA_TEST_RUN_ID="$(date -u +%Y%m%d-%H%M%S)-$(../.venv/bin/python -c 'import secrets; print(secrets.token_hex(2))')"
29
+ export ATHENA_TEST_STACK="datus-athena-test-$ATHENA_TEST_RUN_ID"
30
+ export ATHENA_DATABASE="datus_athena_${ATHENA_TEST_RUN_ID//-/_}"
31
+
32
+ aws cloudformation create-stack \
33
+ --stack-name "$ATHENA_TEST_STACK" \
34
+ --template-body file://scripts/aws-test-environment.yaml \
35
+ --parameters "ParameterKey=RunId,ParameterValue=$ATHENA_TEST_RUN_ID" \
36
+ "ParameterKey=DatabaseName,ParameterValue=$ATHENA_DATABASE" \
37
+ --tags Key=Purpose,Value=datus-athena-acceptance "Key=RunId,Value=$ATHENA_TEST_RUN_ID" \
38
+ --timeout-in-minutes 10 --on-failure DELETE
39
+ aws cloudformation wait stack-create-complete --stack-name "$ATHENA_TEST_STACK"
40
+
41
+ export ATHENA_TEST_BUCKET="$(aws cloudformation describe-stacks \
42
+ --stack-name "$ATHENA_TEST_STACK" \
43
+ --query "Stacks[0].Outputs[?OutputKey=='BucketName'].OutputValue | [0]" --output text)"
44
+ export ATHENA_WORKGROUP="datus-athena-test-$ATHENA_TEST_RUN_ID"
45
+ export ATHENA_CATALOG=AwsDataCatalog
46
+ export ATHENA_DATA_LOCATION="s3://$ATHENA_TEST_BUCKET/fixtures/"
47
+ export ATHENA_EXPECT_MANAGED_RESULTS=false
48
+ unset ATHENA_OUTPUT_LOCATION
49
+
50
+ ../.venv/bin/python scripts/init_tpch_data.py
51
+ ../.venv/bin/python -m pytest tests/integration -m integration -q
52
+ ```
53
+
54
+ The Workgroup initially allows CTAS `external_location`; the fixture initializer
55
+ needs that setting and an empty S3 prefix. Do not initialize under an enforced
56
+ result location ([AWS CTAS restrictions](https://docs.aws.amazon.com/athena/latest/ug/create-table-as.html)).
57
+ The initializer creates the five tiny shared TPC-H tables and one view, without
58
+ copying production data. No credentials are written to repository files.
59
+
60
+ ## Reuse the same Workgroup for result-mode acceptance
61
+
62
+ Run these steps sequentially, with no other test run using the Workgroup.
63
+ Enforce its S3 result location after fixture initialization:
64
+
65
+ ```bash
66
+ aws athena update-work-group --work-group "$ATHENA_WORKGROUP" \
67
+ --configuration-updates '{"EnforceWorkGroupConfiguration":true}'
68
+ ATHENA_EXPECT_MANAGED_RESULTS=false \
69
+ ../.venv/bin/python -m pytest tests/integration -m integration -q
70
+ ```
71
+
72
+ Switch to managed results, using the default AWS owned key:
73
+
74
+ ```bash
75
+ aws athena update-work-group --work-group "$ATHENA_WORKGROUP" \
76
+ --configuration-updates '{"EnforceWorkGroupConfiguration":true,"ResultConfigurationUpdates":{"RemoveOutputLocation":true,"RemoveEncryptionConfiguration":true},"ManagedQueryResultsConfigurationUpdates":{"Enabled":true}}'
77
+ ATHENA_EXPECT_MANAGED_RESULTS=true \
78
+ ../.venv/bin/python -m pytest tests/integration -m integration -q
79
+ ```
80
+
81
+ Restore the template's S3 configuration after acceptance. This keeps the same
82
+ minimal resources and permits future fixture initialization:
83
+
84
+ ```bash
85
+ aws athena update-work-group --work-group "$ATHENA_WORKGROUP" \
86
+ --configuration-updates "{\"EnforceWorkGroupConfiguration\":false,\"ManagedQueryResultsConfigurationUpdates\":{\"Enabled\":false},\"ResultConfigurationUpdates\":{\"OutputLocation\":\"s3://$ATHENA_TEST_BUCKET/query-results/\",\"EncryptionConfiguration\":{\"EncryptionOption\":\"SSE_S3\"}}}"
87
+ export ATHENA_EXPECT_MANAGED_RESULTS=false
88
+ ```
89
+
90
+ Managed query results are available for 24 hours. Source fixture data still lives
91
+ in the test bucket. See [managed results](https://docs.aws.amazon.com/athena/latest/ug/managed-results.html).
92
+ Optional denied-identity and KMS/Lake Formation cases require their separately
93
+ documented environment variables; this minimal stack does not create IAM roles,
94
+ KMS keys or Lake Formation grants. Their skips do not mean those behaviors passed.
95
+
96
+ To include creation, insertion, metadata, typed retrieval and cleanup through
97
+ the shared `TableContractCase`, set
98
+ `ATHENA_WRITABLE_DATA_LOCATION="s3://$ATHENA_TEST_BUCKET/contracts/"`.
99
+ Grant scoped Glue table creation/deletion and S3 list/read/write/delete access.
100
+ The test creates one UUID-named external Parquet table and removes both its
101
+ definition and its private child prefix, including files written before a
102
+ failed assertion. Missing this optional variable skips only the writable case;
103
+ service or cleanup errors after configuration fail the test. It uses explicit
104
+ CREATE EXTERNAL TABLE and INSERT rather than CTAS, so it does not require
105
+ disabling Workgroup-enforced result settings.
106
+
107
+ For extended acceptance, provide isolated test resources and SDK profiles. Do not
108
+ change account-wide Lake Formation defaults or apply policies to existing users:
109
+
110
+ - `ATHENA_DENIED_PROFILE`: a valid AssumeRole profile with discovery permissions
111
+ and an explicit `athena:StartQueryExecution` deny on the test Workgroup.
112
+ - `ATHENA_ROLE_PROFILE`: an allowed reader profile with `duration_seconds=900`.
113
+ Set `ATHENA_RUN_ROLE_EXPIRY_TEST=1` for the 15-minute real expiry case. The test
114
+ keeps its client/Session, proves the old token is rejected by STS, and verifies
115
+ that the provider renews credentials for a successful subsequent query.
116
+ - `ATHENA_RESTRICTED_PROFILE`: the same query/data scope without the protected
117
+ fixtures' KMS decrypt and Lake Formation SELECT grants.
118
+ - `ATHENA_KMS_SQL` and `ATHENA_LAKEFORMATION_SQL`: each must select `r_regionkey`
119
+ from a dedicated copy of `tpch_region`, ordered and limited to row 0. The allowed
120
+ role must return `[{"r_regionkey": 0}]`; the restricted role must be rejected.
121
+ Use a customer KMS key only for the KMS fixture. Register only the LF fixture
122
+ prefix; revoke IAMAllowedPrincipals on that table, grant SELECT to the allowed
123
+ role and only DESCRIBE to the restricted role. LF can hide inaccessible columns
124
+ and report a column authorization error rather than naming Lake Formation.
125
+ - `ATHENA_EXPECT_FAILURE_SQL`: an additional denied-data query. Set
126
+ `ATHENA_FAILURE_PROFILE` when it needs a different profile from the main suite.
127
+ - `ATHENA_RUN_NETWORK_FAULT_TESTS=1`: requires OpenSSL. A loopback proxy generates
128
+ a temporary trusted certificate for the Athena hostname, forwards signed HTTPS
129
+ requests to real AWS, and injects one HTTP throttle, a lost submission response,
130
+ or socket read timeouts. Upstream TLS verification stays enabled. These are
131
+ controlled transport faults; they do not induce a real AWS service quota breach.
132
+
133
+ Run short cases separately from the real-expiry case, using a private
134
+ `AWS_CONFIG_FILE` rather than editing the workstation's shared config:
135
+
136
+ ```bash
137
+ ATHENA_RUN_NETWORK_FAULT_TESTS=1 ../.venv/bin/python -m pytest tests/integration \
138
+ -m integration -k 'not real_sts_expiry' -q
139
+ ATHENA_RUN_ROLE_EXPIRY_TEST=1 ../.venv/bin/python -m pytest \
140
+ tests/integration/test_aws_authentication.py::test_cached_client_refreshes_after_real_sts_expiry -q
141
+ ```
142
+
143
+ After extended testing, revoke the fixture's LF grants, deregister its prefix,
144
+ delete the two copied tables/objects and the disposable roles, and schedule the
145
+ customer KMS key for deletion with the minimum seven-day waiting period. Run the
146
+ full cleanup below after acceptance is complete. A pending-deletion key cannot be used;
147
+ see [KMS key deletion](https://docs.aws.amazon.com/kms/latest/developerguide/deleting-keys.html).
148
+
149
+ ## Existing local environment
150
+
151
+ For an environment prepared in this checkout, source `.aws-test/env.sh` instead
152
+ of creating another stack. The ignored `.aws-test/` directory records resource
153
+ names, CLI requests, query IDs/statistics and test reports, without AWS credentials.
154
+ Check stack status and fixture age before reusing it.
155
+ The environment recorded on 2026-10-16 has since been fully decommissioned;
156
+ its local `env.sh` is disabled. Recreate the stack before future cloud tests.
157
+
158
+ ```bash
159
+ source .aws-test/env.sh
160
+ aws cloudformation describe-stacks --stack-name "$ATHENA_TEST_STACK" \
161
+ --query 'Stacks[0].StackStatus' --output text
162
+ ../.venv/bin/python -m pytest tests/integration -m integration -q
163
+ ```
164
+
165
+ ## Cleanup
166
+
167
+ Run only for the dedicated environment above. First remove fixture definitions,
168
+ then verify the bucket belongs to this stack before emptying it. The template
169
+ has no bucket versioning. Deleting a nonempty bucket through CloudFormation fails.
170
+
171
+ ```bash
172
+ ../.venv/bin/python scripts/init_tpch_data.py --drop
173
+ owned_bucket="$(aws cloudformation describe-stack-resource \
174
+ --stack-name "$ATHENA_TEST_STACK" --logical-resource-id TestBucket \
175
+ --query 'StackResourceDetail.PhysicalResourceId' --output text)"
176
+ test -n "$ATHENA_TEST_BUCKET" && test "$owned_bucket" = "$ATHENA_TEST_BUCKET" && \
177
+ aws s3 rm "s3://$ATHENA_TEST_BUCKET/" --recursive --only-show-errors && \
178
+ aws cloudformation delete-stack --stack-name "$ATHENA_TEST_STACK" && \
179
+ aws cloudformation wait stack-delete-complete --stack-name "$ATHENA_TEST_STACK"
180
+ ```
181
+
182
+ ## Validation record: 2026-10-16
183
+
184
+ This checkout used the local CLI `default` identity in `us-east-1`, Python
185
+ 3.12.10, boto3/botocore 1.43.114, and Athena SQL engine v3. The stack reached
186
+ `CREATE_COMPLETE` with exactly the three declared resources. Fixture initialization
187
+ created five Parquet objects totaling 5,373 bytes and one Glue view. The Workgroup
188
+ was restored to non-enforced S3 output after testing; the 10 MiB cutoff remains.
189
+
190
+ | Check | Result |
191
+ | --- | --- |
192
+ | Unit tests | 111 passed; 98% source coverage |
193
+ | Enforced S3 results, extended suite | 28 passed, zero skipped, 195.17 seconds; real-expiry case deselected and run separately |
194
+ | Managed results, same Workgroup | 28 passed, zero skipped, 203.32 seconds; real-expiry case run separately |
195
+ | Actual STS expiry and renewal | 1 passed, 929.80 seconds; AWS rejected the expired static token, and the same client/Session renewed credentials for a successful query |
196
+ | IAM denial | Valid denied role could discover the catalog; StartQueryExecution was denied before a query ID was created |
197
+ | Source KMS and Lake Formation | Allowed/restricted roles queried the same copied fixtures; successful controls proved table/column existence before denial assertions |
198
+ | Wait/result deadlines and budgets | Actual AWS wait cancellation, result read deadline, row/byte truncation metadata passed |
199
+ | Transport faults | Controlled HTTP 429, a lost submission response, and socket read timeouts; retry/recovery passed against real AWS |
200
+ | Native idempotency | Retried HTTP submissions retained one token and one actual QueryExecutionId |
201
+ | Formatting and lint | Ruff lint/format and whitespace checks passed |
202
+
203
+ The extended matrix used four temporary scoped IAM roles, one temporary customer
204
+ KMS key, two copied fixture tables/objects and one LF-registered prefix. Account
205
+ Lake Formation defaults and existing IAM user policies were not changed. The LF
206
+ table was verified as registered; its restricted identity had DESCRIBE without
207
+ SELECT, so AWS hid inaccessible columns and returned an authorization diagnostic.
208
+ The KMS object was verified as SSE-KMS using the test key; only the allowed role
209
+ had decrypt permission. No stored access keys or global AWS config changes were
210
+ required. Test profiles and reports stayed in the ignored `.aws-test/` directory.
211
+
212
+ Cleanup removed all four temporary roles, both protected tables/objects and the
213
+ LF prefix registration. The test KMS key is `PendingDeletion` with a seven-day
214
+ waiting period. A subsequent full teardown on 2026-10-16 reached
215
+ `DELETE_COMPLETE`: the original bucket (328 objects), database and Workgroup
216
+ were deleted. Account LF settings were verified unchanged. The final query audit found no
217
+ active queries and confirmed that the client output override was ignored.
218
+ These zero-skip results used the temporary extended environment. Future cloud
219
+ runs require the environment and any optional permission/role profiles to be
220
+ recreated. The writable table contract added on 2026-10-19 is not included in
221
+ the historical counts above; its separate live validation is recorded below.
222
+
223
+ The network tests inject faults at a local TLS proxy while forwarding other
224
+ signed requests to real AWS with normal upstream TLS verification. They do not
225
+ generate AWS service quota pressure or reproduce a regional AWS outage. Socket
226
+ timeouts may cause valid HTTP submission retries; assertions check one logical
227
+ token/query ID rather than requiring exactly one physical HTTP request.
228
+
229
+ Reported scanned bytes are not the billing quantity: billing can apply per-query
230
+ minimums, and S3 requests/storage and catalog usage are separate. Source-data
231
+ SSE-KMS allow/deny and AWS-owned managed results were tested; customer-KMS managed
232
+ result encryption, cross-account trust, SSO and web identity are outside this run.
233
+ The cloud CI target remains disabled until CI credentials and its workflow are
234
+ assigned; the local default identity is not copied to CI.
235
+
236
+ Real connection setup exposed an assumption in `test_connection()`: the service
237
+ listed `AwsDataCatalog` as `GLUE`, while `GetDataCatalog` returned not found.
238
+ Connection validation now reuses paginated catalog discovery for type/access
239
+ checks, then verifies database metadata and an actual SELECT. It does not add
240
+ catalog-name exceptions or suppress metadata errors. The existing connection-test
241
+ category caught this during live acceptance; offline regression covers discovery,
242
+ missing/unsupported catalogs and the actual-query requirement.
243
+
244
+ ## Additional EXPLAIN validation: 2026-10-19
245
+
246
+ The standalone `test_explain_returns_query_plan` case ran twice against actual
247
+ Athena SQL engine v3 in `us-east-1` with managed results: one passed case per run
248
+ (3.84 and 3.97 seconds), zero skips, and `DataScannedInBytes=0` for each query.
249
+ The test uses `EXPLAIN SELECT 1 AS datus_explain_value` and verifies a successful,
250
+ nonempty plan containing the output alias. It requires only region/Workgroup
251
+ configuration, without a source database or prepared data.
252
+
253
+ The captured service response reported `StatementType=DML`, with `Query Plan`
254
+ as the first API row and `Fragment 0 [SINGLE]` as the first plan row. The connector
255
+ correctly removes the DML header and retains the plan. The unit regression now
256
+ uses this observed protocol rather than fabricating a headerless UTILITY EXPLAIN.
257
+
258
+ Each run created one uniquely tagged temporary Workgroup with managed results
259
+ and a 10 MiB scan cutoff, then disabled/deleted it and verified it was absent.
260
+ No S3 buckets, Glue databases, capacity reservations or servers were created.
261
+ These extra EXPLAIN checks are separate from the historical 28-case result-mode
262
+ counts.
263
+
264
+ See [AWS EXPLAIN behavior and scan considerations](https://docs.aws.amazon.com/athena/latest/ug/athena-explain-statement.html).
265
+
266
+ ## Additional writable validation: 2026-10-19
267
+
268
+ A fresh stack in `us-east-1` created exactly one private SSE-S3 bucket, one Glue
269
+ database and one Athena SQL engine v3 Workgroup with a 10 MiB scan cutoff. No
270
+ TPC-H initialization or additional IAM/KMS resources were needed. The optional
271
+ writable table contract and EXPLAIN ran against this environment in both result
272
+ modes with Workgroup enforcement enabled:
273
+
274
+ | Result mode | Result |
275
+ | --- | --- |
276
+ | S3 results | 2 passed, zero skipped, 25.41 seconds |
277
+ | Managed results | 2 passed, zero skipped, 24.38 seconds |
278
+
279
+ The shared table contract created physical `Mixed Case` and `special-name`
280
+ columns, inserted both typed rows, verified Glue schema plus qualified SELECT
281
+ and LIMIT results, and dropped its table. After each run, Glue listed no tables
282
+ and the `contracts/` prefix contained no objects. This validates the actual Hive
283
+ DDL and Trino INSERT/SELECT identifiers rather than result aliases alone.
284
+
285
+ All 16 queries succeeded and reported 1,006 scanned bytes in total; per-query
286
+ billing minimums and S3/catalog requests apply separately. Teardown removed the
287
+ 13 remaining query-result objects and the stack reached `DELETE_COMPLETE`.
288
+ Subsequent AWS API checks verified the bucket, Glue database and Workgroup were
289
+ absent. These four passed cases are separate from the historical 28-case suites
290
+ and the earlier standalone EXPLAIN checks.
@@ -0,0 +1,266 @@
1
+ Metadata-Version: 2.5
2
+ Name: datus-athena
3
+ Version: 0.1.0
4
+ Summary: AWS Athena database adapter for Datus using the official boto3 SDK
5
+ Project-URL: Repository, https://github.com/Datus-ai/datus-db-adapters
6
+ Project-URL: Issues, https://github.com/Datus-ai/datus-db-adapters/issues
7
+ Author-email: DatusAI <support@datus.ai>
8
+ License: Apache-2.0
9
+ Requires-Python: >=3.12
10
+ Requires-Dist: boto3<2,>=1.43.91
11
+ Requires-Dist: datus-db-core>=0.1.10
12
+ Requires-Dist: pandas>=2.0.0
13
+ Requires-Dist: pyarrow>=18.0.0
14
+ Requires-Dist: pydantic>=2.0.0
15
+ Requires-Dist: sqlglot>=27.27.0
16
+ Provides-Extra: test
17
+ Requires-Dist: pytest-cov>=4; extra == 'test'
18
+ Requires-Dist: pytest>=8; extra == 'test'
19
+ Description-Content-Type: text/markdown
20
+
21
+ # datus-athena
22
+
23
+ Amazon Athena adapter using the official boto3 SDK. Python 3.12 or later.
24
+
25
+ ```bash
26
+ pip install datus-athena
27
+ ```
28
+
29
+ ## Configuration
30
+
31
+ ```python
32
+ from datus_athena import AthenaConfig, AthenaConnector
33
+
34
+ with AthenaConnector(AthenaConfig(
35
+ region="us-east-1",
36
+ workgroup="datus-test",
37
+ catalog="AwsDataCatalog",
38
+ database="analytics",
39
+ )) as connector:
40
+ result = connector.execute_query("SELECT '数据' AS message", "list")
41
+ assert result.success, result.error
42
+ print(result.sql_return, result.query_execution_id, result.execution_info)
43
+ ```
44
+
45
+ Datus Agent discovers the `datus.adapters` entry point. Adapter fields pass
46
+ through `DbConfig.extra`; no Agent database-name branch is needed:
47
+
48
+ ```yaml
49
+ services:
50
+ datasources:
51
+ lake:
52
+ type: athena
53
+ aws:
54
+ region: us-east-1
55
+ # profile: datus-role # optional standard AWS role profile
56
+ workgroup: datus-test
57
+ catalog: AwsDataCatalog
58
+ database: analytics
59
+ query_timeout_seconds: 600
60
+ max_rows: 10000
61
+ ```
62
+
63
+ The namespace is `catalog.database.table`. There is no additional Datus schema
64
+ level. Per-call `catalog_name` and `database_name` override defaults without
65
+ changing another thread/task's context. SELECT identifiers use double quotes;
66
+ Hive table DDL uses backticks. The registered SQLGlot dialect is `athena`.
67
+
68
+ | Setting | Default | Meaning |
69
+ | --- | --- | --- |
70
+ | `region` | SDK configuration | AWS region |
71
+ | `profile_name` | SDK default chain | Named profile, including AssumeRole profiles |
72
+ | `ca_bundle` | SDK trust configuration | Optional trusted CA bundle; verification stays enabled |
73
+ | `workgroup` | `primary` | Athena SQL Workgroup |
74
+ | `catalog` | `AwsDataCatalog` | AWS Glue data catalog |
75
+ | `database` | unset | Default database |
76
+ | `output_location` | unset | Optional `s3://bucket/prefix/` for query results |
77
+ | `timeout_seconds` | 30 | Each SDK connection/socket read timeout |
78
+ | `query_timeout_seconds` | 600 | Foreground query wait deadline |
79
+ | `result_timeout_seconds` | 60 | Result pagination deadline |
80
+ | `cancel_timeout_seconds` | 10 | Wait after native cancellation; zero requests only |
81
+ | `poll_interval_seconds` | 1 | State polling interval |
82
+ | `max_rows` | 10000 | Maximum returned rows |
83
+ | `max_result_bytes` | 16777216 | Maximum retained UTF-8 cell bytes before type conversion |
84
+ | `page_size` | 1000 | Result API rows per response, including any header |
85
+
86
+ ## Agent SQL skill
87
+
88
+ The adapter packages [`db-athena-sql`](datus_athena/skills/db-athena-sql/SKILL.md)
89
+ through the `datus.skills` entry point. Datus Agent 0.4.4 loads it for a bound
90
+ Athena connector. It explains Athena's Trino/Hive syntax split, names and types,
91
+ numeric and time semantics, nested data, partition pruning and Workgroup limits.
92
+ [Table design and writes](datus_athena/skills/db-athena-sql/references/table-design-and-writes.md)
93
+ is a task-specific reference for S3 DDL, CTAS, INSERT and Iceberg operations.
94
+ The SQL-generation notes hook reads the same skill body so the two paths share
95
+ one set of maintained rules.
96
+
97
+ ## AWS identity and permissions
98
+
99
+ Credentials use the SDK default provider chain (environment, shared profiles,
100
+ SSO, web identity, container or instance role). No access keys or short-lived
101
+ tokens are stored in this adapter's configuration. Explicit temporary credentials
102
+ in environment variables expire when their upstream issuer says they expire;
103
+ use a refreshable provider for long-lived processes.
104
+
105
+ For cross-account access, configure a standard AWS profile and set
106
+ `profile_name="datus-role"`:
107
+
108
+ ```ini
109
+ # ~/.aws/config; the source profile's credentials remain outside agent.yml
110
+ [profile datus-role]
111
+ role_arn = arn:aws:iam::123456789012:role/DatusAthenaReader
112
+ source_profile = development
113
+ region = us-east-1
114
+ ```
115
+
116
+ Boto3 owns AssumeRole caching and refresh. This package uses the shared
117
+ `datus-db-core>=0.1.8` `AWSIdentity` for Session/client ownership, timeouts, retries
118
+ and cleanup. The AWS connection fields are `region`, `profile_name`, and
119
+ `ca_bundle`. The shared CORE-01 shape
120
+ `aws: {region: ..., profile: ..., ca_bundle: ...}` is also accepted and normalized
121
+ to these fields; conflicting nested/flat values fail configuration validation.
122
+ An existing `boto3.Session` can also be passed as `session=`.
123
+
124
+ Grant the test identity the scoped Athena actions used here:
125
+ `GetWorkGroup`, `ListDataCatalogs`, `ListDatabases`,
126
+ `GetDatabase`, `ListTableMetadata`, `GetTableMetadata`, `StartQueryExecution`,
127
+ `GetQueryExecution`, `GetQueryResults`, and `StopQueryExecution`.
128
+ Also grant the required Glue database/table access and source S3 reads. S3
129
+ results require access to the result prefix, including `s3:GetObject` and query
130
+ result writes. Encrypted data/results require the corresponding KMS permissions;
131
+ Lake Formation protected data requires its grants as well as IAM access.
132
+ Managed results use Workgroup-scoped Athena result permissions and the managed
133
+ results KMS key policy when configured. Metadata access alone does not prove
134
+ query/data access: `test_connection()` performs both checks and a small SELECT.
135
+
136
+ ## Query behavior and support boundary
137
+
138
+ Each submission reads the Workgroup configuration. Managed results and enforced
139
+ Workgroup settings take precedence: the adapter omits client output overrides.
140
+ Otherwise `output_location` is passed only when explicitly supplied. A Workgroup
141
+ with its own S3 result location needs no client output location. Both result
142
+ modes use `GetQueryResults`; the adapter does not download complete S3 files.
143
+
144
+ Queries return an `AthenaExecuteSQLResult`, a subclass of `ExecuteSQLResult`.
145
+ `query_execution_id` identifies the native query; `execution_info` includes state,
146
+ Athena error details, engine version, scan/timing statistics and truncation.
147
+ List results preserve NULL, Decimal, dates, timestamps and Chinese text. Complex
148
+ types, timezone timestamps, and high-precision timestamp signatures stay as text;
149
+ no lossy complex-type parsing is attempted. A plain `timestamp` value with more
150
+ than six fractional digits fails with a `CAST(... AS VARCHAR)` directive rather
151
+ than silently losing precision. Duplicate column names require aliases
152
+ for list output; positional Arrow/pandas/CSV results retain them.
153
+
154
+ Result row/byte budgets stop pagination and set `truncated` and
155
+ `truncation_reason`. The byte budget counts retained cell text, not HTTP response
156
+ bytes or Python object overhead; one SDK response is still received in full.
157
+ Query and pagination deadlines are checked between SDK calls; socket timeouts
158
+ and up to three SDK attempts can extend wall-clock completion beyond a deadline.
159
+ Neither a result cap nor SQL LIMIT is a scan-cost guarantee. Set Workgroup
160
+ per-query scanned-byte controls and service-side limits before running queries.
161
+
162
+ On wait timeout or polling failure, the adapter requests `StopQueryExecution`,
163
+ then polls for a bounded period. `cancellation_requested` and
164
+ `cancellation_confirmed` are separate; a running query after cancellation wait
165
+ is reported with its last state and ID. Failed queries are not resubmitted even
166
+ when Athena marks them retryable. One UUID token is generated per logical
167
+ execution and reused by SDK request retries. `execute_query(...,
168
+ client_request_token=...)` permits explicit retry identity; the caller must reuse
169
+ the same SQL/context with a token. Cancellation is also available through
170
+ `cancel_query(query_execution_id)`.
171
+
172
+ Glue Catalog queries, tables, views, partition columns, SHOW CREATE metadata and
173
+ bounded sampling are implemented. DDL and DML are sent unchanged to Athena;
174
+ availability depends on table format and catalog permissions (UPDATE/DELETE/MERGE
175
+ require supported Iceberg tables). Generic dataframe migration is explicitly
176
+ unsupported. Transactions, session SET/USE, Spark Workgroups, federation-specific
177
+ features and durable cross-process task recovery are outside this release.
178
+
179
+ ## Testing
180
+
181
+ Install from this checkout in a dedicated environment:
182
+
183
+ ```bash
184
+ uv venv --python 3.12 .venv
185
+ uv pip install --python .venv/bin/python -e ../datus-db-core -e '.[test]'
186
+ .venv/bin/python -m pytest tests/unit -m 'not integration'
187
+ .venv/bin/python -m pytest tests/integration -m integration
188
+ ```
189
+
190
+ Unit tests use real botocore `Stubber` responses, never AWS network access.
191
+ The cloud target remains disabled until CI-specific credentials and its workflow
192
+ are configured. No paid LocalStack dependency is required.
193
+
194
+ The EXPLAIN smoke case only needs `AWS_REGION` (or `AWS_DEFAULT_REGION`) and
195
+ `ATHENA_WORKGROUP`. It can use managed results without a source database or S3
196
+ fixtures:
197
+
198
+ ```bash
199
+ env -u ATHENA_DATABASE .venv/bin/python -m pytest \
200
+ tests/integration/test_sql_execution.py::test_explain_returns_query_plan -q
201
+ ```
202
+
203
+ The prepared-fixture suite supports read-only source permissions. Set
204
+ `ATHENA_WRITABLE_DATA_LOCATION=s3://test-bucket/contracts/` to additionally run
205
+ the shared writable table contract. This requires scoped Glue table creation,
206
+ insertion and deletion, plus S3 list/read/write/delete access. The test creates
207
+ one UUID-named Parquet table under a new child prefix and cleans up its metadata
208
+ and data. The supplied prefix must be dedicated to disposable test data.
209
+ The writable contract creates physical `Mixed Case` and `special-name` columns,
210
+ using backticks in Hive DDL and double quotes in INSERT/SELECT. Metadata must
211
+ report those names; the read contract also checks quoted result aliases.
212
+
213
+ For real AWS validation, provision a dedicated SQL Workgroup with a per-query
214
+ scan cutoff and either an S3 result prefix or managed results, a disposable Glue
215
+ database, and a dedicated S3 data prefix. Set `AWS_REGION`, `ATHENA_WORKGROUP`,
216
+ `ATHENA_DATABASE`, `ATHENA_DATA_LOCATION`, and optionally `AWS_PROFILE` and
217
+ `ATHENA_OUTPUT_LOCATION`. `ATHENA_DATA_LOCATION` must be a dedicated empty
218
+ `s3://bucket/prefix/`; do not reuse application data locations.
219
+
220
+ For the smallest local-CLI environment in `us-east-1`, use the three-resource
221
+ [CloudFormation setup and cleanup guide](AWS_TESTING.md). It reuses one Workgroup
222
+ for S3 and managed-results acceptance, with a 10 MiB per-query scan cutoff.
223
+
224
+ ```bash
225
+ # Creates five tiny Parquet TPC-H tables and a view from shared fixtures. Run once.
226
+ .venv/bin/python scripts/init_tpch_data.py
227
+ # Cloud tests query the prepared fixtures; no fixture initialization is skipped on error.
228
+ .venv/bin/python -m pytest tests/integration -m integration
229
+ # Removes only fixture table definitions. S3 objects remain for operator-controlled cleanup.
230
+ .venv/bin/python scripts/init_tpch_data.py --drop
231
+ ```
232
+
233
+ Run the suite separately against S3-results and managed-results Workgroups.
234
+ Set `ATHENA_EXPECT_MANAGED_RESULTS=true` or `false` to assert the storage mode.
235
+ CTAS fixture initialization uses explicit data locations: prepare fixtures with
236
+ a Workgroup that does not enforce its result location, then run read-only
237
+ acceptance with enforced Workgroups against the same database. Athena rejects
238
+ CTAS `external_location` under an enforced result location
239
+ ([AWS CTAS restrictions](https://docs.aws.amazon.com/athena/latest/ug/create-table-as.html)).
240
+ Without required environment variables, cloud fixtures skip with their names;
241
+ with those variables present, failed authentication/readiness/queries fail tests.
242
+ The environment owner must remove the dedicated S3 fixture/output prefix and
243
+ database after acceptance; the script does not delete buckets or arbitrary data.
244
+
245
+ Local AWS acceptance in `us-east-1` on 2026-10-16 passed with SQL engine v3:
246
+ 28 short integration tests passed in enforced S3-results mode and 28 in
247
+ managed-results mode, with zero skipped. The actual STS-expiry case separately
248
+ passed after 15 minutes 29 seconds. These include scoped IAM denial, source KMS
249
+ and Lake Formation allow/deny controls, native cancellation, result/wait deadlines,
250
+ result budgets and controlled loopback TLS transport faults with real AWS queries.
251
+ See [environment setup, limits and validation](AWS_TESTING.md). CI credentials
252
+ and its workflow remain unassigned; the local identity is not copied to CI.
253
+ On 2026-10-19 the EXPLAIN smoke case also passed against SQL engine v3 with
254
+ managed results, reporting zero scanned bytes. Its temporary Workgroups were
255
+ deleted immediately after validation. The observed DML/header result shape is
256
+ recorded in [AWS_TESTING.md](AWS_TESTING.md#additional-explain-validation-2026-10-19).
257
+ On the same date, a fresh minimal environment passed the writable table contract
258
+ and EXPLAIN in both enforced S3 and managed-results modes (two passed cases per
259
+ mode, zero skips). Physical quoted columns, typed inserts, schema and cleanup
260
+ were verified against AWS, then the bucket, database and Workgroup were deleted.
261
+ These checks are separate from the historical counts above; see the
262
+ [additional writable validation record](AWS_TESTING.md#additional-writable-validation-2026-10-19).
263
+
264
+ References: [Athena API](https://docs.aws.amazon.com/boto3/latest/reference/services/athena.html),
265
+ [SDK credential providers](https://docs.aws.amazon.com/boto3/latest/guide/credentials.html),
266
+ [managed results](https://docs.aws.amazon.com/athena/latest/ug/managed-results.html).