diff --git a/examples/client/index.html b/examples/client/index.html index caedefd2..23fb0859 100644 --- a/examples/client/index.html +++ b/examples/client/index.html @@ -19,7 +19,7 @@ db.fields().then((fields) => { fields.map((field) => { dbFields.add(field.fieldName); - fieldTypes[field.fieldName] = field.fieldType; + fieldTypes[field.fieldName] = db.decodeType(field.fieldType); }); document.getElementById("fields").innerHTML = JSON.stringify( @@ -88,8 +88,34 @@ return "Error: 'value' in 'where' clause is missing."; } - // todo: find out how to decode bigint field types. i.e. what is a 34n? - // assert `where` type + const allowedRanks = fieldTypes[whereNode.key]; + + let valueRank; + let valueType; + + if (whereNode.value === null) { + valueRank = 1; + valueType = "null"; + } + if (typeof whereNode.value === "boolean") { + valueRank = 2; + valueType = "boolean"; + } + if ( + typeof whereNode.value === "number" || + typeof whereNode.value === "bigint" + ) { + valueRank = 3; + valueType = "number or bigint"; + } + if (typeof whereNode.value === "string") { + valueRank = 4; + valueType = "string"; + } + + if (!allowedRanks.has(valueRank)) { + return `Error: 'key: ${whereNode.key} does not have type: ${valueType}.`; + } } if (query.orderBy) { diff --git a/src/database.ts b/src/database.ts index eeefe6ed..2f7af0ab 100644 --- a/src/database.ts +++ b/src/database.ts @@ -21,6 +21,16 @@ export type Query = { orderBy?: OrderBy[]; }; +// This record maps from fieldRank -> Bitmask +export const typeRankToBits: Record = { + 4: BigInt(1), // FieldTypeString = 1 << 0 + 3: BigInt(2), // FieldTypeNumber = 1 << 1 + //'Object': BigInt(4), // FieldTypeObject = 1 << 2 + //'Array': BigInt(8), // FieldTypeArray = 1 << 3 + 2: BigInt(16), // FieldTypeBoolean = 1 << 4 + 1: BigInt(32), // FieldTypeNull = 1 << 5 +}; + function parseIgnoringSuffix(x: string) { // TODO: implement a proper parser. try { @@ -203,4 +213,17 @@ export class Database { } } } + + // given a bitmask, we decode and return the types + decodeType(bitmask: bigint): Set { + let decodedRanks = new Set(); + + for (const [fieldRank, bitValue] of Object.entries(typeRankToBits)) { + if ((bitmask & bitValue) !== BigInt(0)) { + decodedRanks.add(Number(fieldRank)); + } + } + + return decodedRanks; + } } diff --git a/src/tests/database.test.ts b/src/tests/database.test.ts index e6a787e0..266d0815 100644 --- a/src/tests/database.test.ts +++ b/src/tests/database.test.ts @@ -94,3 +94,43 @@ describe("test query relation", () => { expect(results).toEqual([25]); }); }); + +describe("test type bitmask decoding", () => { + let mockDataFile: jest.Mocked; + let mockIndexFile: jest.Mocked>; + let database: Database; + beforeEach(() => { + (DataFile.forUrl as jest.Mock).mockReturnValue({ + get: jest.fn().mockResolvedValue("mocked response"), + }); + mockDataFile = DataFile.forUrl( + "http://example.com/data" + ) as jest.Mocked; + + mockIndexFile = { + indexFileHeader: jest.fn(), + indexHeaders: jest.fn(), + indexRecord: jest.fn(), + dataRecord: jest.fn(), + } as jest.Mocked>; + + // instantiate a Database object with given mocked data file and index file + database = Database.forDataFileAndIndexFile(mockDataFile, mockIndexFile); + }); + + it("it should return the correct field rank from fieldtype", async () => { + const testCases = [ + { fieldType: BigInt(34), expected: new Set([1, 3]) }, + { fieldType: BigInt(1), expected: new Set([4]) }, + { fieldType: BigInt(2), expected: new Set([3]) }, + { fieldType: BigInt(16), expected: new Set([2]) }, + { fieldType: BigInt(32), expected: new Set([1]) }, + { fieldType: BigInt(33), expected: new Set([1, 4]) }, + ]; + + testCases.forEach(({ fieldType, expected }) => { + const result = database.decodeType(fieldType); + expect(result).toEqual(expected); + }); + }); +});