Repository navigation
[BSE-4458] Refactor Iceberg IO to Use PyIceberg Under the Hood - #235
Conversation
7362849 to
ef8e93f
Compare
commit 8805b95 Author: Srinivas Lade <[email protected]> Date: Fri Jan 31 11:43:13 2025 -0500 [BSE-4471] Move SF Prefetch Support to Python (#189) commit 9821639 Author: Srinivas Lade <[email protected]> Date: Wed Jan 29 12:33:52 2025 -0500 [BSE-4470][BSE-4474] Add PyIceberg Read Support (#160)
2c4a674 to
85fb58b
Compare
| // Benefits: Potentially some speed up in collecting repeated metadata like | ||
| // schema | ||
| // Downsides: Does not refresh if another program modifies the Catalog | ||
| return CachingCatalog.wrap(catalog); |
There was a problem hiding this comment.
Removed the CachingCatalog change because it was causing issues when jumping between PyIceberg and Iceberg-Java. This shouldn't be as much as a problem anymore because we use some caching on the PyIceberg side, which is still the predominant caller for getting metadata. Iceberg Java is only used for more heavy handed stuff like commits.
| catalog = S3TablesBuilder.create(connStr); | ||
| return CachingCatalog.wrap(catalog); | ||
| if (connStr.startsWith("iceberg+arn:aws:s3tables") && catalogType.equals("s3tables")) { | ||
| catalog = S3TablesBuilder.create(connStr.replaceFirst("^iceberg\\+", "")); |
There was a problem hiding this comment.
Was a bug, I removed the part where we strip the prefix during compilation.
|
|
||
| def _table_path(self, identifier: Identifier) -> str: | ||
| wh_path = self.warehouse_path.removesuffix("/") | ||
| return f"{wh_path}/{'/'.join(identifier)}" |
There was a problem hiding this comment.
Bug here to remove cases where {warehouse_path}//...
There was a problem hiding this comment.
You mean this fixes a bug? Could you add comment in the code instead.
There was a problem hiding this comment.
To clarify it was a bug in my own code in the base read PR that was discovered when running nightly.
| @@ -0,0 +1,83 @@ | |||
| import os | |||
There was a problem hiding this comment.
Added a FileIO wrapper to call into PyArrow's AzureFileSystem
| conn_str = ( | ||
| f"snowflake://{account}.snowflakecomputing.com/?{urlencode(conn_contents)}" | ||
| ) | ||
| def _fs_from_file_path(file_path: str, io: FileIO) -> FileSystem: |
There was a problem hiding this comment.
Mostly a duplicate of a internal function from PyIceberg, but slightly modified to take use of our monkey-patch
There was a problem hiding this comment.
Could you add a link to the original function that was copied?
| @@ -0,0 +1,79 @@ | |||
| import pyiceberg.io | |||
There was a problem hiding this comment.
Sadly I needed to monkey patch a few things from PyIceberg to fix various issues. See below
There was a problem hiding this comment.
We can make the monkey patch more robust by following the numba_compat.py format (check hash for code changes and add comments with "BODO change" notes).
There was a problem hiding this comment.
Makes sense, I'm going to leave it like this for the time being because I will have to make some large changes when PyIceberg 0.9 comes out anyways
| Monkey-patched version of pyiceberg.schema.build_position_accessors to | ||
| use the new _BuildPositionAccessors class. | ||
| """ | ||
| return visit(schema_or_type, _BuildPositionAccessors()) |
There was a problem hiding this comment.
Fix to apply filters to top-level struct fields. Right now, PyIceberg only works on inner struct fields
There was a problem hiding this comment.
Let's add as comment in the code.
| pyiceberg.schema.build_position_accessors = build_position_accessors | ||
|
|
||
| # Moneky-patch inferred IO to support Bodo's | ||
| pyiceberg.io.pyarrow.PyArrowFileIO = BodoPyArrowFileIO |
There was a problem hiding this comment.
Hook to apply our AzureFileSystem changes to PyIceberg. I need to do this here because there are places where PyIceberg just calls PyArrowFileIO directly instead of using their own hooks for custom FileIO.
There was a problem hiding this comment.
Let's add the comment in the code.
| pa.Schema, | ||
| # Runtime Schema | ||
| list[str], | ||
| list[types.ArrayCompatible], |
There was a problem hiding this comment.
I messed up this part initially which caused some schema evolution tests to fail. Basically, this function should return the original schema info (names, types, PyArrow schema) and schema after apply BodoSQL column pruning.
| return DateLiteral((val - datetime.date(1970, 1, 1)).days) | ||
| if isinstance(val, datetime.datetime): | ||
| return TimestampLiteral((val - datetime.datetime(1970, 1, 1)).microseconds) | ||
| if isinstance(val, (list, pd.core.arrays.ExtensionArray)): |
There was a problem hiding this comment.
Had to add pd.core.arrays.ExtensionArray
There was a problem hiding this comment.
Why? This is for nested types?
There was a problem hiding this comment.
A comment in the code would be good.
There was a problem hiding this comment.
I'm not sure exactly why, just that sometimes Pandas arrays are used instead of lists especially when coming from BodoSQL
There was a problem hiding this comment.
The list/array difference is fine, but my point is that we may need other nested types (struct/map) here too. A TODO may be enough for now.
IsaacWarren
left a comment
There was a problem hiding this comment.
Just reviewed the places commented on, assuming others are unchanged. Thanks Srinivas!
| "AZURE_STORAGE_ACCOUNT_NAME" | ||
| ) | ||
|
|
||
| account_key = self.properties.get(ADLS_ACCOUNT_KEY) or os.environ.get( |
There was a problem hiding this comment.
Add a todo to pass sas token for arrow 19? Also we should support ADLS_CLIENT_ID, ADLS_TENANT_ID, and ADLS_CLIENT_SECRET
Codecov ReportAttention: Patch coverage is
❌ Your project status has failed because the head coverage (66.98%) is below the target coverage (80.00%). You can increase the head coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## main #235 +/- ##
==========================================
+ Coverage 61.48% 66.98% +5.50%
==========================================
Files 169 176 +7
Lines 61920 62627 +707
Branches 8663 8729 +66
==========================================
+ Hits 38070 41950 +3880
+ Misses 21430 18077 -3353
- Partials 2420 2600 +180 |
| pytest.mark.skip( | ||
| reason="[BSE-4569] MERGE INTO with PyIceberg is not supported yet" | ||
| ), |
There was a problem hiding this comment.
Are we disabling our current MERGE INTO support?
There was a problem hiding this comment.
Yes for the time being
|
|
||
| return impl | ||
| this_module = sys.modules[__name__] | ||
| PyArrowFSType, pyarrow_fs_type = install_opaque_class( |
There was a problem hiding this comment.
Did we decide that this doesn't need memory management?
There was a problem hiding this comment.
Yes I believe so, because it is something that we don't explicitly create a reference to, and the transaction that we use later on in the function hold a reference to it
|
|
||
| def _table_path(self, identifier: Identifier) -> str: | ||
| wh_path = self.warehouse_path.removesuffix("/") | ||
| return f"{wh_path}/{'/'.join(identifier)}" |
There was a problem hiding this comment.
You mean this fixes a bug? Could you add comment in the code instead.
| # TODO | ||
| pass |
There was a problem hiding this comment.
This is supposed to be TODO? Raise an error instead?
| def drop_table(self, identifier: str | Identifier) -> None: | ||
| # TODO | ||
| pass | ||
|
|
||
| def purge_table(self, identifier: str | Identifier) -> None: | ||
| # TODO | ||
| pass |
| # that have been pushed down to I/O. | ||
| rtjf_terms: list[tuple[ir.Var, tuple[int], tuple[int, int, str]]] | None = None, | ||
| ): | ||
| # Info requireds to connect to the catalog and table |
There was a problem hiding this comment.
| # Info requireds to connect to the catalog and table | |
| # Info required to connect to the catalog and table |
| return DateLiteral((val - datetime.date(1970, 1, 1)).days) | ||
| if isinstance(val, datetime.datetime): | ||
| return TimestampLiteral((val - datetime.datetime(1970, 1, 1)).microseconds) | ||
| if isinstance(val, (list, pd.core.arrays.ExtensionArray)): |
There was a problem hiding this comment.
Why? This is for nested types?
| return DateLiteral((val - datetime.date(1970, 1, 1)).days) | ||
| if isinstance(val, datetime.datetime): | ||
| return TimestampLiteral((val - datetime.datetime(1970, 1, 1)).microseconds) | ||
| if isinstance(val, (list, pd.core.arrays.ExtensionArray)): |
There was a problem hiding this comment.
A comment in the code would be good.
| rtjf_filters = pie.And( | ||
| rtjf_filters, pie.In(col, literal(unique_vals)) | ||
| ) | ||
| print(rtjf_filters, unique_vals) |
There was a problem hiding this comment.
Remove print?
| print(rtjf_filters, unique_vals) |
| from bodo.tests.utils import pytest_mark_one_rank | ||
|
|
||
| pytestmark = pytest.mark.iceberg | ||
| pytestmark = [pytest.mark.iceberg, pytest.mark.skip] |
There was a problem hiding this comment.
Why are we skipping this file. Add a comment in the code?
There was a problem hiding this comment.
Oh just missed that, undid it
Changes included in this PR
Refactor our Iceberg IO to support PyIceberg as a backend. Replaces a lot of metadata-related operations to use PyIceberg instead, delete any helper code that PyIceberg already does, and limit the use of the Iceberg connector.
Testing strategy
PR Ci and Nightly both
User facing changes
Users no longer need to install the Iceberg connector for Iceberg support. Instead, they need to either install
bodo[iceberg]orbodoandpyiceberg.Checklist
[run CI]in your commit message.