diff --git a/README.md b/README.md
index 178c2a6f..441a1455 100644
--- a/README.md
+++ b/README.md
@@ -114,7 +114,7 @@ bal run
The `salesforce` connector provides practical examples illustrating usage in various scenarios. Explore these examples below, covering use cases like creating sObjects, retrieving records, and executing bulk operations.
-1. [Salesforce REST API use cases](https://github.com/ballerina-platform/module-ballerinax-sfdc/tree/master/examples/rest_api_usecases) - How to employ REST API of Salesforce to carryout varies tasks.
+1. [Salesforce REST API use cases](https://github.com/ballerina-platform/module-ballerinax-sfdc/tree/master/examples/rest_api_usecases) - How to employ REST API of Salesforce to carryout various tasks.
2. [Salesforce Bulk API use cases](https://github.com/ballerina-platform/module-ballerinax-sfdc/tree/master/examples/bulk_api_usecases) - How to employ Bulk API of Salesforce to execute Bulk jobs.
diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml
index a9fb8cb9..c2a9ca76 100644
--- a/ballerina/Ballerina.toml
+++ b/ballerina/Ballerina.toml
@@ -2,7 +2,7 @@
distribution = "2201.8.0"
org = "ballerinax"
name = "salesforce"
-version = "8.0.1"
+version = "8.0.2"
export = ["salesforce", "salesforce.bulk", "salesforce.soap"]
license= ["Apache-2.0"]
authors = ["Ballerina"]
@@ -17,10 +17,10 @@ observabilityIncluded = true
graalvmCompatible = true
[[platform.java17.dependency]]
-path = "../native/build/libs/salesforce-native-8.0.1.jar"
+path = "../native/build/libs/salesforce-native-8.0.2-SNAPSHOT.jar"
groupId = "io.ballerinax"
artifactId = "salesforce"
-version = "8.0.1"
+version = "8.0.2-SNAPSHOT"
[[platform.java17.dependency]]
groupId = "com.opencsv"
diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml
index 0ff4b82c..43a55be3 100644
--- a/ballerina/Dependencies.toml
+++ b/ballerina/Dependencies.toml
@@ -377,7 +377,7 @@ modules = [
[[package]]
org = "ballerinax"
name = "salesforce"
-version = "8.0.1"
+version = "8.0.2"
dependencies = [
{org = "ballerina", name = "http"},
{org = "ballerina", name = "io"},
diff --git a/ballerina/Module.md b/ballerina/Module.md
index 9244c15c..7d5e9c38 100644
--- a/ballerina/Module.md
+++ b/ballerina/Module.md
@@ -16,16 +16,16 @@ Ballerina Salesforce connector supports [Salesforce v59.0 REST API](https://deve
- - Here we will be using https://test.salesforce.com as we are using sandbox enviorenment. Users can use https://login.salesforce.com for normal usage.
+ - Here we will be using https://test.salesforce.com as we are using sandbox environment. Users can use https://login.salesforce.com for normal usage.
-4. After the creation user can get consumer key and secret through clicking on the `Manage Consume Details` button.
+4. After the creation user can get consumer key and secret through clicking on the `Manage Consumer Details` button.
5. Next step would be to get the token.
- - Log in to salesforce in your prefered browser and enter the following url.
+ - Log in to salesforce in your preferred browser and enter the following url.
```
https://.salesforce.com/services/oauth2/authorize?response_type=code&client_id=&redirect_uri=
```
@@ -71,7 +71,7 @@ salesforce:ConnectionConfig config = {
}
};
-salesforce:Client salesforce = new(config);
+salesforce:Client salesforce = check new (config);
```
#### Step 3: Invoke connector operation
@@ -99,7 +99,7 @@ bal run
The `salesforce` connector provides practical examples illustrating usage in various scenarios. Explore these examples below, covering use cases like creating sObjects, retrieving records, and executing bulk operations.
-1. [Salesforce REST API use cases](https://github.com/ballerina-platform/module-ballerinax-sfdc/tree/master/examples/rest_api_usecases) - How to employ REST API of Salesforce to carryout varies tasks.
+1. [Salesforce REST API use cases](https://github.com/ballerina-platform/module-ballerinax-sfdc/tree/master/examples/rest_api_usecases) - How to employ REST API of Salesforce to carryout various tasks.
2. [Salesforce Bulk API use cases](https://github.com/ballerina-platform/module-ballerinax-sfdc/tree/master/examples/bulk_api_usecases) - How to employ Bulk API of Salesforce to execute Bulk jobs.
diff --git a/ballerina/Package.md b/ballerina/Package.md
index 755e9fc3..6e3c1808 100644
--- a/ballerina/Package.md
+++ b/ballerina/Package.md
@@ -16,16 +16,16 @@ Ballerina Salesforce connector supports [Salesforce v59.0 REST API](https://deve
- - Here we will be using https://test.salesforce.com as we are using sandbox enviorenment. Users can use https://login.salesforce.com for normal usage.
+ - Here we will be using https://test.salesforce.com as we are using sandbox environment. Users can use https://login.salesforce.com for normal usage.
-4. After the creation user can get consumer key and secret through clicking on the `Manage Consume Details` button.
+4. After the creation user can get consumer key and secret through clicking on the `Manage Consumer Details` button.
5. Next step would be to get the token.
- - Log in to salesforce in your prefered browser and enter the following url.
+ - Log in to salesforce in your preferred browser and enter the following url.
```https://.salesforce.com/services/oauth2/authorize?response_type=code&client_id=&redirect_uri=```
- Allow access if an alert pops up and the browser will be redirected to a Url like follows.
@@ -65,7 +65,7 @@ salesforce:ConnectionConfig config = {
}
};
-salesforce:Client salesforce = new(config);
+salesforce:Client salesforce = check new (config);
```
#### Step 3: Invoke connector operation
@@ -97,7 +97,7 @@ The `salesforce` integration samples illustrate its usage in various integration
The `salesforce` connector provides practical examples illustrating usage in various scenarios. Explore these examples below, covering use cases like creating sObjects, retrieving records, and executing bulk operations.
-1. [Salesforce REST API use cases](https://github.com/ballerina-platform/module-ballerinax-sfdc/tree/master/examples/rest_api_usecases) - How to employ REST API of Salesforce to carryout varies tasks.
+1. [Salesforce REST API use cases](https://github.com/ballerina-platform/module-ballerinax-sfdc/tree/master/examples/rest_api_usecases) - How to employ REST API of Salesforce to carryout various tasks.
2. [Salesforce Bulk API use cases](https://github.com/ballerina-platform/module-ballerinax-sfdc/tree/master/examples/bulk_api_usecases) - How to employ Bulk API of Salesforce to execute Bulk jobs.
diff --git a/ballerina/client.bal b/ballerina/client.bal
index 9f3b57fc..07cb5776 100644
--- a/ballerina/client.bal
+++ b/ballerina/client.bal
@@ -27,6 +27,7 @@ import ballerinax/salesforce.utils;
public isolated client class Client {
private final http:Client salesforceClient;
+ private map sfLocators = {};
# Initializes the connector. During initialization you can pass either http:BearerTokenConfig if you have a bearer
# token or http:OAuth2RefreshTokenGrantConfig if you have Oauth tokens.
@@ -90,7 +91,7 @@ public isolated client class Client {
//Describe Organization
# Lists summary details about each REST API version available.
#
- # + return - List of `Version` if successful. Else, the occured `error`
+ # + return - List of `Version` if successful. Else, the occurred `error`
isolated remote function getApiVersions() returns Version[]|error {
string path = utils:prepareUrl([BASE_PATH]);
return check self.salesforceClient->get(path);
@@ -400,10 +401,10 @@ public isolated client class Client {
return check self.salesforceClient->get(path);
}
- # Executes up to 25 subrequests in a single request.
+ # Executes up to 25 sub-requests in a single request.
#
# + batchRequests - A record containing all the requests
- # + haltOnError - If true, the request halts when an error occurs on an individual subrequest
+ # + haltOnError - If true, the request halts when an error occurs on an individual sub-request
# + return - `BatchResult` if successful or else `error`
isolated remote function batch(Subrequest[] batchRequests, boolean haltOnError = false) returns BatchResult|error {
string path = utils:prepareUrl([API_BASE_PATH, COMPOSITE, BATCH]);
@@ -651,18 +652,57 @@ public isolated client class Client {
}
- # Get query job results
+ # Get bulk query job results
+ #
# + bulkJobId - Id of the bulk job
- # + return - string[][] if successful else `error`
- isolated remote function getQueryResult(string bulkJobId)
- returns string[][]|error {
- string path = utils:prepareUrl([API_BASE_PATH, JOBS, QUERY, bulkJobId, RESULT]);
+ # + maxRecords - The maximum number of records to retrieve per set of results for the query
+ # + return - The resulting string[][] if successful else `error`
+ isolated remote function getQueryResult(string bulkJobId, int? maxRecords = ()) returns string[][]|error {
+
+ string path = "";
+ string batchingParams = "";
+
+ if maxRecords != () {
+ lock {
+ if self.sfLocators.hasKey(bulkJobId) {
+ string locator = self.sfLocators.get(bulkJobId);
+ if locator is "null" {
+ return [];
+ }
+ batchingParams = string `results?maxRecords=${maxRecords}&locator=${locator}`;
+ } else {
+ batchingParams = string `results?maxRecords=${maxRecords}`;
+ }
+ }
+ path = utils:prepareUrl([API_BASE_PATH, JOBS, QUERY, bulkJobId, batchingParams]);
+ // Max records value default, we might not know when the locator comes
+ } else {
+ lock {
+ if self.sfLocators.hasKey(bulkJobId) {
+ string locator = self.sfLocators.get(bulkJobId);
+ if locator is "null" {
+ return [];
+ }
+ batchingParams = string `results?locator=${locator}`;
+ path = utils:prepareUrl([API_BASE_PATH, JOBS, QUERY, bulkJobId, batchingParams]);
+ } else {
+ path = utils:prepareUrl([API_BASE_PATH, JOBS, QUERY, bulkJobId, RESULT]);
+ }
+ }
+ }
+
http:Response response = check self.salesforceClient->get(path);
if response.statusCode == 200 {
string textPayload = check response.getTextPayload();
if textPayload == "" {
return [];
}
+ lock {
+ string|http:HeaderNotFoundError locatorValue = response.getHeader("sforce-locator");
+ if locatorValue is string {
+ self.sfLocators[bulkJobId] = locatorValue;
+ } // header not found error ignored
+ }
string[][] result = check parseCsvString(textPayload);
return result;
} else {
diff --git a/ballerina/main.bal b/ballerina/main.bal
deleted file mode 100644
index 68a42f4c..00000000
--- a/ballerina/main.bal
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright (c) 2021 WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
-//
-// WSO2 Inc. licenses this file to you under the Apache License,
-// Version 2.0 (the "License"); you may not use this file except
-// in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied. See the License for the
-// specific language governing permissions and limitations
-// under the License.
diff --git a/ballerina/modules/bulk/Module.md b/ballerina/modules/bulk/Module.md
index 76153f78..13ff25b5 100644
--- a/ballerina/modules/bulk/Module.md
+++ b/ballerina/modules/bulk/Module.md
@@ -15,16 +15,16 @@ This module supports [Salesforce Bulk API v1](https://developer.salesforce.com/d
- - Here we will be using https://test.salesforce.com as we are using sandbox enviorenment. Users can use https://login.salesforce.com for normal usage.
+ - Here we will be using https://test.salesforce.com as we are using sandbox environment. Users can use https://login.salesforce.com for normal usage.
-4. After the creation user can get consumer key and secret through clicking on the `Manage Consume Details` button.
+4. After the creation user can get consumer key and secret through clicking on the `Manage Consumer Details` button.
5. Next step would be to get the token.
- - Log in to salesforce in your prefered browser and enter the following url.
+ - Log in to salesforce in your preferred browser and enter the following url.
`https://.salesforce.com/services/oauth2/authorize?response_type=code&client_id=&redirect_uri=`
- Allow access if an alert pops up and the browser will be redirected to a Url like follows.
`https://login.salesforce.com/?code=`
@@ -64,7 +64,7 @@ bulk:ConnectionConfig sfConfig = {
}
};
-bulk:Client bulkClient = new (sfConfig);
+bulk:Client bulkClient = check new (sfConfig);
```
### Step 3: Invoke connector operation
diff --git a/ballerina/modules/soap/Module.md b/ballerina/modules/soap/Module.md
index 09126e46..bff3912f 100644
--- a/ballerina/modules/soap/Module.md
+++ b/ballerina/modules/soap/Module.md
@@ -15,16 +15,16 @@ This module supports [Salesforce v48.0 SOAP API Enterprise WDSL](https://develop
- - Here we will be using https://test.salesforce.com as we are using sandbox enviorenment. Users can use https://login.salesforce.com for normal usage.
+ - Here we will be using https://test.salesforce.com as we are using sandbox environment. Users can use https://login.salesforce.com for normal usage.
-4. After the creation user can get consumer key and secret through clicking on the `Manage Consume Details` button.
+4. After the creation user can get consumer key and secret through clicking on the `Manage Consumer Details` button.
5. Next step would be to get the token.
- - Log in to salesforce in your prefered browser and enter the following url.
+ - Log in to salesforce in your preferred browser and enter the following url.
`https://.salesforce.com/services/oauth2/authorize?response_type=code&client_id=&redirect_uri=`
- Allow access if an alert pops up and the browser will be redirected to a Url like follows.
`https://login.salesforce.com/?code=`
@@ -61,7 +61,7 @@ soap:ConnectionConfig config = {
}
};
-soap:Client salesforce = new(sfConfig);
+soap:Client salesforce = check new (config);
```
### Step 3: Invoke connector operation
diff --git a/ballerina/tests/bulk_csv_query.bal b/ballerina/tests/bulk_csv_query.bal
index ae6ac29c..58db85a3 100644
--- a/ballerina/tests/bulk_csv_query.bal
+++ b/ballerina/tests/bulk_csv_query.bal
@@ -61,9 +61,121 @@ function queryCsv() returns error? {
}
}
}
+}
+
+
+@test:Config {
+ enable: true,
+ dependsOn: [insertCsvFromFile, insertCsv, insertCsvStringArrayFromFile, insertCsvStreamFromFile]
+}
+function queryWithLowerMaxRecordsValue() returns error? {
+ runtime:sleep(delayInSecs);
+ log:printInfo("baseClient -> queryCsv");
+ string queryStr = "SELECT Id, Name FROM Contact WHERE Title='Professor Level 02'";
+
+ BulkCreatePayload payloadq = {
+ operation : "query",
+ query : queryStr
+ };
+
+ //create job
+ BulkJob queryJob = check baseClient->createQueryJob(payloadq);
+ int totalRecordsReceived = 0;
+ int totalIterationsOfGetResult = 0;
+ string[][]|error batchResult = [];
+
+ //get batch result
+ foreach int currentRetry in 1 ..< maxIterations + 1 {
+ while true {
+ batchResult = baseClient->getQueryResult(queryJob.id, 5);
+ if batchResult is error || batchResult.length() == 0 {
+ break;
+ } else {
+ totalRecordsReceived += batchResult.length();
+ totalIterationsOfGetResult += 1;
+ }
+ }
+
+ if totalIterationsOfGetResult != 0 {
+ if totalRecordsReceived == 7 {
+ test:assertTrue(totalIterationsOfGetResult == 2, msg = "Retrieving batch result failed.");
+ break;
+ } else {
+ if currentRetry != maxIterations {
+ log:printWarn("getBatchResult Operation Failed! Retrying...");
+ runtime:sleep(delayInSecs);
+ } else {
+ log:printWarn("getBatchResult Operation Failed! Giving up after 5 tries.");
+ }
+ }
+ } else if batchResult is error {
+ if currentRetry != maxIterations {
+ log:printWarn("getBatchResult Operation Failed! Retrying...");
+ runtime:sleep(delayInSecs);
+ } else {
+ log:printWarn("getBatchResult Operation Failed! Giving up after 5 tries.");
+ test:assertFail(msg = batchResult.message());
+ }
+ }
+ }
+}
+
+@test:Config {
+ enable: true,
+ dependsOn: [insertCsvFromFile, insertCsv, insertCsvStringArrayFromFile, insertCsvStreamFromFile]
}
+function queryWithHigherMaxRecordsValue() returns error? {
+ runtime:sleep(delayInSecs);
+ log:printInfo("baseClient -> queryCsv");
+ string queryStr = "SELECT Id, Name FROM Contact WHERE Title='Professor Level 02'";
+
+ BulkCreatePayload payloadq = {
+ operation : "query",
+ query : queryStr
+ };
+
+ //create job
+ BulkJob queryJob = check baseClient->createQueryJob(payloadq);
+ int totalRecordsReceived = 0;
+ int totalIterationsOfGetResult = 0;
+ string[][]|error batchResult = [];
+ //get batch result
+ foreach int currentRetry in 1 ..< maxIterations + 1 {
+ while true {
+ batchResult = baseClient->getQueryResult(queryJob.id, 10);
+ if batchResult is error || batchResult.length() == 0 {
+ break;
+ } else {
+ totalRecordsReceived += batchResult.length();
+ totalIterationsOfGetResult += 1;
+ }
+ }
+
+ if totalIterationsOfGetResult != 0 {
+ if totalRecordsReceived == 7 {
+ test:assertTrue(totalIterationsOfGetResult == 1, msg = "Retrieving batch result failed.");
+ break;
+ } else {
+ if currentRetry != maxIterations {
+ log:printWarn("getBatchResult Operation Failed! Retrying...");
+ runtime:sleep(delayInSecs);
+ } else {
+ log:printWarn("getBatchResult Operation Failed! Giving up after 5 tries.");
+ }
+ }
+ } else if batchResult is error {
+ if currentRetry != maxIterations {
+ log:printWarn("getBatchResult Operation Failed! Retrying...");
+ runtime:sleep(delayInSecs);
+ } else {
+ log:printWarn("getBatchResult Operation Failed! Giving up after 5 tries.");
+ test:assertFail(msg = batchResult.message());
+ }
+ }
+ }
+}
@test:Config {
enable: true,
diff --git a/changelog.md b/changelog.md
new file mode 100644
index 00000000..764892fc
--- /dev/null
+++ b/changelog.md
@@ -0,0 +1,14 @@
+# Change Log
+This file contains all the notable changes done to the Ballerina Salesforce package through the releases.
+
+## [Unreleased]
+
+### Added
+
+- [[#6577] Add support for pagination/chunking](https://github.com/ballerina-platform/ballerina-library/issues/6577)
+
+## [8.0.1] - 2024-05-29
+
+### Fixed
+
+- [[#6556] Fixed connector returning invalid CSV data when values contain new lines](https://github.com/ballerina-platform/ballerina-library/issues/6556)