diff --git a/sdk/tables/data-tables/CHANGELOG.md b/sdk/tables/data-tables/CHANGELOG.md index 89714671970e..0bfc5b8d4bed 100644 --- a/sdk/tables/data-tables/CHANGELOG.md +++ b/sdk/tables/data-tables/CHANGELOG.md @@ -8,6 +8,8 @@ ### Bugs Fixed +- Fix encoding for Date objects when filtering on a DateTime field [#23058](https://github.com/Azure/azure-sdk-for-js/pull/23058) + ### Other Changes ## 13.1.2 (2022-06-07) diff --git a/sdk/tables/data-tables/recordings/browsers/tableclient_listentities/recording_should_filter_dates_correctly.json b/sdk/tables/data-tables/recordings/browsers/tableclient_listentities/recording_should_filter_dates_correctly.json new file mode 100644 index 000000000000..aa45b916b197 --- /dev/null +++ b/sdk/tables/data-tables/recordings/browsers/tableclient_listentities/recording_should_filter_dates_correctly.json @@ -0,0 +1,202 @@ +{ + "Entries": [ + { + "RequestUri": "https://fakeaccountname.table.core.windows.net/tableClientTestbrowser?st=2021-08-03T08:52:15Z\u0026spr=https\u0026sig=fakesigval", + "RequestMethod": "POST", + "RequestHeaders": { + "Accept": "application/json;odata=minimalmetadata", + "Accept-Encoding": "gzip, deflate, br", + "Accept-Language": "en-US", + "Content-Length": "102", + "Content-Type": "application/json;odata=nometadata", + "dataserviceversion": "3.0", + "prefer": "return-no-content", + "Referer": "http://localhost:9876/", + "sec-ch-ua": "", + "sec-ch-ua-mobile": "?0", + "sec-ch-ua-platform": "", + "Sec-Fetch-Dest": "empty", + "Sec-Fetch-Mode": "cors", + "Sec-Fetch-Site": "same-site", + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/103.0.5058.0 Safari/537.36", + "x-ms-client-request-id": "5ea894b7-b959-4070-a2a7-c6de78ea59a6", + "x-ms-useragent": "azsdk-js-data-tables/13.1.2 core-rest-pipeline/1.9.2 OS/Linuxx86_64", + "x-ms-version": "2019-02-02" + }, + "RequestBody": { + "PartitionKey": "p1", + "RowKey": "r1", + "date": "2019-07-10T18:59:59.999Z", + "date@odata.type": "Edm.DateTime" + }, + "StatusCode": 204, + "ResponseHeaders": { + "Cache-Control": "no-cache", + "Content-Length": "0", + "DataServiceId": "https://fakeaccountname.table.core.windows.net/tableClientTestbrowser(PartitionKey=\u0027p1\u0027,RowKey=\u0027r1\u0027)", + "Date": "Tue, 06 Sep 2022 18:24:25 GMT", + "ETag": "W/\u0022datetime\u00272022-09-06T18%3A24%3A25.8083421Z\u0027\u0022", + "Location": "https://fakeaccountname.table.core.windows.net/tableClientTestbrowser(PartitionKey=\u0027p1\u0027,RowKey=\u0027r1\u0027)", + "Preference-Applied": "return-no-content", + "Server": [ + "Windows-Azure-Table/1.0", + "Microsoft-HTTPAPI/2.0" + ], + "X-Content-Type-Options": "nosniff", + "x-ms-client-request-id": "5ea894b7-b959-4070-a2a7-c6de78ea59a6", + "x-ms-request-id": "22b598b7-8002-00f9-721d-c299a3000000", + "x-ms-version": "2019-02-02" + }, + "ResponseBody": null + }, + { + "RequestUri": "https://fakeaccountname.table.core.windows.net/tableClientTestbrowser?st=2021-08-03T08:52:15Z\u0026spr=https\u0026sig=fakesigval", + "RequestMethod": "POST", + "RequestHeaders": { + "Accept": "application/json;odata=minimalmetadata", + "Accept-Encoding": "gzip, deflate, br", + "Accept-Language": "en-US", + "Content-Length": "102", + "Content-Type": "application/json;odata=nometadata", + "dataserviceversion": "3.0", + "prefer": "return-no-content", + "Referer": "http://localhost:9876/", + "sec-ch-ua": "", + "sec-ch-ua-mobile": "?0", + "sec-ch-ua-platform": "", + "Sec-Fetch-Dest": "empty", + "Sec-Fetch-Mode": "cors", + "Sec-Fetch-Site": "same-site", + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/103.0.5058.0 Safari/537.36", + "x-ms-client-request-id": "74adb1b9-4ff5-4789-b5ac-60c1693f0edd", + "x-ms-useragent": "azsdk-js-data-tables/13.1.2 core-rest-pipeline/1.9.2 OS/Linuxx86_64", + "x-ms-version": "2019-02-02" + }, + "RequestBody": { + "PartitionKey": "p1", + "RowKey": "r2", + "date": "2019-07-10T19:00:00.000Z", + "date@odata.type": "Edm.DateTime" + }, + "StatusCode": 204, + "ResponseHeaders": { + "Cache-Control": "no-cache", + "Content-Length": "0", + "DataServiceId": "https://fakeaccountname.table.core.windows.net/tableClientTestbrowser(PartitionKey=\u0027p1\u0027,RowKey=\u0027r2\u0027)", + "Date": "Tue, 06 Sep 2022 18:24:25 GMT", + "ETag": "W/\u0022datetime\u00272022-09-06T18%3A24%3A25.8283295Z\u0027\u0022", + "Location": "https://fakeaccountname.table.core.windows.net/tableClientTestbrowser(PartitionKey=\u0027p1\u0027,RowKey=\u0027r2\u0027)", + "Preference-Applied": "return-no-content", + "Server": [ + "Windows-Azure-Table/1.0", + "Microsoft-HTTPAPI/2.0" + ], + "X-Content-Type-Options": "nosniff", + "x-ms-client-request-id": "74adb1b9-4ff5-4789-b5ac-60c1693f0edd", + "x-ms-request-id": "22b598ba-8002-00f9-731d-c299a3000000", + "x-ms-version": "2019-02-02" + }, + "ResponseBody": null + }, + { + "RequestUri": "https://fakeaccountname.table.core.windows.net/tableClientTestbrowser?st=2021-08-03T08:52:15Z\u0026spr=https\u0026sig=fakesigval", + "RequestMethod": "POST", + "RequestHeaders": { + "Accept": "application/json;odata=minimalmetadata", + "Accept-Encoding": "gzip, deflate, br", + "Accept-Language": "en-US", + "Content-Length": "102", + "Content-Type": "application/json;odata=nometadata", + "dataserviceversion": "3.0", + "prefer": "return-no-content", + "Referer": "http://localhost:9876/", + "sec-ch-ua": "", + "sec-ch-ua-mobile": "?0", + "sec-ch-ua-platform": "", + "Sec-Fetch-Dest": "empty", + "Sec-Fetch-Mode": "cors", + "Sec-Fetch-Site": "same-site", + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/103.0.5058.0 Safari/537.36", + "x-ms-client-request-id": "99ecc173-ae35-4779-9206-85b9d9eb06d8", + "x-ms-useragent": "azsdk-js-data-tables/13.1.2 core-rest-pipeline/1.9.2 OS/Linuxx86_64", + "x-ms-version": "2019-02-02" + }, + "RequestBody": { + "PartitionKey": "p1", + "RowKey": "r3", + "date": "2019-07-10T19:00:00.001Z", + "date@odata.type": "Edm.DateTime" + }, + "StatusCode": 204, + "ResponseHeaders": { + "Cache-Control": "no-cache", + "Content-Length": "0", + "DataServiceId": "https://fakeaccountname.table.core.windows.net/tableClientTestbrowser(PartitionKey=\u0027p1\u0027,RowKey=\u0027r3\u0027)", + "Date": "Tue, 06 Sep 2022 18:24:25 GMT", + "ETag": "W/\u0022datetime\u00272022-09-06T18%3A24%3A25.8343262Z\u0027\u0022", + "Location": "https://fakeaccountname.table.core.windows.net/tableClientTestbrowser(PartitionKey=\u0027p1\u0027,RowKey=\u0027r3\u0027)", + "Preference-Applied": "return-no-content", + "Server": [ + "Windows-Azure-Table/1.0", + "Microsoft-HTTPAPI/2.0" + ], + "X-Content-Type-Options": "nosniff", + "x-ms-client-request-id": "99ecc173-ae35-4779-9206-85b9d9eb06d8", + "x-ms-request-id": "22b598bb-8002-00f9-741d-c299a3000000", + "x-ms-version": "2019-02-02" + }, + "ResponseBody": null + }, + { + "RequestUri": "https://fakeaccountname.table.core.windows.net/tableClientTestbrowser()?st=2021-08-03T08:52:15Z\u0026spr=https\u0026sig=fakesigval\u0026$filter=date%20lt%20datetime%272019-07-10T19%3A00%3A00.000Z%27", + "RequestMethod": "GET", + "RequestHeaders": { + "Accept": "application/json;odata=minimalmetadata", + "Accept-Encoding": "gzip, deflate, br", + "Accept-Language": "en-US", + "dataserviceversion": "3.0", + "Referer": "http://localhost:9876/", + "sec-ch-ua": "", + "sec-ch-ua-mobile": "?0", + "sec-ch-ua-platform": "", + "Sec-Fetch-Dest": "empty", + "Sec-Fetch-Mode": "cors", + "Sec-Fetch-Site": "same-site", + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/103.0.5058.0 Safari/537.36", + "x-ms-client-request-id": "342982ab-4c5d-4c68-bdbc-4cc69e6e6b07", + "x-ms-useragent": "azsdk-js-data-tables/13.1.2 core-rest-pipeline/1.9.2 OS/Linuxx86_64", + "x-ms-version": "2019-02-02" + }, + "RequestBody": null, + "StatusCode": 200, + "ResponseHeaders": { + "Cache-Control": "no-cache", + "Content-Type": "application/json; odata=minimalmetadata; streaming=true; charset=utf-8", + "Date": "Tue, 06 Sep 2022 18:24:25 GMT", + "Server": [ + "Windows-Azure-Table/1.0", + "Microsoft-HTTPAPI/2.0" + ], + "Transfer-Encoding": "chunked", + "X-Content-Type-Options": "nosniff", + "x-ms-client-request-id": "342982ab-4c5d-4c68-bdbc-4cc69e6e6b07", + "x-ms-request-id": "22b598bc-8002-00f9-751d-c299a3000000", + "x-ms-version": "2019-02-02" + }, + "ResponseBody": { + "odata.metadata": "https://fakeaccountname.table.core.windows.net/$metadata#tableClientTestbrowser", + "value": [ + { + "odata.etag": "W/\u0022datetime\u00272022-09-06T18%3A24%3A25.8083421Z\u0027\u0022", + "PartitionKey": "p1", + "RowKey": "r1", + "Timestamp": "2022-09-06T18:24:25.8083421Z", + "date@odata.type": "Edm.DateTime", + "date": "2019-07-10T18:59:59.999Z" + } + ] + } + } + ], + "Variables": {} +} diff --git a/sdk/tables/data-tables/recordings/node/tableclient_listentities/recording_should_filter_dates_correctly.json b/sdk/tables/data-tables/recordings/node/tableclient_listentities/recording_should_filter_dates_correctly.json new file mode 100644 index 000000000000..2faf0fe987b6 --- /dev/null +++ b/sdk/tables/data-tables/recordings/node/tableclient_listentities/recording_should_filter_dates_correctly.json @@ -0,0 +1,166 @@ +{ + "Entries": [ + { + "RequestUri": "https://fakeaccountname.table.core.windows.net/tableClientTestnode?st=2021-08-03T08:52:15Z\u0026spr=https\u0026sig=fakesigval", + "RequestMethod": "POST", + "RequestHeaders": { + "Accept": "application/json;odata=minimalmetadata", + "Accept-Encoding": "gzip,deflate", + "Content-Length": "102", + "Content-Type": "application/json;odata=nometadata", + "DataServiceVersion": "3.0", + "Prefer": "return-no-content", + "User-Agent": "azsdk-js-data-tables/13.1.2 core-rest-pipeline/1.9.2 Node/v16.14.2 OS/(x64-Linux-5.4.0-1089-azure)", + "x-ms-client-request-id": "254a2b6b-d647-4cc3-9151-2b0da799f3af", + "x-ms-version": "2019-02-02" + }, + "RequestBody": { + "PartitionKey": "p1", + "RowKey": "r1", + "date": "2019-07-10T18:59:59.999Z", + "date@odata.type": "Edm.DateTime" + }, + "StatusCode": 204, + "ResponseHeaders": { + "Cache-Control": "no-cache", + "Content-Length": "0", + "DataServiceId": "https://fakeaccountname.table.core.windows.net/tableClientTestnode(PartitionKey=\u0027p1\u0027,RowKey=\u0027r1\u0027)", + "Date": "Tue, 06 Sep 2022 18:24:18 GMT", + "ETag": "W/\u0022datetime\u00272022-09-06T18%3A24%3A19.0162561Z\u0027\u0022", + "Location": "https://fakeaccountname.table.core.windows.net/tableClientTestnode(PartitionKey=\u0027p1\u0027,RowKey=\u0027r1\u0027)", + "Preference-Applied": "return-no-content", + "Server": [ + "Windows-Azure-Table/1.0", + "Microsoft-HTTPAPI/2.0" + ], + "X-Content-Type-Options": "nosniff", + "x-ms-client-request-id": "254a2b6b-d647-4cc3-9151-2b0da799f3af", + "x-ms-request-id": "7cf6a80d-8002-0032-461d-c29af6000000", + "x-ms-version": "2019-02-02" + }, + "ResponseBody": null + }, + { + "RequestUri": "https://fakeaccountname.table.core.windows.net/tableClientTestnode?st=2021-08-03T08:52:15Z\u0026spr=https\u0026sig=fakesigval", + "RequestMethod": "POST", + "RequestHeaders": { + "Accept": "application/json;odata=minimalmetadata", + "Accept-Encoding": "gzip,deflate", + "Content-Length": "102", + "Content-Type": "application/json;odata=nometadata", + "DataServiceVersion": "3.0", + "Prefer": "return-no-content", + "User-Agent": "azsdk-js-data-tables/13.1.2 core-rest-pipeline/1.9.2 Node/v16.14.2 OS/(x64-Linux-5.4.0-1089-azure)", + "x-ms-client-request-id": "1300d756-cf87-4f4e-84c5-444d6c688064", + "x-ms-version": "2019-02-02" + }, + "RequestBody": { + "PartitionKey": "p1", + "RowKey": "r2", + "date": "2019-07-10T19:00:00.000Z", + "date@odata.type": "Edm.DateTime" + }, + "StatusCode": 204, + "ResponseHeaders": { + "Cache-Control": "no-cache", + "Content-Length": "0", + "DataServiceId": "https://fakeaccountname.table.core.windows.net/tableClientTestnode(PartitionKey=\u0027p1\u0027,RowKey=\u0027r2\u0027)", + "Date": "Tue, 06 Sep 2022 18:24:18 GMT", + "ETag": "W/\u0022datetime\u00272022-09-06T18%3A24%3A19.0352448Z\u0027\u0022", + "Location": "https://fakeaccountname.table.core.windows.net/tableClientTestnode(PartitionKey=\u0027p1\u0027,RowKey=\u0027r2\u0027)", + "Preference-Applied": "return-no-content", + "Server": [ + "Windows-Azure-Table/1.0", + "Microsoft-HTTPAPI/2.0" + ], + "X-Content-Type-Options": "nosniff", + "x-ms-client-request-id": "1300d756-cf87-4f4e-84c5-444d6c688064", + "x-ms-request-id": "7cf6a810-8002-0032-471d-c29af6000000", + "x-ms-version": "2019-02-02" + }, + "ResponseBody": null + }, + { + "RequestUri": "https://fakeaccountname.table.core.windows.net/tableClientTestnode?st=2021-08-03T08:52:15Z\u0026spr=https\u0026sig=fakesigval", + "RequestMethod": "POST", + "RequestHeaders": { + "Accept": "application/json;odata=minimalmetadata", + "Accept-Encoding": "gzip,deflate", + "Content-Length": "102", + "Content-Type": "application/json;odata=nometadata", + "DataServiceVersion": "3.0", + "Prefer": "return-no-content", + "User-Agent": "azsdk-js-data-tables/13.1.2 core-rest-pipeline/1.9.2 Node/v16.14.2 OS/(x64-Linux-5.4.0-1089-azure)", + "x-ms-client-request-id": "5b894d49-b4e4-4966-8ff7-15b232b652e1", + "x-ms-version": "2019-02-02" + }, + "RequestBody": { + "PartitionKey": "p1", + "RowKey": "r3", + "date": "2019-07-10T19:00:00.001Z", + "date@odata.type": "Edm.DateTime" + }, + "StatusCode": 204, + "ResponseHeaders": { + "Cache-Control": "no-cache", + "Content-Length": "0", + "DataServiceId": "https://fakeaccountname.table.core.windows.net/tableClientTestnode(PartitionKey=\u0027p1\u0027,RowKey=\u0027r3\u0027)", + "Date": "Tue, 06 Sep 2022 18:24:18 GMT", + "ETag": "W/\u0022datetime\u00272022-09-06T18%3A24%3A19.0422417Z\u0027\u0022", + "Location": "https://fakeaccountname.table.core.windows.net/tableClientTestnode(PartitionKey=\u0027p1\u0027,RowKey=\u0027r3\u0027)", + "Preference-Applied": "return-no-content", + "Server": [ + "Windows-Azure-Table/1.0", + "Microsoft-HTTPAPI/2.0" + ], + "X-Content-Type-Options": "nosniff", + "x-ms-client-request-id": "5b894d49-b4e4-4966-8ff7-15b232b652e1", + "x-ms-request-id": "7cf6a811-8002-0032-481d-c29af6000000", + "x-ms-version": "2019-02-02" + }, + "ResponseBody": null + }, + { + "RequestUri": "https://fakeaccountname.table.core.windows.net/tableClientTestnode()?st=2021-08-03T08:52:15Z\u0026spr=https\u0026sig=fakesigval\u0026$filter=date%20lt%20datetime%272019-07-10T19%3A00%3A00.000Z%27", + "RequestMethod": "GET", + "RequestHeaders": { + "Accept": "application/json;odata=minimalmetadata", + "Accept-Encoding": "gzip,deflate", + "DataServiceVersion": "3.0", + "User-Agent": "azsdk-js-data-tables/13.1.2 core-rest-pipeline/1.9.2 Node/v16.14.2 OS/(x64-Linux-5.4.0-1089-azure)", + "x-ms-client-request-id": "4a1d558b-9bfa-421d-a07a-b0f296b9f746", + "x-ms-version": "2019-02-02" + }, + "RequestBody": null, + "StatusCode": 200, + "ResponseHeaders": { + "Cache-Control": "no-cache", + "Content-Type": "application/json; odata=minimalmetadata; streaming=true; charset=utf-8", + "Date": "Tue, 06 Sep 2022 18:24:18 GMT", + "Server": [ + "Windows-Azure-Table/1.0", + "Microsoft-HTTPAPI/2.0" + ], + "Transfer-Encoding": "chunked", + "X-Content-Type-Options": "nosniff", + "x-ms-client-request-id": "4a1d558b-9bfa-421d-a07a-b0f296b9f746", + "x-ms-request-id": "7cf6a812-8002-0032-491d-c29af6000000", + "x-ms-version": "2019-02-02" + }, + "ResponseBody": { + "odata.metadata": "https://fakeaccountname.table.core.windows.net/$metadata#tableClientTestnode", + "value": [ + { + "odata.etag": "W/\u0022datetime\u00272022-09-06T18%3A24%3A19.0162561Z\u0027\u0022", + "PartitionKey": "p1", + "RowKey": "r1", + "Timestamp": "2022-09-06T18:24:19.0162561Z", + "date@odata.type": "Edm.DateTime", + "date": "2019-07-10T18:59:59.999Z" + } + ] + } + } + ], + "Variables": {} +} diff --git a/sdk/tables/data-tables/src/odata.ts b/sdk/tables/data-tables/src/odata.ts index 517f4e977ae6..e05ecf230197 100644 --- a/sdk/tables/data-tables/src/odata.ts +++ b/sdk/tables/data-tables/src/odata.ts @@ -7,7 +7,7 @@ function escapeQuotesIfString(input: unknown, previous: string): string | unknow if (typeof input === "string") { result = escapeQuotes(input); // check if we need to escape this literal - if (!previous.trim().endsWith("'")) { + if (previous !== "" && !previous.trim().endsWith("'")) { result = `'${result}'`; } } @@ -18,15 +18,23 @@ export function escapeQuotes(input: string): string { return input.replace(/'/g, "''"); } +function encodeDate(input: unknown): string | unknown { + return input instanceof Date ? `datetime'${input.toISOString()}'` : input; +} + /** * Escapes an odata filter expression to avoid errors with quoting string literals. + * Encodes Date objects. */ export function odata(strings: TemplateStringsArray, ...values: unknown[]): string { + const fixEncoding = (value: unknown, string: string): string | unknown => { + return encodeDate(escapeQuotesIfString(value, string)); + }; const results = []; for (let i = 0; i < strings.length; i++) { results.push(strings[i]); if (i < values.length) { - results.push(escapeQuotesIfString(values[i], strings[i])); + results.push(fixEncoding(values[i], strings[i])); } } return results.join(""); diff --git a/sdk/tables/data-tables/test/internal/odata.spec.ts b/sdk/tables/data-tables/test/internal/odata.spec.ts index ce80ae4e87ea..8981a6790e91 100644 --- a/sdk/tables/data-tables/test/internal/odata.spec.ts +++ b/sdk/tables/data-tables/test/internal/odata.spec.ts @@ -38,4 +38,10 @@ describe("odata", () => { const testString = odata`test string ${testValue}`; assert.equal(testString, "test string '\"FooBar\"'"); }); + + it("should not escape property names unnecessarily", () => { + const testDate = new Date(1); + const testString = odata`test lt ${testDate}`; + assert.equal(testString, `test lt datetime'${testDate.toISOString()}'`); + }); }); diff --git a/sdk/tables/data-tables/test/public/tableclient.spec.ts b/sdk/tables/data-tables/test/public/tableclient.spec.ts index 01521a8e432e..62209d9b79c9 100644 --- a/sdk/tables/data-tables/test/public/tableclient.spec.ts +++ b/sdk/tables/data-tables/test/public/tableclient.spec.ts @@ -192,6 +192,48 @@ describe(`TableClient`, () => { assert.deepEqual(String.fromCharCode(...all[0].foo), "Bar"); } }); + + it("should filter dates correctly", async function () { + const propertyName = "date"; + const comparisonDate = new Date("2019-07-10T12:00:00-0700"); + + const entities = [ + { + partitionKey: "p1", + rowKey: "r1", + [propertyName]: new Date(comparisonDate.valueOf() - 1), + }, + { + partitionKey: "p1", + rowKey: "r2", + [propertyName]: comparisonDate, + }, + { + partitionKey: "p1", + rowKey: "r3", + [propertyName]: new Date(comparisonDate.valueOf() + 1), + }, + ]; + + for (const entity of entities) { + await client.createEntity(entity); + } + + const entityIterable = client.listEntities({ + queryOptions: { + filter: odata`${propertyName} lt ${comparisonDate}`, + }, + }); + + const responseDates = []; + for await (const entity of entityIterable) { + assert.property(entity, propertyName); + assert.typeOf(entity[propertyName], "Date"); + responseDates.push(entity[propertyName] as Date); + } + + assert.deepEqual(new Set(responseDates), new Set([entities[0][propertyName]])); + }); }); describe("createEntity, getEntity and delete", () => {