Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions src/cdbToSql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { inferKeys } from "./keyInference";
import type { TableKeys } from "./keyInference";
import { CDBReader } from "./reader";
import { escapeSqlIdentifier } from "./sqlUtils";
import { CHUNK_TYPE, DATA_TYPE } from "./tableMetadata";
import { ChunkType, DataType } from "./types";
import type {
CdbToSqlOptions,
SqlDatabase,
Expand Down Expand Up @@ -126,7 +126,7 @@ export function cdbToSql(
}

const tables =
(wrapperChildren[CHUNK_TYPE.DATABASE_TABLES] as TableInfo[] | undefined) ??
(wrapperChildren[ChunkType.DATABASE_TABLES] as TableInfo[] | undefined) ??
[];

// DB_STRUCTURE mirrors the PCM convention used by sqlToCdb: TableName keeps the
Expand Down Expand Up @@ -188,33 +188,33 @@ export function cdbToSql(
let baseType: string;
let encodedType: number;
switch (col.type) {
case DATA_TYPE.FLOAT:
case DataType.FLOAT:
baseType = "REAL";
encodedType = col.type;
break;
case DATA_TYPE.STRING:
case DATA_TYPE.INTEGER_LIST:
case DATA_TYPE.FLOAT_LIST:
case DataType.STRING:
case DataType.INTEGER_LIST:
case DataType.FLOAT_LIST:
baseType = "TEXT";
encodedType = col.type;
break;
case DATA_TYPE.BOOLEAN:
case DataType.BOOLEAN:
// `SQLiteExporter` (the official PCM tool) has no case for BOOLEAN,
// INTEGER_BYTE or INTEGER_SHORT: they all fall into its default
// branch and get encoded as plain INTEGER. Match that by default so
// our output stays importable there; preciseTypes opts back into
// preserving the exact CDB type for our own round-trip.
baseType = options?.preciseTypes ? "NUMERIC" : "INTEGER";
encodedType = options?.preciseTypes ? col.type : DATA_TYPE.INTEGER;
encodedType = options?.preciseTypes ? col.type : DataType.INTEGER;
break;
case DATA_TYPE.INTEGER_BYTE:
case DATA_TYPE.INTEGER_SHORT:
case DataType.INTEGER_BYTE:
case DataType.INTEGER_SHORT:
baseType = "INTEGER";
encodedType = options?.preciseTypes ? col.type : DATA_TYPE.INTEGER;
encodedType = options?.preciseTypes ? col.type : DataType.INTEGER;
break;
default:
baseType = "INTEGER";
encodedType = DATA_TYPE.INTEGER;
encodedType = DataType.INTEGER;
break;
}

Expand Down
6 changes: 3 additions & 3 deletions src/compression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/

import { deflate, inflate } from "pako";
import { MAGIC } from "./tableMetadata";
import { Magic } from "./types";

function toUint8Array(data: ArrayBuffer | Uint8Array): Uint8Array {
return data instanceof Uint8Array ? data : new Uint8Array(data);
Expand All @@ -25,7 +25,7 @@ export function decompressCdb(data: ArrayBuffer | Uint8Array): ArrayBuffer {
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);

// Check if data is compressed (magic header)
if (view.getUint32(0, true) !== MAGIC.COMPRESSION_MAGIC) {
if (view.getUint32(0, true) !== Magic.COMPRESSION_MAGIC) {
return bytes.slice().buffer;
}

Expand Down Expand Up @@ -78,7 +78,7 @@ export function compressCdb(data: ArrayBuffer | Uint8Array): ArrayBuffer {
const result = new Uint8Array(12 + compressed.length);
const view = new DataView(result.buffer);

view.setUint32(0, MAGIC.COMPRESSION_MAGIC, true);
view.setUint32(0, Magic.COMPRESSION_MAGIC, true);
view.setUint32(4, uncompressedData.length, true);
view.setUint32(8, compressed.length, true);
result.set(compressed, 12);
Expand Down
7 changes: 1 addition & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,4 @@ export type {
} from "./types";

export { ChunkType, DataType, Magic } from "./types";
export {
CHUNK_TYPE,
DATA_TYPE,
MAGIC,
TABLE_FLAGS_BY_ID,
} from "./tableMetadata";
export { TABLE_FLAGS_BY_ID } from "./tableMetadata";
88 changes: 41 additions & 47 deletions src/reader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,8 @@
* Handles parsing of chunk hierarchy, data types, and value unpacking
*/

import type {
ChunkHeader,
CDBChunk,
ColumnData,
DataType,
ColumnInfo,
} from "./types";
import { CHUNK_TYPE, DATA_TYPE, MAGIC } from "./tableMetadata";
import type { ChunkHeader, CDBChunk, ColumnData, ColumnInfo } from "./types";
import { ChunkType, DataType, Magic } from "./types";

type ColumnDefinition = Omit<ColumnInfo, "data"> & {
data?: ColumnData;
Expand Down Expand Up @@ -106,7 +100,7 @@ export class CDBReader {
}

private readChunkHeader(): ChunkHeader {
this.readMagic(MAGIC.CHUNK_BEGIN, "CHUNK_BEGIN");
this.readMagic(Magic.CHUNK_BEGIN, "CHUNK_BEGIN");
const chunkSize = this.read32();
const chunkType = this.read32();
const flags = this.read32();
Expand All @@ -121,7 +115,7 @@ export class CDBReader {
}

this.readPadding();
this.readMagic(MAGIC.CHUNK_SEPARATOR, "CHUNK_SEPARATOR");
this.readMagic(Magic.CHUNK_SEPARATOR, "CHUNK_SEPARATOR");

return { chunkSize, chunkType, flags, description };
}
Expand All @@ -134,16 +128,16 @@ export class CDBReader {
let result: CDBChunk;

switch (header.chunkType) {
case CHUNK_TYPE.ROW_COUNT:
case CHUNK_TYPE.TABLE_ID:
case CHUNK_TYPE.TABLE_FLAGS:
case CHUNK_TYPE.DATABASE_FLAGS:
case CHUNK_TYPE.COLUMN_INDEX:
case CHUNK_TYPE.COLUMN_DATA_TYPE:
case ChunkType.ROW_COUNT:
case ChunkType.TABLE_ID:
case ChunkType.TABLE_FLAGS:
case ChunkType.DATABASE_FLAGS:
case ChunkType.COLUMN_INDEX:
case ChunkType.COLUMN_DATA_TYPE:
result = { type: header.chunkType, value: this.read32() };
break;

case CHUNK_TYPE.COLUMN_VALUES:
case ChunkType.COLUMN_VALUES:
{
const dataBytes = chunkEndPos - this.pos - 4;
const values: number[] = [];
Expand All @@ -154,7 +148,7 @@ export class CDBReader {
}
break;

case CHUNK_TYPE.COLUMN_BLOB_DATA:
case ChunkType.COLUMN_BLOB_DATA:
{
const sizedDataBytes = chunkEndPos - this.pos - 4;
result = {
Expand All @@ -164,16 +158,16 @@ export class CDBReader {
}
break;

case CHUNK_TYPE.DATABASE_TABLES:
case ChunkType.DATABASE_TABLES:
{
const tables = this.readArray(() => {
const tableChunk = this.readChunk();
const rowCount =
this.getRequiredChild<number>(tableChunk, CHUNK_TYPE.ROW_COUNT) ||
this.getRequiredChild<number>(tableChunk, ChunkType.ROW_COUNT) ||
0;
const columnDefinitions = this.getRequiredChild<ColumnDefinition[]>(
tableChunk,
CHUNK_TYPE.COLUMN_DEFINITIONS,
ChunkType.COLUMN_DEFINITIONS,
);
const columns: ColumnInfo[] = columnDefinitions.map((column) => ({
name: column.name,
Expand All @@ -192,19 +186,19 @@ export class CDBReader {
columns,
tableId: this.getRequiredChild<number>(
tableChunk,
CHUNK_TYPE.TABLE_ID,
ChunkType.TABLE_ID,
),
tableFlags: this.getRequiredChild<number>(
tableChunk,
CHUNK_TYPE.TABLE_FLAGS,
ChunkType.TABLE_FLAGS,
),
};
});
result = { type: header.chunkType, value: tables };
}
break;

case CHUNK_TYPE.COLUMN_DEFINITIONS:
case ChunkType.COLUMN_DEFINITIONS:
{
const columns = this.readArray(() => {
const columnChunk = this.readChunk();
Expand All @@ -214,11 +208,11 @@ export class CDBReader {
name: colName,
type: this.getRequiredChild<DataType>(
columnChunk,
CHUNK_TYPE.COLUMN_DATA_TYPE,
ChunkType.COLUMN_DATA_TYPE,
),
columnIndex: this.getRequiredChild<number>(
columnChunk,
CHUNK_TYPE.COLUMN_INDEX,
ChunkType.COLUMN_INDEX,
),
columnChunk: columnChunk, // Store for later conversion
};
Expand All @@ -227,9 +221,9 @@ export class CDBReader {
}
break;

case CHUNK_TYPE.WRAPPER:
case CHUNK_TYPE.TABLE:
case CHUNK_TYPE.COLUMN:
case ChunkType.WRAPPER:
case ChunkType.TABLE:
case ChunkType.COLUMN:
{
const children: Record<number, unknown> = {};
while (this.pos < chunkEndPos) {
Expand Down Expand Up @@ -269,20 +263,20 @@ export class CDBReader {
}

this.readPadding();
this.readMagic(MAGIC.CHUNK_END, "CHUNK_END");
this.readMagic(Magic.CHUNK_END, "CHUNK_END");
return result;
}

private readArray<T>(itemReader: () => T): T[] {
this.readMagic(MAGIC.ARRAY_BEGIN, "ARRAY_BEGIN");
this.readMagic(Magic.ARRAY_BEGIN, "ARRAY_BEGIN");
const count = this.read32();
const items: T[] = [];

for (let i = 0; i < count; i++) {
items.push(itemReader());
}

this.readMagic(MAGIC.ARRAY_END, "ARRAY_END");
this.readMagic(Magic.ARRAY_END, "ARRAY_END");
return items;
}

Expand All @@ -292,37 +286,37 @@ export class CDBReader {
): ColumnData {
const dataType = this.getRequiredChild<DataType>(
columnChunk,
CHUNK_TYPE.COLUMN_DATA_TYPE,
ChunkType.COLUMN_DATA_TYPE,
);
const rawData =
(this.getChunkChildren(columnChunk)[CHUNK_TYPE.COLUMN_VALUES] as
(this.getChunkChildren(columnChunk)[ChunkType.COLUMN_VALUES] as
| number[]
| undefined) ?? [];
const sizedData =
(this.getChunkChildren(columnChunk)[CHUNK_TYPE.COLUMN_BLOB_DATA] as
(this.getChunkChildren(columnChunk)[ChunkType.COLUMN_BLOB_DATA] as
| Uint8Array
| undefined) ?? new Uint8Array([0, 0, 0, 0]);

// If no data, return array of zeros/empty strings based on type
if (rawData.length === 0 && rowCount !== undefined) {
switch (dataType) {
case DATA_TYPE.STRING:
case DataType.STRING:
return Array(rowCount).fill("");
case DATA_TYPE.FLOAT:
case DataType.FLOAT:
return Array(rowCount).fill(0.0);
case DATA_TYPE.FLOAT_LIST:
case DATA_TYPE.INTEGER_LIST:
case DataType.FLOAT_LIST:
case DataType.INTEGER_LIST:
return Array(rowCount).fill("()");
default:
return Array(rowCount).fill(0);
}
}

switch (dataType) {
case DATA_TYPE.INTEGER:
case DataType.INTEGER:
return rawData.map((value) => value | 0);

case DATA_TYPE.BOOLEAN: {
case DataType.BOOLEAN: {
if (rowCount === undefined) {
throw new Error("Row count required for boolean type");
}
Expand All @@ -336,15 +330,15 @@ export class CDBReader {
return boolValues;
}

case DATA_TYPE.INTEGER_BYTE: {
case DataType.INTEGER_BYTE: {
const bytes = new Uint8Array(new Uint32Array(rawData).buffer).slice(
0,
rowCount,
);
return Array.from(bytes, (b) => (b > 127 ? b - 256 : b));
}

case DATA_TYPE.INTEGER_SHORT: {
case DataType.INTEGER_SHORT: {
const bytes = new Uint8Array(new Uint32Array(rawData).buffer);
const int16Values: number[] = [];
for (let i = 0; i < rowCount; i++) {
Expand All @@ -354,23 +348,23 @@ export class CDBReader {
return int16Values;
}

case DATA_TYPE.FLOAT: {
case DataType.FLOAT: {
const view = new DataView(new ArrayBuffer(4));
return rawData.map((intValue) => {
view.setUint32(0, intValue, true);
return view.getFloat32(0, true);
});
}

case DATA_TYPE.STRING:
case DataType.STRING:
return this.parseStrings(sizedData, rawData);

case DATA_TYPE.INTEGER_LIST:
case DataType.INTEGER_LIST:
return this.parseNumericLists(sizedData, rawData, (view, offset) => {
return view.getUint32(offset, true) | 0;
});

case DATA_TYPE.FLOAT_LIST:
case DataType.FLOAT_LIST:
return this.parseNumericLists(
sizedData,
rawData,
Expand Down
Loading