Skip to content
Closed
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
cd3d51e
createDataFrame working but with fixed schema in python
BryanCutler Sep 29, 2017
c73c7c6
added schema conversion
BryanCutler Oct 4, 2017
e9c6de7
add from_arrow_schema, test and cleanup
BryanCutler Oct 9, 2017
06b033f
fix style
BryanCutler Oct 9, 2017
9d667c6
fixed xrange for Python 3
BryanCutler Oct 10, 2017
31851f8
Merge remote-tracking branch 'upstream/master' into arrow-createDataF…
BryanCutler Oct 10, 2017
ca474db
moved python jvm call to PythonSQLUtils, added tearDownClass to tests
BryanCutler Oct 10, 2017
c7ddee6
forgot to rename conf
BryanCutler Oct 10, 2017
b00a924
fixed typo
BryanCutler Oct 13, 2017
e36a176
using schema if passed in to createDataFrame, added unit test to veri…
BryanCutler Oct 14, 2017
fc3a554
Merge remote-tracking branch 'upstream/master' into arrow-createDataF…
BryanCutler Oct 14, 2017
f42e351
updated function name to_arrow_type
BryanCutler Oct 14, 2017
76e87dc
revert DataFrame schema arg, added test for wrong schema, fixed typos
BryanCutler Oct 16, 2017
81ddfa9
moved common code between parallelize to _serialize_to_jvm
BryanCutler Oct 18, 2017
5e8e11f
when schema provided, attempt to cast series and fallback if not matc…
BryanCutler Oct 18, 2017
3052f30
added support for schema as list of names
BryanCutler Oct 18, 2017
9f7b1c0
Simplify `_createFromPandasWithArrow()`.
ueshin Oct 19, 2017
dc03657
changed to use izip
BryanCutler Oct 24, 2017
f421e2d
added check for case of specifying schema with like 'int'
BryanCutler Oct 24, 2017
0de3126
changed single type to fallback and error
BryanCutler Oct 24, 2017
c41cf33
Merge remote-tracking branch 'upstream/master' into arrow-createDataF…
BryanCutler Oct 27, 2017
b6df7bf
add support for date and timestamp for from_arrow_type
BryanCutler Oct 27, 2017
cfb1c3d
using _create_batch to make arrow batches also without explicit schem…
BryanCutler Oct 30, 2017
b362b9a
Merge remote-tracking branch 'upstream/master' into arrow-createDataF…
BryanCutler Nov 7, 2017
1c244d1
some minor cleanup of _convert_from_pandas
BryanCutler Nov 7, 2017
99ce1e4
minor cleanup of _create_from_pandas_with_arrow
BryanCutler Nov 7, 2017
7d9cc3e
avoid double copies of series with nulls
BryanCutler Nov 9, 2017
126f2e7
added test to make sure input is unchanged
BryanCutler Nov 9, 2017
421d0be
removed copy=True option, did not improve anything
BryanCutler Nov 9, 2017
0ad736b
fix pydoc
BryanCutler Nov 9, 2017
6c72e37
added schema tuple support, refactored creating schema
BryanCutler Nov 10, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 17 additions & 11 deletions python/pyspark/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,24 +475,30 @@ def f(split, iterator):
return xrange(getStart(split), getStart(split + 1), step)

return self.parallelize([], numSlices).mapPartitionsWithIndex(f)
# Calling the Java parallelize() method with an ArrayList is too slow,
# because it sends O(n) Py4J commands. As an alternative, serialized
# objects are written to a file and loaded through textFile().

# Make sure we distribute data evenly if it's smaller than self.batchSize
if "__len__" not in dir(c):
c = list(c) # Make it a list so we can compute its length
batchSize = max(1, min(len(c) // numSlices, self._batchSize or 1024))
serializer = BatchedSerializer(self._unbatched_serializer, batchSize)
jrdd = self._serialize_to_jvm(c, numSlices, serializer)
return RDD(jrdd, self, serializer)

def _serialize_to_jvm(self, data, parallelism, serializer):
"""
Calling the Java parallelize() method with an ArrayList is too slow,
because it sends O(n) Py4J commands. As an alternative, serialized
objects are written to a file and loaded through textFile().
"""
tempFile = NamedTemporaryFile(delete=False, dir=self._temp_dir)
try:
# Make sure we distribute data evenly if it's smaller than self.batchSize
if "__len__" not in dir(c):
c = list(c) # Make it a list so we can compute its length
batchSize = max(1, min(len(c) // numSlices, self._batchSize or 1024))
serializer = BatchedSerializer(self._unbatched_serializer, batchSize)
serializer.dump_stream(c, tempFile)
serializer.dump_stream(data, tempFile)
tempFile.close()
readRDDFromFile = self._jvm.PythonRDD.readRDDFromFile
jrdd = readRDDFromFile(self._jsc, tempFile.name, numSlices)
return readRDDFromFile(self._jsc, tempFile.name, parallelism)
finally:
# readRDDFromFile eagerily reads the file so we can delete right after.
os.unlink(tempFile.name)
return RDD(jrdd, self, serializer)

def pickleFile(self, name, minPartitions=None):
"""
Expand Down
1 change: 1 addition & 0 deletions python/pyspark/java_gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ def killChild():
java_import(gateway.jvm, "org.apache.spark.mllib.api.python.*")
# TODO(davies): move into sql
java_import(gateway.jvm, "org.apache.spark.sql.*")
java_import(gateway.jvm, "org.apache.spark.sql.api.python.*")
java_import(gateway.jvm, "org.apache.spark.sql.hive.*")
java_import(gateway.jvm, "scala.Tuple2")

Expand Down
10 changes: 3 additions & 7 deletions python/pyspark/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ def __repr__(self):


def _create_batch(series):
from pyspark.sql.types import _cast_pandas_series_type
import pyarrow as pa
# Make input conform to [(series1, type1), (series2, type2), ...]
if not isinstance(series, (list, tuple)) or \
Expand All @@ -222,13 +223,8 @@ def _create_batch(series):

# If a nullable integer series has been promoted to floating point with NaNs, need to cast
# NOTE: this is not necessary with Arrow >= 0.7
def cast_series(s, t):
if t is None or s.dtype == t.to_pandas_dtype():
return s
else:
return s.fillna(0).astype(t.to_pandas_dtype(), copy=False)

arrs = [pa.Array.from_pandas(cast_series(s, t), mask=s.isnull(), type=t) for s, t in series]
arrs = [pa.Array.from_pandas(_cast_pandas_series_type(s, t) if t is not None else s,
mask=s.isnull(), type=t) for s, t in series]
return pa.RecordBatch.from_arrays(arrs, ["_%d" % i for i in xrange(len(arrs))])


Expand Down
74 changes: 74 additions & 0 deletions python/pyspark/sql/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

if sys.version >= '3':
basestring = unicode = str
xrange = range
else:
from itertools import imap as map

Expand Down Expand Up @@ -414,6 +415,73 @@ def _createFromLocal(self, data, schema):
data = [schema.toInternal(row) for row in data]
return self._sc.parallelize(data), schema

def _createFromPandasWithArrow(self, pdf, schema):
"""
Create a DataFrame from a given pandas.DataFrame by slicing it into partitions, converting
to Arrow data, then sending to the JVM to parallelize. If a schema is passed in, the
data types will be used to coerce the data in Pandas to Arrow conversion.
"""
from pyspark.serializers import ArrowSerializer
from pyspark.sql.types import from_arrow_schema, to_arrow_type, _cast_pandas_series_type
import pyarrow as pa

# Slice the DataFrame into batches
step = -(-len(pdf) // self.sparkContext.defaultParallelism) # round int up
pdf_slices = (pdf[start:start + step] for start in xrange(0, len(pdf), step))

if schema is None or isinstance(schema, list):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isinstance(schema, list) -> isinstance(schema, (list, tuple)) maybe?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe, a test like spark.createDataFrame([[1]], ("v",)) would be great.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this check should include tuples as well for converting from unicode?
https://github.com/apache/spark/pull/19459/files#diff-3b5463566251d5b09fd328738a9e9bc5L579
I'll change that since it's related..

batches = [pa.RecordBatch.from_pandas(pdf_slice, preserve_index=False)
for pdf_slice in pdf_slices]

# There will be at least 1 batch after slicing the pandas.DataFrame
schema_from_arrow = from_arrow_schema(batches[0].schema)

# If passed schema as a list of names then rename fields
if isinstance(schema, list):
fields = []
for i, field in enumerate(schema_from_arrow):
field.name = schema[i]
fields.append(field)
schema = StructType(fields)
else:
schema = schema_from_arrow
else:
batches = []
for i, pdf_slice in enumerate(pdf_slices):

# convert to series to pyarrow.Arrays to use mask when creating Arrow batches
arrs = []
names = []
for c, (_, series) in enumerate(pdf_slice.iteritems()):
field = schema[c]
names.append(field.name)
t = to_arrow_type(field.dataType)
try:
# NOTE: casting is not necessary with Arrow >= 0.7
arrs.append(pa.Array.from_pandas(_cast_pandas_series_type(series, t),

@viirya viirya Oct 20, 2017

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any chance that the given data type for the field is not correct? By a wrong data type of a field, what the behavior of this casting?

For example, for a Series of numpy.int16`, if the given data type is bytetype, this casting will turn it to numpy.int8, so we will get:

>>> s = pd.Series([1, 2, 10001], dtype=np.int16)                                                                   
>>> s
0        1
1        2
2    10001
dtype: int16
>>> s.astype(np.int8)
0     1
1     2
2    17
dtype: int8

createDataFrame will check the data type of input data when converting to DataFrame and throw exception if verifySchema is true (default value).This implicit type casting seems inconsistent with original behavior.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that is a problem with using astype which doesn't provide any checks afaik. This casting is better done in Arrow, but since we are currently stuck on 0.4.1 we need this workaround. Trying this out with the latest arrow would give the user a nice error:

>>> pa.Array.from_pandas(s, type=pa.int16())
<pyarrow.lib.Int16Array object at 0x7f18361fecb0>
[
  1,
  2,
  10001
]
>>> pa.Array.from_pandas(s, type=pa.int8())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "pyarrow/array.pxi", line 279, in pyarrow.lib.Array.from_pandas (/home/bryan/git/arrow/python/build/temp.linux-x86_64-2.7/lib.cxx:25865)
  File "pyarrow/array.pxi", line 169, in pyarrow.lib.array (/home/bryan/git/arrow/python/build/temp.linux-x86_64-2.7/lib.cxx:24833)
  File "pyarrow/array.pxi", line 70, in pyarrow.lib._ndarray_to_array (/home/bryan/git/arrow/python/build/temp.linux-x86_64-2.7/lib.cxx:24083)
  File "pyarrow/error.pxi", line 77, in pyarrow.lib.check_status (/home/bryan/git/arrow/python/build/temp.linux-x86_64-2.7/lib.cxx:7876)
pyarrow.lib.ArrowInvalid: Integer value out of bounds

mask=series.isnull(), type=t))
except ValueError as e:

@viirya viirya Oct 20, 2017

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this guard only works to prevent casting like:

>>> s = pd.Series(["abc", "2", "10001"])                                                                           
>>> s
0      abc
1        2
2    10001
dtype: object
>>> s.astype(np.int8)                                                                                              
...
ValueError: invalid literal for long() with base 10: 'abc'

For the casting that can cause overflow, this seems don't work.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, there doesn't seem to be a way to guard against overflow with astype

warnings.warn("Arrow will not be used in createDataFrame: %s" % str(e))
return None
batches.append(pa.RecordBatch.from_arrays(arrs, names))

# Verify schema of first batch, return None if not equal and fallback without Arrow
if i == 0:
schema_from_arrow = from_arrow_schema(batches[i].schema)
if schema != schema_from_arrow:
warnings.warn("Arrow will not be used in createDataFrame.\n" +

@ueshin ueshin Oct 19, 2017

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will we reach this block?
I guess not because all datatypes are casted to the types specified by the schema otherwise some exception like ValueError is raised and fallback to withtout-Arrow.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we won't reach the block, I think we can simplify _createFromPandasWithArrow() like BryanCutler#28.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @ueshin! This does simplify things quite a bit, which I like. My only concerns are that we rely on Arrow/Pandas to raise an error somewhere during the casting in order to fallback, and the fields in the Arrow record batches get arbitrary names (doesn't use schema names). What are your thoughts @HyukjinKwon ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK by me too but let's keep on our eyes on mailing list and JIRAs that complains about it in the future, and improve it next time if this sounds more important than we think here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, merging now

"Supplied schema: %s\n!=\nArrow schema: %s"
% (str(schema), str(schema_from_arrow)))
return None

# Create the Spark DataFrame directly from the Arrow data and schema
jrdd = self._sc._serialize_to_jvm(batches, len(batches), ArrowSerializer())
jdf = self._jvm.PythonSQLUtils.arrowPayloadToDataFrame(
jrdd, schema.json(), self._wrapped._jsqlContext)
df = DataFrame(jdf, self._wrapped)
df._schema = schema
return df

@since(2.0)
@ignore_unicode_prefix
def createDataFrame(self, data, schema=None, samplingRatio=None, verifySchema=True):
Expand Down Expand Up @@ -510,6 +578,12 @@ def createDataFrame(self, data, schema=None, samplingRatio=None, verifySchema=Tr
except Exception:
has_pandas = False
if has_pandas and isinstance(data, pandas.DataFrame):
if self.conf.get("spark.sql.execution.arrow.enabled", "false").lower() == "true" \
and len(data) > 0:
df = self._createFromPandasWithArrow(data, schema)

@HyukjinKwon HyukjinKwon Oct 20, 2017

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As of #19459 (comment), schema from _parse_datatype_string could be not a StructType:

self.assertEqual(IntegerType(), _parse_datatype_string("int"))

although I don't think we have (intendedly and properly) supported this case with pd.DataFrame as int case resembles Dataset with primitive types, up to my knowledge:

spark.createDataFrame(["a", "b"], "string").show()
+-----+
|value|
+-----+
|    a|
|    b|
+-----+

For pd.DataFrame case, looks we always have a list of list.

data = [r.tolist() for r in data.to_records(index=False)]

So, I think we should only support list of strings maybe with a proper exception for int case for fallback for sure.

Of course, this case should work:

>>> spark.createDataFrame(pd.DataFrame([1]), "struct<a: int>").show()
+---+
|  a|
+---+
|  1|
+---+

# Fallback to create DataFrame without arrow if return None
if df is not None:

@viirya viirya Oct 20, 2017

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we show some log messages to users in this case?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the PR from @ueshin better captures errors to show a warning, i'll merge that now

return df
if schema is None:
schema = [str(x) for x in data.columns]
data = [r.tolist() for r in data.to_records(index=False)]
Expand Down
73 changes: 59 additions & 14 deletions python/pyspark/sql/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -3095,16 +3095,32 @@ def setUpClass(cls):
StructField("3_long_t", LongType(), True),
StructField("4_float_t", FloatType(), True),
StructField("5_double_t", DoubleType(), True)])
cls.data = [("a", 1, 10, 0.2, 2.0),
("b", 2, 20, 0.4, 4.0),
("c", 3, 30, 0.8, 6.0)]
cls.data = [(u"a", 1, 10, 0.2, 2.0),
(u"b", 2, 20, 0.4, 4.0),
(u"c", 3, 30, 0.8, 6.0)]

@classmethod
def tearDownClass(cls):
ReusedPySparkTestCase.tearDownClass()
cls.spark.stop()

def assertFramesEqual(self, df_with_arrow, df_without):
msg = ("DataFrame from Arrow is not equal" +
("\n\nWith Arrow:\n%s\n%s" % (df_with_arrow, df_with_arrow.dtypes)) +
("\n\nWithout:\n%s\n%s" % (df_without, df_without.dtypes)))
self.assertTrue(df_without.equals(df_with_arrow), msg=msg)

def createPandasDataFrameFromData(self):
import pandas as pd
import numpy as np
data_dict = {}
for j, name in enumerate(self.schema.names):
data_dict[name] = [self.data[i][j] for i in range(len(self.data))]
# need to convert these to numpy types first
data_dict["2_int_t"] = np.int32(data_dict["2_int_t"])
data_dict["4_float_t"] = np.float32(data_dict["4_float_t"])
return pd.DataFrame(data=data_dict)

def test_unsupported_datatype(self):
schema = StructType([StructField("dt", DateType(), True)])
df = self.spark.createDataFrame([(datetime.date(1970, 1, 1),)], schema=schema)
Expand All @@ -3121,21 +3137,15 @@ def test_null_conversion(self):
def test_toPandas_arrow_toggle(self):
df = self.spark.createDataFrame(self.data, schema=self.schema)
self.spark.conf.set("spark.sql.execution.arrow.enabled", "false")
pdf = df.toPandas()
self.spark.conf.set("spark.sql.execution.arrow.enabled", "true")
try:
pdf = df.toPandas()
finally:
self.spark.conf.set("spark.sql.execution.arrow.enabled", "true")
pdf_arrow = df.toPandas()
self.assertFramesEqual(pdf_arrow, pdf)

def test_pandas_round_trip(self):
import pandas as pd
import numpy as np
data_dict = {}
for j, name in enumerate(self.schema.names):
data_dict[name] = [self.data[i][j] for i in range(len(self.data))]
# need to convert these to numpy types first
data_dict["2_int_t"] = np.int32(data_dict["2_int_t"])
data_dict["4_float_t"] = np.float32(data_dict["4_float_t"])
pdf = pd.DataFrame(data=data_dict)
pdf = self.createPandasDataFrameFromData()
df = self.spark.createDataFrame(self.data, schema=self.schema)
pdf_arrow = df.toPandas()
self.assertFramesEqual(pdf_arrow, pdf)
Expand All @@ -3147,6 +3157,41 @@ def test_filtered_frame(self):
self.assertEqual(pdf.columns[0], "i")
self.assertTrue(pdf.empty)

def test_createDataFrame_toggle(self):
pdf = self.createPandasDataFrameFromData()
self.spark.conf.set("spark.sql.execution.arrow.enabled", "false")
try:
df_no_arrow = self.spark.createDataFrame(pdf)
finally:
self.spark.conf.set("spark.sql.execution.arrow.enabled", "true")
df_arrow = self.spark.createDataFrame(pdf)
self.assertEquals(df_no_arrow.collect(), df_arrow.collect())

def test_createDataFrame_with_schema(self):
pdf = self.createPandasDataFrameFromData()
df = self.spark.createDataFrame(pdf, schema=self.schema)
self.assertEquals(self.schema, df.schema)
pdf_arrow = df.toPandas()
self.assertFramesEqual(pdf_arrow, pdf)

def test_createDataFrame_with_incorrect_schema(self):
pdf = self.createPandasDataFrameFromData()
wrong_schema = StructType([field for field in reversed(self.schema)])

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a big deal at all: StructType([field for field in reversed(self.schema)]) -> StructType(list(reversed(st)))

with QuietTest(self.sc):
with self.assertRaisesRegexp(TypeError, ".*field.*can.not.accept.*type"):
self.spark.createDataFrame(pdf, schema=wrong_schema)

def test_createDataFrame_with_names(self):
pdf = self.createPandasDataFrameFromData()
df = self.spark.createDataFrame(pdf, schema=list('abcde'))
self.assertEquals(df.schema.fieldNames(), list('abcde'))

def test_schema_conversion_roundtrip(self):
from pyspark.sql.types import from_arrow_schema, to_arrow_schema
arrow_schema = to_arrow_schema(self.schema)
schema_rt = from_arrow_schema(arrow_schema)
self.assertEquals(self.schema, schema_rt)


@unittest.skipIf(not _have_pandas or not _have_arrow, "Pandas or Arrow not installed")
class VectorizedUDFTests(ReusedPySparkTestCase):
Expand Down
55 changes: 55 additions & 0 deletions python/pyspark/sql/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -1624,6 +1624,61 @@ def to_arrow_type(dt):
return arrow_type


def to_arrow_schema(schema):
""" Convert a schema from Spark to Arrow
"""
import pyarrow as pa
fields = [pa.field(field.name, to_arrow_type(field.dataType), nullable=field.nullable)
for field in schema]
return pa.schema(fields)


def from_arrow_type(at):
""" Convert pyarrow type to Spark data type.
"""
# TODO: newer pyarrow has is_boolean(at) functions that would be better to check type
import pyarrow as pa
if at == pa.bool_():
spark_type = BooleanType()
elif at == pa.int8():
spark_type = ByteType()
elif at == pa.int16():
spark_type = ShortType()
elif at == pa.int32():
spark_type = IntegerType()
elif at == pa.int64():
spark_type = LongType()
elif at == pa.float32():
spark_type = FloatType()
elif at == pa.float64():
spark_type = DoubleType()
elif type(at) == pa.DecimalType:
spark_type = DecimalType(precision=at.precision, scale=at.scale)
elif at == pa.string():
spark_type = StringType()
else:
raise TypeError("Unsupported type in conversion from Arrow: " + str(at))
return spark_type


def from_arrow_schema(arrow_schema):
""" Convert schema from Arrow to Spark.
"""
return StructType(
[StructField(field.name, from_arrow_type(field.type), nullable=field.nullable)
for field in arrow_schema])


def _cast_pandas_series_type(series, arrow_type):
""" Cast a pandas.Series to the given arrow_type
"""
to_pandas_dtype = arrow_type.to_pandas_dtype()
if series.dtype == to_pandas_dtype:
return series
else:
return series.fillna(0).astype(to_pandas_dtype, copy=False)


def _test():
import doctest
from pyspark.context import SparkContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@

package org.apache.spark.sql.api.python

import org.apache.spark.api.java.JavaRDD
import org.apache.spark.sql.{DataFrame, SQLContext}
import org.apache.spark.sql.catalyst.analysis.FunctionRegistry
import org.apache.spark.sql.catalyst.expressions.ExpressionInfo
import org.apache.spark.sql.catalyst.parser.CatalystSqlParser
import org.apache.spark.sql.execution.arrow.ArrowConverters
import org.apache.spark.sql.types.DataType

private[sql] object PythonSQLUtils {
Expand All @@ -29,4 +32,19 @@ private[sql] object PythonSQLUtils {
def listBuiltinFunctionInfos(): Array[ExpressionInfo] = {
FunctionRegistry.functionSet.flatMap(f => FunctionRegistry.builtin.lookupFunction(f)).toArray
}

/**
* Python Callable function to convert ArrowPayloads into a [[DataFrame]].
*
* @param payloadRDD A JavaRDD of ArrowPayloads.
* @param schemaString JSON Formatted Schema for ArrowPayloads.
* @param sqlContext The active [[SQLContext]].
* @return The converted [[DataFrame]].
*/
def arrowPayloadToDataFrame(
payloadRDD: JavaRDD[Array[Byte]],
schemaString: String,
sqlContext: SQLContext): DataFrame = {
ArrowConverters.toDataFrame(payloadRDD, schemaString, sqlContext)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import org.apache.arrow.vector.schema.ArrowRecordBatch
import org.apache.arrow.vector.util.ByteArrayReadableSeekableByteChannel

import org.apache.spark.TaskContext
import org.apache.spark.api.java.JavaRDD
import org.apache.spark.sql.{DataFrame, SQLContext}
import org.apache.spark.sql.catalyst.InternalRow
import org.apache.spark.sql.execution.vectorized.{ArrowColumnVector, ColumnarBatch, ColumnVector}
import org.apache.spark.sql.types._
Expand Down Expand Up @@ -203,4 +205,16 @@ private[sql] object ArrowConverters {
reader.close()
}
}

private[sql] def toDataFrame(
payloadRDD: JavaRDD[Array[Byte]],
schemaString: String,
sqlContext: SQLContext): DataFrame = {
val rdd = payloadRDD.rdd.mapPartitions { iter =>
val context = TaskContext.get()
ArrowConverters.fromPayloadIterator(iter.map(new ArrowPayload(_)), context)
}
val schema = DataType.fromJson(schemaString).asInstanceOf[StructType]
sqlContext.internalCreateDataFrame(rdd, schema)
}
}