You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@shardingsphere.apache.org by zh...@apache.org on 2026/08/16 09:11:57 UTC
(shardingsphere) branch master updated: Refactor Firebird result row building and BLOB reads (#39484)
This is an automated email from the ASF dual-hosted git repository.
terrymanu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shardingsphere.git
The following commit(s) were added to refs/heads/master by this push:
new 506aaf424be Refactor Firebird result row building and BLOB reads (#39484)
506aaf424be is described below
commit 506aaf424be4e84f888d1cb08831ec4f7fcea638
Author: Liang Zhang <zh...@apache.org>
AuthorDate: Sun Aug 16 17:11:50 2026 +0800
Refactor Firebird result row building and BLOB reads (#39484)
---
.../FirebirdGetBlobSegmentCommandExecutor.java | 21 +---
.../query/blob/cache/FirebirdBlobReadCache.java | 78 +++++++++---
.../query/info/FirebirdBlobInfoExecutor.java | 8 +-
.../query/statement/FirebirdBinaryRowBuilder.java | 50 ++++++++
.../FirebirdExecuteStatementCommandExecutor.java | 14 +--
.../FirebirdFetchStatementCommandExecutor.java | 16 +--
.../FirebirdGetBlobSegmentCommandExecutorTest.java | 43 +++++--
.../blob/cache/FirebirdBlobReadCacheTest.java | 139 +++++++++++++++++++++
.../query/info/FirebirdBlobInfoExecutorTest.java | 64 ++++++++--
.../statement/FirebirdBinaryRowBuilderTest.java | 54 ++++++++
10 files changed, 407 insertions(+), 80 deletions(-)
diff --git a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/blob/FirebirdGetBlobSegmentCommandExecutor.java b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/blob/FirebirdGetBlobSegmentCommandExecutor.java
index ad2ccc0203d..bd646e2d905 100644
--- a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/blob/FirebirdGetBlobSegmentCommandExecutor.java
+++ b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/blob/FirebirdGetBlobSegmentCommandExecutor.java
@@ -25,9 +25,9 @@ import org.apache.shardingsphere.database.protocol.packet.DatabasePacket;
import org.apache.shardingsphere.proxy.backend.session.ConnectionSession;
import org.apache.shardingsphere.proxy.frontend.command.executor.CommandExecutor;
import org.apache.shardingsphere.proxy.frontend.firebird.command.query.blob.cache.FirebirdBlobReadCache;
+import org.apache.shardingsphere.proxy.frontend.firebird.command.query.blob.cache.FirebirdBlobReadCache.BlobSegment;
import org.apache.shardingsphere.proxy.frontend.firebird.command.query.blob.generator.FirebirdBlobHandleGenerator;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
@@ -60,22 +60,13 @@ public final class FirebirdGetBlobSegmentCommandExecutor implements CommandExecu
public Collection<DatabasePacket> execute() {
int connectionId = connectionSession.getConnectionId();
int blobHandle = FirebirdBlobHandleGenerator.getInstance().resolveBlobHandle(connectionId, packet.getBlobHandle());
- Optional<byte[]> remaining = FirebirdBlobReadCache.getInstance().getSegment(connectionId, blobHandle);
- if (!remaining.isPresent() || 0 == remaining.get().length) {
+ Optional<BlobSegment> blobSegment = FirebirdBlobReadCache.getInstance().readSegment(connectionId, blobHandle, getMaxSegmentDataLength());
+ if (!blobSegment.isPresent()) {
return Collections.singleton(new FirebirdGenericResponsePacket().setHandle(SEGMENT_STATE_EOF));
}
- byte[] segment = remaining.get();
- int segmentLength = Math.min(getMaxSegmentDataLength(), segment.length);
- byte[] payloadSegment = Arrays.copyOf(segment, segmentLength);
- int segmentState;
- if (segmentLength >= segment.length) {
- FirebirdBlobReadCache.getInstance().removeBlob(connectionId, blobHandle);
- segmentState = SEGMENT_STATE_COMPLETE;
- } else {
- FirebirdBlobReadCache.getInstance().setSegment(connectionId, blobHandle, Arrays.copyOfRange(segment, segmentLength, segment.length));
- segmentState = SEGMENT_STATE_PARTIAL;
- }
- FirebirdGetBlobSegmentResponsePacket responsePacket = new FirebirdGetBlobSegmentResponsePacket(payloadSegment);
+ BlobSegment segment = blobSegment.get();
+ int segmentState = segment.isComplete() ? SEGMENT_STATE_COMPLETE : SEGMENT_STATE_PARTIAL;
+ FirebirdGetBlobSegmentResponsePacket responsePacket = new FirebirdGetBlobSegmentResponsePacket(segment.getData());
return Collections.singleton(new FirebirdGenericResponsePacket().setHandle(segmentState).setData(responsePacket));
}
diff --git a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/blob/cache/FirebirdBlobReadCache.java b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/blob/cache/FirebirdBlobReadCache.java
index 166e7cd4dc2..b401f9db3cb 100644
--- a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/blob/cache/FirebirdBlobReadCache.java
+++ b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/blob/cache/FirebirdBlobReadCache.java
@@ -18,17 +18,21 @@
package org.apache.shardingsphere.proxy.frontend.firebird.command.query.blob.cache;
import lombok.AccessLevel;
+import lombok.Getter;
import lombok.NoArgsConstructor;
+import lombok.RequiredArgsConstructor;
+import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
+import java.util.OptionalInt;
import java.util.concurrent.ConcurrentHashMap;
/**
- * Registry for Firebird BLOB reads buffered by the proxy.
+ * Cache for Firebird BLOB reads buffered by the proxy.
*
* <p>Read direction counterpart of the write cache: open_blob puts the whole BLOB content here,
- * and get_segment hands it out to the client chunk by chunk, replacing the stored value with the remainder.
+ * and get_segment hands it out to the client chunk by chunk using a cursor over the original content.
* Entries are keyed by connection id and blob handle, so concurrent connections and BLOBs do not interfere.</p>
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@@ -36,7 +40,7 @@ public final class FirebirdBlobReadCache {
private static final FirebirdBlobReadCache INSTANCE = new FirebirdBlobReadCache();
- private final Map<Integer, Map<Integer, byte[]>> remainingSegments = new ConcurrentHashMap<>(16);
+ private final Map<Integer, Map<Integer, BlobReadCursor>> cursors = new ConcurrentHashMap<>(16);
public static FirebirdBlobReadCache getInstance() {
return INSTANCE;
@@ -48,7 +52,7 @@ public final class FirebirdBlobReadCache {
* @param connectionId connection id
*/
public void registerConnection(final int connectionId) {
- remainingSegments.put(connectionId, new ConcurrentHashMap<>(4));
+ cursors.put(connectionId, new ConcurrentHashMap<>(4));
}
/**
@@ -57,7 +61,7 @@ public final class FirebirdBlobReadCache {
* @param connectionId connection id
*/
public void unregisterConnection(final int connectionId) {
- remainingSegments.remove(connectionId);
+ cursors.remove(connectionId);
}
/**
@@ -68,29 +72,43 @@ public final class FirebirdBlobReadCache {
* @param content blob content
*/
public void registerBlob(final int connectionId, final int blobHandle, final byte[] content) {
- getSegmentMap(connectionId).put(blobHandle, content);
+ getCursorMap(connectionId).put(blobHandle, new BlobReadCursor(content));
}
/**
- * Get remaining segment data by handle.
+ * Read segment data by handle.
*
* @param connectionId connection id
* @param blobHandle blob handle
- * @return optional remaining segment data
+ * @param maximumLength maximum segment length
+ * @return optional segment data
*/
- public Optional<byte[]> getSegment(final int connectionId, final int blobHandle) {
- return Optional.ofNullable(getSegmentMap(connectionId).get(blobHandle));
+ public Optional<BlobSegment> readSegment(final int connectionId, final int blobHandle, final int maximumLength) {
+ Map<Integer, BlobReadCursor> cursorMap = getCursorMap(connectionId);
+ BlobReadCursor cursor = cursorMap.get(blobHandle);
+ if (null == cursor || 0 == cursor.getRemainingSize()) {
+ return Optional.empty();
+ }
+ int segmentLength = Math.min(maximumLength, cursor.getRemainingSize());
+ byte[] data = Arrays.copyOfRange(cursor.content, cursor.offset, cursor.offset + segmentLength);
+ cursor.offset += segmentLength;
+ boolean complete = 0 == cursor.getRemainingSize();
+ if (complete) {
+ cursorMap.remove(blobHandle);
+ }
+ return Optional.of(new BlobSegment(data, complete));
}
/**
- * Replace remaining segment data by handle.
+ * Get remaining BLOB size by handle.
*
* @param connectionId connection id
* @param blobHandle blob handle
- * @param segment remaining segment data
+ * @return optional remaining BLOB size
*/
- public void setSegment(final int connectionId, final int blobHandle, final byte[] segment) {
- getSegmentMap(connectionId).put(blobHandle, segment);
+ public OptionalInt getRemainingSize(final int connectionId, final int blobHandle) {
+ BlobReadCursor cursor = getCursorMap(connectionId).get(blobHandle);
+ return null == cursor ? OptionalInt.empty() : OptionalInt.of(cursor.getRemainingSize());
}
/**
@@ -100,11 +118,35 @@ public final class FirebirdBlobReadCache {
* @param blobHandle blob handle
*/
public void removeBlob(final int connectionId, final int blobHandle) {
- getSegmentMap(connectionId).remove(blobHandle);
+ getCursorMap(connectionId).remove(blobHandle);
}
- private Map<Integer, byte[]> getSegmentMap(final int connectionId) {
- Map<Integer, byte[]> result = remainingSegments.get(connectionId);
- return null == result ? remainingSegments.computeIfAbsent(connectionId, key -> new ConcurrentHashMap<>(4)) : result;
+ private Map<Integer, BlobReadCursor> getCursorMap(final int connectionId) {
+ Map<Integer, BlobReadCursor> result = cursors.get(connectionId);
+ return null == result ? cursors.computeIfAbsent(connectionId, key -> new ConcurrentHashMap<>(4)) : result;
+ }
+
+ @RequiredArgsConstructor
+ private static final class BlobReadCursor {
+
+ private final byte[] content;
+
+ private int offset;
+
+ private int getRemainingSize() {
+ return content.length - offset;
+ }
+ }
+
+ /**
+ * Firebird BLOB segment.
+ */
+ @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+ @Getter
+ public static final class BlobSegment {
+
+ private final byte[] data;
+
+ private final boolean complete;
}
}
diff --git a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/info/FirebirdBlobInfoExecutor.java b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/info/FirebirdBlobInfoExecutor.java
index 364a2816a25..e4e87455096 100644
--- a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/info/FirebirdBlobInfoExecutor.java
+++ b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/info/FirebirdBlobInfoExecutor.java
@@ -30,7 +30,7 @@ import org.apache.shardingsphere.proxy.frontend.firebird.command.query.blob.gene
import java.util.Collection;
import java.util.Collections;
-import java.util.Optional;
+import java.util.OptionalInt;
/**
* Blob info command executor for Firebird.
@@ -50,9 +50,9 @@ public final class FirebirdBlobInfoExecutor implements CommandExecutor {
private int getBlobLength() {
int connectionId = connectionSession.getConnectionId();
int blobHandle = FirebirdBlobHandleGenerator.getInstance().resolveBlobHandle(connectionId, packet.getHandle());
- Optional<byte[]> readSegment = FirebirdBlobReadCache.getInstance().getSegment(connectionId, blobHandle);
- if (readSegment.isPresent()) {
- return readSegment.get().length;
+ OptionalInt remainingSize = FirebirdBlobReadCache.getInstance().getRemainingSize(connectionId, blobHandle);
+ if (remainingSize.isPresent()) {
+ return remainingSize.getAsInt();
}
return FirebirdBlobWriteCache.getInstance().getBlobSizeByHandle(connectionId, blobHandle).orElse(0);
}
diff --git a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/FirebirdBinaryRowBuilder.java b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/FirebirdBinaryRowBuilder.java
new file mode 100644
index 00000000000..9b16a8cabc2
--- /dev/null
+++ b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/FirebirdBinaryRowBuilder.java
@@ -0,0 +1,50 @@
+/*
+ * 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.shardingsphere.proxy.frontend.firebird.command.query.statement;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.apache.shardingsphere.database.protocol.binary.BinaryCell;
+import org.apache.shardingsphere.database.protocol.binary.BinaryRow;
+import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.FirebirdBinaryColumnType;
+import org.apache.shardingsphere.proxy.backend.response.data.QueryResponseCell;
+import org.apache.shardingsphere.proxy.backend.response.data.QueryResponseRow;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Firebird binary row builder.
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class FirebirdBinaryRowBuilder {
+
+ /**
+ * Build binary row.
+ *
+ * @param row query response row
+ * @return binary row
+ */
+ public static BinaryRow build(final QueryResponseRow row) {
+ List<BinaryCell> result = new ArrayList<>(row.getCells().size());
+ for (QueryResponseCell each : row.getCells()) {
+ result.add(new BinaryCell(FirebirdBinaryColumnType.valueOfJDBCType(each.getJdbcType()), each.getData()));
+ }
+ return new BinaryRow(result);
+ }
+}
diff --git a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/execute/FirebirdExecuteStatementCommandExecutor.java b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/execute/FirebirdExecuteStatementCommandExecutor.java
index fdb0db60819..f10abfa61d0 100644
--- a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/execute/FirebirdExecuteStatementCommandExecutor.java
+++ b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/execute/FirebirdExecuteStatementCommandExecutor.java
@@ -22,7 +22,6 @@ import lombok.RequiredArgsConstructor;
import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidStatementHandleException;
import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidTransactionHandleException;
-import org.apache.shardingsphere.database.protocol.binary.BinaryCell;
import org.apache.shardingsphere.database.protocol.binary.BinaryRow;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.FirebirdBinaryColumnType;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.statement.execute.FirebirdExecuteStatementPacket;
@@ -38,7 +37,6 @@ import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
import org.apache.shardingsphere.proxy.backend.context.ProxyContext;
import org.apache.shardingsphere.proxy.backend.handler.ProxyBackendHandler;
import org.apache.shardingsphere.proxy.backend.handler.ProxyBackendHandlerFactory;
-import org.apache.shardingsphere.proxy.backend.response.data.QueryResponseCell;
import org.apache.shardingsphere.proxy.backend.response.data.QueryResponseRow;
import org.apache.shardingsphere.proxy.backend.response.header.ResponseHeader;
import org.apache.shardingsphere.proxy.backend.response.header.query.QueryResponseHeader;
@@ -48,12 +46,12 @@ import org.apache.shardingsphere.proxy.frontend.command.executor.CommandExecutor
import org.apache.shardingsphere.proxy.frontend.command.executor.ResponseType;
import org.apache.shardingsphere.proxy.frontend.firebird.command.query.FirebirdServerPreparedStatement;
import org.apache.shardingsphere.proxy.frontend.firebird.command.query.blob.cache.FirebirdBlobWriteCache;
+import org.apache.shardingsphere.proxy.frontend.firebird.command.query.statement.FirebirdBinaryRowBuilder;
import org.apache.shardingsphere.proxy.frontend.firebird.command.query.statement.FirebirdStatementResourceCleaner;
import org.apache.shardingsphere.proxy.frontend.firebird.command.query.statement.fetch.FirebirdFetchStatementCache;
import org.apache.shardingsphere.proxy.frontend.firebird.command.query.transaction.FirebirdTransactionIdGenerator;
import java.sql.SQLException;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
@@ -166,15 +164,7 @@ public final class FirebirdExecuteStatementCommandExecutor implements CommandExe
private FirebirdSQLResponsePacket getSQLResponse() throws SQLException {
QueryResponseRow queryResponseRow = proxyBackendHandler.getRowData();
- BinaryRow row = createBinaryRow(queryResponseRow);
+ BinaryRow row = FirebirdBinaryRowBuilder.build(queryResponseRow);
return new FirebirdSQLResponsePacket(row);
}
-
- private BinaryRow createBinaryRow(final QueryResponseRow queryResponseRow) {
- List<BinaryCell> result = new ArrayList<>(queryResponseRow.getCells().size());
- for (QueryResponseCell each : queryResponseRow.getCells()) {
- result.add(new BinaryCell(FirebirdBinaryColumnType.valueOfJDBCType(each.getJdbcType()), each.getData()));
- }
- return new BinaryRow(result);
- }
}
diff --git a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/fetch/FirebirdFetchStatementCommandExecutor.java b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/fetch/FirebirdFetchStatementCommandExecutor.java
index 153194c1563..5808855ae7e 100644
--- a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/fetch/FirebirdFetchStatementCommandExecutor.java
+++ b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/fetch/FirebirdFetchStatementCommandExecutor.java
@@ -17,24 +17,19 @@
package org.apache.shardingsphere.proxy.frontend.firebird.command.query.statement.fetch;
-import org.apache.shardingsphere.database.protocol.binary.BinaryCell;
import org.apache.shardingsphere.database.protocol.binary.BinaryRow;
-import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.FirebirdBinaryColumnType;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.statement.FirebirdFetchStatementPacket;
import org.apache.shardingsphere.database.protocol.firebird.packet.generic.FirebirdFetchResponsePacket;
import org.apache.shardingsphere.database.protocol.packet.DatabasePacket;
import org.apache.shardingsphere.proxy.backend.handler.ProxyBackendHandler;
-import org.apache.shardingsphere.proxy.backend.response.data.QueryResponseCell;
-import org.apache.shardingsphere.proxy.backend.response.data.QueryResponseRow;
import org.apache.shardingsphere.proxy.backend.session.ConnectionSession;
import org.apache.shardingsphere.proxy.frontend.command.executor.QueryCommandExecutor;
import org.apache.shardingsphere.proxy.frontend.command.executor.ResponseType;
+import org.apache.shardingsphere.proxy.frontend.firebird.command.query.statement.FirebirdBinaryRowBuilder;
import java.sql.SQLException;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.List;
/**
* Firebird fetch statement command executor.
@@ -77,7 +72,7 @@ public final class FirebirdFetchStatementCommandExecutor implements QueryCommand
fetchCount--;
if (0 <= fetchCount) {
if (proxyBackendHandler.next()) {
- BinaryRow row = createBinaryRow(proxyBackendHandler.getRowData());
+ BinaryRow row = FirebirdBinaryRowBuilder.build(proxyBackendHandler.getRowData());
return FirebirdFetchResponsePacket.getFetchRowPacket(row);
} else {
connectionSession.getDatabaseConnectionManager().unmarkResourceInUse(proxyBackendHandler);
@@ -88,11 +83,4 @@ public final class FirebirdFetchStatementCommandExecutor implements QueryCommand
return FirebirdFetchResponsePacket.getFetchEndPacket();
}
- private BinaryRow createBinaryRow(final QueryResponseRow queryResponseRow) {
- List<BinaryCell> result = new ArrayList<>(queryResponseRow.getCells().size());
- for (QueryResponseCell each : queryResponseRow.getCells()) {
- result.add(new BinaryCell(FirebirdBinaryColumnType.valueOfJDBCType(each.getJdbcType()), each.getData()));
- }
- return new BinaryRow(result);
- }
}
diff --git a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/blob/FirebirdGetBlobSegmentCommandExecutorTest.java b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/blob/FirebirdGetBlobSegmentCommandExecutorTest.java
index 36a67a45c24..e21a8fedd6a 100644
--- a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/blob/FirebirdGetBlobSegmentCommandExecutorTest.java
+++ b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/blob/FirebirdGetBlobSegmentCommandExecutorTest.java
@@ -94,16 +94,25 @@ class FirebirdGetBlobSegmentCommandExecutorTest {
}
@Test
- void assertExecuteWithZeroRequestedLengthExpectsPartial() {
+ void assertExecuteWithEmptyBlobExpectsEof() {
+ FirebirdBlobReadCache.getInstance().registerBlob(CONNECTION_ID, BLOB_HANDLE, new byte[0]);
+ FirebirdGetBlobSegmentCommandExecutor executor = new FirebirdGetBlobSegmentCommandExecutor(packet, connectionSession);
+ Collection<DatabasePacket> actualPackets = executor.execute();
+ FirebirdGenericResponsePacket actualGenericPacket = (FirebirdGenericResponsePacket) actualPackets.iterator().next();
+ assertThat(actualGenericPacket.getHandle(), is(2));
+ assertNull(actualGenericPacket.getData());
+ }
+
+ @Test
+ void assertExecuteWithRequestedLengthBelowPrefixExpectsPartial() {
FirebirdBlobReadCache.getInstance().registerBlob(CONNECTION_ID, BLOB_HANDLE, new byte[]{9, 8});
- when(packet.getSegmentLength()).thenReturn(0);
+ when(packet.getSegmentLength()).thenReturn(1);
FirebirdGetBlobSegmentCommandExecutor executor = new FirebirdGetBlobSegmentCommandExecutor(packet, connectionSession);
Collection<DatabasePacket> actualPackets = executor.execute();
FirebirdGenericResponsePacket actualGenericPacket = (FirebirdGenericResponsePacket) actualPackets.iterator().next();
assertThat(actualGenericPacket.getHandle(), is(1));
assertThat(getResponseSegment(actualGenericPacket).length, is(0));
- byte[] actualRemainingSegment = FirebirdBlobReadCache.getInstance().getSegment(CONNECTION_ID, BLOB_HANDLE).orElse(new byte[0]);
- assertThat(actualRemainingSegment, is(new byte[]{9, 8}));
+ assertThat(FirebirdBlobReadCache.getInstance().getRemainingSize(CONNECTION_ID, BLOB_HANDLE).getAsInt(), is(2));
}
@Test
@@ -116,8 +125,7 @@ class FirebirdGetBlobSegmentCommandExecutorTest {
assertThat(actualGenericPacket.getHandle(), is(1));
assertThat(actualGenericPacket.getData(), isA(FirebirdGetBlobSegmentResponsePacket.class));
assertThat(getResponseSegment(actualGenericPacket), is(new byte[]{1, 2}));
- byte[] actualRemainingSegment = FirebirdBlobReadCache.getInstance().getSegment(CONNECTION_ID, BLOB_HANDLE).orElse(new byte[0]);
- assertThat(actualRemainingSegment, is(new byte[]{3}));
+ assertThat(FirebirdBlobReadCache.getInstance().getRemainingSize(CONNECTION_ID, BLOB_HANDLE).getAsInt(), is(1));
}
@Test
@@ -129,7 +137,7 @@ class FirebirdGetBlobSegmentCommandExecutorTest {
FirebirdGenericResponsePacket actualGenericPacket = (FirebirdGenericResponsePacket) actualPackets.iterator().next();
assertThat(actualGenericPacket.getHandle(), is(0));
assertThat(getResponseSegment(actualGenericPacket), is(new byte[]{4, 5}));
- assertFalse(FirebirdBlobReadCache.getInstance().getSegment(CONNECTION_ID, BLOB_HANDLE).isPresent());
+ assertFalse(FirebirdBlobReadCache.getInstance().getRemainingSize(CONNECTION_ID, BLOB_HANDLE).isPresent());
}
@Test
@@ -143,8 +151,23 @@ class FirebirdGetBlobSegmentCommandExecutorTest {
FirebirdGenericResponsePacket actualGenericPacket = (FirebirdGenericResponsePacket) actualPackets.iterator().next();
assertThat(actualGenericPacket.getHandle(), is(1));
assertThat(getResponseSegment(actualGenericPacket).length, is(0xFFFF));
- byte[] actualRemainingSegment = FirebirdBlobReadCache.getInstance().getSegment(CONNECTION_ID, BLOB_HANDLE).orElse(new byte[0]);
- assertThat(actualRemainingSegment, is(new byte[]{42}));
+ assertThat(FirebirdBlobReadCache.getInstance().getRemainingSize(CONNECTION_ID, BLOB_HANDLE).getAsInt(), is(1));
+ }
+
+ @Test
+ void assertExecuteConsecutivelyReturnsAllDataOnce() {
+ FirebirdBlobReadCache.getInstance().registerBlob(CONNECTION_ID, BLOB_HANDLE, new byte[]{1, 2, 3, 4, 5});
+ when(packet.getSegmentLength()).thenReturn(4);
+ FirebirdGetBlobSegmentCommandExecutor executor = new FirebirdGetBlobSegmentCommandExecutor(packet, connectionSession);
+ FirebirdGenericResponsePacket firstPacket = (FirebirdGenericResponsePacket) executor.execute().iterator().next();
+ assertThat(firstPacket.getHandle(), is(1));
+ assertThat(getResponseSegment(firstPacket), is(new byte[]{1, 2}));
+ FirebirdGenericResponsePacket secondPacket = (FirebirdGenericResponsePacket) executor.execute().iterator().next();
+ assertThat(secondPacket.getHandle(), is(1));
+ assertThat(getResponseSegment(secondPacket), is(new byte[]{3, 4}));
+ FirebirdGenericResponsePacket lastPacket = (FirebirdGenericResponsePacket) executor.execute().iterator().next();
+ assertThat(lastPacket.getHandle(), is(0));
+ assertThat(getResponseSegment(lastPacket), is(new byte[]{5}));
}
@Test
@@ -155,7 +178,7 @@ class FirebirdGetBlobSegmentCommandExecutorTest {
Collection<DatabasePacket> actualPackets = executor.execute();
FirebirdGenericResponsePacket actualGenericPacket = (FirebirdGenericResponsePacket) actualPackets.iterator().next();
assertThat(actualGenericPacket.getHandle(), is(2));
- assertThat(FirebirdBlobReadCache.getInstance().getSegment(2, BLOB_HANDLE).orElse(new byte[0]), is(new byte[]{1, 2, 3}));
+ assertThat(FirebirdBlobReadCache.getInstance().getRemainingSize(2, BLOB_HANDLE).getAsInt(), is(3));
FirebirdBlobReadCache.getInstance().unregisterConnection(2);
}
diff --git a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/blob/cache/FirebirdBlobReadCacheTest.java b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/blob/cache/FirebirdBlobReadCacheTest.java
new file mode 100644
index 00000000000..db6a74f02a9
--- /dev/null
+++ b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/blob/cache/FirebirdBlobReadCacheTest.java
@@ -0,0 +1,139 @@
+/*
+ * 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.shardingsphere.proxy.frontend.firebird.command.query.blob.cache;
+
+import org.apache.shardingsphere.proxy.frontend.firebird.command.query.blob.cache.FirebirdBlobReadCache.BlobSegment;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.Optional;
+import java.util.OptionalInt;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class FirebirdBlobReadCacheTest {
+
+ private static final FirebirdBlobReadCache CACHE = FirebirdBlobReadCache.getInstance();
+
+ private static final int CONNECTION_ID = 1;
+
+ private static final int OTHER_CONNECTION_ID = 2;
+
+ private static final int BLOB_HANDLE = 3;
+
+ @AfterEach
+ void tearDown() {
+ CACHE.unregisterConnection(CONNECTION_ID);
+ CACHE.unregisterConnection(OTHER_CONNECTION_ID);
+ }
+
+ @Test
+ void assertUnregisterConnectionRemovesBlobs() {
+ CACHE.registerConnection(CONNECTION_ID);
+ CACHE.registerBlob(CONNECTION_ID, BLOB_HANDLE, new byte[]{1});
+ CACHE.unregisterConnection(CONNECTION_ID);
+ assertFalse(CACHE.getRemainingSize(CONNECTION_ID, BLOB_HANDLE).isPresent());
+ }
+
+ @Test
+ void assertRegisterBlobKeepsContentReference() {
+ CACHE.registerConnection(CONNECTION_ID);
+ byte[] content = new byte[]{1, 2};
+ CACHE.registerBlob(CONNECTION_ID, BLOB_HANDLE, content);
+ content[0] = 9;
+ Optional<BlobSegment> actual = CACHE.readSegment(CONNECTION_ID, BLOB_HANDLE, content.length);
+ assertTrue(actual.isPresent());
+ assertThat(actual.get().getData(), is(new byte[]{9, 2}));
+ assertThat(actual.get().getData(), not(sameInstance(content)));
+ }
+
+ @Test
+ void assertReadMissingBlob() {
+ CACHE.registerConnection(CONNECTION_ID);
+ assertFalse(CACHE.readSegment(CONNECTION_ID, BLOB_HANDLE, 1).isPresent());
+ }
+
+ @Test
+ void assertReadEmptyBlob() {
+ CACHE.registerConnection(CONNECTION_ID);
+ CACHE.registerBlob(CONNECTION_ID, BLOB_HANDLE, new byte[0]);
+ assertFalse(CACHE.readSegment(CONNECTION_ID, BLOB_HANDLE, 1).isPresent());
+ OptionalInt remainingSize = CACHE.getRemainingSize(CONNECTION_ID, BLOB_HANDLE);
+ assertTrue(remainingSize.isPresent());
+ assertThat(remainingSize.getAsInt(), is(0));
+ }
+
+ @Test
+ void assertReadPartialSegmentAdvancesCursor() {
+ CACHE.registerConnection(CONNECTION_ID);
+ CACHE.registerBlob(CONNECTION_ID, BLOB_HANDLE, new byte[]{1, 2, 3});
+ Optional<BlobSegment> actual = CACHE.readSegment(CONNECTION_ID, BLOB_HANDLE, 2);
+ assertTrue(actual.isPresent());
+ assertThat(actual.get().getData(), is(new byte[]{1, 2}));
+ assertFalse(actual.get().isComplete());
+ assertThat(CACHE.getRemainingSize(CONNECTION_ID, BLOB_HANDLE).getAsInt(), is(1));
+ }
+
+ @Test
+ void assertReadCompleteSegmentRemovesCursor() {
+ CACHE.registerConnection(CONNECTION_ID);
+ CACHE.registerBlob(CONNECTION_ID, BLOB_HANDLE, new byte[]{1, 2});
+ Optional<BlobSegment> actual = CACHE.readSegment(CONNECTION_ID, BLOB_HANDLE, 2);
+ assertTrue(actual.isPresent());
+ assertThat(actual.get().getData(), is(new byte[]{1, 2}));
+ assertTrue(actual.get().isComplete());
+ assertFalse(CACHE.getRemainingSize(CONNECTION_ID, BLOB_HANDLE).isPresent());
+ }
+
+ @Test
+ void assertReadZeroLengthDoesNotAdvanceCursor() {
+ CACHE.registerConnection(CONNECTION_ID);
+ CACHE.registerBlob(CONNECTION_ID, BLOB_HANDLE, new byte[]{1, 2});
+ Optional<BlobSegment> actual = CACHE.readSegment(CONNECTION_ID, BLOB_HANDLE, 0);
+ assertTrue(actual.isPresent());
+ assertThat(actual.get().getData(), is(new byte[0]));
+ assertFalse(actual.get().isComplete());
+ assertThat(CACHE.getRemainingSize(CONNECTION_ID, BLOB_HANDLE).getAsInt(), is(2));
+ }
+
+ @Test
+ void assertRemoveBlob() {
+ CACHE.registerConnection(CONNECTION_ID);
+ CACHE.registerBlob(CONNECTION_ID, BLOB_HANDLE, new byte[]{1});
+ CACHE.removeBlob(CONNECTION_ID, BLOB_HANDLE);
+ assertFalse(CACHE.getRemainingSize(CONNECTION_ID, BLOB_HANDLE).isPresent());
+ }
+
+ @Test
+ void assertConnectionsAndHandlesAreIsolated() {
+ CACHE.registerConnection(CONNECTION_ID);
+ CACHE.registerConnection(OTHER_CONNECTION_ID);
+ CACHE.registerBlob(CONNECTION_ID, BLOB_HANDLE, new byte[]{1});
+ CACHE.registerBlob(CONNECTION_ID, BLOB_HANDLE + 1, new byte[]{2, 3});
+ CACHE.registerBlob(OTHER_CONNECTION_ID, BLOB_HANDLE, new byte[]{4, 5, 6});
+ CACHE.readSegment(CONNECTION_ID, BLOB_HANDLE, 1);
+ assertFalse(CACHE.getRemainingSize(CONNECTION_ID, BLOB_HANDLE).isPresent());
+ assertThat(CACHE.getRemainingSize(CONNECTION_ID, BLOB_HANDLE + 1).getAsInt(), is(2));
+ assertThat(CACHE.getRemainingSize(OTHER_CONNECTION_ID, BLOB_HANDLE).getAsInt(), is(3));
+ }
+}
diff --git a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/info/FirebirdBlobInfoExecutorTest.java b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/info/FirebirdBlobInfoExecutorTest.java
index c7333b2e1ea..95692641a87 100644
--- a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/info/FirebirdBlobInfoExecutorTest.java
+++ b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/info/FirebirdBlobInfoExecutorTest.java
@@ -22,6 +22,11 @@ import org.apache.shardingsphere.database.protocol.firebird.packet.command.query
import org.apache.shardingsphere.database.protocol.firebird.packet.generic.FirebirdGenericResponsePacket;
import org.apache.shardingsphere.database.protocol.packet.DatabasePacket;
import org.apache.shardingsphere.proxy.backend.session.ConnectionSession;
+import org.apache.shardingsphere.proxy.frontend.firebird.command.query.blob.cache.FirebirdBlobReadCache;
+import org.apache.shardingsphere.proxy.frontend.firebird.command.query.blob.cache.FirebirdBlobWriteCache;
+import org.apache.shardingsphere.proxy.frontend.firebird.command.query.blob.generator.FirebirdBlobHandleGenerator;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
@@ -31,25 +36,70 @@ import java.util.Collection;
import java.util.Collections;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.isA;
+import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class FirebirdBlobInfoExecutorTest {
+ private static final int CONNECTION_ID = 1;
+
+ private static final int BLOB_HANDLE = 2;
+
@Mock
private FirebirdInfoPacket packet;
@Mock
private ConnectionSession connectionSession;
- @Test
- void assertExecute() {
+ @BeforeEach
+ void setUp() {
+ FirebirdBlobHandleGenerator.getInstance().registerConnection(CONNECTION_ID);
+ FirebirdBlobReadCache.getInstance().registerConnection(CONNECTION_ID);
+ FirebirdBlobWriteCache.getInstance().registerConnection(CONNECTION_ID);
+ when(connectionSession.getConnectionId()).thenReturn(CONNECTION_ID);
+ when(packet.getHandle()).thenReturn(BLOB_HANDLE);
when(packet.getInfoItems()).thenReturn(Collections.emptyList());
- FirebirdBlobInfoExecutor executor = new FirebirdBlobInfoExecutor(packet, connectionSession);
- Collection<DatabasePacket> actual = executor.execute();
- assertThat(actual.iterator().next(), isA(FirebirdGenericResponsePacket.class));
+ }
+
+ @AfterEach
+ void tearDown() {
+ FirebirdBlobHandleGenerator.getInstance().unregisterConnection(CONNECTION_ID);
+ FirebirdBlobReadCache.getInstance().unregisterConnection(CONNECTION_ID);
+ FirebirdBlobWriteCache.getInstance().unregisterConnection(CONNECTION_ID);
+ }
+
+ @Test
+ void assertExecuteWithReadBlob() {
+ FirebirdBlobReadCache.getInstance().registerBlob(CONNECTION_ID, BLOB_HANDLE, new byte[]{1, 2, 3});
+ Collection<DatabasePacket> actual = new FirebirdBlobInfoExecutor(packet, connectionSession).execute();
+ assertThat(getBlobLength(actual), is(3));
+ }
+
+ @Test
+ void assertExecuteWithPartiallyReadBlob() {
+ FirebirdBlobReadCache.getInstance().registerBlob(CONNECTION_ID, BLOB_HANDLE, new byte[]{1, 2, 3});
+ FirebirdBlobReadCache.getInstance().readSegment(CONNECTION_ID, BLOB_HANDLE, 1);
+ Collection<DatabasePacket> actual = new FirebirdBlobInfoExecutor(packet, connectionSession).execute();
+ assertThat(getBlobLength(actual), is(2));
+ }
+
+ @Test
+ void assertExecuteWithWriteBlob() {
+ FirebirdBlobWriteCache.getInstance().registerBlob(CONNECTION_ID, BLOB_HANDLE, 3L);
+ FirebirdBlobWriteCache.getInstance().appendSegment(CONNECTION_ID, BLOB_HANDLE, new byte[]{1, 2, 3, 4});
+ Collection<DatabasePacket> actual = new FirebirdBlobInfoExecutor(packet, connectionSession).execute();
+ assertThat(getBlobLength(actual), is(4));
+ }
+
+ @Test
+ void assertExecuteWithMissingBlob() {
+ Collection<DatabasePacket> actual = new FirebirdBlobInfoExecutor(packet, connectionSession).execute();
+ assertThat(getBlobLength(actual), is(0));
+ }
+
+ private int getBlobLength(final Collection<DatabasePacket> actual) {
FirebirdGenericResponsePacket actualResponsePacket = (FirebirdGenericResponsePacket) actual.iterator().next();
- assertThat(actualResponsePacket.getData(), isA(FirebirdBlobInfoReturnPacket.class));
+ return ((FirebirdBlobInfoReturnPacket) actualResponsePacket.getData()).getBlobLength();
}
}
diff --git a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/FirebirdBinaryRowBuilderTest.java b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/FirebirdBinaryRowBuilderTest.java
new file mode 100644
index 00000000000..12dc6ae8ff7
--- /dev/null
+++ b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/FirebirdBinaryRowBuilderTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.shardingsphere.proxy.frontend.firebird.command.query.statement;
+
+import org.apache.shardingsphere.database.protocol.binary.BinaryCell;
+import org.apache.shardingsphere.database.protocol.binary.BinaryRow;
+import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.FirebirdBinaryColumnType;
+import org.apache.shardingsphere.proxy.backend.response.data.QueryResponseCell;
+import org.apache.shardingsphere.proxy.backend.response.data.QueryResponseRow;
+import org.junit.jupiter.api.Test;
+
+import java.sql.Types;
+import java.util.Arrays;
+import java.util.Iterator;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+class FirebirdBinaryRowBuilderTest {
+
+ @Test
+ void assertBuildPreservesCellOrderAndData() {
+ byte[] expectedData = new byte[]{1, 2};
+ QueryResponseRow row = new QueryResponseRow(Arrays.asList(new QueryResponseCell(Types.BLOB, expectedData), new QueryResponseCell(Types.INTEGER, null)));
+ BinaryRow actual = FirebirdBinaryRowBuilder.build(row);
+ assertThat(actual.getCells().size(), is(2));
+ Iterator<BinaryCell> iterator = actual.getCells().iterator();
+ BinaryCell firstCell = iterator.next();
+ assertThat(firstCell.getColumnType(), is(FirebirdBinaryColumnType.BLOB));
+ assertThat(firstCell.getData(), sameInstance(expectedData));
+ BinaryCell secondCell = iterator.next();
+ assertThat(secondCell.getColumnType(), is(FirebirdBinaryColumnType.LONG));
+ assertNull(secondCell.getData());
+ assertFalse(iterator.hasNext());
+ }
+}