pg-copy-streams
Version:
Low-Level COPY TO and COPY FROM streams for PostgreSQL in JavaScript using
377 lines (299 loc) • 8.59 kB
JavaScript
const Buffer = require('buffer').Buffer
function OffsetBuffer() {
this.offset = 0
this.size = 0
this.buffers = []
}
module.exports = OffsetBuffer
OffsetBuffer.prototype.isEmpty = function isEmpty() {
return this.size === 0
}
OffsetBuffer.prototype.clone = function clone(size) {
const r = new OffsetBuffer()
r.offset = this.offset
r.size = size
r.buffers = this.buffers.slice()
return r
}
OffsetBuffer.prototype.toChunks = function toChunks() {
if (this.size === 0) return []
// We are going to slice it anyway
if (this.offset !== 0) {
this.buffers[0] = this.buffers[0].slice(this.offset)
this.offset = 0
}
const chunks = []
let off = 0
let i
for (i = 0; off <= this.size && i < this.buffers.length; i++) {
let buf = this.buffers[i]
off += buf.length
// Slice off last buffer
if (off > this.size) {
buf = buf.slice(0, buf.length - (off - this.size))
this.buffers[i] = buf
}
chunks.push(buf)
}
// If some buffers were skipped - trim length
if (i < this.buffers.length) this.buffers.length = i
return chunks
}
OffsetBuffer.prototype.toString = function toString(enc) {
return this.toChunks()
.map(function (c) {
return c.toString(enc)
})
.join('')
}
OffsetBuffer.prototype.use = function use(buf, off, n) {
this.buffers = [buf]
this.offset = off
this.size = n
}
OffsetBuffer.prototype.push = function push(data) {
// Ignore empty writes
if (data.length === 0) return
this.size += data.length
this.buffers.push(data)
}
OffsetBuffer.prototype.has = function has(n) {
return this.size >= n
}
OffsetBuffer.prototype.skip = function skip(n) {
if (this.size === 0) return
this.size -= n
// Fast case, skip bytes in a first buffer
if (this.offset + n < this.buffers[0].length) {
this.offset += n
return
}
let left = n - (this.buffers[0].length - this.offset)
this.offset = 0
let shift
for (shift = 1; left > 0 && shift < this.buffers.length; shift++) {
const buf = this.buffers[shift]
if (buf.length > left) {
this.offset = left
break
}
left -= buf.length
}
this.buffers = this.buffers.slice(shift)
}
OffsetBuffer.prototype.copy = function copy(target, targetOff, off, n) {
if (this.size === 0) return
if (off !== 0) throw new Error('Unsupported offset in .copy()')
let toff = targetOff
const first = this.buffers[0]
const toCopy = Math.min(n, first.length - this.offset)
first.copy(target, toff, this.offset, this.offset + toCopy)
toff += toCopy
let left = n - toCopy
for (let i = 1; left > 0 && i < this.buffers.length; i++) {
const buf = this.buffers[i]
const toCopy = Math.min(left, buf.length)
buf.copy(target, toff, 0, toCopy)
toff += toCopy
left -= toCopy
}
}
OffsetBuffer.prototype.take = function take(n) {
if (n === 0) return Buffer.alloc(0)
this.size -= n
// Fast cases
const first = this.buffers[0].length - this.offset
if (first === n) {
let r = this.buffers.shift()
if (this.offset !== 0) {
r = r.slice(this.offset)
this.offset = 0
}
return r
} else if (first > n) {
const r = this.buffers[0].slice(this.offset, this.offset + n)
this.offset += n
return r
}
// Allocate and fill buffer
const out = Buffer.alloc(n)
let toOff = 0
let startOff = this.offset
let i
for (i = 0; toOff !== n && i < this.buffers.length; i++) {
const buf = this.buffers[i]
const toCopy = Math.min(buf.length - startOff, n - toOff)
buf.copy(out, toOff, startOff, startOff + toCopy)
if (startOff + toCopy < buf.length) {
this.offset = startOff + toCopy
break
} else {
toOff += toCopy
startOff = 0
}
}
this.buffers = this.buffers.slice(i)
if (this.buffers.length === 0) this.offset = 0
return out
}
OffsetBuffer.prototype.peekUInt8 = function peekUInt8() {
return this.buffers[0][this.offset]
}
OffsetBuffer.prototype.readUInt8 = function readUInt8() {
this.size -= 1
const first = this.buffers[0]
const r = first[this.offset]
if (++this.offset === first.length) {
this.offset = 0
this.buffers.shift()
}
return r
}
OffsetBuffer.prototype.readUInt16LE = function readUInt16LE() {
const first = this.buffers[0]
this.size -= 2
let r
let shift
// Fast case - first buffer has all bytes
if (first.length - this.offset >= 2) {
r = first.readUInt16LE(this.offset)
shift = 0
this.offset += 2
// One byte here - one byte there
} else {
r = first[this.offset] | (this.buffers[1][0] << 8)
shift = 1
this.offset = 1
}
if (this.offset === this.buffers[shift].length) {
this.offset = 0
shift++
}
if (shift !== 0) this.buffers = this.buffers.slice(shift)
return r
}
OffsetBuffer.prototype.readUInt24LE = function readUInt24LE() {
const first = this.buffers[0]
let r
let shift
const firstHas = first.length - this.offset
// Fast case - first buffer has all bytes
if (firstHas >= 3) {
r = first.readUInt16LE(this.offset) | (first[this.offset + 2] << 16)
shift = 0
this.offset += 3
// First buffer has 2 of 3 bytes
} else if (firstHas >= 2) {
r = first.readUInt16LE(this.offset) | (this.buffers[1][0] << 16)
shift = 1
this.offset = 1
// Slow case: First buffer has 1 of 3 bytes
} else {
r = first[this.offset]
this.offset = 0
this.buffers.shift()
this.size -= 1
r |= this.readUInt16LE() << 8
return r
}
this.size -= 3
if (this.offset === this.buffers[shift].length) {
this.offset = 0
shift++
}
if (shift !== 0) this.buffers = this.buffers.slice(shift)
return r
}
OffsetBuffer.prototype.readUInt32LE = function readUInt32LE() {
const first = this.buffers[0]
let r
let shift
const firstHas = first.length - this.offset
// Fast case - first buffer has all bytes
if (firstHas >= 4) {
r = first.readUInt32LE(this.offset)
shift = 0
this.offset += 4
// First buffer has 3 of 4 bytes
} else if (firstHas >= 3) {
r = (first.readUInt16LE(this.offset) | (first[this.offset + 2] << 16)) + this.buffers[1][0] * 0x1000000
shift = 1
this.offset = 1
// Slow case: First buffer has 2 of 4 bytes
} else if (firstHas >= 2) {
r = first.readUInt16LE(this.offset)
this.offset = 0
this.buffers.shift()
this.size -= 2
r += this.readUInt16LE() * 0x10000
return r
// Slow case: First buffer has 1 of 4 bytes
} else {
r = first[this.offset]
this.offset = 0
this.buffers.shift()
this.size -= 1
r += this.readUInt24LE() * 0x100
return r
}
this.size -= 4
if (this.offset === this.buffers[shift].length) {
this.offset = 0
shift++
}
if (shift !== 0) this.buffers = this.buffers.slice(shift)
return r
}
OffsetBuffer.prototype.readUInt16BE = function readUInt16BE() {
const r = this.readUInt16LE()
return ((r & 0xff) << 8) | (r >> 8)
}
OffsetBuffer.prototype.readUInt24BE = function readUInt24BE() {
const r = this.readUInt24LE()
return ((r & 0xff) << 16) | (((r >> 8) & 0xff) << 8) | (r >> 16)
}
OffsetBuffer.prototype.readUInt32BE = function readUInt32BE() {
const r = this.readUInt32LE()
return (((r & 0xff) << 24) | (((r >>> 8) & 0xff) << 16) | (((r >>> 16) & 0xff) << 8) | (r >>> 24)) >>> 0
}
// Signed number APIs
function signedInt8(num) {
if (num >= 0x80) return -(0xff ^ num) - 1
else return num
}
OffsetBuffer.prototype.peekInt8 = function peekInt8() {
return signedInt8(this.peekUInt8())
}
OffsetBuffer.prototype.readInt8 = function readInt8() {
return signedInt8(this.readUInt8())
}
function signedInt16(num) {
if (num >= 0x8000) return -(0xffff ^ num) - 1
else return num
}
OffsetBuffer.prototype.readInt16BE = function readInt16BE() {
return signedInt16(this.readUInt16BE())
}
OffsetBuffer.prototype.readInt16LE = function readInt16LE() {
return signedInt16(this.readUInt16LE())
}
function signedInt24(num) {
if (num >= 0x800000) return -(0xffffff ^ num) - 1
else return num
}
OffsetBuffer.prototype.readInt24BE = function readInt24BE() {
return signedInt24(this.readUInt24BE())
}
OffsetBuffer.prototype.readInt24LE = function readInt24LE() {
return signedInt24(this.readUInt24LE())
}
function signedInt32(num) {
if (num >= 0x80000000) return -(0xffffffff ^ num) - 1
else return num
}
OffsetBuffer.prototype.readInt32BE = function readInt32BE() {
return signedInt32(this.readUInt32BE())
}
OffsetBuffer.prototype.readInt32LE = function readInt32LE() {
return signedInt32(this.readUInt32LE())
}