QuakeWang commented on code in PR #538:
URL: https://github.com/apache/paimon-rust/pull/538#discussion_r3608020714


##########
docs/src/python-binding.md:
##########
@@ -0,0 +1,455 @@
+<!--
+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.
+-->
+
+# Python Integration
+
+The Python integration is a binding built on top of Apache Paimon Rust, 
allowing you to access Paimon tables from Python programs. It uses 
[PyArrow](https://arrow.apache.org/docs/python/) for zero-copy data transfer 
via the [Arrow C Data 
Interface](https://arrow.apache.org/docs/format/CDataInterface.html).
+
+## Prerequisites
+
+- Python 3.10 or later
+- Supported platforms: Linux (amd64, arm64), macOS (amd64, arm64)

Review Comment:
   The supported-platform list does not match the published artifacts: the 
release workflow builds a Windows wheel, and a win_amd64 wheel is available on 
PyPI.



##########
docs/src/python-binding.md:
##########
@@ -0,0 +1,455 @@
+<!--
+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.
+-->
+
+# Python Integration
+
+The Python integration is a binding built on top of Apache Paimon Rust, 
allowing you to access Paimon tables from Python programs. It uses 
[PyArrow](https://arrow.apache.org/docs/python/) for zero-copy data transfer 
via the [Arrow C Data 
Interface](https://arrow.apache.org/docs/format/CDataInterface.html).
+
+## Prerequisites
+
+- Python 3.10 or later
+- Supported platforms: Linux (amd64, arm64), macOS (amd64, arm64)
+
+## Installation
+
+```bash
+pip install pypaimon-rust pyarrow
+```
+
+The pre-built native library is embedded in the package and automatically 
loaded at runtime — no manual build step is needed. 
[PyArrow](https://arrow.apache.org/docs/python/) is a required peer dependency 
and must be installed separately.
+
+## Creating a Catalog
+
+Use `PaimonCatalog` with a dictionary of options to create a catalog. The 
catalog type is determined by the `metastore` option (default: `filesystem`).
+
+```python
+from pypaimon_rust.datafusion import PaimonCatalog
+
+# Local filesystem
+catalog = PaimonCatalog({"warehouse": "/path/to/warehouse"})
+
+# List databases and tables
+print(catalog.list_databases())
+print(catalog.list_tables("default"))
+
+# Get a table handle
+table = catalog.get_table("default.my_table")
+```
+
+### Alibaba Cloud OSS
+
+```python
+catalog = PaimonCatalog({
+    "warehouse": "oss://bucket/warehouse",
+    "fs.oss.accessKeyId": "your-access-key-id",
+    "fs.oss.accessKeySecret": "your-access-key-secret",
+    "fs.oss.endpoint": "oss-cn-hangzhou.aliyuncs.com",
+})
+```
+
+### REST Catalog
+
+```python
+catalog = PaimonCatalog({
+    "metastore": "rest",
+    "uri": "http://localhost:8080";,
+    "warehouse": "my_warehouse",
+})
+```
+
+## SQL Context
+
+`SQLContext` supports registering multiple Paimon catalogs and executing SQL 
queries with DataFusion.
+
+```python
+from pypaimon_rust.datafusion import SQLContext
+
+ctx = SQLContext()
+ctx.register_catalog("paimon", {"warehouse": "/path/to/warehouse"})
+
+# DDL and DML
+ctx.sql("CREATE SCHEMA paimon.my_db")
+ctx.sql("CREATE TABLE paimon.my_db.t (id INT, name STRING)")
+ctx.sql("INSERT INTO paimon.my_db.t VALUES (1, 'alice'), (2, 'bob')")
+
+# Query returns a list of PyArrow RecordBatches
+batches = ctx.sql("SELECT * FROM paimon.my_db.t")
+for batch in batches:
+    print(batch)
+```
+
+## Reading a Table
+
+Paimon Python uses a **scan-then-read** pattern: first scan the table to 
produce splits, then read data from those splits as PyArrow RecordBatches.
+
+```python
+import pyarrow as pa
+from pypaimon_rust.datafusion import PaimonCatalog
+
+catalog = PaimonCatalog({"warehouse": "/path/to/warehouse"})
+table = catalog.get_table("default.my_table")
+
+# Create a read builder
+rb = table.new_read_builder()
+
+# Step 1: Scan — produces a Plan containing Splits
+scan = rb.new_scan()
+plan = scan.plan()
+splits = plan.splits()
+
+# Step 2: Read — consumes splits and returns PyArrow RecordBatches
+read = rb.new_read()
+batches = read.read(splits)
+
+for batch in batches:
+    print(batch)
+```
+
+Alternatively, read via SQL using `SQLContext`:
+
+```python
+from pypaimon_rust.datafusion import SQLContext
+
+ctx = SQLContext()
+ctx.register_catalog("paimon", {"warehouse": "/path/to/warehouse"})
+
+batches = ctx.sql("SELECT id, name FROM paimon.default.my_table")
+for batch in batches:
+    print(batch)
+```
+
+## Writing to a Table
+
+Paimon Python uses a **write-then-commit** pattern: write PyArrow 
RecordBatches to a writer, prepare commit messages, then commit.
+
+```python
+import pyarrow as pa
+from pypaimon_rust.datafusion import PaimonCatalog
+
+catalog = PaimonCatalog({"warehouse": "/path/to/warehouse"})
+table = catalog.get_table("default.my_table")
+
+# Build a batch matching the table schema
+batch = pa.record_batch(
+    [pa.array([1, 2, 3], pa.int32()), pa.array(["a", "b", "c"], pa.string())],
+    names=["id", "name"],
+)
+
+# Create a write builder (shared commit_user for writer and committer)
+wb = table.new_write_builder()
+
+# Write batches
+write = wb.new_write()
+write.write_arrow(batch)
+
+# Prepare commit messages
+messages = write.prepare_commit()
+
+# Commit
+wb.new_commit().commit(messages)
+```
+
+Alternatively, write via SQL using `SQLContext`:
+
+```python
+from pypaimon_rust.datafusion import SQLContext
+
+ctx = SQLContext()
+ctx.register_catalog("paimon", {"warehouse": "/path/to/warehouse"})
+
+ctx.sql("INSERT INTO paimon.default.my_table VALUES (1, 'alice'), (2, 'bob')")
+```
+
+!!! warning "Schema Validation"
+    The input batch schema is strictly validated against the table schema: 
field count, order, names, and types must match exactly. A `ValueError` is 
raised on mismatch.
+
+!!! note "Write Builder Consistency"
+    The writer and committer must come from the same `WriteBuilder` — they 
share a `commit_user` for duplicate-commit detection. Passing messages from one 
builder's writer to another builder's committer will raise a `ValueError`.
+
+## Column Projection
+
+Use `with_projection` to select specific columns. Only the requested columns 
are read, reducing I/O.
+
+```python
+rb = table.new_read_builder()
+rb.with_projection(["id", "name"])
+
+# Continue with scan-then-read as above...
+```
+
+## Limit
+
+Use `with_limit` to set a hint for the number of rows returned. A limit of `0` 
returns zero rows.
+
+```python
+rb = table.new_read_builder()
+rb.with_limit(100)
+```
+
+!!! warning
+    `with_limit` is a scan-planning hint, not an exact row cap. When all rows 
fall within a single split, the entire split is returned regardless of the 
limit value. Callers should apply application-level limiting if an exact upper 
bound is required.
+
+## Case Sensitivity
+
+Use `with_case_sensitive` to control whether column-name matching in 
projections and predicates is case-sensitive. Defaults to `True` (exact match). 
Set to `False` for case-insensitive matching (ASCII case-folding).
+
+```python
+rb = table.new_read_builder()
+rb.with_case_sensitive(False)
+```
+
+!!! note
+    `with_case_sensitive` must be called **before** `with_filter` to affect 
predicate construction. The predicate is built using the case-sensitivity 
setting at the time `with_filter` is invoked; changing it afterward has no 
effect on an already-constructed predicate.
+
+## Filter Push-Down
+
+Filter push-down prunes data at two levels:
+
+1. **Scan planning** — skips partitions, buckets, and data files based on 
file-level statistics (min/max).
+2. **Read-side** — applies row-level filtering via Parquet native row filters 
for leaf predicates.
+
+!!! warning
+    Filter push-down is a **best-effort** optimization. The returned results 
may still contain rows that do not satisfy the filter condition. Callers should 
always apply residual filtering on the returned records to ensure correctness.

Review Comment:
   The filter semantics described here do not match the current implementation. 
Scan pruning can be conservative, but the read path applies exact residual 
filtering and does not return rows that fail the predicate.



-- 
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