Skip to content

Commit fcfaba5

Browse files
authored
Fix issue of returning incorrect entities when querying table with int64 values (#2386)
1 parent d98901e commit fcfaba5

File tree

6 files changed

+222
-8
lines changed

6 files changed

+222
-8
lines changed

ChangeLog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ Table:
1515

1616
- Fail the insert entity request with double property whose value is greater than MAX_VALUE (Issue #2387)
1717

18+
Table:
19+
20+
- Fixed issue of returning incorrect entities when querying table with int64 values. (issue #2385)
21+
1822
## 2023.12 Version 3.29.0
1923

2024
General:
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { IQueryContext } from "../IQueryContext";
2+
import IQueryNode from "./IQueryNode";
3+
import ValueNode from "./ValueNode";
4+
5+
/**
6+
* Represents a constant value which is stored in its underlying JavaScript representation.
7+
*
8+
* This is used to hold boolean, number, and string values that are provided in the query.
9+
* For example, the query `PartitionKey eq 'foo'` would contain a `ConstantNode` with the value `foo`.
10+
*/
11+
export default class BigNumberNode extends ValueNode {
12+
get name(): string {
13+
return "BigNumber";
14+
}
15+
16+
compare(context: IQueryContext, other: IQueryNode): number {
17+
const thisValue = this.evaluate(context) as string;
18+
const otherValue = other.evaluate(context) as string;
19+
20+
if (thisValue === undefined || otherValue === undefined || otherValue === null) {
21+
return NaN;
22+
}
23+
24+
if (thisValue.startsWith("-")) {
25+
// Compare two negative number
26+
if (otherValue.startsWith("-")) {
27+
return -(this.comparePositiveNumber(thisValue.substring(1), otherValue.substring(1)));
28+
}
29+
else {
30+
// Could be two 0s formated with -000 and 000
31+
if (this.trimZeros(thisValue.substring(1)).length === 0
32+
&& this.trimZeros(otherValue).length === 0) {
33+
return 0;
34+
}
35+
else {
36+
return -1;
37+
}
38+
}
39+
}
40+
else {
41+
// Could be two 0s formated with -000 and 000
42+
if (otherValue.startsWith("-")) {
43+
if (this.trimZeros(thisValue.substring(1)).length === 0
44+
&& this.trimZeros(otherValue).length === 0) {
45+
return 0;
46+
}
47+
else {
48+
return 1;
49+
}
50+
}
51+
else {
52+
return this.comparePositiveNumber(thisValue, otherValue);
53+
}
54+
}
55+
}
56+
57+
comparePositiveNumber(thisValue: string, otherValue: string): number {
58+
const thisNumberValue = this.trimZeros(thisValue);
59+
const otherNumberValue = this.trimZeros(otherValue);
60+
61+
if (thisNumberValue.length < otherNumberValue.length) {
62+
return -1
63+
}
64+
else if (thisNumberValue.length > otherNumberValue.length) {
65+
return 1;
66+
}
67+
68+
let index = 0;
69+
while (index < thisNumberValue.length) {
70+
if (thisNumberValue[index] < otherNumberValue[index]) {
71+
return -1;
72+
}
73+
else if (thisNumberValue[index] > otherNumberValue[index]) {
74+
return 1;
75+
}
76+
++index
77+
}
78+
79+
return 0;
80+
}
81+
82+
trimZeros(numberString: string): string {
83+
let index = 0;
84+
while (index < numberString.length) {
85+
if (numberString[index] === '0') {
86+
++index;
87+
}
88+
else {
89+
break;
90+
}
91+
}
92+
93+
return numberString.substring(index);
94+
}
95+
}

src/table/persistence/QueryInterpreter/QueryParser.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { QueryLexer, QueryTokenKind } from "./QueryLexer";
22
import AndNode from "./QueryNodes/AndNode";
3+
import BigNumberNode from "./QueryNodes/BigNumberNode";
34
import BinaryDataNode from "./QueryNodes/BinaryDataNode";
45
import ConstantNode from "./QueryNodes/ConstantNode";
56
import DateTimeNode from "./QueryNodes/DateTimeNode";
@@ -246,7 +247,7 @@ class QueryParser {
246247

247248
if (token.value!.endsWith("L")) {
248249
// This is a "long" number, which should be represented by its string equivalent
249-
return new ConstantNode(token.value!.substring(0, token.value!.length - 1));
250+
return new BigNumberNode(token.value!.substring(0, token.value!.length - 1));
250251
} else {
251252
return new ConstantNode(parseFloat(token.value!));
252253
}

tests/table/apis/table.entity.query.test.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1397,4 +1397,107 @@ describe("table Entity APIs test - using Azure/data-tables", () => {
13971397

13981398
await tableClient.deleteTable();
13991399
});
1400+
1401+
it("23. should find the correct long int, @loki", async () => {
1402+
const tableClient = createAzureDataTablesClient(
1403+
testLocalAzuriteInstance,
1404+
getUniqueName("longint")
1405+
);
1406+
const partitionKey = createUniquePartitionKey("");
1407+
const testEntity: TableTestEntity =
1408+
entityFactory.createBasicEntityForTest(partitionKey);
1409+
1410+
await tableClient.createTable({ requestOptions: { timeout: 60000 } });
1411+
let result = await tableClient.createEntity(testEntity);
1412+
1413+
const anotherPartitionKey = createUniquePartitionKey("");
1414+
const anotherEntity: TableTestEntity =
1415+
entityFactory.createBasicEntityForTest(anotherPartitionKey);
1416+
anotherEntity.int64Field = { value: "1234", type: "Int64" };
1417+
1418+
result = await tableClient.createEntity(anotherEntity);
1419+
assert.ok(result.etag);
1420+
1421+
for await (const entity of tableClient
1422+
.listEntities<TableTestEntity>({
1423+
queryOptions: {
1424+
filter: `int64Field gt 1233L and int64Field lt 1235L`
1425+
}
1426+
})) {
1427+
assert.deepStrictEqual(entity.int64Field, 1234n);
1428+
}
1429+
1430+
await tableClient.deleteTable();
1431+
});
1432+
1433+
it("24. should find the correct negative long int, @loki", async () => {
1434+
const tableClient = createAzureDataTablesClient(
1435+
testLocalAzuriteInstance,
1436+
getUniqueName("longint")
1437+
);
1438+
const partitionKey = createUniquePartitionKey("");
1439+
const testEntity: TableTestEntity =
1440+
entityFactory.createBasicEntityForTest(partitionKey);
1441+
testEntity.int64Field = { value: "-12345", type: "Int64" };
1442+
1443+
await tableClient.createTable({ requestOptions: { timeout: 60000 } });
1444+
let result = await tableClient.createEntity(testEntity);
1445+
1446+
const anotherPartitionKey = createUniquePartitionKey("");
1447+
const anotherEntity: TableTestEntity =
1448+
entityFactory.createBasicEntityForTest(anotherPartitionKey);
1449+
anotherEntity.int64Field = { value: "-1234", type: "Int64" };
1450+
1451+
result = await tableClient.createEntity(anotherEntity);
1452+
assert.ok(result.etag);
1453+
1454+
for await (const entity of tableClient
1455+
.listEntities<TableTestEntity>({
1456+
queryOptions: {
1457+
filter: `int64Field lt -1233L and int64Field gt -1235L`
1458+
}
1459+
})) {
1460+
assert.deepStrictEqual(entity.int64Field, -1234n);
1461+
}
1462+
1463+
await tableClient.deleteTable();
1464+
});
1465+
1466+
it("25. should find the correct negative long int, @loki", async () => {
1467+
const tableClient = createAzureDataTablesClient(
1468+
testLocalAzuriteInstance,
1469+
getUniqueName("longint")
1470+
);
1471+
const partitionKey = createUniquePartitionKey("");
1472+
const testEntity: TableTestEntity =
1473+
entityFactory.createBasicEntityForTest(partitionKey);
1474+
testEntity.int64Field = { value: "12345", type: "Int64" };
1475+
1476+
await tableClient.createTable({ requestOptions: { timeout: 60000 } });
1477+
let result = await tableClient.createEntity(testEntity);
1478+
1479+
const anotherPartitionKey = createUniquePartitionKey("");
1480+
const anotherEntity: TableTestEntity =
1481+
entityFactory.createBasicEntityForTest(anotherPartitionKey);
1482+
anotherEntity.int64Field = { value: "-1234", type: "Int64" };
1483+
1484+
result = await tableClient.createEntity(anotherEntity);
1485+
assert.ok(result.etag);
1486+
1487+
let count = 0;
1488+
1489+
for await (const entity of tableClient
1490+
.listEntities<TableTestEntity>({
1491+
queryOptions: {
1492+
filter: `int64Field gt -1235L`
1493+
}
1494+
})) {
1495+
entity;
1496+
++count;
1497+
}
1498+
1499+
assert.deepStrictEqual(count, 2);
1500+
1501+
await tableClient.deleteTable();
1502+
});
14001503
});

tests/table/unit/query.parser.unit.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,12 +260,12 @@ describe("Query Parser", () => {
260260
{
261261
name: "Correctly handles longs",
262262
originalQuery: "myInt lt 123.01L",
263-
expectedQuery: "(lt (id myInt) \"123.01\")"
263+
expectedQuery: "(lt (id myInt) (BigNumber 123.01))"
264264
},
265265
{
266266
name: "Correctly handles longs with a negative sign",
267267
originalQuery: "myInt gt -123.01L",
268-
expectedQuery: "(gt (id myInt) \"-123.01\")"
268+
expectedQuery: "(gt (id myInt) (BigNumber -123.01))"
269269
}
270270
])
271271

tsconfig.json

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"preserveConstEnums": true,
66
"sourceMap": true,
77
"newLine": "LF",
8-
"target": "es2017",
8+
"target": "ES2020",
99
"moduleResolution": "node",
1010
"noUnusedLocals": true,
1111
"noUnusedParameters": false,
@@ -16,13 +16,24 @@
1616
"declarationMap": true,
1717
"importHelpers": true,
1818
"declarationDir": "./typings",
19-
"lib": ["es5", "es6", "es7", "esnext", "dom"],
19+
"lib": [
20+
"es5",
21+
"es6",
22+
"es7",
23+
"esnext",
24+
"dom"
25+
],
2026
"esModuleInterop": true,
2127
"downlevelIteration": true,
2228
"useUnknownInCatchVariables": false,
2329
"skipLibCheck": true,
2430
},
2531
"compileOnSave": true,
26-
"exclude": ["node_modules"],
27-
"include": ["./src/**/*.ts", "./tests/**/*.ts"]
28-
}
32+
"exclude": [
33+
"node_modules"
34+
],
35+
"include": [
36+
"./src/**/*.ts",
37+
"./tests/**/*.ts"
38+
]
39+
}

0 commit comments

Comments
 (0)