-
Notifications
You must be signed in to change notification settings - Fork 116
Keep complex values that the parsers lost or mixed up #1054
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -49,6 +49,40 @@ def _split_array_items(inner: str) -> list[str]: | |
| return items | ||
|
|
||
|
|
||
| def _split_native_array_items(inner: str) -> list[str]: | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Self-review round one (implementation behavior): CLEAN Base Covered:
|
||
| """Split the items of an array in Athena's native format. | ||
|
|
||
| Athena joins the items with ``", "``, so only a top-level comma followed by a space | ||
| separates items. Other commas, leading and trailing spaces, and empty items belong | ||
| to the items. Brace and bracket groupings are respected. | ||
|
|
||
| Args: | ||
| inner: Interior content of the array without brackets, not stripped. | ||
|
|
||
| Returns: | ||
| List of item strings. | ||
| """ | ||
| items: list[str] = [] | ||
| current: list[str] = [] | ||
| depth = 0 | ||
| index = 0 | ||
| while index < len(inner): | ||
| char = inner[index] | ||
| if char in "{[": | ||
| depth += 1 | ||
| elif char in "}]": | ||
| depth -= 1 | ||
| elif char == "," and depth == 0 and inner.startswith(" ", index + 1): | ||
| items.append("".join(current)) | ||
| current = [] | ||
| index += 2 | ||
| continue | ||
| current.append(char) | ||
| index += 1 | ||
| items.append("".join(current)) | ||
| return items | ||
|
|
||
|
|
||
| @dataclass | ||
| class TypeNode: | ||
| """Parsed representation of an Athena DDL type signature. | ||
|
|
@@ -321,9 +355,14 @@ def _convert_typed_array(self, value: str, type_node: TypeNode) -> list[Any] | N | |
|
|
||
| element_type = type_node.children[0] if type_node.children else TypeNode("varchar") | ||
|
|
||
| # Try JSON first (only if content looks like JSON) | ||
| # Try JSON first if the elements are JSON, whose values Athena renders as JSON | ||
| # text, or if the content looks like JSON | ||
| inner_preview = value[1:10] if len(value) > 10 else value[1:-1] | ||
| if '"' in inner_preview or value.startswith(("[{", "[null", "[[")): | ||
| if ( | ||
| element_type.type_name == "json" | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Self-review round two (claims, callers, operations): CLEAN Base
|
||
| or '"' in inner_preview | ||
| or value.startswith(("[{", "[null", "[[")) | ||
| ): | ||
| try: | ||
| parsed = json.loads(value) | ||
| if isinstance(parsed, list): | ||
|
|
@@ -337,19 +376,16 @@ def _convert_typed_array(self, value: str, type_node: TypeNode) -> list[Any] | N | |
| pass | ||
|
|
||
| # Native format | ||
| inner = value[1:-1].strip() | ||
| inner = value[1:-1] | ||
| if not inner: | ||
| return [] | ||
|
|
||
| if "[" in inner: | ||
| return None # Nested arrays not supported in native format | ||
|
|
||
| items = _split_array_items(inner) | ||
| items = _split_native_array_items(inner) | ||
| result: list[Any] = [] | ||
| for item in items: | ||
| item = item.strip() | ||
| if not item: | ||
| continue | ||
| if item.startswith("{") and item.endswith("}"): | ||
| if element_type.type_name in ("row", "struct"): | ||
| result.append(self._convert_typed_struct(item, element_type)) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Independent review (relayed): no regressions; 1 pre-existing finding → folded in
Reviewer: Codex CLI 0.160.0, model
gpt-6-astra, reasoning effort high, sandboxread-only, session01a102c7-ffb5-7902-8830-9fe54dca52a8. Static review ofec5323ea..a3566039. Snapshot: detached worktree ata3566039fd28bc6d5f5e0b90e550f4768e3726dd. The prompt contained the literal diff and the intended behavior only. Afterwards, the snapshot and the PR worktree were clean ata3566039.Covered (reviewer): typed/untyped arrays; scalar, row/map, and nested-array elements; NULLs, whitespace, commas, quotes,
=and brackets; fallback contracts and callers; pandas whole and chunked reads, all-NULL columns, fetch methods,as_pandas(), engine selection, and test compatibility. No regressions. By source comparison, the first five new native-array cases and the long-prefix JSON case fail on the base.Finding, pre-existing P2,
pyathena/pandas/result_set.py:166:PandasDataFrameIterator.get_chunk()(documented indocs/pandas.md) returned the reader's chunk without_trunc_date. Verified live on this branch withchunksize=2:get_chunk()gave[Timestamp('2026-10-04 12:34:56'), NaT]for a TIME column, while iteration gave[time(12, 34, 56), None].Repaired in bab2687:
get_chunk()appliesself._trunc_dateto both reader kinds, as__next__does. The non-chunked iterators use_no_trunc_date, so the whole-result path is unchanged. NewTestPandasCursor::test_get_chunk_timepasses, and fails with the previousget_chunk().tests/pyathena/pandasandtests/pyathena/aio/pandas: 309 passed;just lintpassed. Self-review of the repair (both rounds): no findings. The release note was extended to coverget_chunk().There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Independent follow-up (relayed): CLEAN for
a3566039..bab2687a6a644dc4240d758aa88860905d6a6085.Reviewer: Codex CLI 0.160.0,
gpt-6-astra, effort high,read-only, session01a102d3-db57-7892-8c52-dd2169ae7b3f. Static review; afterwards, the snapshot was clean atbab2687a. Covered:TextFileReaderchunks and single-DataFrame iterators with either callback (one conversion, as in iteration); the explicit-chunksize, auto-optimized, and whole-result paths (_no_trunc_dateprevents double truncation); UNLOAD results; and close, exhaustion, and size handling (unchanged; conversion errors get the same cleanup as iteration). The test fails with the previous implementation.