This is an automated email from the ASF dual-hosted git repository.
wchevreuil pushed a commit to branch HBASE-30189
in repository https://gitbox.apache.org/repos/asf/hbase-connectors.git
The following commit(s) were added to refs/heads/HBASE-30189 by this push:
new 2c15325 Port pure utility classes with no Spark SQL dependency (#159)
2c15325 is described below
commit 2c15325ffe431cb393c58f6bad42859215e808fb
Author: Wellington Ramos Chevreuil <[email protected]>
AuthorDate: Wed Aug 26 12:02:09 2026 +0100
Port pure utility classes with no Spark SQL dependency (#159)
Co-authored-by: Claude Code (claude-opus-4-6) <[email protected]>
Signed-off-by: Peter Somogyi <[email protected]>
---
.../hbase/spark/datasources/NaiveEncoder.scala | 2 +-
.../spark/datasources/NaiveEncoderSuite.scala | 106 +++++++++++++++++++
.../hadoop/hbase/spark/HBaseConnectionCache.scala | 5 +-
.../hadoop/hbase/spark/datasources/Bound.scala | 116 +++++++++++++++++++++
.../spark/datasources/DataTypeParserWrapper.scala | 32 ++++++
.../hbase/spark/datasources/HBaseSparkConf.scala | 76 ++++++++++++++
.../hbase/spark/datasources/NaiveEncoder.scala | 51 +++------
.../datasources/SerializableConfiguration.scala | 45 ++++++++
.../hadoop/hbase/spark/datasources/package.scala | 45 ++++++++
.../spark/datasources/NaiveEncoderSuite.scala | 112 ++++++++++++++++++++
10 files changed, 551 insertions(+), 39 deletions(-)
diff --git
a/spark/hbase-spark/src/main/scala/org/apache/hadoop/hbase/spark/datasources/NaiveEncoder.scala
b/spark/hbase-spark/src/main/scala/org/apache/hadoop/hbase/spark/datasources/NaiveEncoder.scala
index b54d279..27adb17 100644
---
a/spark/hbase-spark/src/main/scala/org/apache/hadoop/hbase/spark/datasources/NaiveEncoder.scala
+++
b/spark/hbase-spark/src/main/scala/org/apache/hadoop/hbase/spark/datasources/NaiveEncoder.scala
@@ -243,7 +243,7 @@ class NaiveEncoder extends BytesEncoder with Logging {
Bytes.putDouble(result, 1, value.asInstanceOf[Double])
result
case BinaryType =>
- val v = value.asInstanceOf[Array[Bytes]]
+ val v = value.asInstanceOf[Array[Byte]]
val result = new Array[Byte](v.length + 1)
result(0) = BinaryEnc
System.arraycopy(v, 0, result, 1, v.length)
diff --git
a/spark/hbase-spark/src/test/scala/org/apache/hadoop/hbase/spark/datasources/NaiveEncoderSuite.scala
b/spark/hbase-spark/src/test/scala/org/apache/hadoop/hbase/spark/datasources/NaiveEncoderSuite.scala
new file mode 100644
index 0000000..374f09f
--- /dev/null
+++
b/spark/hbase-spark/src/test/scala/org/apache/hadoop/hbase/spark/datasources/NaiveEncoderSuite.scala
@@ -0,0 +1,106 @@
+/*
+ * 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.
+ */
+package org.apache.hadoop.hbase.spark.datasources
+
+import org.apache.hadoop.hbase.util.Bytes
+import org.apache.spark.sql.types._
+import org.scalatest.FunSuite
+
+class NaiveEncoderSuite extends FunSuite {
+
+ val encoder = new NaiveEncoder
+
+ test("encode BinaryType should preserve bytes") {
+ val input = Array[Byte](1, 2, 3, 4, 5)
+ val encoded = encoder.encode(BinaryType, input)
+ assert(encoded.length == input.length + 1)
+ assert(encoded(0) == encoder.BinaryEnc)
+ assert(encoded.slice(1, encoded.length).sameElements(input))
+ }
+
+ test("encode IntegerType") {
+ val encoded = encoder.encode(IntegerType, 42: Int)
+ assert(encoded.length == Bytes.SIZEOF_INT + 1)
+ assert(encoded(0) == encoder.IntEnc)
+ assert(Bytes.toInt(encoded, 1) == 42)
+ }
+
+ test("encode LongType") {
+ val encoded = encoder.encode(LongType, 123456789L)
+ assert(encoded.length == Bytes.SIZEOF_LONG + 1)
+ assert(encoded(0) == encoder.LongEnc)
+ assert(Bytes.toLong(encoded, 1) == 123456789L)
+ }
+
+ test("encode DoubleType") {
+ val encoded = encoder.encode(DoubleType, 3.14d)
+ assert(encoded.length == Bytes.SIZEOF_DOUBLE + 1)
+ assert(encoded(0) == encoder.DoubleEnc)
+ assert(Bytes.toDouble(encoded, 1) == 3.14d)
+ }
+
+ test("encode FloatType") {
+ val encoded = encoder.encode(FloatType, 2.5f)
+ assert(encoded.length == Bytes.SIZEOF_FLOAT + 1)
+ assert(encoded(0) == encoder.FloatEnc)
+ assert(Bytes.toFloat(encoded, 1) == 2.5f)
+ }
+
+ test("encode ShortType") {
+ val encoded = encoder.encode(ShortType, 7: Short)
+ assert(encoded.length == Bytes.SIZEOF_SHORT + 1)
+ assert(encoded(0) == encoder.ShortEnc)
+ assert(Bytes.toShort(encoded, 1) == 7)
+ }
+
+ test("encode StringType") {
+ val encoded = encoder.encode(StringType, "hello")
+ val expected = Bytes.toBytes("hello")
+ assert(encoded.length == expected.length + 1)
+ assert(encoded(0) == encoder.StringEnc)
+ assert(encoded.slice(1, encoded.length).sameElements(expected))
+ }
+
+ test("encode BooleanType true") {
+ val encoded = encoder.encode(BooleanType, true)
+ assert(encoded.length == Bytes.SIZEOF_BOOLEAN + 1)
+ assert(encoded(0) == encoder.BooleanEnc)
+ assert(encoded(1) == (-1: Byte))
+ }
+
+ test("encode BooleanType false") {
+ val encoded = encoder.encode(BooleanType, false)
+ assert(encoded.length == Bytes.SIZEOF_BOOLEAN + 1)
+ assert(encoded(0) == encoder.BooleanEnc)
+ assert(encoded(1) == (0: Byte))
+ }
+
+ test("filter with IntegerType GreaterThan") {
+ val encoded = encoder.encode(IntegerType, 10: Int)
+ val input = Bytes.toBytes(20: Int)
+ assert(
+ encoder.filter(input, 0, input.length, encoded, 0, encoded.length,
JavaBytesEncoder.Greater))
+ }
+
+ test("filter with IntegerType LessThan") {
+ val encoded = encoder.encode(IntegerType, 10: Int)
+ val input = Bytes.toBytes(5: Int)
+ assert(
+ encoder.filter(input, 0, input.length, encoded, 0, encoded.length,
JavaBytesEncoder.Less))
+ }
+}
diff --git
a/spark4/hbase-spark4/src/main/scala/org/apache/hadoop/hbase/spark/HBaseConnectionCache.scala
b/spark4/hbase-spark4/src/main/scala/org/apache/hadoop/hbase/spark/HBaseConnectionCache.scala
index 70b4c62..3f1cf3a 100644
---
a/spark4/hbase-spark4/src/main/scala/org/apache/hadoop/hbase/spark/HBaseConnectionCache.scala
+++
b/spark4/hbase-spark4/src/main/scala/org/apache/hadoop/hbase/spark/HBaseConnectionCache.scala
@@ -29,6 +29,7 @@ import org.apache.hadoop.hbase.client.Table
import org.apache.hadoop.hbase.ipc.RpcControllerFactory
import org.apache.hadoop.hbase.security.User
import org.apache.hadoop.hbase.security.UserProvider
+import org.apache.hadoop.hbase.spark.datasources.HBaseSparkConf
import org.apache.yetus.audience.InterfaceAudience
import scala.collection.mutable
@@ -39,9 +40,7 @@ private[spark] object HBaseConnectionCache extends Logging {
val cacheStat = HBaseConnectionCacheStat(0, 0, 0)
- // in milliseconds
- private final val DEFAULT_TIME_OUT: Long = 10 * 60 * 1000
- private var timeout = DEFAULT_TIME_OUT
+ private var timeout: Long = HBaseSparkConf.DEFAULT_CONNECTION_CLOSE_DELAY
private var closed: Boolean = false
var housekeepingThread = new Thread(new Runnable {
diff --git
a/spark4/hbase-spark4/src/main/scala/org/apache/hadoop/hbase/spark/datasources/Bound.scala
b/spark4/hbase-spark4/src/main/scala/org/apache/hadoop/hbase/spark/datasources/Bound.scala
new file mode 100644
index 0000000..7b12982
--- /dev/null
+++
b/spark4/hbase-spark4/src/main/scala/org/apache/hadoop/hbase/spark/datasources/Bound.scala
@@ -0,0 +1,116 @@
+/*
+ * 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.
+ */
+package org.apache.hadoop.hbase.spark.datasources
+
+import org.apache.yetus.audience.InterfaceAudience
+
+/**
+ * The Bound represent the boudary for the scan
+ *
+ * @param b The byte array of the bound
+ * @param inc inclusive or not.
+ */
[email protected]
+case class Bound(b: Array[Byte], inc: Boolean)
+
[email protected]
+case class Range(lower: Option[Bound], upper: Option[Bound])
+
[email protected]
+object Range {
+ def apply(region: HBaseRegion): Range = {
+ Range(
+ region.start.map(Bound(_, true)),
+ if (region.end.get.length == 0) {
+ None
+ } else {
+ region.end.map(Bound(_, false))
+ })
+ }
+}
+
[email protected]
+object Ranges {
+ def and(r: Range, rs: Seq[Range]): Seq[Range] = {
+ rs.flatMap { s =>
+ val lower = s.lower
+ .map { x =>
+ r.lower
+ .map { y =>
+ if (ord.compare(x.b, y.b) < 0) {
+ Some(y)
+ } else {
+ Some(x)
+ }
+ }
+ .getOrElse(Some(x))
+ }
+ .getOrElse(r.lower)
+
+ val upper = s.upper
+ .map { x =>
+ r.upper
+ .map { y =>
+ if (ord.compare(x.b, y.b) >= 0) {
+ Some(y)
+ } else {
+ Some(x)
+ }
+ }
+ .getOrElse(Some(x))
+ }
+ .getOrElse(r.upper)
+
+ val c = lower
+ .map { x =>
+ upper
+ .map { y =>
+ ord.compare(x.b, y.b)
+ }
+ .getOrElse(-1)
+ }
+ .getOrElse(-1)
+ if (c < 0) {
+ Some(Range(lower, upper))
+ } else {
+ None
+ }
+ }
+ }
+}
+
[email protected]
+object Points {
+ def and(r: Range, ps: Seq[Array[Byte]]): Seq[Array[Byte]] = {
+ ps.flatMap { p =>
+ if (ord.compare(r.lower.get.b, p) <= 0) {
+ if (r.upper.isDefined) {
+ if (ord.compare(r.upper.get.b, p) > 0) {
+ Some(p)
+ } else {
+ None
+ }
+ } else {
+ Some(p)
+ }
+ } else {
+ None
+ }
+ }
+ }
+}
diff --git
a/spark4/hbase-spark4/src/main/scala/org/apache/hadoop/hbase/spark/datasources/DataTypeParserWrapper.scala
b/spark4/hbase-spark4/src/main/scala/org/apache/hadoop/hbase/spark/datasources/DataTypeParserWrapper.scala
new file mode 100644
index 0000000..936276d
--- /dev/null
+++
b/spark4/hbase-spark4/src/main/scala/org/apache/hadoop/hbase/spark/datasources/DataTypeParserWrapper.scala
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+package org.apache.hadoop.hbase.spark.datasources
+
+import org.apache.spark.sql.catalyst.parser.CatalystSqlParser
+import org.apache.spark.sql.types.DataType
+import org.apache.yetus.audience.InterfaceAudience
+
[email protected]
+trait DataTypeParser {
+ def parse(dataTypeString: String): DataType
+}
+
[email protected]
+object DataTypeParserWrapper extends DataTypeParser {
+ def parse(dataTypeString: String): DataType =
CatalystSqlParser.parseDataType(dataTypeString)
+}
diff --git
a/spark4/hbase-spark4/src/main/scala/org/apache/hadoop/hbase/spark/datasources/HBaseSparkConf.scala
b/spark4/hbase-spark4/src/main/scala/org/apache/hadoop/hbase/spark/datasources/HBaseSparkConf.scala
new file mode 100644
index 0000000..b236af0
--- /dev/null
+++
b/spark4/hbase-spark4/src/main/scala/org/apache/hadoop/hbase/spark/datasources/HBaseSparkConf.scala
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+package org.apache.hadoop.hbase.spark.datasources
+
+import org.apache.yetus.audience.InterfaceAudience;
+
+/**
+ * This is the hbase configuration. User can either set them in SparkConf,
which
+ * will take effect globally, or configure it per table, which will overwrite
the value
+ * set in SparkConf. If not set, the default value will take effect.
+ */
[email protected]
+object HBaseSparkConf {
+
+ /**
+ * Set to false to disable server-side caching of blocks for this scan,
+ * false by default, since full table scans generate too much BC churn.
+ */
+ val QUERY_CACHEBLOCKS = "hbase.spark.query.cacheblocks"
+ val DEFAULT_QUERY_CACHEBLOCKS = false
+
+ /** The number of rows for caching that will be passed to scan. */
+ val QUERY_CACHEDROWS = "hbase.spark.query.cachedrows"
+
+ /** Set the maximum number of values to return for each call to next() in
scan. */
+ val QUERY_BATCHSIZE = "hbase.spark.query.batchsize"
+
+ /** The number of BulkGets send to HBase. */
+ val BULKGET_SIZE = "hbase.spark.bulkget.size"
+ val DEFAULT_BULKGET_SIZE = 1000
+
+ /** Set to specify the location of hbase configuration file. */
+ val HBASE_CONFIG_LOCATION = "hbase.spark.config.location"
+
+ /** Set to specify whether create or use latest cached HBaseContext */
+ val USE_HBASECONTEXT = "hbase.spark.use.hbasecontext"
+ val DEFAULT_USE_HBASECONTEXT = true
+
+ /** Pushdown the filter to data source engine to increase the performance of
queries. */
+ val PUSHDOWN_COLUMN_FILTER = "hbase.spark.pushdown.columnfilter"
+ val DEFAULT_PUSHDOWN_COLUMN_FILTER = true
+
+ /** Class name of the encoder, which encode data types from Spark to HBase
bytes. */
+ val QUERY_ENCODER = "hbase.spark.query.encoder"
+ val DEFAULT_QUERY_ENCODER = classOf[NaiveEncoder].getCanonicalName
+
+ /** The timestamp used to filter columns with a specific timestamp. */
+ val TIMESTAMP = "hbase.spark.query.timestamp"
+
+ /** The starting timestamp used to filter columns with a specific range of
versions. */
+ val TIMERANGE_START = "hbase.spark.query.timerange.start"
+
+ /** The ending timestamp used to filter columns with a specific range of
versions. */
+ val TIMERANGE_END = "hbase.spark.query.timerange.end"
+
+ /** The maximum number of version to return. */
+ val MAX_VERSIONS = "hbase.spark.query.maxVersions"
+
+ /** Delayed time to close hbase-spark connection when no reference to this
connection, in milliseconds. */
+ val DEFAULT_CONNECTION_CLOSE_DELAY = 10 * 60 * 1000
+}
\ No newline at end of file
diff --git
a/spark/hbase-spark/src/main/scala/org/apache/hadoop/hbase/spark/datasources/NaiveEncoder.scala
b/spark4/hbase-spark4/src/main/scala/org/apache/hadoop/hbase/spark/datasources/NaiveEncoder.scala
similarity index 86%
copy from
spark/hbase-spark/src/main/scala/org/apache/hadoop/hbase/spark/datasources/NaiveEncoder.scala
copy to
spark4/hbase-spark4/src/main/scala/org/apache/hadoop/hbase/spark/datasources/NaiveEncoder.scala
index b54d279..2e32fae 100644
---
a/spark/hbase-spark/src/main/scala/org/apache/hadoop/hbase/spark/datasources/NaiveEncoder.scala
+++
b/spark4/hbase-spark4/src/main/scala/org/apache/hadoop/hbase/spark/datasources/NaiveEncoder.scala
@@ -16,26 +16,9 @@
* limitations under the License.
*/
package org.apache.hadoop.hbase.spark.datasources
-/*
- * 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.
- */
import org.apache.hadoop.hbase.spark.Logging
import
org.apache.hadoop.hbase.spark.datasources.JavaBytesEncoder.JavaBytesEncoder
-import org.apache.hadoop.hbase.spark.hbase._
import org.apache.hadoop.hbase.util.Bytes
import org.apache.spark.sql.types._
import org.apache.spark.unsafe.types.UTF8String
@@ -55,16 +38,16 @@ class NaiveEncoder extends BytesEncoder with Logging {
code += 1
(code - 1).asInstanceOf[Byte]
}
- val BooleanEnc = nextCode
- val ShortEnc = nextCode
- val IntEnc = nextCode
- val LongEnc = nextCode
- val FloatEnc = nextCode
- val DoubleEnc = nextCode
- val StringEnc = nextCode
- val BinaryEnc = nextCode
- val TimestampEnc = nextCode
- val UnknownEnc = nextCode
+ val BooleanEnc: Byte = nextCode
+ val ShortEnc: Byte = nextCode
+ val IntEnc: Byte = nextCode
+ val LongEnc: Byte = nextCode
+ val FloatEnc: Byte = nextCode
+ val DoubleEnc: Byte = nextCode
+ val StringEnc: Byte = nextCode
+ val BinaryEnc: Byte = nextCode
+ val TimestampEnc: Byte = nextCode
+ val UnknownEnc: Byte = nextCode
/**
* Evaluate the java primitive type and return the BoundRanges. For one
value, it may have
@@ -79,7 +62,7 @@ class NaiveEncoder extends BytesEncoder with Logging {
* But the order of negative number is the reverse order of byte array.
Please refer to IEEE-754
* and https://en.wikipedia.org/wiki/Single-precision_floating-point_format
*/
- def ranges(in: Any): Option[BoundRanges] = in match {
+ override def ranges(in: Any): Option[BoundRanges] = in match {
case a: Integer =>
val b = Bytes.toBytes(a)
if (a >= 0) {
@@ -203,8 +186,8 @@ class NaiveEncoder extends BytesEncoder with Logging {
* encode the data type into byte array. Note that it is a naive
implementation with the
* data type byte appending to the head of the serialized byte array.
*
- * @param dt: The data type of the input
- * @param value: the value of the input
+ * @param dt : The data type of the input
+ * @param value : the value of the input
* @return the byte array with the first byte indicating the data type.
*/
override def encode(dt: DataType, value: Any): Array[Byte] = {
@@ -213,8 +196,8 @@ class NaiveEncoder extends BytesEncoder with Logging {
val result = new Array[Byte](Bytes.SIZEOF_BOOLEAN + 1)
result(0) = BooleanEnc
value.asInstanceOf[Boolean] match {
- case true => result(1) = -1: Byte
- case false => result(1) = 0: Byte
+ case true => result(1) = (-1: Byte)
+ case false => result(1) = (0: Byte)
}
result
case ShortType =>
@@ -243,7 +226,7 @@ class NaiveEncoder extends BytesEncoder with Logging {
Bytes.putDouble(result, 1, value.asInstanceOf[Double])
result
case BinaryType =>
- val v = value.asInstanceOf[Array[Bytes]]
+ val v = value.asInstanceOf[Array[Byte]]
val result = new Array[Byte](v.length + 1)
result(0) = BinaryEnc
System.arraycopy(v, 0, result, 1, v.length)
@@ -293,8 +276,6 @@ class NaiveEncoder extends BytesEncoder with Logging {
val value = Bytes.toDouble(filterBytes, offset2 + 1)
compare(in.compareTo(value), ops)
case _ =>
- // for String, Byte, Binary, Boolean and other types
- // we can use the order of byte array directly.
compare(
Bytes.compareTo(input, offset1, length1, filterBytes, offset2 + 1,
length2 - 1),
ops)
diff --git
a/spark4/hbase-spark4/src/main/scala/org/apache/hadoop/hbase/spark/datasources/SerializableConfiguration.scala
b/spark4/hbase-spark4/src/main/scala/org/apache/hadoop/hbase/spark/datasources/SerializableConfiguration.scala
new file mode 100644
index 0000000..552abda
--- /dev/null
+++
b/spark4/hbase-spark4/src/main/scala/org/apache/hadoop/hbase/spark/datasources/SerializableConfiguration.scala
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+package org.apache.hadoop.hbase.spark.datasources
+
+import java.io.{IOException, ObjectInputStream, ObjectOutputStream}
+import org.apache.hadoop.conf.Configuration
+import org.apache.yetus.audience.InterfaceAudience
+import scala.util.control.NonFatal
+
[email protected]
+class SerializableConfiguration(@transient var value: Configuration) extends
Serializable {
+ private def writeObject(out: ObjectOutputStream): Unit = tryOrIOException {
+ out.defaultWriteObject()
+ value.write(out)
+ }
+
+ private def readObject(in: ObjectInputStream): Unit = tryOrIOException {
+ value = new Configuration(false)
+ value.readFields(in)
+ }
+
+ private def tryOrIOException(block: => Unit): Unit = {
+ try {
+ block
+ } catch {
+ case e: IOException => throw e
+ case NonFatal(t) => throw new IOException(t)
+ }
+ }
+}
diff --git
a/spark4/hbase-spark4/src/main/scala/org/apache/hadoop/hbase/spark/datasources/package.scala
b/spark4/hbase-spark4/src/main/scala/org/apache/hadoop/hbase/spark/datasources/package.scala
new file mode 100644
index 0000000..2292422
--- /dev/null
+++
b/spark4/hbase-spark4/src/main/scala/org/apache/hadoop/hbase/spark/datasources/package.scala
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+package org.apache.hadoop.hbase.spark
+
+import org.apache.hadoop.hbase.util.Bytes
+import org.apache.spark.Partition
+import org.apache.yetus.audience.InterfaceAudience
+import scala.math.Ordering
+
+package object datasources {
+ type HBaseType = Array[Byte]
+ def bytesMin: Array[Byte] = new Array[Byte](0)
+ def bytesMax: Array[Byte] = null
+ val ByteMax: Byte = -1.asInstanceOf[Byte]
+ val ByteMin: Byte = 0.asInstanceOf[Byte]
+ val ord: Ordering[HBaseType] = new Ordering[HBaseType] {
+ def compare(x: Array[Byte], y: Array[Byte]): Int = {
+ Bytes.compareTo(x, y)
+ }
+ }
+ implicit val order: Ordering[HBaseType] = ord
+
+ @InterfaceAudience.Private
+ case class HBaseRegion(
+ override val index: Int,
+ start: Option[HBaseType] = None,
+ end: Option[HBaseType] = None,
+ server: Option[String] = None)
+ extends Partition
+}
diff --git
a/spark4/hbase-spark4/src/test/scala/org/apache/hadoop/hbase/spark/datasources/NaiveEncoderSuite.scala
b/spark4/hbase-spark4/src/test/scala/org/apache/hadoop/hbase/spark/datasources/NaiveEncoderSuite.scala
new file mode 100644
index 0000000..2828f17
--- /dev/null
+++
b/spark4/hbase-spark4/src/test/scala/org/apache/hadoop/hbase/spark/datasources/NaiveEncoderSuite.scala
@@ -0,0 +1,112 @@
+/*
+ * 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.
+ */
+package org.apache.hadoop.hbase.spark.datasources
+
+import org.apache.hadoop.hbase.util.Bytes
+import org.apache.spark.sql.types._
+import org.scalatest.funsuite.AnyFunSuite
+
+class NaiveEncoderSuite extends AnyFunSuite {
+
+ val encoder = new NaiveEncoder
+
+ test("encode BinaryType should preserve bytes") {
+ val input = Array[Byte](1, 2, 3, 4, 5)
+ val encoded = encoder.encode(BinaryType, input)
+ assert(encoded.length == input.length + 1)
+ assert(encoded(0) == encoder.BinaryEnc)
+ assert(encoded.slice(1, encoded.length).sameElements(input))
+ }
+
+ test("encode IntegerType") {
+ val encoded = encoder.encode(IntegerType, 42: Int)
+ assert(encoded.length == Bytes.SIZEOF_INT + 1)
+ assert(encoded(0) == encoder.IntEnc)
+ assert(Bytes.toInt(encoded, 1) == 42)
+ }
+
+ test("encode LongType") {
+ val encoded = encoder.encode(LongType, 123456789L)
+ assert(encoded.length == Bytes.SIZEOF_LONG + 1)
+ assert(encoded(0) == encoder.LongEnc)
+ assert(Bytes.toLong(encoded, 1) == 123456789L)
+ }
+
+ test("encode DoubleType") {
+ val encoded = encoder.encode(DoubleType, 3.14d)
+ assert(encoded.length == Bytes.SIZEOF_DOUBLE + 1)
+ assert(encoded(0) == encoder.DoubleEnc)
+ assert(Bytes.toDouble(encoded, 1) == 3.14d)
+ }
+
+ test("encode FloatType") {
+ val encoded = encoder.encode(FloatType, 2.5f)
+ assert(encoded.length == Bytes.SIZEOF_FLOAT + 1)
+ assert(encoded(0) == encoder.FloatEnc)
+ assert(Bytes.toFloat(encoded, 1) == 2.5f)
+ }
+
+ test("encode ShortType") {
+ val encoded = encoder.encode(ShortType, 7: Short)
+ assert(encoded.length == Bytes.SIZEOF_SHORT + 1)
+ assert(encoded(0) == encoder.ShortEnc)
+ assert(Bytes.toShort(encoded, 1) == 7)
+ }
+
+ test("encode StringType") {
+ val encoded = encoder.encode(StringType, "hello")
+ val expected = Bytes.toBytes("hello")
+ assert(encoded.length == expected.length + 1)
+ assert(encoded(0) == encoder.StringEnc)
+ assert(encoded.slice(1, encoded.length).sameElements(expected))
+ }
+
+ test("encode BooleanType true") {
+ val encoded = encoder.encode(BooleanType, true)
+ assert(encoded.length == Bytes.SIZEOF_BOOLEAN + 1)
+ assert(encoded(0) == encoder.BooleanEnc)
+ assert(encoded(1) == (-1: Byte))
+ }
+
+ test("encode BooleanType false") {
+ val encoded = encoder.encode(BooleanType, false)
+ assert(encoded.length == Bytes.SIZEOF_BOOLEAN + 1)
+ assert(encoded(0) == encoder.BooleanEnc)
+ assert(encoded(1) == (0: Byte))
+ }
+
+ test("filter with IntegerType GreaterThan") {
+ val encoded = encoder.encode(IntegerType, 10: Int)
+ val input = Bytes.toBytes(20: Int)
+ assert(
+ encoder.filter(
+ input, 0, input.length,
+ encoded, 0, encoded.length,
+ JavaBytesEncoder.Greater))
+ }
+
+ test("filter with IntegerType LessThan") {
+ val encoded = encoder.encode(IntegerType, 10: Int)
+ val input = Bytes.toBytes(5: Int)
+ assert(
+ encoder.filter(
+ input, 0, input.length,
+ encoded, 0, encoded.length,
+ JavaBytesEncoder.Less))
+ }
+}