diff --git a/presto-delta/pom.xml b/presto-delta/pom.xml
index 71ae91587b262..9f6aa8dc7e784 100644
--- a/presto-delta/pom.xml
+++ b/presto-delta/pom.xml
@@ -343,6 +343,11 @@
jakarta.servlet-api
test
+
+
+ com.facebook.airlift.drift
+ drift-codec
+
@@ -354,6 +359,7 @@
org.scala-lang:scala-library:jar
commons-io:commons-io:jar
+ com.facebook.airlift.drift:drift-codec:jar
diff --git a/presto-delta/src/test/java/com/facebook/presto/delta/TestDeltaTableHandle.java b/presto-delta/src/test/java/com/facebook/presto/delta/TestDeltaTableHandle.java
index ae6ae1b4bec21..0a0d387331950 100644
--- a/presto-delta/src/test/java/com/facebook/presto/delta/TestDeltaTableHandle.java
+++ b/presto-delta/src/test/java/com/facebook/presto/delta/TestDeltaTableHandle.java
@@ -16,6 +16,7 @@
import com.facebook.airlift.bootstrap.Bootstrap;
import com.facebook.airlift.json.JsonCodec;
import com.facebook.airlift.json.JsonModule;
+import com.facebook.drift.codec.guice.ThriftCodecModule;
import com.facebook.presto.block.BlockJsonSerde;
import com.facebook.presto.common.block.Block;
import com.facebook.presto.common.block.BlockEncoding;
@@ -24,6 +25,7 @@
import com.facebook.presto.common.type.StandardTypes;
import com.facebook.presto.common.type.Type;
import com.facebook.presto.common.type.TypeManager;
+import com.facebook.presto.connector.ConnectorManager;
import com.facebook.presto.metadata.FunctionAndTypeManager;
import com.facebook.presto.metadata.HandleJsonModule;
import com.facebook.presto.metadata.HandleResolver;
@@ -92,6 +94,8 @@ private JsonCodec getJsonCodec()
Module module = binder -> {
binder.install(new JsonModule());
binder.install(new HandleJsonModule());
+ binder.bind(ConnectorManager.class).toProvider(() -> null).in(Scopes.SINGLETON);
+ binder.install(new ThriftCodecModule());
configBinder(binder).bindConfig(FeaturesConfig.class);
FunctionAndTypeManager functionAndTypeManager = createTestFunctionAndTypeManager();
binder.bind(TypeManager.class).toInstance(functionAndTypeManager);
diff --git a/presto-docs/src/main/sphinx/admin/properties.rst b/presto-docs/src/main/sphinx/admin/properties.rst
index b1aea2b875fb1..0cff2b013b675 100644
--- a/presto-docs/src/main/sphinx/admin/properties.rst
+++ b/presto-docs/src/main/sphinx/admin/properties.rst
@@ -571,6 +571,24 @@ shared across all of the partitioned consumers. Increasing this value may
improve network throughput for data transferred between stages if the
network has high latency or if there are many nodes in the cluster.
+``use-connector-provided-serialization-codecs``
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+* **Type:** ``boolean``
+* **Default value:** ``false``
+
+Enables the use of custom connector-provided serialization codecs for handles.
+This feature allows connectors to use their own serialization format for
+handle objects (such as table handles, column handles, and splits) instead
+of standard JSON serialization.
+
+When enabled, connectors that provide a ``ConnectorCodecProvider`` with
+appropriate codecs will have their handles serialized using custom binary
+formats, which are then Base64-encoded for transport. Connectors without
+codec support automatically fall back to standard JSON serialization.
+Internal Presto handles (prefixed with ``$``) always use JSON serialization
+regardless of this setting.
+
.. _task-properties:
Task Properties
diff --git a/presto-hive/src/test/java/com/facebook/presto/hive/TestHiveSplit.java b/presto-hive/src/test/java/com/facebook/presto/hive/TestHiveSplit.java
index b5d7f6b3c8f43..395ef65a24096 100644
--- a/presto-hive/src/test/java/com/facebook/presto/hive/TestHiveSplit.java
+++ b/presto-hive/src/test/java/com/facebook/presto/hive/TestHiveSplit.java
@@ -16,6 +16,7 @@
import com.facebook.airlift.bootstrap.Bootstrap;
import com.facebook.airlift.json.JsonCodec;
import com.facebook.airlift.json.JsonModule;
+import com.facebook.drift.codec.guice.ThriftCodecModule;
import com.facebook.presto.block.BlockJsonSerde;
import com.facebook.presto.common.block.Block;
import com.facebook.presto.common.block.BlockEncoding;
@@ -23,6 +24,7 @@
import com.facebook.presto.common.block.BlockEncodingSerde;
import com.facebook.presto.common.type.Type;
import com.facebook.presto.common.type.TypeManager;
+import com.facebook.presto.connector.ConnectorManager;
import com.facebook.presto.hive.metastore.Column;
import com.facebook.presto.hive.metastore.Storage;
import com.facebook.presto.hive.metastore.StorageFormat;
@@ -153,8 +155,10 @@ private JsonCodec getJsonCodec()
{
Module module = binder -> {
binder.install(new JsonModule());
+ binder.install(new ThriftCodecModule());
binder.install(new HandleJsonModule());
configBinder(binder).bindConfig(FeaturesConfig.class);
+ binder.bind(ConnectorManager.class).toProvider(() -> null);
FunctionAndTypeManager functionAndTypeManager = createTestFunctionAndTypeManager();
binder.bind(TypeManager.class).toInstance(functionAndTypeManager);
jsonBinder(binder).addDeserializerBinding(Type.class).to(TypeDeserializer.class);
diff --git a/presto-main-base/src/main/java/com/facebook/presto/connector/ConnectorManager.java b/presto-main-base/src/main/java/com/facebook/presto/connector/ConnectorManager.java
index dc85eea82f0e5..de35836a2215a 100644
--- a/presto-main-base/src/main/java/com/facebook/presto/connector/ConnectorManager.java
+++ b/presto-main-base/src/main/java/com/facebook/presto/connector/ConnectorManager.java
@@ -393,6 +393,16 @@ private Connector createConnector(ConnectorId connectorId, ConnectorFactory fact
}
}
+ public Optional getConnectorCodecProvider(ConnectorId connectorId)
+ {
+ requireNonNull(connectorId, "connectorId is null");
+ MaterializedConnector materializedConnector = connectors.get(connectorId);
+ if (materializedConnector == null) {
+ return Optional.empty();
+ }
+ return materializedConnector.getConnectorCodecProvider();
+ }
+
private static class MaterializedConnector
{
private final ConnectorId connectorId;
diff --git a/presto-main-base/src/main/java/com/facebook/presto/index/IndexHandleJacksonModule.java b/presto-main-base/src/main/java/com/facebook/presto/index/IndexHandleJacksonModule.java
index 6536d9d63bf23..499f3f58088af 100644
--- a/presto-main-base/src/main/java/com/facebook/presto/index/IndexHandleJacksonModule.java
+++ b/presto-main-base/src/main/java/com/facebook/presto/index/IndexHandleJacksonModule.java
@@ -13,19 +13,47 @@
*/
package com.facebook.presto.index;
+import com.facebook.presto.connector.ConnectorManager;
import com.facebook.presto.metadata.AbstractTypedJacksonModule;
import com.facebook.presto.metadata.HandleResolver;
+import com.facebook.presto.spi.ConnectorCodec;
+import com.facebook.presto.spi.ConnectorId;
import com.facebook.presto.spi.ConnectorIndexHandle;
+import com.facebook.presto.spi.connector.ConnectorCodecProvider;
+import com.facebook.presto.sql.analyzer.FeaturesConfig;
import jakarta.inject.Inject;
+import jakarta.inject.Provider;
+
+import java.util.Optional;
+import java.util.function.Function;
public class IndexHandleJacksonModule
extends AbstractTypedJacksonModule
{
@Inject
- public IndexHandleJacksonModule(HandleResolver handleResolver)
+ public IndexHandleJacksonModule(
+ HandleResolver handleResolver,
+ Provider connectorManagerProvider,
+ FeaturesConfig featuresConfig)
+ {
+ super(ConnectorIndexHandle.class,
+ handleResolver::getId,
+ handleResolver::getIndexHandleClass,
+ featuresConfig.isUseConnectorProvidedSerializationCodecs(),
+ connectorId -> connectorManagerProvider.get()
+ .getConnectorCodecProvider(connectorId)
+ .flatMap(ConnectorCodecProvider::getConnectorIndexHandleCodec));
+ }
+
+ public IndexHandleJacksonModule(
+ HandleResolver handleResolver,
+ FeaturesConfig featuresConfig,
+ Function>> codecExtractor)
{
super(ConnectorIndexHandle.class,
handleResolver::getId,
- handleResolver::getIndexHandleClass);
+ handleResolver::getIndexHandleClass,
+ featuresConfig.isUseConnectorProvidedSerializationCodecs(),
+ codecExtractor);
}
}
diff --git a/presto-main-base/src/main/java/com/facebook/presto/metadata/AbstractTypedJacksonModule.java b/presto-main-base/src/main/java/com/facebook/presto/metadata/AbstractTypedJacksonModule.java
index 489bb076d764c..3176c95664419 100644
--- a/presto-main-base/src/main/java/com/facebook/presto/metadata/AbstractTypedJacksonModule.java
+++ b/presto-main-base/src/main/java/com/facebook/presto/metadata/AbstractTypedJacksonModule.java
@@ -13,6 +13,8 @@
*/
package com.facebook.presto.metadata;
+import com.facebook.presto.spi.ConnectorCodec;
+import com.facebook.presto.spi.ConnectorId;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
@@ -38,6 +40,7 @@
import com.google.common.cache.CacheBuilder;
import java.io.IOException;
+import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;
@@ -49,18 +52,38 @@ public abstract class AbstractTypedJacksonModule
extends SimpleModule
{
private static final String TYPE_PROPERTY = "@type";
+ private static final String DATA_PROPERTY = "customSerializedValue";
protected AbstractTypedJacksonModule(
Class baseClass,
Function nameResolver,
- Function> classResolver)
+ Function> classResolver,
+ boolean binarySerializationEnabled,
+ Function>> codecExtractor)
{
super(baseClass.getSimpleName() + "Module", Version.unknownVersion());
- TypeIdResolver typeResolver = new InternalTypeResolver<>(nameResolver, classResolver);
+ requireNonNull(baseClass, "baseClass is null");
+ requireNonNull(nameResolver, "nameResolver is null");
+ requireNonNull(classResolver, "classResolver is null");
+ requireNonNull(codecExtractor, "codecExtractor is null");
- addSerializer(baseClass, new InternalTypeSerializer<>(baseClass, typeResolver));
- addDeserializer(baseClass, new InternalTypeDeserializer<>(baseClass, typeResolver));
+ if (binarySerializationEnabled) {
+ // Use codec serialization
+ addSerializer(baseClass, new CodecSerializer<>(
+ TYPE_PROPERTY,
+ DATA_PROPERTY,
+ codecExtractor,
+ nameResolver,
+ new InternalTypeResolver<>(nameResolver, classResolver)));
+ addDeserializer(baseClass, new CodecDeserializer<>(TYPE_PROPERTY, DATA_PROPERTY, codecExtractor, classResolver));
+ }
+ else {
+ // Use legacy typed serialization
+ TypeIdResolver typeResolver = new InternalTypeResolver<>(nameResolver, classResolver);
+ addSerializer(baseClass, new InternalTypeSerializer<>(baseClass, typeResolver));
+ addDeserializer(baseClass, new InternalTypeDeserializer<>(baseClass, typeResolver));
+ }
}
private static class InternalTypeDeserializer
diff --git a/presto-main-base/src/main/java/com/facebook/presto/metadata/CodecDeserializer.java b/presto-main-base/src/main/java/com/facebook/presto/metadata/CodecDeserializer.java
new file mode 100644
index 0000000000000..1e9d5a8785316
--- /dev/null
+++ b/presto-main-base/src/main/java/com/facebook/presto/metadata/CodecDeserializer.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed 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 com.facebook.presto.metadata;
+
+import com.facebook.presto.spi.ConnectorCodec;
+import com.facebook.presto.spi.ConnectorId;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.core.TreeNode;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import java.io.IOException;
+import java.util.Base64;
+import java.util.Optional;
+import java.util.function.Function;
+
+import static java.util.Objects.requireNonNull;
+
+class CodecDeserializer
+ extends JsonDeserializer
+{
+ private final Function> classResolver;
+ private final Function>> codecExtractor;
+ private final String typePropertyName;
+ private final String dataPropertyName;
+
+ public CodecDeserializer(
+ String typePropertyName,
+ String dataPropertyName,
+ Function>> codecExtractor,
+ Function> classResolver)
+ {
+ this.classResolver = requireNonNull(classResolver, "classResolver is null");
+ this.codecExtractor = requireNonNull(codecExtractor, "codecExtractor is null");
+ this.typePropertyName = requireNonNull(typePropertyName, "typePropertyName is null");
+ this.dataPropertyName = requireNonNull(dataPropertyName, "dataPropertyName is null");
+ }
+
+ @Override
+ public T deserialize(JsonParser parser, DeserializationContext context)
+ throws IOException
+ {
+ if (parser.getCurrentToken() == JsonToken.VALUE_NULL) {
+ return null;
+ }
+
+ if (parser.getCurrentToken() != JsonToken.START_OBJECT) {
+ throw new IOException("Expected START_OBJECT, got " + parser.getCurrentToken());
+ }
+
+ // Parse the JSON tree
+ TreeNode tree = parser.readValueAsTree();
+
+ if (tree instanceof ObjectNode) {
+ ObjectNode node = (ObjectNode) tree;
+
+ // Get the @type field
+ if (!node.has(typePropertyName)) {
+ throw new IOException("Missing " + typePropertyName + " field");
+ }
+ String connectorIdString = node.get(typePropertyName).asText();
+ // Check if @data field is present (binary serialization)
+ if (node.has(dataPropertyName)) {
+ // Binary data is present, we need a codec to deserialize it
+ // Special handling for internal handles like "$remote"
+ if (!connectorIdString.startsWith("$")) {
+ ConnectorId connectorId = new ConnectorId(connectorIdString);
+ Optional> codec = codecExtractor.apply(connectorId);
+ if (codec.isPresent()) {
+ String base64Data = node.get(dataPropertyName).asText();
+ byte[] data = Base64.getDecoder().decode(base64Data);
+ return codec.get().deserialize(data);
+ }
+ }
+ // @data field present but no codec available or internal handle
+ throw new IOException("Type " + connectorIdString + " has binary data (" + dataPropertyName + " field) but no codec available to deserialize it");
+ }
+
+ // No @data field - use standard JSON deserialization
+ Class extends T> handleClass = classResolver.apply(connectorIdString);
+
+ // Remove the @type field and deserialize the remaining content
+ node.remove(typePropertyName);
+ return context.readTreeAsValue(node, handleClass);
+ }
+
+ throw new IOException("Unable to deserialize");
+ }
+
+ @Override
+ public T deserializeWithType(JsonParser p, DeserializationContext ctxt,
+ TypeDeserializer typeDeserializer)
+ throws IOException
+ {
+ // We handle the type ourselves
+ return deserialize(p, ctxt);
+ }
+
+ @Override
+ public T deserializeWithType(JsonParser p, DeserializationContext ctxt,
+ TypeDeserializer typeDeserializer, T intoValue)
+ throws IOException
+ {
+ // We handle the type ourselves
+ return deserialize(p, ctxt);
+ }
+}
diff --git a/presto-main-base/src/main/java/com/facebook/presto/metadata/CodecSerializer.java b/presto-main-base/src/main/java/com/facebook/presto/metadata/CodecSerializer.java
new file mode 100644
index 0000000000000..9948e95a9c323
--- /dev/null
+++ b/presto-main-base/src/main/java/com/facebook/presto/metadata/CodecSerializer.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed 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 com.facebook.presto.metadata;
+
+import com.facebook.presto.spi.ConnectorCodec;
+import com.facebook.presto.spi.ConnectorId;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
+import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
+import com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeSerializer;
+import com.fasterxml.jackson.databind.ser.BeanSerializerFactory;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+
+import java.io.IOException;
+import java.util.Base64;
+import java.util.Optional;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Function;
+
+import static com.google.common.base.Throwables.throwIfInstanceOf;
+import static java.util.Objects.requireNonNull;
+
+class CodecSerializer
+ extends JsonSerializer
+{
+ private final Function nameResolver;
+ private final Function>> codecExtractor;
+ private final TypeIdResolver typeResolver;
+ private final TypeSerializer typeSerializer;
+ private final Cache, JsonSerializer