
io.questdb.std.bytes.DirectByteSink Maven / Gradle / Ivy
Show all versions of questdb Show documentation
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2024 QuestDB
*
* 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 io.questdb.std.bytes;
import io.questdb.cairo.CairoException;
import io.questdb.std.*;
import org.jetbrains.annotations.NotNull;
public class DirectByteSink implements DirectByteSequence, BorrowableAsNativeByteSink, QuietCloseable, Mutable {
private static final int BYTE_SINK_PTR_OFFSET = 0; // 0
private static final int BYTE_SINK_LO_OFFSET = BYTE_SINK_PTR_OFFSET + 8; // 8
private static final int BYTE_SINK_HI_OFFSET = BYTE_SINK_LO_OFFSET + 8; // 16
private static final int BYTE_SINK_OVERFLOW_OFFSET = BYTE_SINK_HI_OFFSET + 8; // 24
private static final int BYTE_SINK_ASCII_OFFSET = BYTE_SINK_OVERFLOW_OFFSET + 4; // 28
private final long initialCapacity;
/**
* Pointer to the C `questdb_byte_sink_t` structure. See `byte_sink.h`.
*
* Fields accessible from `impl` via `GetLong`/`PutLong`:
* * `ptr` - pointer to the first writable byte, offset: 0
* * `lo` - pointer to the first byte in the buffer, offset: 8
* * `hi` - pointer to the last byte in the buffer, offset: 16
* * `overflow` - bool flag set to true if the buffer was asked to resize beyond 2GiB.
* * `ascii` - bool flag set to true if the buffer is a UTF-8 buffer and contains non-ASCII characters.
*
* These indirect fields are get/set by {@link #getImplPtr()},
* {@link #setImplPtr(long)}, {@link #getImplLo()}, {@link #getImplHi()}.
*
* The {@link #checkCapacity(long)} method updates `impl`'s `lo` and `hi` fields.
*/
private long impl;
/**
* Capacity before borrowing out as {@link NativeByteSink}.
*/
private long lastAllocatedCapacity;
private final NativeByteSink byteSink = new NativeByteSink() {
@Override
public void close() {
closeByteSink();
}
@Override
public long ptr() {
return impl;
}
};
public DirectByteSink(long initialCapacity) {
this(initialCapacity, true);
}
public DirectByteSink(long initialCapacity, boolean alloc) {
assert initialCapacity >= 0;
assert initialCapacity <= Integer.MAX_VALUE;
// this will allocate a minimum of 32 bytes of "allocated capacity"
this.initialCapacity = initialCapacity;
if (alloc) {
inflate();
} else {
impl = 0;
}
}
public static native long implBook(long impl, long len);
public static native long implCreate(long capacity);
public static native void implDestroy(long impl);
/**
* Low-level access to advance the internal write cursor by `written` bytes.
* Use in conjunction with {@link #checkCapacity(long)}.
*/
public void advance(long written) {
setImplPtr(getImplPtr() + written);
}
public long allocatedCapacity() {
return getImplHi() - getImplLo();
}
@Override
public @NotNull NativeByteSink borrowDirectByteSink() {
assert impl != 0;
lastAllocatedCapacity = allocatedCapacity();
return byteSink;
}
/**
* Low-level access to ensure that at least `required` bytes are available for writing.
* Returns the address of the first writable byte.
* Use in conjunction with {@link #advance(long)}.
*/
public long checkCapacity(long required) {
assert required >= 0;
long p = getImplPtr();
final long available = getImplHi() - p;
if (available >= required) {
return p;
}
final long initCapacity = allocatedCapacity();
p = implBook(impl, required);
if (p == 0) {
if (getImplOverflow()) {
throw CairoException.critical(0).put("buffer overflow, buffer capacity is requested to be over 2 GiB");
} else {
throw CairoException.nonCritical().setOutOfMemory(true).put("could not allocate DirectByteSink [required=").put(required).put(']');
}
}
final long newCapacity = allocatedCapacity();
if (newCapacity > initCapacity) {
Unsafe.incrReallocCount();
Unsafe.recordMemAlloc(newCapacity - initCapacity, memoryTag());
}
return p;
}
@Override
public void clear() {
setImplPtr(getImplLo());
setAscii(true);
}
@Override
public void close() {
deflate();
}
/**
* One past the last readable byte.
*/
@Override
public long hi() {
return getImplPtr();
}
/**
* Returns true when the buffer contains a UTF-8 encoded string containing non-ASCII characters.
*/
public boolean isAscii() {
return Unsafe.getUnsafe().getByte(impl + BYTE_SINK_ASCII_OFFSET) != 0;
}
/**
* First readable byte in the sequence.
*/
@Override
public long ptr() {
return getImplLo();
}
public DirectByteSink put(byte b) {
final long dest = checkCapacity(1);
Unsafe.getUnsafe().putByte(dest, b);
advance(1);
return this;
}
public DirectByteSink put(ByteSequence bs) {
if (bs != null) {
final int bsSize = bs.size();
final long dest = checkCapacity(bsSize);
for (int i = 0; i < bsSize; i++) {
Unsafe.getUnsafe().putByte(dest + i, bs.byteAt(i));
}
advance(bsSize);
}
return this;
}
public DirectByteSink put(DirectByteSequence dbs) {
if (dbs == null) {
return this;
}
return put(dbs.lo(), dbs.hi());
}
public DirectByteSink put(long lo, long hi) {
final long len = hi - lo;
final long dest = checkCapacity(len);
Vect.memcpy(dest, lo, len);
advance(len);
return this;
}
public void reopen() {
if (impl == 0) {
inflate();
}
}
/**
* Ensure that the buffer has at least `minCapacity`.
*
* After this call, `capacity() >= minCapacity` is guaranteed.
*/
public void reserve(long minCapacity) {
if (minCapacity > allocatedCapacity()) {
checkCapacity(minCapacity);
}
}
public void resetCapacity() {
if (allocatedCapacity() > initialCapacity) {
deflate();
inflate();
} else {
clear();
}
}
public void setAscii(boolean ascii) {
Unsafe.getUnsafe().putByte(impl + BYTE_SINK_ASCII_OFFSET, (byte) (ascii ? 1 : 0));
}
/**
* Number of readable bytes in the sequence.
*/
@Override
public int size() {
return (int) (getImplPtr() - getImplLo());
}
public long tailPadding() {
return getImplHi() - getImplPtr();
}
private void closeByteSink() {
final long capacityChange = allocatedCapacity() - lastAllocatedCapacity;
if (capacityChange != 0) {
Unsafe.incrReallocCount();
Unsafe.recordMemAlloc(capacityChange, memoryTag());
}
}
private void deflate() {
if (impl == 0) {
return;
}
final long capAdjustment = -1 * allocatedCapacity();
implDestroy(impl);
Unsafe.incrFreeCount();
Unsafe.recordMemAlloc(capAdjustment, memoryTag());
impl = 0;
}
private long getImplHi() {
assert impl != 0;
return Unsafe.getUnsafe().getLong(impl + BYTE_SINK_HI_OFFSET);
}
private long getImplLo() {
assert impl != 0;
return Unsafe.getUnsafe().getLong(impl + BYTE_SINK_LO_OFFSET);
}
private boolean getImplOverflow() {
return Unsafe.getUnsafe().getByte(impl + BYTE_SINK_OVERFLOW_OFFSET) != 0;
}
private long getImplPtr() {
return Unsafe.getUnsafe().getLong(impl + BYTE_SINK_PTR_OFFSET);
}
private void inflate() {
impl = implCreate(initialCapacity);
if (impl == 0) {
throw CairoException.nonCritical().setOutOfMemory(true).put("could not allocate direct byte sink [maxCapacity=").put(initialCapacity).put(']');
}
Unsafe.recordMemAlloc(this.allocatedCapacity(), memoryTag());
Unsafe.incrMallocCount();
}
private void setImplPtr(long ptr) {
Unsafe.getUnsafe().putLong(impl + BYTE_SINK_PTR_OFFSET, ptr);
}
protected int memoryTag() {
return MemoryTag.NATIVE_DIRECT_BYTE_SINK;
}
static {
Os.init();
}
}