55import { SQLiteCloudError , type SQLCloudRowsetMetadata , type SQLiteCloudDataTypes } from './types'
66import { SQLiteCloudRowset } from './rowset'
77
8+ import fs from 'fs'
89const lz4 = require ( 'lz4js' )
910
11+ import lz4bis from 'lz4'
12+
1013// The server communicates with clients via commands defined in
1114// SQLiteCloud Server Protocol (SCSP), see more at:
1215// https://github.com/sqlitecloud/sdk/blob/master/PROTOCOL.md
@@ -47,32 +50,39 @@ export function parseCommandLength(data: Buffer): number {
4750}
4851
4952/** Receive a compressed buffer, decompress with lz4, return buffer and datatype */
50- export function decompressBuffer ( buffer : Buffer ) : { buffer : Buffer ; dataType : string } {
53+ export function decompressBuffer ( buffer : Buffer ) : { buffer : Buffer ; dataType : string ; remainingBuffer : Buffer } {
54+ // https://github.com/sqlitecloud/sdk/blob/master/PROTOCOL.md#scsp-compression
55+ // jest test/database.test.ts -t "select large result set"
56+
57+ // starts with %<commandLength> <compressed> <uncompressed>
5158 const spaceIndex = buffer . indexOf ( ' ' )
52- buffer = buffer . subarray ( spaceIndex + 1 )
59+ const commandLength = parseInt ( buffer . subarray ( 1 , spaceIndex ) . toString ( 'utf8' ) )
5360
54- // extract compressed size
55- const compressedSize = parseInt ( buffer . subarray ( 0 , buffer . indexOf ( ' ' ) + 1 ) . toString ( 'utf8' ) )
56- buffer = buffer . subarray ( buffer . indexOf ( ' ' ) + 1 )
61+ let commandBuffer = buffer . subarray ( spaceIndex + 1 , spaceIndex + 1 + commandLength )
62+ const remainingBuffer = buffer . subarray ( spaceIndex + 1 + commandLength )
5763
58- // extract decompressed size
59- const decompressedSize = parseInt ( buffer . subarray ( 0 , buffer . indexOf ( ' ' ) + 1 ) . toString ( 'utf8' ) )
60- buffer = buffer . subarray ( buffer . indexOf ( ' ' ) + 1 )
64+ // extract compressed + decompressed size
65+ const compressedSize = parseInt ( commandBuffer . subarray ( 0 , commandBuffer . indexOf ( ' ' ) + 1 ) . toString ( 'utf8' ) )
66+ commandBuffer = commandBuffer . subarray ( commandBuffer . indexOf ( ' ' ) + 1 )
67+ const decompressedSize = parseInt ( commandBuffer . subarray ( 0 , commandBuffer . indexOf ( ' ' ) + 1 ) . toString ( 'utf8' ) )
68+ commandBuffer = commandBuffer . subarray ( commandBuffer . indexOf ( ' ' ) + 1 )
6169
6270 // extract compressed dataType
63- const dataType = buffer . subarray ( 0 , 1 ) . toString ( 'utf8' )
64- const decompressedBuffer = Buffer . alloc ( decompressedSize )
65- const compressedBuffer = buffer . subarray ( buffer . length - compressedSize )
71+ const dataType = commandBuffer . subarray ( 0 , 1 ) . toString ( 'utf8' )
72+ let decompressedBuffer = Buffer . alloc ( decompressedSize )
73+ const compressedBuffer = commandBuffer . subarray ( commandBuffer . length - compressedSize )
6674
6775 // lz4js library is javascript and doesn't have types so we silence the type check
6876 // eslint-disable-next-line
6977 const decompressionResult : number = lz4 . decompressBlock ( compressedBuffer , decompressedBuffer , 0 , compressedSize , 0 )
70- buffer = Buffer . concat ( [ buffer . subarray ( 0 , buffer . length - compressedSize ) , decompressedBuffer ] )
78+ // the entire command is composed of the header (which is not compressed) + the decompressed block
79+ decompressedBuffer = Buffer . concat ( [ commandBuffer . subarray ( 0 , commandBuffer . length - compressedSize ) , decompressedBuffer ] )
80+
7181 if ( decompressionResult <= 0 || decompressionResult !== decompressedSize ) {
7282 throw new Error ( `lz4 decompression error at offset ${ decompressionResult } ` )
7383 }
7484
75- return { buffer, dataType }
85+ return { buffer : decompressedBuffer , dataType, remainingBuffer }
7686}
7787
7888/** Parse error message or extended error message */
@@ -136,7 +146,7 @@ export function parseRowsetHeader(buffer: Buffer): { index: number; metadata: SQ
136146 // extract rowset header
137147 const { data, fwdBuffer } = popIntegers ( buffer , 3 )
138148
139- return {
149+ const result = {
140150 index,
141151 metadata : {
142152 version : data [ 0 ] ,
@@ -146,6 +156,9 @@ export function parseRowsetHeader(buffer: Buffer): { index: number; metadata: SQ
146156 } ,
147157 fwdBuffer
148158 }
159+
160+ // console.debug(`parseRowsetHeader`, result)
161+ return result
149162}
150163
151164/** Extract column names and, optionally, more metadata out of a rowset's header */
@@ -218,7 +231,7 @@ export function parseRowsetChunks(buffers: Buffer[]): SQLiteCloudRowset {
218231
219232 // validate and skip data type
220233 const dataType = buffer . subarray ( 0 , 1 ) . toString ( )
221- console . assert ( dataType === CMD_ROWSET_CHUNK )
234+ if ( dataType !== CMD_ROWSET_CHUNK ) throw new Error ( `parseRowsetChunks - dataType: ${ dataType } should be CMD_ROWSET_CHUNK` )
222235 buffer = buffer . subarray ( buffer . indexOf ( ' ' ) + 1 )
223236
224237 while ( buffer . length > 0 && ! bufferStartsWith ( buffer , ROWSET_CHUNKS_END ) ) {
@@ -268,23 +281,25 @@ export function popData(buffer: Buffer): { data: SQLiteCloudDataTypes | SQLiteCl
268281
269282 // first character is the data type
270283 console . assert ( buffer && buffer instanceof Buffer )
271- const dataType : string = buffer . subarray ( 0 , 1 ) . toString ( 'utf8' )
272- console . assert ( dataType ! == CMD_COMPRESSED , " Compressed data shouldn't be decompressed before parsing" )
273- console . assert ( dataType ! == CMD_ROWSET_CHUNK , 'Chunked data should be parsed by parseRowsetChunks' )
284+ let dataType : string = buffer . subarray ( 0 , 1 ) . toString ( 'utf8' )
285+ if ( dataType == CMD_COMPRESSED ) throw new Error ( ' Compressed data should be decompressed before parsing' )
286+ if ( dataType == CMD_ROWSET_CHUNK ) throw new Error ( 'Chunked data should be parsed by parseRowsetChunks' )
274287
275288 let spaceIndex = buffer . indexOf ( ' ' )
276289 if ( spaceIndex === - 1 ) {
277290 spaceIndex = buffer . length - 1
278291 }
279292
280- let commandEnd = - 1
293+ let commandEnd = - 1 ,
294+ commandLength = - 1
281295 if ( dataType === CMD_INT || dataType === CMD_FLOAT || dataType === CMD_NULL ) {
282296 commandEnd = spaceIndex + 1
283297 } else {
284- const commandLength = parseInt ( buffer . subarray ( 1 , spaceIndex ) . toString ( ) )
298+ commandLength = parseInt ( buffer . subarray ( 1 , spaceIndex ) . toString ( ) )
285299 commandEnd = spaceIndex + 1 + commandLength
286300 }
287301
302+ // console.debug(`popData - dataType: ${dataType}, spaceIndex: ${spaceIndex}, commandLength: ${commandLength}, commandEnd: ${commandEnd}`)
288303 switch ( dataType ) {
289304 case CMD_INT :
290305 return popResults ( parseInt ( buffer . subarray ( 1 , spaceIndex ) . toString ( ) ) )
@@ -313,7 +328,9 @@ export function popData(buffer: Buffer): { data: SQLiteCloudDataTypes | SQLiteCl
313328 break
314329 }
315330
316- throw new TypeError ( `Data type: ${ dataType } is not defined in SCSP` )
331+ const msg = `popData - Data type: ${ Number ( dataType ) } '${ dataType } ' is not defined in SCSP, spaceIndex: ${ spaceIndex } , commandLength: ${ commandLength } , commandEnd: ${ commandEnd } `
332+ console . error ( msg )
333+ throw new TypeError ( msg )
317334}
318335
319336/** Format a command to be sent via SCSP protocol */
0 commit comments