Repository navigation
JVM insert_columns: a column of str and None crosses as one UTF-8 blob plus int32 offsets (refs #324) - #334
Merged
Merged
Conversation
…b plus int32 offsets (refs #324) A string column used to cross JPype one element at a time: convert_python_to_java per value and an Object[] of Java Strings built element by element, all alive at once. A column of nothing but str and None is now one byte[] (the UTF-8 text of all rows), one int[] of row + 1 offsets and, when there is a null, one boolean[], wrapped in the new bridge class StringColumn; DocumentBatcher.insertColumns and insertColumnsParallel decode each row when they build its document, so the Java Strings are short-lived. The blob crosses through bytes into JArray(JByte), a copy; nothing relies on a JPype view of Python memory. Kept as before: a mixed column (an int, bytes, a date-time) and an empty column convert per element; a column with a lone surrogate (no UTF-8 form) and one beyond an int offset keep the per-element path. insert_dataframe and insert_arrow reach the change through insert_columns. Stored values are the same on both wheels (tests compare with the per-element path and with the values given). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…by the per-element path as before (refs #324) The blob builder returns None for a column with a lone surrogate; the UnicodeEncodeError of the first CI run came from JPype in the per-element path that ran before, which is the refusal every bulk entry point has. The test now checks the builder at the first, middle and last row, the refusal of _column_to_java and insert_columns with nothing written, and that a non-BMP character still takes the blob. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This was referenced Nov 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
For users nothing changes in what is stored: a string column given to
insert_columns(and through it toinsert_dataframeandinsert_arrow) on the JVM wheel stores the same values as before, and the same as the native wheel. How the column crosses JPype changes.Refs #324, part of #315. This targets
release/26.11.1(the base of the wheels-only26.11.1.post1); it changes the bridge jar, so it ships with the rebuilt wheel, and the engine commit, jars and image are untouched.What changed:
strandNoneis now sent as onebyte[](the UTF-8 text of all rows back to back), oneint[]of row + 1 offsets and, when the column has a null, oneboolean[]of the null rows. They are wrapped in a new bridge class,StringColumn, andDocumentBatcher.insertColumnsandinsertColumnsParalleldecode each row when they build its document. Before,_column_to_javacalledconvert_python_to_javafor every value and built anObject[]of Java Strings element by element.bytesintojpype.JArray(jpype.JByte)and the offsets and mask through numpy intoJArray(JInt)andJArray(JBoolean), as the numeric columns already do. I did not use a memoryview of Java or Python memory, since the JPype guide does not say whether it pins or copies.set(map(type, ...)),np.fromiter(map(len, ...)),"".join).UnicodeEncodeErrorJPype raises, before anything is written, as it does for every bulk entry point), and a column whose text exceeds an int offset (2 GiB).dataframe.py(it reaches the change throughinsert_columns), and the native wheel.Memory, not measured: the blob holds the UTF-8 bytes plus 4 bytes per row (plus 1 per row for a mask) in Java at once, where the old path held an
Object[]of references plus one Java String per distinct value (UTF-16) for the whole column, so a high-cardinality column holds less and a column of a few distinct values holds a little more. On the Python side the ASCII case builds the joined text and its encoded copy once (about the column's byte size each), and the non-ASCII case a list of the encoded rows (about 45 bytes per row plus the text) that is freed before the transfer. The Java Strings now live only for the row being built. The untimed profile study (queued) and the laptop A/B are what measure it; I make no speed or memory claim here.Tests, which run in CI only:
tests/test_insert_columns_string_blob.py(JVM wheel) spies onconvert_python_to_java(no call for a string column, which fails on the old code), checks which columns become aStringColumnand which keep the per-element path, compares the stored table with the one the per-element path stores for the same columns (non-BMP characters, NUL, an empty string, a null, a 5,000-character string, in the synchronous and the parallel mode), and checks that the Java side refuses a malformed column.tests/test_insert_columns_strings.py(both wheels) checks that those values come back as given from a list, a numpy string array and an object array, in a mixed column, with a single row, and in the parallel mode.🤖 Generated with Claude Code