JingsongLi commented on code in PR #9821:
URL: https://github.com/apache/paimon/pull/9821#discussion_r4023484275


##########
paimon-python/pypaimon/filesystem/pyarrow_file_io.py:
##########
@@ -465,6 +516,107 @@ def delete(self, path: str, recursive: bool = False) -> 
bool:
             self.filesystem.delete_file(path_str)
         return True
 
+    def _delete_s3_compatible_directory(self, path_str: str) -> bool:
+        client = self._get_s3_delete_client()
+        bucket, key = self._split_s3_path(path_str)
+        prefix = key.rstrip("/")
+        if prefix:
+            prefix += "/"
+        while True:
+            response = client.list_objects_v2(
+                Bucket=bucket, Prefix=prefix, MaxKeys=1000)
+            keys = (
+                item["Key"] for item in response.get("Contents", ())
+                if item["Key"] != prefix
+            )
+            if self._delete_s3_objects(client, bucket, keys):
+                continue
+            if prefix:
+                client.delete_object(Bucket=bucket, Key=prefix)

Review Comment:
   **[P2] Preserve implicit parent directory markers**
   
   Both compatibility paths delete the target directory marker and return 
without recreating its immediate parent. PyArrow's `S3FileSystem::DeleteDir` 
explicitly calls `EnsureParentExists` after deleting a directory because an 
implicit parent can otherwise disappear. This is reachable in the filesystem 
catalog: `list_databases()` and `get_database()` accept an implicit S3 prefix, 
but dropping its last table removes the prefix's only child and makes the 
database become `NotFound`. The request-level cross-bucket test already models 
`parent/child/` without a `parent/` marker, but currently expects the bucket to 
become empty. Please recreate the immediate parent marker after successful 
recursive and non-recursive directory deletion, except when the parent is the 
bucket root.



##########
paimon-python/pypaimon/filesystem/pyarrow_file_io.py:
##########
@@ -98,11 +107,21 @@ def __getstate__(self):
         state = self.__dict__.copy()
         # threading.Lock cannot be pickled; recreated in __setstate__.
         state.pop("_legacy_bucket_lock", None)
+        state.pop("_s3_delete_client", None)
+        # Recreate S3-compatible clients with the worker's AWS SDK settings.
+        if self._uses_s3_compatibility():
+            state.pop("filesystem", None)
         return state
 
     def __setstate__(self, state):
         self.__dict__.update(state)
         self._legacy_bucket_lock = threading.Lock()
+        self._s3_delete_client = None
+        if self._uses_s3_compatibility():

Review Comment:
   **[P2] Recompute compatibility state in the worker**
   
   This rebuilds a worker-local filesystem while retaining the producer's 
serialized PyArrow gates (`_pyarrow_gte_8`, `_pyarrow_gte_16`, 
`_pyarrow_gte_22`, and `_oss_bucket_in_endpoint`). With a supported PyArrow 21 
driver and PyArrow 23 worker, `_pyarrow_gte_22` remains false, so recursive 
deletion takes the incompatible native batch path that this PR is intended to 
avoid. A pre-PR non-OSS pickle also has no `_s3_endpoint`, causing 
`__setstate__` to fail with `AttributeError`. Please migrate missing fields and 
recompute all version-dependent state from the worker's installed PyArrow 
before rebuilding the client, or reconstruct from stable path/options state.



##########
paimon-python/pypaimon/filesystem/pyarrow_file_io.py:
##########
@@ -465,6 +516,107 @@ def delete(self, path: str, recursive: bool = False) -> 
bool:
             self.filesystem.delete_file(path_str)
         return True
 
+    def _delete_s3_compatible_directory(self, path_str: str) -> bool:
+        client = self._get_s3_delete_client()
+        bucket, key = self._split_s3_path(path_str)
+        prefix = key.rstrip("/")
+        if prefix:
+            prefix += "/"
+        while True:

Review Comment:
   **[P2] Bound the relist loop under concurrent writers**
   
   This loop exits only after a verification listing contains no non-marker 
object. A writer that continues adding objects under the prefix—or an 
eventually consistent compatible endpoint that repeatedly returns a recently 
deleted key—can therefore keep `drop_table`/`drop_database` issuing GET and 
DELETE requests forever. The regression test injects exactly one late object 
and does not cover sustained churn. Please add a pass, deadline, or request 
budget and raise a clear concurrent-modification error when the prefix does not 
quiesce.



##########
paimon-python/pypaimon/filesystem/pyarrow_file_io.py:
##########
@@ -163,6 +182,21 @@ def _get_s3_boolean_property(self, name: str) -> bool:
             return value
         return OptionsUtils.convert_to_boolean(value)
 
+    def _uses_s3_compatibility(self) -> bool:
+        return (not self._use_jindo
+                and (self._is_oss or bool(self._s3_endpoint)))
+
+    def _uses_s3_delete_fallback(self) -> bool:
+        return (self._uses_s3_compatibility()
+                and self._pyarrow_gte_22
+                and not (self._is_s3 and self._get_s3_boolean_property(
+                    "delete.batch-enabled")))
+
+    @staticmethod
+    def _configure_s3_compatibility():
+        os.environ.setdefault(

Review Comment:
   **[P2] Do not implement endpoint-local behavior through conditional 
process-global state**
   
   `setdefault` has two conflicting failure modes. If the host already sets the 
valid value `AWS_REQUEST_CHECKSUM_CALCULATION=WHEN_SUPPORTED`, the 
OSS/custom-endpoint workaround is silently skipped and uploads can still use 
the incompatible optional checksum trailers. If the variable is initially 
absent, this permanently changes the checksum policy of native AWS S3 clients 
created later in the same process. The tests clear `os.environ` before each 
construction, so they cover neither case. Please scope and restore this setting 
around compatible client construction where possible, or detect an incompatible 
pre-existing value and fail with an actionable message rather than silently 
leaving the workaround disabled.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to