| 77 | } |
| 78 | |
| 79 | void Replicator::storeBlob(Transaction* transaction, ISC_QUAD blobId) |
| 80 | { |
| 81 | FbLocalStatus localStatus; |
| 82 | |
| 83 | // We need the blob exactly as stored, without possible transliteration to the connection charset |
| 84 | const UCHAR bpb[] = {isc_bpb_version1, isc_bpb_target_interp, 1, CS_NONE}; |
| 85 | |
| 86 | BlobWrapper blob(&localStatus); |
| 87 | if (!blob.open(m_attachment, transaction->getInterface(), blobId, sizeof(bpb), bpb)) |
| 88 | localStatus.raise(); |
| 89 | |
| 90 | UCharBuffer buffer; |
| 91 | const auto bufferLength = MAX_USHORT; |
| 92 | auto data = buffer.getBuffer(bufferLength); |
| 93 | |
| 94 | auto& txnData = transaction->getData(); |
| 95 | bool newOp = true; |
| 96 | |
| 97 | FB_SIZE_T segmentLength; |
| 98 | while (blob.getSegment(bufferLength, data, segmentLength)) |
| 99 | { |
| 100 | if (!segmentLength) |
| 101 | continue; // Zero-length segments are unusual but OK |
| 102 | |
| 103 | if (newOp) |
| 104 | { |
| 105 | txnData.putTag(opStoreBlob); |
| 106 | txnData.putInt32(blobId.gds_quad_high); |
| 107 | txnData.putInt32(blobId.gds_quad_low); |
| 108 | newOp = false; |
| 109 | } |
| 110 | |
| 111 | fb_assert(segmentLength <= MAX_USHORT); |
| 112 | txnData.putInt16(segmentLength); |
| 113 | txnData.putBinary(segmentLength, data); |
| 114 | |
| 115 | if (txnData.getSize() > m_config->bufferSize) |
| 116 | { |
| 117 | flush(txnData, FLUSH_OVERFLOW); |
| 118 | newOp = true; |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | localStatus.check(); |
| 123 | |
| 124 | blob.close(); |
| 125 | |
| 126 | if (newOp) |
| 127 | { |
| 128 | txnData.putTag(opStoreBlob); |
| 129 | txnData.putInt32(blobId.gds_quad_high); |
| 130 | txnData.putInt32(blobId.gds_quad_low); |
| 131 | } |
| 132 | |
| 133 | txnData.putInt16(0); // end-of-blob marker |
| 134 | |
| 135 | if (txnData.getSize() > m_config->bufferSize) |
| 136 | flush(txnData, FLUSH_OVERFLOW); |
nothing calls this directly
no test coverage detected