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.
- datus_athena-0.1.0/.gitignore +1 -0
- datus_athena-0.1.0/AWS_TESTING.md +290 -0
- datus_athena-0.1.0/PKG-INFO +266 -0
- datus_athena-0.1.0/README.md +246 -0
- datus_athena-0.1.0/datus_athena/__init__.py +34 -0
- datus_athena-0.1.0/datus_athena/config.py +92 -0
- datus_athena-0.1.0/datus_athena/connector.py +607 -0
- datus_athena-0.1.0/datus_athena/handlers.py +97 -0
- datus_athena-0.1.0/datus_athena/models.py +15 -0
- datus_athena-0.1.0/datus_athena/results.py +63 -0
- datus_athena-0.1.0/datus_athena/skills/db-athena-sql/SKILL.md +105 -0
- datus_athena-0.1.0/datus_athena/skills/db-athena-sql/references/table-design-and-writes.md +65 -0
- datus_athena-0.1.0/datus_athena/skills.py +27 -0
- datus_athena-0.1.0/datus_athena/tpch_data.py +52 -0
- datus_athena-0.1.0/pyproject.toml +62 -0
- datus_athena-0.1.0/scripts/aws-test-environment.yaml +81 -0
- datus_athena-0.1.0/scripts/init_tpch_data.py +50 -0
- datus_athena-0.1.0/tests/__init__.py +0 -0
- datus_athena-0.1.0/tests/conftest.py +1 -0
- datus_athena-0.1.0/tests/integration/__init__.py +0 -0
- datus_athena-0.1.0/tests/integration/conftest.py +116 -0
- datus_athena-0.1.0/tests/integration/http_faults.py +168 -0
- datus_athena-0.1.0/tests/integration/test_aws_authentication.py +86 -0
- datus_athena-0.1.0/tests/integration/test_connection.py +35 -0
- datus_athena-0.1.0/tests/integration/test_contract.py +113 -0
- datus_athena-0.1.0/tests/integration/test_metadata_retrieval.py +45 -0
- datus_athena-0.1.0/tests/integration/test_resilience.py +102 -0
- datus_athena-0.1.0/tests/integration/test_sql_execution.py +53 -0
- datus_athena-0.1.0/tests/integration/test_tpch.py +35 -0
- datus_athena-0.1.0/tests/unit/__init__.py +0 -0
- datus_athena-0.1.0/tests/unit/conftest.py +84 -0
- datus_athena-0.1.0/tests/unit/test_config.py +90 -0
- datus_athena-0.1.0/tests/unit/test_connector_unit.py +527 -0
- datus_athena-0.1.0/tests/unit/test_handlers.py +113 -0
- datus_athena-0.1.0/tests/unit/test_metadata.py +367 -0
- datus_athena-0.1.0/tests/unit/test_migration_mixin.py +11 -0
- datus_athena-0.1.0/tests/unit/test_registration.py +38 -0
- datus_athena-0.1.0/tests/unit/test_skills.py +59 -0
- datus_athena-0.1.0/tests/unit/test_tpch_data.py +21 -0
- 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).
|