Maintains a single {@link SeekableInputStream}. If the stream implements {@link
+ * VectoredReadable}, reads use {@link VectoredReadable#preadFully} which is thread-safe. Otherwise,
+ * reads are synchronized to protect seek+read sequences.
+ */
+public class MosaicInputFileAdapter implements InputFile, Closeable {
+
+ private final Path path;
+ private final SeekableInputStream in;
+ private final VectoredReadable vectoredReadable;
+
+ public MosaicInputFileAdapter(FileIO fileIO, Path path) throws IOException {
+ this.path = path;
+ this.in = fileIO.newInputStream(path);
+ this.vectoredReadable = in instanceof VectoredReadable ? (VectoredReadable) in : null;
+ }
+
+ @Override
+ public void readFully(long position, byte[] buffer, int offset, int length) throws IOException {
+ if (vectoredReadable != null) {
+ vectoredReadable.preadFully(position, buffer, offset, length);
+ } else {
+ synchronized (in) {
+ in.seek(position);
+ int remaining = length;
+ int off = offset;
+ while (remaining > 0) {
+ int read = in.read(buffer, off, remaining);
+ if (read < 0) {
+ throw new EOFException(
+ "Reached end of file while reading "
+ + path
+ + " at position "
+ + position);
+ }
+ off += read;
+ remaining -= read;
+ }
+ }
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ in.close();
+ }
+}
diff --git a/paimon-mosaic/src/main/java/org/apache/paimon/format/mosaic/MosaicObjects.java b/paimon-mosaic/src/main/java/org/apache/paimon/format/mosaic/MosaicObjects.java
new file mode 100644
index 000000000000..54d15c43c09e
--- /dev/null
+++ b/paimon-mosaic/src/main/java/org/apache/paimon/format/mosaic/MosaicObjects.java
@@ -0,0 +1,103 @@
+/*
+ * 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.paimon.format.mosaic;
+
+import org.apache.paimon.data.BinaryString;
+import org.apache.paimon.data.Decimal;
+import org.apache.paimon.data.Timestamp;
+import org.apache.paimon.types.DataType;
+import org.apache.paimon.types.DecimalType;
+import org.apache.paimon.types.LocalZonedTimestampType;
+import org.apache.paimon.types.TimestampType;
+
+import javax.annotation.Nullable;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+
+/** Converts Mosaic's byte[] statistics to Paimon objects. */
+public class MosaicObjects {
+
+ @Nullable
+ public static Object convertStatsValue(byte[] bytes, DataType dataType) {
+ if (bytes == null) {
+ return null;
+ }
+ switch (dataType.getTypeRoot()) {
+ case CHAR:
+ case VARCHAR:
+ return BinaryString.fromBytes(bytes);
+ case BINARY:
+ case VARBINARY:
+ return bytes;
+ default:
+ break;
+ }
+ if (bytes.length == 0) {
+ return null;
+ }
+ ByteBuffer buf = ByteBuffer.wrap(bytes);
+ switch (dataType.getTypeRoot()) {
+ case BOOLEAN:
+ return bytes[0] != 0;
+ case TINYINT:
+ return bytes[0];
+ case SMALLINT:
+ return buf.getShort();
+ case INTEGER:
+ case DATE:
+ case TIME_WITHOUT_TIME_ZONE:
+ return buf.getInt();
+ case BIGINT:
+ return buf.getLong();
+ case FLOAT:
+ return buf.getFloat();
+ case DOUBLE:
+ return buf.getDouble();
+ case DECIMAL:
+ DecimalType decimalType = (DecimalType) dataType;
+ BigInteger unscaled = new BigInteger(bytes);
+ BigDecimal decimal = new BigDecimal(unscaled, decimalType.getScale());
+ return Decimal.fromBigDecimal(
+ decimal, decimalType.getPrecision(), decimalType.getScale());
+ case TIMESTAMP_WITHOUT_TIME_ZONE:
+ return convertTimestamp(buf, ((TimestampType) dataType).getPrecision());
+ case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+ return convertTimestamp(buf, ((LocalZonedTimestampType) dataType).getPrecision());
+ default:
+ return null;
+ }
+ }
+
+ private static Timestamp convertTimestamp(ByteBuffer buf, int precision) {
+ if (precision <= 3) {
+ return Timestamp.fromEpochMillis(buf.getLong());
+ } else if (precision <= 6) {
+ return Timestamp.fromMicros(buf.getLong());
+ } else {
+ // precision 7-9: 12 bytes = i64 millis (BE) + i32 nanos_of_milli (BE)
+ long millis = buf.getLong();
+ int nanosOfMilli = buf.getInt();
+ return Timestamp.fromEpochMillis(millis, nanosOfMilli);
+ }
+ }
+
+ private MosaicObjects() {}
+}
diff --git a/paimon-mosaic/src/main/java/org/apache/paimon/format/mosaic/MosaicReaderFactory.java b/paimon-mosaic/src/main/java/org/apache/paimon/format/mosaic/MosaicReaderFactory.java
new file mode 100644
index 000000000000..5b39c867e290
--- /dev/null
+++ b/paimon-mosaic/src/main/java/org/apache/paimon/format/mosaic/MosaicReaderFactory.java
@@ -0,0 +1,60 @@
+/*
+ * 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.paimon.format.mosaic;
+
+import org.apache.paimon.data.InternalRow;
+import org.apache.paimon.format.FormatReaderFactory;
+import org.apache.paimon.predicate.Predicate;
+import org.apache.paimon.reader.FileRecordReader;
+import org.apache.paimon.types.RowType;
+
+import javax.annotation.Nullable;
+
+import java.io.IOException;
+import java.util.List;
+
+/** A factory to create Mosaic reader. */
+public class MosaicReaderFactory implements FormatReaderFactory {
+
+ private final RowType dataSchemaRowType;
+ private final RowType projectedRowType;
+ @Nullable private final List