Repository navigation
feat(encryption): [9/N] Read encrypted manifest lists #4159
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
0ad7405
696206b
1d711e5
8049b89
b0b4160
068ecfd
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 |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| # Licensed to the Apache Software Foundation (ASF) under one | ||
| # or more contributor license agreements. See the NOTICE file | ||
| # distributed with this work for additional information | ||
| # regarding copyright ownership. The ASF licenses this file | ||
| # to you under the Apache License, Version 2.0 (the | ||
| # "License"); you may not use this file except in compliance | ||
| # with the License. You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, | ||
| # software distributed under the License is distributed on an | ||
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| # KIND, either express or implied. See the License for the | ||
| # specific language governing permissions and limitations | ||
| # under the License. | ||
| """Input files for metadata encrypted with AGS1 streams.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import io | ||
|
|
||
| from pyiceberg.encryption.key_metadata import StandardKeyMetadata | ||
| from pyiceberg.encryption.stream import _decrypt_ags1 | ||
| from pyiceberg.io import InputFile, InputStream | ||
|
|
||
|
|
||
| class EncryptedInputFile(InputFile): | ||
| """Decrypts an AGS1 encrypted file, such as a manifest list, when it is opened.""" | ||
|
|
||
| def __init__(self, input_file: InputFile, key_metadata: StandardKeyMetadata) -> None: | ||
| super().__init__(input_file.location) | ||
| self._input_file = input_file | ||
| self._key_metadata = key_metadata | ||
|
|
||
| def __len__(self) -> int: | ||
| """Return the length of the decrypted file, in bytes.""" | ||
| return len(self._decrypt()) | ||
|
|
||
| def exists(self) -> bool: | ||
| """Check whether the encrypted file exists.""" | ||
| return self._input_file.exists() | ||
|
|
||
| def open(self, seekable: bool = True) -> InputStream: | ||
| """Read and decrypt the whole file, authenticating every block before returning any of it.""" | ||
| return io.BytesIO(self._decrypt()) | ||
|
|
||
| def _decrypt(self) -> bytes: | ||
| with self._input_file.open(seekable=False) as stream: | ||
| return _decrypt_ags1(stream.read(), self._key_metadata) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -232,7 +232,7 @@ def _readable_metrics_struct(bound_type: PrimitiveType) -> pa.StructType: | |
|
|
||
| entries = [] | ||
| snapshot = self._get_snapshot(snapshot_id) | ||
| for manifest in snapshot.manifests(self.tbl.io): | ||
| for manifest in snapshot.manifests(self.tbl.io, self.tbl.encryption_manager): | ||
| for entry in manifest.fetch_manifest_entry(io=self.tbl.io, discard_deleted=False): | ||
| column_sizes = entry.data_file.column_sizes or {} | ||
| value_counts = entry.data_file.value_counts or {} | ||
|
|
@@ -383,7 +383,7 @@ def partitions( | |
| ) | ||
|
|
||
| snapshot = self._get_snapshot(snapshot_id) | ||
| spec_ids = {manifest.partition_spec_id for manifest in snapshot.manifests(self.tbl.io)} | ||
| spec_ids = {manifest.partition_spec_id for manifest in snapshot.manifests(self.tbl.io, self.tbl.encryption_manager)} | ||
|
Contributor
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. Should the As I read it, this is the main cost of passing the manager separately, which bears on the design question in the description. Every caller of |
||
| partition_record = self.tbl.metadata.specs_struct(spec_ids=spec_ids) | ||
| has_partitions = len(partition_record.fields) > 0 | ||
|
|
||
|
|
@@ -404,6 +404,7 @@ def partitions( | |
| row_filter=row_filter, | ||
| case_sensitive=case_sensitive, | ||
| snapshot_id=snapshot.snapshot_id, | ||
| encryption_manager=self.tbl.encryption_manager, | ||
| ) | ||
|
|
||
| partitions_map: dict[tuple[str, Any], Any] = {} | ||
|
|
@@ -590,7 +591,7 @@ def _partition_summaries_to_rows( | |
| specs = self.tbl.metadata.specs() | ||
| manifests = [] | ||
| if snapshot: | ||
| for manifest in snapshot.manifests(self.tbl.io): | ||
| for manifest in snapshot.manifests(self.tbl.io, self.tbl.encryption_manager): | ||
| is_data_file = manifest.content == ManifestContent.DATA | ||
| is_delete_file = manifest.content == ManifestContent.DELETES | ||
| manifest_row = { | ||
|
|
@@ -882,7 +883,8 @@ def _files(self, snapshot_id: int | None = None, data_file_filter: set[DataFileC | |
| executor = ExecutorFactory.get_or_create() | ||
| results = list( | ||
| executor.map( | ||
| lambda manifest_list: self._get_files_from_manifest(manifest_list, data_file_filter), snapshot.manifests(io) | ||
| lambda manifest_list: self._get_files_from_manifest(manifest_list, data_file_filter), | ||
| snapshot.manifests(io, self.tbl.encryption_manager), | ||
| ) | ||
| ) | ||
| return pa.concat_tables(results) | ||
|
|
@@ -970,7 +972,8 @@ def _all_files(self, data_file_filter: set[DataFileContent] | None = None) -> pa | |
| return pa.Table.from_pylist([], schema=self._get_files_schema()) | ||
|
|
||
| executor = ExecutorFactory.get_or_create() | ||
| manifest_lists = executor.map(lambda snapshot: snapshot.manifests(self.tbl.io), snapshots) | ||
| encryption_manager = self.tbl.encryption_manager | ||
| manifest_lists = executor.map(lambda snapshot: snapshot.manifests(self.tbl.io, encryption_manager), snapshots) | ||
|
|
||
| unique_manifests = {(manifest.manifest_path, manifest) for manifest_list in manifest_lists for manifest in manifest_list} | ||
|
|
||
|
|
||
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.
this logic looks a bit weird for the non-encrypted case.
EncryptionManager.from_table_metadatareturnsNonewhen the table is not encrypted, and the whole function returnsNone