diff --git a/.gitignore b/.gitignore
index 26ad46e..de11101 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,6 @@
*.log
target
.metadata
+/node_modules/
+/package-lock.json
+/sf-jdbc-driver/src/test/java/com/ascendix/jdbc/salesforce/ForceDriverConnectivityTest.java
diff --git a/README.md b/README.md
index e7b17b6..38f276e 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,90 @@
-# sforce-jdbc [](https://travis-ci.org/ascendix/salesforce-jdbc) [](https://sonarcloud.io/dashboard?id=com.ascendix.salesforce%3Asalesforce-jdbc)
-Salesforce JDBC driver allows Java programs to connect to Salesforce data services using standard, database independent Java code. It is an open source JDBC driver written in Pure Java, and communicates over SOAP/HTTP(S) protocol.
-
+# sforce-jdbc
+Salesforce JDBC driver allows Java programs to connect to a Salesforce data services using standard, database independent Java code. Is an open source JDBC driver written in Pure Java,
+and communicates over SOAP/HTTP(S) protocol.
The main purpose of the driver is to retrieve (only) data from Salesforce services for data analysis. Primary target platform for the driver usage is Eclipse BIRT engine.
+The original Git repository for this driver is [here](https://github.com/ascendix/salesforce-jdbc)
+However that version is not compatible with IntelliJ because of a lot of unsupported features:
+* table names and columns names filtration is not implemented
+* table name and column names are case sensitive
+* no metadata provided for queries so IntelliJ just ignores the results returned by the driver
+
+These issues were fixed in the current version in this fork.
+
+[Watch the demo video](https://spuliaiev-sfdc.github.io/salesforce-jdbc/docs/SOQL-JDBC-IntelliJ-demo-264.mp4)
+
+[](https://spuliaiev-sfdc.github.io/salesforce-jdbc/docs/SOQL-JDBC-IntelliJ-demo-264.mp4)
+
## Supported Salesforce and Java versions
The current version of the driver should be compatible with **Salesforce Partner API version 39.0 and higher** and **Java 8**.
## Get the driver
-Download the driver [here](https://github.com/ascendix/mvnrepo/raw/master/com/ascendix/salesforce/salesforce-jdbc/1.1-SNAPSHOT/salesforce-jdbc-1.1-20180403.104727-1-single.jar)
+Download the driver JAR file:
+1. Read-Only version 1.3.1 : from [here](https://spuliaiev-sfdc.github.io/salesforce-jdbc/deliverables/sf-jdbc-driver-1.3.1-SNAPSHOT-jar-with-dependencies.jar)
+2. Write support version 1.4.0 : from [here](https://spuliaiev-sfdc.github.io/salesforce-jdbc/deliverables/sf-jdbc-driver-1.4.0-SNAPSHOT-jar-with-dependencies.jar)
+
+## Supported features
+1. Queries support native SOQL;
+ ```SQL
+ select Id, Account.Name, Owner.id, Owner.Name from Account
+```
+2. Nested queries are supported;
+3. Write is supported as INSERT/UPDATE statements for version >= 1.4.0
+
+ The following functions are supported as part of calculation of new values:
+ * NOW()
+ * GETDATE()
+
+ For Example:
+ ```SQL
+ INSERT INTO Account(Name, Phone) VALUES
+ ('Account01', '555-123-1111'),
+ ('Account02', '555-123-2222');
+
+ INSERT INTO Contact(FirstName, LastName, AccountId)
+ SELECT Name, Phone, Id
+ FROM Account
+ WHERE Name like 'Account0%';
+
+ UPDATE Contact SET LastName = 'Updated_Now_'+NOW()
+ WHERE AccountId IN (
+ SELECT ID from Account where Phone = '555-123-1111'
+ );
+```
+4. Request caching support on local drive. Caching supports 2 modes: global and session. Global mode means that the cached result will be accessible for all system users for certain JVM session. Session cache mode works for each Salesforce connection session separately. Both modes cache stores request result while JVM still running but no longer than for 1 hour. The cache mode can be enabled with a prefix of SOQL query.
+
+How to use:
+ * Global cache mode:
+ ```SQL
+ CACHE GLOBAL SELECT Id, Name FROM Account
+ ```
+ * Session cache mode
+ ```SQL
+ CACHE SESSION SELECT Id, Name FROM Account
+ ```
+5. Reconnect to other organization at the same host
+```SQL
+-- Postgres Notation
+CONNECT USER admin@OtherOrg.com IDENTIFIED by "123456"
+
+-- Oracle Notation
+CONNECT admin@OtherOrg.com/123456
+
+-- Postgres Notation to a different host using secure connection (by default)
+CONNECT
+ TO ap1.stmpa.stm.salesforce.com
+ USER admin@OtherOrg.com IDENTIFIED by "123456"
+
+-- Postgres Notation to a different host - local host using insecure connection
+CONNECT
+ TO http://localhost:6109
+ USER admin@OtherOrg.com IDENTIFIED by "123456"
+```
+P.S. You need to use the machine host name in the connection url - not MyDomain org host name.
+
+## Limitations
+1. ***Version < 1.4.0*** The driver is only for read-only purposes now. Insert/update/delete functionality is not implemented yet.
+2. ***Version >= 1.4.0*** Limited support of INSERT/UPDATE operations
## With Maven
@@ -30,8 +107,8 @@ Download the driver [here](https://github.com/ascendix/mvnrepo/raw/master/com/as
### Add dependency
com.ascendix.salesforce
- salesforce-jdbc
- 1.1-20180403.104727-1
+ sf-jdbc-driver
+ 1.4.0-SNAPSHOT
@@ -68,24 +145,7 @@ jdbc:ascendix:salesforce://;sessionId=uniqueIdAssociatedWithTheSession
| _https_ | Switch to use HTTP protocol instead of HTTPS
Default value is _true_|
| _api_ | Api version to use.
Default value is _50.0_.
Set _test.salesforce.com_ value to use sandbox. |
| _client_ | Client Id to use.
Default value is empty. |
-
-
-## Supported features
-1. Queries support native SOQL;
-2. Nested queries are supported;
-3. Request caching support on local drive. Caching supports 2 modes: global and session. Global mode means that the cached result will be accessible for all system users for certain JVM session. Session cache mode works for each Salesforce connection session separately. Both modes cache stores request result while JVM still running but no longer than for 1 hour. The cache mode can be enabled with a prefix of SOQL query. How to use:
-=======
- * Global cache mode:
- ```SQL
- CACHE GLOBAL SELECT Id, Name FROM Account
- ```
- * Session cache mode
- ```SQL
- CACHE SESSION SELECT Id, Name FROM Account
- ```
-
-## Limitations
-1. The driver is only for read-only purposes now. Insert/udate/delete functionality is not implemented yet.
+| _insecurehttps_ | Allow invalid certificates for SSL. |
## Configure BIRT Studio to use Salesforce JDBC driver
@@ -99,6 +159,67 @@ jdbc:ascendix:salesforce://;sessionId=uniqueIdAssociatedWithTheSession
See how it's done in [Salesforce JDBC report sample](docs/birt/Salesforce JDBC sample.rptdesign)
+## Configure IntelliJ to use Salesforce JDBC driver
+
+1. [How to add a JDBC driver](https://www.jetbrains.com/help/idea/data-sources-and-drivers-dialog.html)
+2. How to set configuration properties for Salesforce JDBC driver.
+
+ IntelliJ provides various ways to set parameters for JDBC driver. For example, it can be done with the property binding feature in the data source editor and a report parameter.
+ Example JDBC Url:
+
+ ```jdbc:ascendix:salesforce://dev@Local.org:123456@localorg.localhost.internal.salesforce.com:6109?https=false&api=48.0```
+
+ Please check what kind of access do you have to your org - HTTP or HTTPS and the API version to use.
+ Here is screenshot about results output and autocomplete support for SOQL queries in IntelliJ:
+
+ 
+
+
+## In case of issues with the WSDL
+
+Steps to update the partners.wsdl
+
+1. Get and build https://github.com/forcedotcom/wsc
+2. Run command:
+
+ `java -jar target/force-wsc-50.0.0-uber.jar blt/app/main/core/shared/submodules/wsdl/src/main/wsdl/partner.wsdl sforce-partner.jar`
+3. Copy the com.sforce.soap to the driver
+
+## SOQL Parser
+
+This project uses a bit modified version of MuleSoft SOQL Parser which also supports quotes around field names.
+It could be obtained from here: https://github.com/spuliaiev-sfdc/salesforce-soql-parser
+
+
+## Version History
+
+### 1.3.1.3
+CONNECT command parsing fixes
+
+### 1.3.1.0
+Re-connection to a different host using CONNECT command
+
+### 1.3.0.1
+ Insecure HTTPS - disabling the SSL Certificate verification
+
+### 1.2.6.03
+ Update force-partner-api to 51.0.0
+
+### 1.2.6.02
+ Fields for SubSelects aliases
+
+ Returning flat resultset for field
+
+### 1.2.6.01
+ Update force-partner-api to 50.0.0
+
+ Implement parameters:
+* loginDomain
+* client
+* https
+* api
+
+ Implement missing JDBC methods which are required for JetBrains IntelliJ IDE
### Sponsors
[Ascendix Technologies Inc.](https://ascendix.com/)
diff --git a/build.sh b/build.sh
new file mode 100755
index 0000000..ec1eeec
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,2 @@
+source ~/java8
+mvn clean install -P single-jar && cp -f ./sf-jdbc-driver/target/sf-jdbc-driver-*-SNAPSHOT-jar-with-dependencies.jar ./deliverables/
diff --git a/deliverables/sf-jdbc-driver-1.2-SNAPSHOT-jar-with-dependencies.jar b/deliverables/sf-jdbc-driver-1.2-SNAPSHOT-jar-with-dependencies.jar
new file mode 100644
index 0000000..80ed52b
Binary files /dev/null and b/deliverables/sf-jdbc-driver-1.2-SNAPSHOT-jar-with-dependencies.jar differ
diff --git a/deliverables/sf-jdbc-driver-1.3.0-SNAPSHOT-jar-with-dependencies.jar b/deliverables/sf-jdbc-driver-1.3.0-SNAPSHOT-jar-with-dependencies.jar
new file mode 100644
index 0000000..de8358c
Binary files /dev/null and b/deliverables/sf-jdbc-driver-1.3.0-SNAPSHOT-jar-with-dependencies.jar differ
diff --git a/deliverables/sf-jdbc-driver-1.3.1-SNAPSHOT-jar-with-dependencies.jar b/deliverables/sf-jdbc-driver-1.3.1-SNAPSHOT-jar-with-dependencies.jar
new file mode 100644
index 0000000..7700db5
Binary files /dev/null and b/deliverables/sf-jdbc-driver-1.3.1-SNAPSHOT-jar-with-dependencies.jar differ
diff --git a/deliverables/sf-jdbc-driver-1.4.0-SNAPSHOT-jar-with-dependencies.jar b/deliverables/sf-jdbc-driver-1.4.0-SNAPSHOT-jar-with-dependencies.jar
new file mode 100644
index 0000000..d2d4f96
Binary files /dev/null and b/deliverables/sf-jdbc-driver-1.4.0-SNAPSHOT-jar-with-dependencies.jar differ
diff --git a/docs/Autocomplete-SOQL.png b/docs/Autocomplete-SOQL.png
new file mode 100644
index 0000000..b72bb1d
Binary files /dev/null and b/docs/Autocomplete-SOQL.png differ
diff --git a/docs/SOQL-JDBC-IntelliJ-demo-264.mp4 b/docs/SOQL-JDBC-IntelliJ-demo-264.mp4
new file mode 100644
index 0000000..c691764
Binary files /dev/null and b/docs/SOQL-JDBC-IntelliJ-demo-264.mp4 differ
diff --git a/docs/cases/intellij/run_flow_00.txt b/docs/cases/intellij/run_flow_00.txt
new file mode 100644
index 0000000..620c5c4
--- /dev/null
+++ b/docs/cases/intellij/run_flow_00.txt
@@ -0,0 +1,81 @@
+2020-08-13 13:10:26,965 [4842581] INFO - g.FileBasedIndexProjectHandler - Has changed files: false; project=Project (name=untitled, containerState=ACTIVE, componentStore=/Users/spuliaiev/IdeaProjects/untitled)
+2020-08-13 13:10:28,764 [4844380] WARN - ution.rmi.RemoteProcessSupport - Aug 13, 2020 8:10:28 PM com.ascendix.jdbc.salesforce.connection.ForceConnection isValid
+2020-08-13 13:10:28,764 [4844380] WARN - ution.rmi.RemoteProcessSupport - INFO: isValid NOT_IMPLEMENTED
+2020-08-13 13:10:28,766 [4844382] WARN - ution.rmi.RemoteProcessSupport - Aug 13, 2020 8:10:28 PM com.ascendix.jdbc.salesforce.connection.ForceConnection isValid
+2020-08-13 13:10:28,766 [4844382] WARN - ution.rmi.RemoteProcessSupport - INFO: isValid NOT_IMPLEMENTED
+2020-08-13 13:10:28,767 [4844383] WARN - ution.rmi.RemoteProcessSupport - Aug 13, 2020 8:10:28 PM com.ascendix.jdbc.salesforce.connection.ForceConnection isValid
+2020-08-13 13:10:28,767 [4844383] WARN - ution.rmi.RemoteProcessSupport - INFO: isValid NOT_IMPLEMENTED
+2020-08-13 13:10:28,780 [4844396] WARN - ution.rmi.RemoteProcessSupport - Aug 13, 2020 8:10:28 PM com.ascendix.jdbc.salesforce.connection.ForceConnection getCatalog
+2020-08-13 13:10:28,781 [4844397] WARN - ution.rmi.RemoteProcessSupport - INFO: getCatalog NOT_IMPLEMENTED
+2020-08-13 13:10:28,782 [4844398] WARN - ution.rmi.RemoteProcessSupport - Aug 13, 2020 8:10:28 PM com.ascendix.jdbc.salesforce.connection.ForceConnection createStatement
+2020-08-13 13:10:28,782 [4844398] WARN - ution.rmi.RemoteProcessSupport - INFO: createStatement
+2020-08-13 13:10:28,782 [4844398] ERROR - ij.database.console.JdbcEngine - connection.remoteConnection.createStatement() must not be null
+java.lang.IllegalStateException: connection.remoteConnection.createStatement() must not be null
+ at com.intellij.database.dataSource.connection.statements.JdbcBasedSmartStatement$Simple.createStatement(JdbcBasedSmartStatement.kt:238)
+ at com.intellij.database.dataSource.connection.statements.JdbcBasedSmartStatement$Simple.createStatement(JdbcBasedSmartStatement.kt:219)
+ at com.intellij.database.dataSource.connection.statements.JdbcBasedSmartStatement$statementInstance$1.invoke(JdbcBasedSmartStatement.kt:100)
+ at com.intellij.database.dataSource.connection.statements.JdbcBasedSmartStatement$statementInstance$1.invoke(JdbcBasedSmartStatement.kt:18)
+ at com.intellij.database.dataSource.connection.statements.SmartStatementsUtil.runReporting(SmartStatementsUtil.kt:80)
+ at com.intellij.database.dataSource.connection.statements.JdbcBasedSmartStatement.statementInstance(JdbcBasedSmartStatement.kt:99)
+ at com.intellij.database.dataSource.connection.statements.JdbcBasedSmartStatement.access$statementInstance(JdbcBasedSmartStatement.kt:18)
+ at com.intellij.database.dataSource.connection.statements.JdbcBasedSmartStatement$getCurrentStatement$1.invoke(JdbcBasedSmartStatement.kt:118)
+ at com.intellij.database.dataSource.connection.statements.JdbcBasedSmartStatement$getCurrentStatement$1.invoke(JdbcBasedSmartStatement.kt:18)
+ at com.intellij.database.dataSource.connection.statements.StatementHolder$OneShot.invoke(StatementHolder.kt:36)
+ at com.intellij.database.dataSource.connection.statements.StatementHolder$OneShot.invoke(StatementHolder.kt:31)
+ at com.intellij.database.dataSource.connection.statements.JdbcBasedSmartStatement.getCurrentStatement(JdbcBasedSmartStatement.kt:118)
+ at com.intellij.database.dataSource.connection.statements.JdbcBasedSmartStatement.executeInCommonWay(JdbcBasedSmartStatement.kt:64)
+ at com.intellij.database.dataSource.connection.statements.JdbcBasedSmartStatement.execute(JdbcBasedSmartStatement.kt:42)
+ at com.intellij.database.dataSource.connection.statements.NoisyStatement.execute(NoisyStatement.kt:23)
+ at com.intellij.database.console.JdbcEngine$RegularExecutor.execute(JdbcEngine.java:1502)
+ at com.intellij.database.console.JdbcEngine$QueryExecutionOperation.lambda$execute$1(JdbcEngine.java:2086)
+ at com.intellij.database.dialects.base.BaseExecutionEnvironmentHelper.runInSpecificEnvironment(BaseExecutionEnvironmentHelper.java:52)
+ at com.intellij.database.console.JdbcEngine$QueryExecutionOperation.execute(JdbcEngine.java:2085)
+ at com.intellij.database.console.JdbcEngine$QueryExecutionOperation.perform(JdbcEngine.java:2030)
+ at com.intellij.database.console.JdbcEngine$OperationBase.perform(JdbcEngine.java:1699)
+ at com.intellij.database.console.JdbcEngine.lambda$visitQuery$5(JdbcEngine.java:384)
+ at com.intellij.database.console.AbstractEngine.lambda$submitRequest$4(AbstractEngine.java:182)
+ at com.intellij.database.console.AbstractEngine.lambda$null$2(AbstractEngine.java:156)
+ at com.intellij.openapi.progress.impl.CoreProgressManager.executeProcessUnderProgress(CoreProgressManager.java:577)
+ at com.intellij.openapi.progress.impl.ProgressManagerImpl.executeProcessUnderProgress(ProgressManagerImpl.java:61)
+ at com.intellij.database.dataSource.AsyncUtil.underProgress(AsyncUtil.java:180)
+ at com.intellij.database.console.AbstractEngine.lambda$submitRunnable$3(AbstractEngine.java:154)
+ at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
+ at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
+ at java.base/java.lang.Thread.run(Thread.java:834)
+2020-08-13 13:10:28,783 [4844399] ERROR - ij.database.console.JdbcEngine - IntelliJ IDEA 2020.1 Build #IU-201.6668.121
+2020-08-13 13:10:28,783 [4844399] ERROR - ij.database.console.JdbcEngine - JDK: 11.0.6; VM: OpenJDK 64-Bit Server VM; Vendor: JetBrains s.r.o
+2020-08-13 13:10:28,783 [4844399] ERROR - ij.database.console.JdbcEngine - OS: Mac OS X
+2020-08-13 13:10:28,783 [4844399] ERROR - ij.database.console.JdbcEngine - Last Action: Console.Jdbc.Execute
+2020-08-13 13:10:28,783 [4844399] WARN - ic.GenericDatabaseErrorHandler - connection.remoteConnection.createStatement() must not be null
+java.lang.IllegalStateException: connection.remoteConnection.createStatement() must not be null
+ at com.intellij.database.dataSource.connection.statements.JdbcBasedSmartStatement$Simple.createStatement(JdbcBasedSmartStatement.kt:238)
+ at com.intellij.database.dataSource.connection.statements.JdbcBasedSmartStatement$Simple.createStatement(JdbcBasedSmartStatement.kt:219)
+ at com.intellij.database.dataSource.connection.statements.JdbcBasedSmartStatement$statementInstance$1.invoke(JdbcBasedSmartStatement.kt:100)
+ at com.intellij.database.dataSource.connection.statements.JdbcBasedSmartStatement$statementInstance$1.invoke(JdbcBasedSmartStatement.kt:18)
+ at com.intellij.database.dataSource.connection.statements.SmartStatementsUtil.runReporting(SmartStatementsUtil.kt:80)
+ at com.intellij.database.dataSource.connection.statements.JdbcBasedSmartStatement.statementInstance(JdbcBasedSmartStatement.kt:99)
+ at com.intellij.database.dataSource.connection.statements.JdbcBasedSmartStatement.access$statementInstance(JdbcBasedSmartStatement.kt:18)
+ at com.intellij.database.dataSource.connection.statements.JdbcBasedSmartStatement$getCurrentStatement$1.invoke(JdbcBasedSmartStatement.kt:118)
+ at com.intellij.database.dataSource.connection.statements.JdbcBasedSmartStatement$getCurrentStatement$1.invoke(JdbcBasedSmartStatement.kt:18)
+ at com.intellij.database.dataSource.connection.statements.StatementHolder$OneShot.invoke(StatementHolder.kt:36)
+ at com.intellij.database.dataSource.connection.statements.StatementHolder$OneShot.invoke(StatementHolder.kt:31)
+ at com.intellij.database.dataSource.connection.statements.JdbcBasedSmartStatement.getCurrentStatement(JdbcBasedSmartStatement.kt:118)
+ at com.intellij.database.dataSource.connection.statements.JdbcBasedSmartStatement.executeInCommonWay(JdbcBasedSmartStatement.kt:64)
+ at com.intellij.database.dataSource.connection.statements.JdbcBasedSmartStatement.execute(JdbcBasedSmartStatement.kt:42)
+ at com.intellij.database.dataSource.connection.statements.NoisyStatement.execute(NoisyStatement.kt:23)
+ at com.intellij.database.console.JdbcEngine$RegularExecutor.execute(JdbcEngine.java:1502)
+ at com.intellij.database.console.JdbcEngine$QueryExecutionOperation.lambda$execute$1(JdbcEngine.java:2086)
+ at com.intellij.database.dialects.base.BaseExecutionEnvironmentHelper.runInSpecificEnvironment(BaseExecutionEnvironmentHelper.java:52)
+ at com.intellij.database.console.JdbcEngine$QueryExecutionOperation.execute(JdbcEngine.java:2085)
+ at com.intellij.database.console.JdbcEngine$QueryExecutionOperation.perform(JdbcEngine.java:2030)
+ at com.intellij.database.console.JdbcEngine$OperationBase.perform(JdbcEngine.java:1699)
+ at com.intellij.database.console.JdbcEngine.lambda$visitQuery$5(JdbcEngine.java:384)
+ at com.intellij.database.console.AbstractEngine.lambda$submitRequest$4(AbstractEngine.java:182)
+ at com.intellij.database.console.AbstractEngine.lambda$null$2(AbstractEngine.java:156)
+ at com.intellij.openapi.progress.impl.CoreProgressManager.executeProcessUnderProgress(CoreProgressManager.java:577)
+ at com.intellij.openapi.progress.impl.ProgressManagerImpl.executeProcessUnderProgress(ProgressManagerImpl.java:61)
+ at com.intellij.database.dataSource.AsyncUtil.underProgress(AsyncUtil.java:180)
+ at com.intellij.database.console.AbstractEngine.lambda$submitRunnable$3(AbstractEngine.java:154)
+ at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
+ at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
+ at java.base/java.lang.Thread.run(Thread.java:834)
diff --git a/docs/cases/intellij/run_flow_01.txt b/docs/cases/intellij/run_flow_01.txt
new file mode 100644
index 0000000..bce8c7a
--- /dev/null
+++ b/docs/cases/intellij/run_flow_01.txt
@@ -0,0 +1,52 @@
+2020-08-13 19:06:40,407 [ 32699] WARN - ution.rmi.RemoteProcessSupport - Aug 14, 2020 2:06:40 AM com.ascendix.jdbc.salesforce.statement.ForcePreparedStatement executeQuery
+2020-08-13 19:06:40,407 [ 32699] WARN - ution.rmi.RemoteProcessSupport - INFO: executeQuery IMPLEMENTED select Id from Account
+2020-08-13 19:06:40,409 [ 32701] WARN - ution.rmi.RemoteProcessSupport - Aug 14, 2020 2:06:40 AM com.ascendix.jdbc.salesforce.statement.ForcePreparedStatement executeQuery
+2020-08-13 19:06:40,409 [ 32701] WARN - ution.rmi.RemoteProcessSupport - INFO: executeQuery IMPLEMENTED select Id from Account
+2020-08-13 19:06:40,410 [ 32702] WARN - ution.rmi.RemoteProcessSupport - Aug 14, 2020 2:06:40 AM com.ascendix.jdbc.salesforce.statement.ForcePreparedStatement query
+2020-08-13 19:06:40,410 [ 32702] WARN - ution.rmi.RemoteProcessSupport - INFO: query IMPLEMENTED select Id from Account
+2020-08-13 19:06:40,410 [ 32702] WARN - ution.rmi.RemoteProcessSupport - Aug 14, 2020 2:06:40 AM com.ascendix.jdbc.salesforce.statement.ForcePreparedStatement prepareQuery
+2020-08-13 19:06:40,410 [ 32702] WARN - ution.rmi.RemoteProcessSupport - INFO: prepareQuery IMPLEMENTED select Id from Account
+2020-08-13 19:06:40,411 [ 32703] WARN - ution.rmi.RemoteProcessSupport - Aug 14, 2020 2:06:40 AM com.ascendix.jdbc.salesforce.statement.ForcePreparedStatement setParams
+2020-08-13 19:06:40,411 [ 32703] WARN - ution.rmi.RemoteProcessSupport - INFO: setParams IMPLEMENTED select Id from Account
+2020-08-13 19:06:40,411 [ 32703] WARN - ution.rmi.RemoteProcessSupport - Aug 14, 2020 2:06:40 AM com.ascendix.jdbc.salesforce.statement.ForcePreparedStatement getParameters
+2020-08-13 19:06:40,412 [ 32704] WARN - ution.rmi.RemoteProcessSupport - INFO: getParameters IMPLEMENTED select Id from Account
+2020-08-13 19:06:40,415 [ 32707] WARN - ution.rmi.RemoteProcessSupport - Aug 14, 2020 2:06:40 AM com.ascendix.jdbc.salesforce.statement.ForcePreparedStatement getPartnerService
+2020-08-13 19:06:40,415 [ 32707] WARN - ution.rmi.RemoteProcessSupport - INFO: getPartnerService IMPLEMENTED select Id from Account
+2020-08-13 19:06:40,416 [ 32708] WARN - ution.rmi.RemoteProcessSupport - Aug 14, 2020 2:06:40 AM com.ascendix.jdbc.salesforce.statement.ForcePreparedStatement getFieldDefinitions
+2020-08-13 19:06:40,416 [ 32708] WARN - ution.rmi.RemoteProcessSupport - INFO: getFieldDefinitions IMPLEMENTED select Id from Account
+2020-08-13 19:06:40,417 [ 32709] WARN - ution.rmi.RemoteProcessSupport - Aug 14, 2020 2:06:40 AM com.ascendix.jdbc.salesforce.statement.ForcePreparedStatement getQueryAnalyzer
+2020-08-13 19:06:40,417 [ 32709] WARN - ution.rmi.RemoteProcessSupport - INFO: getQueryAnalyzer IMPLEMENTED select Id from Account
+2020-08-13 19:06:40,418 [ 32710] WARN - ution.rmi.RemoteProcessSupport - Aug 14, 2020 2:06:40 AM com.ascendix.jdbc.salesforce.statement.ForcePreparedStatement prepareQuery
+2020-08-13 19:06:40,418 [ 32710] WARN - ution.rmi.RemoteProcessSupport - INFO: prepareQuery IMPLEMENTED select Id from Account
+2020-08-13 19:06:40,419 [ 32711] WARN - ution.rmi.RemoteProcessSupport - Aug 14, 2020 2:06:40 AM com.ascendix.jdbc.salesforce.statement.ForcePreparedStatement setParams
+2020-08-13 19:06:40,419 [ 32711] WARN - ution.rmi.RemoteProcessSupport - INFO: setParams IMPLEMENTED select Id from Account
+2020-08-13 19:06:40,419 [ 32711] WARN - ution.rmi.RemoteProcessSupport - Aug 14, 2020 2:06:40 AM com.ascendix.jdbc.salesforce.statement.ForcePreparedStatement getParameters
+2020-08-13 19:06:40,419 [ 32711] WARN - ution.rmi.RemoteProcessSupport - INFO: getParameters IMPLEMENTED select Id from Account
+2020-08-13 19:06:40,557 [ 32849] WARN - ution.rmi.RemoteProcessSupport - Aug 14, 2020 2:06:40 AM com.ascendix.jdbc.salesforce.statement.ForcePreparedStatement getPartnerService
+2020-08-13 19:06:40,558 [ 32850] WARN - ution.rmi.RemoteProcessSupport - INFO: getPartnerService IMPLEMENTED select Id from Account
+2020-08-13 19:06:41,377 [ 33669] WARN - ution.rmi.RemoteProcessSupport - Aug 14, 2020 2:06:41 AM com.ascendix.jdbc.salesforce.statement.ForcePreparedStatement getMetaData
+2020-08-13 19:06:41,377 [ 33669] WARN - ution.rmi.RemoteProcessSupport - INFO: getMetaData IMPLEMENTED select Id from Account
+2020-08-13 19:06:41,377 [ 33669] WARN - ution.rmi.RemoteProcessSupport - Aug 14, 2020 2:06:41 AM com.ascendix.jdbc.salesforce.statement.ForcePreparedStatement loadMetaData
+2020-08-13 19:06:41,377 [ 33669] WARN - ution.rmi.RemoteProcessSupport - INFO: loadMetaData IMPLEMENTED select Id from Account
+2020-08-13 19:06:41,379 [ 33671] WARN - ution.rmi.RemoteProcessSupport - Aug 14, 2020 2:06:41 AM com.ascendix.jdbc.salesforce.statement.ForcePreparedStatement getQueryAnalyzer
+2020-08-13 19:06:41,379 [ 33671] WARN - ution.rmi.RemoteProcessSupport - INFO: getQueryAnalyzer IMPLEMENTED select Id from Account
+2020-08-13 19:06:41,379 [ 33671] WARN - ution.rmi.RemoteProcessSupport - Aug 14, 2020 2:06:41 AM com.ascendix.jdbc.salesforce.statement.ForcePreparedStatement getFieldDefinitions
+2020-08-13 19:06:41,380 [ 33672] WARN - ution.rmi.RemoteProcessSupport - INFO: getFieldDefinitions IMPLEMENTED select Id from Account
+2020-08-13 19:06:41,380 [ 33672] WARN - ution.rmi.RemoteProcessSupport - Aug 14, 2020 2:06:41 AM com.ascendix.jdbc.salesforce.statement.ForcePreparedStatement flatten
+2020-08-13 19:06:41,380 [ 33672] WARN - ution.rmi.RemoteProcessSupport - INFO: flatten IMPLEMENTED select Id from Account
+2020-08-13 19:06:41,513 [ 33805] WARN - ution.rmi.RemoteProcessSupport - Aug 14, 2020 2:06:41 AM com.ascendix.jdbc.salesforce.statement.ForcePreparedStatement getMoreResults
+2020-08-13 19:06:41,513 [ 33805] WARN - ution.rmi.RemoteProcessSupport - INFO: getMoreResults NOT_IMPLEMENTED select Id from Account
+2020-08-13 19:06:41,514 [ 33806] WARN - ution.rmi.RemoteProcessSupport - Aug 14, 2020 2:06:41 AM com.ascendix.jdbc.salesforce.statement.ForcePreparedStatement getUpdateCount
+2020-08-13 19:06:41,514 [ 33806] WARN - ution.rmi.RemoteProcessSupport - INFO: getUpdateCount NOT_IMPLEMENTED select Id from Account
+2020-08-13 19:06:41,516 [ 33808] WARN - ution.rmi.RemoteProcessSupport - Aug 14, 2020 2:06:41 AM com.ascendix.jdbc.salesforce.statement.ForcePreparedStatement getMoreResults
+2020-08-13 19:06:41,516 [ 33808] WARN - ution.rmi.RemoteProcessSupport - INFO: getMoreResults NOT_IMPLEMENTED select Id from Account
+2020-08-13 19:06:41,516 [ 33808] WARN - ution.rmi.RemoteProcessSupport - Aug 14, 2020 2:06:41 AM com.ascendix.jdbc.salesforce.statement.ForcePreparedStatement getUpdateCount
+2020-08-13 19:06:41,516 [ 33808] WARN - ution.rmi.RemoteProcessSupport - INFO: getUpdateCount NOT_IMPLEMENTED select Id from Account
+2020-08-13 19:06:41,517 [ 33809] WARN - ution.rmi.RemoteProcessSupport - Aug 14, 2020 2:06:41 AM com.ascendix.jdbc.salesforce.statement.ForcePreparedStatement getMoreResults
+2020-08-13 19:06:41,517 [ 33809] WARN - ution.rmi.RemoteProcessSupport - INFO: getMoreResults NOT_IMPLEMENTED select Id from Account
+2020-08-13 19:06:41,518 [ 33810] WARN - ution.rmi.RemoteProcessSupport - Aug 14, 2020 2:06:41 AM com.ascendix.jdbc.salesforce.statement.ForcePreparedStatement getUpdateCount
+2020-08-13 19:06:41,518 [ 33810] WARN - ution.rmi.RemoteProcessSupport - INFO: getUpdateCount NOT_IMPLEMENTED select Id from Account
+2020-08-13 19:06:41,519 [ 33811] WARN - ution.rmi.RemoteProcessSupport - Aug 14, 2020 2:06:41 AM com.ascendix.jdbc.salesforce.statement.ForcePreparedStatement getMoreResults
+2020-08-13 19:06:41,519 [ 33811] WARN - ution.rmi.RemoteProcessSupport - INFO: getMoreResults NOT_IMPLEMENTED select Id from Account
+2020-08-13 19:06:41,519 [ 33811] WARN - ution.rmi.RemoteProcessSupport - Aug 14, 2020 2:06:41 AM com.ascendix.jdbc.salesforce.statement.ForcePreparedStatement getUpdateCount
+2020-08-13 19:06:41,519 [ 33811] WARN - ution.rmi.RemoteProcessSupport - INFO: getUpdateCount NOT_IMPLEMENTED select Id from Account
diff --git a/docs/conv.sh b/docs/conv.sh
new file mode 100755
index 0000000..5def6e5
--- /dev/null
+++ b/docs/conv.sh
@@ -0,0 +1,5 @@
+time ffmpeg -i SOQL-JDBC-IntelliJ-demo.mp4 -vcodec libx264 \
+-vf "pad=width=1720:height=1300:x=0:y=1:color=black" -acodec copy result.mp4
+#time ffmpeg -i SOQL-JDBC-IntelliJ-demo.mp4 -c:v libx264 -c:a aac -tag:v hvc1 SOQL-JDBC-IntelliJ-demo-264.mp4
+#time ffmpeg -i SOQL-JDBC-IntelliJ-demo.mp4 -c:v libx265 -c:a aac -tag:v -s 1920x1300 hvc1 SOQL-JDBC-IntelliJ-demo-hevc3.mp4
+#time ffmpeg -i SOQL-JDBC-IntelliJ-demo.mp4 -c:v libx265 -crf 28 -c:a aac -b:a 128k -tag:v hvc1 SOQL-JDBC-IntelliJ-demo-hevc3.mp4
\ No newline at end of file
diff --git a/docs/intelliJ.png b/docs/intelliJ.png
new file mode 100644
index 0000000..49a7803
Binary files /dev/null and b/docs/intelliJ.png differ
diff --git a/docs/script-demo-2021-02-01.txt b/docs/script-demo-2021-02-01.txt
new file mode 100644
index 0000000..2310324
--- /dev/null
+++ b/docs/script-demo-2021-02-01.txt
@@ -0,0 +1,63 @@
+
+
+Let me show you the simplest way to run SOQL queries to any environment from single IDE.
+I am ging to show this on the IntelliJ IDE, but the driver I am working on is a regular JDBC driver -
+so it can be used from any IDE you prefer - Eclipse, Oracle SQL Developer or any other.
+
+So in order to get access there I need to download the JDBC driver from the page:
+https://github.com/spuliaiev-sfdc/salesforce-jdbc
+
+And we need to register this driver in the IDE.
+
+Let's start with registration of the database datasoure
+admin@RecColl03.org
+test12345
+jdbc:ascendix:salesforce://ap1.stmpa.stm.salesforce.com
+
+Let's test the connectivity.
+Here we see the driver version reported and that connection is good.
+
+By default API version used is 50.0
+We can also change the API version used by supplying the parameter
+?api=51.0
+
+Now we can run the queries for that org:
+select Id, Name from Organization
+
+And we can pull the list of Accounts:
+select Id, Name, Owner.Id, Owner.Name from Account
+
+When the pull of existing Entities available for this Org is finished we will see the autocomplete
+for the Entity Names and Entity Fields.
+ See in the Database Tab the list of available entities.
+
+
+For now Driver supports only SOQL and some admin commands. And I want to show you how to use CONNECT command.
+The syntax of the command is following:
+
+ Let's say I have a diffent org on the same server - I can swithc to that org and get it's data:
+ CONNECT USER admin@RecColl04.org IDENTIFIED BY test12345;
+
+ And verifying that org has been switched:
+ select Id, Name from Organization
+
+And we can switch not only to another org on this server - we can switch to any server:
+
+
+ Let's test Locally running server:
+ Please use the real host name - not the domain name generated for the particular org by MyDomain feature.
+
+ CONNECT TO jdbc:ascendix:salesforce://dev@Local.org:123456@spuliaiev-wsm1.internal.salesforce.com:6109?https=false&api=51.0
+
+ Select id, name from Organization;
+
+ There is another syntax a bit simplier to do the same:
+ CONNECT TO http://spuliaiev-wsm1.internal.salesforce.com:6109
+ USER demo@jdbc.org IDENTIFIED BY "123456"
+ Select id, name from Organization;
+
+ It uses the schema http or https to identify should it use SSL or not. Sometimes for self signed SSL certificates you will have to use option insecurehttps=true
+
+ That's it for now.
+ I am also working on adding UPDATE and INSERT parsers so you could run these commands
+ to set up entities in a much simpler manner than using workbench now.
diff --git a/pom.xml b/pom.xml
index 4a17429..accd221 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,15 +3,22 @@
4.0.0
com.ascendix.salesforce
salesforce-jdbc
- 1.2-SNAPSHOT
+ ${revision}
pom
+
+ https://github.com/spuliaiev-sfdc/salesforce-jdbc.git
+ https://github.com/spuliaiev-sfdc/salesforce-jdbc.git
+ https://github.com/spuliaiev-sfdc/salesforce-jdbc
+
+
sf-auth-client
sf-jdbc-driver
+ 1.4.0-SNAPSHOT
1.8
1.8
UTF-8
@@ -21,6 +28,7 @@
1.25.0
3.8
4.1
+ 1.3.2
4.13.1
1.3
@@ -32,12 +40,12 @@
3.1.0
3.1.0
2.8.2
- 2.0
- 50.0.0
+ 2.0-sfdc
+ 51.0.0
3.0.5
3.5.2
4.2
- 1.4.8
+ 1.4.16
2.4
@@ -109,6 +117,11 @@
antlr
${antlr.version}
+
+ org.apache.commons
+ commons-io
+ ${commons-io.version}
+
org.apache.commons
commons-collections4
@@ -136,7 +149,8 @@
com.thoughtworks.xstream
xstream
- ${xstream.version}
+ 1.4.16
+
test
diff --git a/sf-auth-client/pom.xml b/sf-auth-client/pom.xml
index c434e27..2840ef7 100644
--- a/sf-auth-client/pom.xml
+++ b/sf-auth-client/pom.xml
@@ -6,7 +6,8 @@
salesforce-jdbc
com.ascendix.salesforce
- 1.2-SNAPSHOT
+ ${revision}
+ ../pom.xml
sf-auth-client
@@ -22,4 +23,4 @@
-
\ No newline at end of file
+
diff --git a/sf-jdbc-driver/pom.xml b/sf-jdbc-driver/pom.xml
index 0649ff5..8a16d34 100644
--- a/sf-jdbc-driver/pom.xml
+++ b/sf-jdbc-driver/pom.xml
@@ -6,7 +6,8 @@
salesforce-jdbc
com.ascendix.salesforce
- 1.2-SNAPSHOT
+ ${revision}
+ ../pom.xml
sf-jdbc-driver
@@ -32,6 +33,10 @@
org.apache.commons
commons-lang3
+
+ org.apache.commons
+ commons-io
+
org.antlr
antlr
@@ -40,6 +45,11 @@
org.apache.commons
commons-collections4
+
+ com.github.jsqlparser
+ jsqlparser
+ 4.0
+
@@ -81,10 +91,15 @@
jar-with-dependencies
+
+
+ com.ascendix.jdbc.salesforce.metadata.ForceDatabaseMetaData
+
+
-
\ No newline at end of file
+
diff --git a/sf-jdbc-driver/src/main/java/com/ascendix/jdbc/salesforce/ForceDriver.java b/sf-jdbc-driver/src/main/java/com/ascendix/jdbc/salesforce/ForceDriver.java
index 50b5120..bf9b49a 100644
--- a/sf-jdbc-driver/src/main/java/com/ascendix/jdbc/salesforce/ForceDriver.java
+++ b/sf-jdbc-driver/src/main/java/com/ascendix/jdbc/salesforce/ForceDriver.java
@@ -4,13 +4,19 @@
import com.ascendix.jdbc.salesforce.connection.ForceConnectionInfo;
import com.ascendix.jdbc.salesforce.connection.ForceService;
import com.sforce.soap.partner.PartnerConnection;
+import com.sforce.soap.partner.fault.ApiFault;
import com.sforce.ws.ConnectionException;
import lombok.extern.slf4j.Slf4j;
+import javax.net.ssl.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.cert.X509Certificate;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
@@ -18,6 +24,7 @@
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Properties;
+import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -26,14 +33,22 @@
@Slf4j
public class ForceDriver implements Driver {
+ private static final String SF_JDBC_DRIVER_NAME = "SF JDBC driver";
+ private static final Logger logger = Logger.getLogger(SF_JDBC_DRIVER_NAME);
+
private static final String ACCEPTABLE_URL = "jdbc:ascendix:salesforce";
private static final Pattern URL_PATTERN = Pattern.compile("\\A" + ACCEPTABLE_URL + "://(.*)");
private static final Pattern URL_HAS_AUTHORIZATION_SEGMENT = Pattern.compile("\\A" + ACCEPTABLE_URL + "://([^:]+):([^@]+)@([^?]*)([?](.*))?");
private static final Pattern PARAM_STANDARD_PATTERN = Pattern.compile("(([^=]+)=([^&]*)&?)");
+ private static final Pattern VALID_IP_ADDRESS_REGEX = Pattern.compile("^(?https?://)?(?(?(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))(:(?[0-9]+))?)$");
+ private static final Pattern VALID_HOST_NAME_REGEX = Pattern.compile("^(?https?://)?(?(?(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9]))(:(?[0-9]+))?)$");
+
static {
try {
+ logger.info("[ForceDriver] registration");
DriverManager.registerDriver(new ForceDriver());
+
} catch (Exception e) {
throw new RuntimeException("Failed register ForceDriver: " + e.getMessage(), e);
}
@@ -58,14 +73,60 @@ public Connection connect(String url, Properties properties) throws SQLException
info.setUserName(properties.getProperty("user"));
info.setClientName(properties.getProperty("client"));
info.setPassword(properties.getProperty("password"));
+ info.setClientName(properties.getProperty("client"));
info.setSessionId(properties.getProperty("sessionId"));
info.setSandbox(resolveSandboxProperty(properties));
info.setHttps(resolveBooleanProperty(properties, "https", true));
+ if (resolveBooleanProperty(properties, "insecurehttps", false)) {
+ HttpsTrustManager.allowAllSSL();
+ }
info.setApiVersion(resolveStringProperty(properties, "api", ForceService.DEFAULT_API_VERSION));
info.setLoginDomain(resolveStringProperty(properties, "loginDomain", ForceService.DEFAULT_LOGIN_DOMAIN));
PartnerConnection partnerConnection = ForceService.createPartnerConnection(info);
- return new ForceConnection(partnerConnection);
+ return new ForceConnection(partnerConnection, (newUrl, userName, userPassword) -> {
+ logger.info("[ForceDriver] relogin helper ");
+ Properties newConnStringProps;
+ Properties newProperties = new Properties();
+ newProperties.putAll(properties);
+ ForceConnectionInfo newInfo = new ForceConnectionInfo();
+ if (newUrl != null) {
+ try {
+ newConnStringProps = getConnStringProperties(newUrl);
+ newProperties.putAll(newConnStringProps);
+ } catch (Exception e) {
+ logger.log(Level.WARNING, "[ForceDriver] relogin helper failed - url parsing error", e);
+ }
+ }
+ if (userName != null && userPassword != null) {
+ newProperties.setProperty("user", userName);
+ newProperties.setProperty("password", userPassword);
+ }
+ newInfo.setUserName(newProperties.getProperty("user"));
+ newInfo.setPassword(newProperties.getProperty("password"));
+ newInfo.setClientName(newProperties.getProperty("client"));
+ newInfo.setSessionId(newProperties.getProperty("sessionId"));
+ newInfo.setSandbox(resolveSandboxProperty(newProperties));
+ newInfo.setHttps(resolveBooleanProperty(newProperties, "https", true));
+ if (resolveBooleanProperty(newProperties, "insecurehttps", false)) {
+ HttpsTrustManager.allowAllSSL();
+ }
+ newInfo.setApiVersion(resolveStringProperty(newProperties, "api", ForceService.DEFAULT_API_VERSION));
+ newInfo.setLoginDomain(resolveStringProperty(newProperties, "loginDomain", ForceService.DEFAULT_LOGIN_DOMAIN));
+
+ PartnerConnection newPartnerConnection;
+ try {
+ newPartnerConnection = ForceService.createPartnerConnection(newInfo);
+ logger.log(Level.WARNING, "[ForceDriver] relogin helper success="+(newPartnerConnection != null));
+ return newPartnerConnection;
+ } catch (ApiFault e) {
+ logger.log(Level.WARNING, "[ForceDriver] relogin helper failed "+ e.getMessage(), e);
+ throw new ConnectionException("Relogin failed ("+e.getExceptionCode()+") "+ e.getExceptionMessage(), e);
+ } catch (ConnectionException e) {
+ logger.log(Level.WARNING, "[ForceDriver] relogin helper failed "+ e.getMessage(), e);
+ throw new ConnectionException("Relogin failed", e);
+ }
+ });
} catch (ConnectionException | IOException e) {
throw new SQLException(e);
}
@@ -83,7 +144,7 @@ private static Boolean resolveSandboxProperty(Properties properties) {
return null;
}
- private static Boolean resolveBooleanProperty(Properties properties, String propertyName, boolean defaultValue) {
+ protected static Boolean resolveBooleanProperty(Properties properties, String propertyName, boolean defaultValue) {
String boolValue = properties.getProperty(propertyName);
if (boolValue != null) {
return Boolean.valueOf(boolValue);
@@ -100,7 +161,7 @@ private static String resolveStringProperty(Properties properties, String proper
}
- protected Properties getConnStringProperties(String urlString) throws IOException {
+ public static Properties getConnStringProperties(String urlString) throws IOException {
Properties result = new Properties();
String urlProperties = null;
@@ -108,8 +169,12 @@ protected Properties getConnStringProperties(String urlString) throws IOExceptio
Matcher authMatcher = URL_HAS_AUTHORIZATION_SEGMENT.matcher(urlString);
if (authMatcher.matches()) {
- result.put("user", authMatcher.group(1));
- result.put("password", authMatcher.group(2));
+ if (authMatcher.group(1) != null) {
+ result.put("user", authMatcher.group(1));
+ }
+ if (authMatcher.group(2) != null) {
+ result.put("password", authMatcher.group(2));
+ }
result.put("loginDomain", authMatcher.group(3));
if (authMatcher.groupCount() > 4 && authMatcher.group(5) != null) {
// has some other parameters - parse them from standard URL format like
@@ -121,11 +186,36 @@ protected Properties getConnStringProperties(String urlString) throws IOExceptio
String value = 3 >= matcher.groupCount() ? matcher.group(3) : null;
result.put(param, value);
}
-
}
} else if (stdMatcher.matches()) {
- urlProperties = stdMatcher.group(1);
+ String dataString = stdMatcher.group(1);
+ int endOfHost = dataString.contains(";") ? dataString.indexOf(";")-1 : dataString.length()-1;
+ String possibleHost = dataString.substring(0, endOfHost+1);
+ if (possibleHost.trim().length() > 0 && !possibleHost.contains("=")) {
+ result.put("loginDomain", possibleHost);
+ urlProperties = dataString.substring(endOfHost+1);
+ } else {
+ urlProperties = dataString;
+ }
urlProperties = urlProperties.replaceAll(";", "\n");
+ } else {
+ Matcher ipMatcher = VALID_IP_ADDRESS_REGEX.matcher(urlString);
+ if (ipMatcher.matches()) {
+ result.put("loginDomain", ipMatcher.group("loginDomain"));
+ result.put("https", "true");
+ if (ipMatcher.group("protocol") != null && ipMatcher.group("protocol").toLowerCase().equals("http://")) {
+ result.put("https", "false");
+ }
+ } else {
+ Matcher hostMatcher = VALID_HOST_NAME_REGEX.matcher(urlString);
+ if (hostMatcher.matches()) {
+ result.put("loginDomain", hostMatcher.group("loginDomain"));
+ result.put("https", "true");
+ if (hostMatcher.group("protocol") != null && hostMatcher.group("protocol").toLowerCase().equals("http://")) {
+ result.put("https", "false");
+ }
+ }
+ }
}
if (urlProperties != null) {
@@ -167,4 +257,53 @@ public Logger getParentLogger() throws SQLFeatureNotSupportedException {
throw new SQLFeatureNotSupportedException();
}
+ public static class HttpsTrustManager implements X509TrustManager {
+ private static TrustManager[] trustManagers;
+ private static final X509Certificate[] _AcceptedIssuers = new X509Certificate[]{};
+
+ @Override
+ public void checkClientTrusted(
+ X509Certificate[] x509Certificates, String s)
+ throws java.security.cert.CertificateException {
+
+ }
+
+ @Override
+ public void checkServerTrusted(
+ X509Certificate[] x509Certificates, String s)
+ throws java.security.cert.CertificateException {
+
+ }
+
+ public boolean isClientTrusted(X509Certificate[] chain) {
+ return true;
+ }
+
+ public boolean isServerTrusted(X509Certificate[] chain) {
+ return true;
+ }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return _AcceptedIssuers;
+ }
+
+ public static void allowAllSSL() {
+ HttpsURLConnection.setDefaultHostnameVerifier((arg0, arg1) -> true);
+
+ if (trustManagers == null) {
+ trustManagers = new TrustManager[]{new HttpsTrustManager()};
+ }
+
+ try {
+ SSLContext context = SSLContext.getInstance("TLS");
+ context.init(null, trustManagers, new SecureRandom());
+ HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());
+ } catch (NoSuchAlgorithmException | KeyManagementException e) {
+ e.printStackTrace();
+ }
+
+ }
+ }
+
}
diff --git a/sf-jdbc-driver/src/main/java/com/ascendix/jdbc/salesforce/connection/ForceConnection.java b/sf-jdbc-driver/src/main/java/com/ascendix/jdbc/salesforce/connection/ForceConnection.java
index 0fe589f..edb4e6c 100644
--- a/sf-jdbc-driver/src/main/java/com/ascendix/jdbc/salesforce/connection/ForceConnection.java
+++ b/sf-jdbc-driver/src/main/java/com/ascendix/jdbc/salesforce/connection/ForceConnection.java
@@ -3,6 +3,7 @@
import com.ascendix.jdbc.salesforce.statement.ForcePreparedStatement;
import com.ascendix.jdbc.salesforce.metadata.ForceDatabaseMetaData;
import com.sforce.soap.partner.PartnerConnection;
+import com.sforce.ws.ConnectionException;
import java.sql.Array;
import java.sql.Blob;
@@ -23,11 +24,32 @@
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;
+import java.util.function.BiFunction;
+import java.util.logging.Level;
import java.util.logging.Logger;
public class ForceConnection implements Connection {
+ @FunctionalInterface
+ public interface UpdateLoginFunction {
+
+ /**
+ * Applies this function to the given arguments.
+ *
+ * @param url the first function argument
+ * @param user the second function argument
+ * @param pass the second function argument
+ * @return the function result
+ */
+ PartnerConnection apply(String url, String user, String pass) throws ConnectionException;
+ }
+
private final PartnerConnection partnerConnection;
+ /** the updated partner connection in case if we want to support relogin command */
+ private PartnerConnection partnerConnectionUpdated;
+ /** the function to provide partner connection in case if we want to support relogin command */
+ UpdateLoginFunction loginHandler;
+
private final DatabaseMetaData metadata;
private static final String SF_JDBC_DRIVER_NAME = "SF JDBC driver";
private static final Logger logger = Logger.getLogger(SF_JDBC_DRIVER_NAME);
@@ -35,21 +57,52 @@ public class ForceConnection implements Connection {
private Map connectionCache = new HashMap<>();
Properties clientInfo = new Properties();
- public ForceConnection(PartnerConnection partnerConnection) {
+ public ForceConnection(PartnerConnection partnerConnection, UpdateLoginFunction loginHandler) {
this.partnerConnection = partnerConnection;
this.metadata = new ForceDatabaseMetaData(this);
+ this.loginHandler = loginHandler;
}
public PartnerConnection getPartnerConnection() {
+ if (partnerConnectionUpdated != null) {
+ return partnerConnectionUpdated;
+ }
return partnerConnection;
}
+ public boolean updatePartnerConnection(String url, String userName, String userPass) throws ConnectionException {
+ boolean result = false;
+ String currentUserName = null;
+ try {
+ currentUserName = partnerConnection.getUserInfo().getUserName();
+ } catch (ConnectionException e) {
+ }
+ logger.info("[Conn] updatePartnerConnection IMPLEMENTED newUserName="+userName + " oldUserName="+currentUserName + " newUrl="+url);
+ if (loginHandler != null) {
+ try {
+ PartnerConnection newPartnerConnection = loginHandler.apply(url, userName, userPass);
+ if (newPartnerConnection != null) {
+ partnerConnectionUpdated = newPartnerConnection;
+ logger.info("[Conn] updatePartnerConnection UPDATED to newUserName="+userName);
+ result = true;
+ } else {
+ logger.log(Level.SEVERE, "[Conn] updatePartnerConnection UPDATE FAILED to newUserName="+userName+" currentUserName="+currentUserName);
+ }
+ } catch (Exception e) {
+ logger.log(Level.SEVERE, "[Conn] updatePartnerConnection UPDATE FAILED to newUserName="+userName+" currentUserName="+currentUserName, e);
+ throw e;
+ }
+ }
+ return result;
+ }
+
public DatabaseMetaData getMetaData() {
return metadata;
}
@Override
public PreparedStatement prepareStatement(String soql) {
+ logger.info("[Conn] prepareStatement IMPLEMENTED "+soql);
return new ForcePreparedStatement(this, soql);
}
@@ -77,7 +130,7 @@ public boolean isWrapperFor(Class> iface) {
@Override
public Statement createStatement() {
logger.info("[Conn] createStatement 1 IMPLEMENTED ");
- return null;
+ return new ForcePreparedStatement(this);
}
@Override
@@ -100,26 +153,22 @@ public void setAutoCommit(boolean autoCommit) {
@Override
public boolean getAutoCommit() throws SQLException {
- // TODO Auto-generated method stub
- return false;
+ return true;
}
@Override
public void commit() throws SQLException {
- // TODO Auto-generated method stub
-
+ logger.info("[Conn] commit NOT_IMPLEMENTED ");
}
@Override
public void rollback() throws SQLException {
- // TODO Auto-generated method stub
-
+ logger.info("[Conn] rollback NOT_IMPLEMENTED ");
}
@Override
public void close() throws SQLException {
- // TODO Auto-generated method stub
-
+ logger.info("[Conn] close NOT_IMPLEMENTED ");
}
@Override
@@ -143,13 +192,13 @@ public boolean isReadOnly() throws SQLException {
@Override
public void setCatalog(String catalog) throws SQLException {
// TODO Auto-generated method stub
-
+ logger.info("[Conn] setCatalog NOT_IMPLEMENTED set to '"+catalog+"'");
}
@Override
public String getCatalog() throws SQLException {
- // TODO Auto-generated method stub
- return null;
+ logger.info("[Conn] getCatalog IMPLEMENTED returning "+ForceDatabaseMetaData.DEFAULT_CATALOG);
+ return ForceDatabaseMetaData.DEFAULT_CATALOG;
}
@Override
@@ -178,26 +227,26 @@ public void clearWarnings() throws SQLException {
@Override
public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
- Logger.getLogger(SF_JDBC_DRIVER_NAME).info(Object.class.getEnclosingMethod().getName());
- return null;
+ logger.info("[Conn] createStatement 2 IMPLEMENTED");
+ return new ForcePreparedStatement(this);
}
@Override
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
throws SQLException {
- Logger.getLogger(SF_JDBC_DRIVER_NAME).info(Object.class.getEnclosingMethod().getName());
- return null;
+ logger.info("[Conn] prepareStatement 1 IMPLEMENTED "+sql);
+ return new ForcePreparedStatement(this, sql);
}
@Override
public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
- Logger.getLogger(SF_JDBC_DRIVER_NAME).info(Object.class.getEnclosingMethod().getName());
+ logger.info("[Conn] prepareCall NOT_IMPLEMENTED "+sql);
return null;
}
@Override
public Map> getTypeMap() throws SQLException {
- Logger.getLogger(SF_JDBC_DRIVER_NAME).info(Object.class.getEnclosingMethod().getName());
+ logger.info("[Conn] getTypeMap NOT_IMPLEMENTED ");
return null;
}
@@ -233,52 +282,52 @@ public Savepoint setSavepoint(String name) throws SQLException {
@Override
public void rollback(Savepoint savepoint) throws SQLException {
- // TODO Auto-generated method stub
+ logger.info("[Conn] rollback Savepoint NOT_IMPLEMENTED");
}
@Override
public void releaseSavepoint(Savepoint savepoint) throws SQLException {
- // TODO Auto-generated method stub
+ logger.info("[Conn] releaseSavepoint NOT_IMPLEMENTED");
}
@Override
public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
throws SQLException {
- Logger.getLogger(SF_JDBC_DRIVER_NAME).info(Object.class.getEnclosingMethod().getName());
- return null;
+ logger.info("[Conn] createStatement 3 NOT_IMPLEMENTED");
+ return new ForcePreparedStatement(this);
}
@Override
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency,
int resultSetHoldability) throws SQLException {
- Logger.getLogger(SF_JDBC_DRIVER_NAME).info(Object.class.getEnclosingMethod().getName());
+ logger.info("[Conn] prepareStatement 2 NOT_IMPLEMENTED "+sql );
return null;
}
@Override
public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency,
int resultSetHoldability) throws SQLException {
- Logger.getLogger(SF_JDBC_DRIVER_NAME).info(Object.class.getEnclosingMethod().getName());
+ logger.info("[Conn] prepareCall 2 NOT_IMPLEMENTED "+sql );
return null;
}
@Override
public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
- Logger.getLogger(SF_JDBC_DRIVER_NAME).info(Object.class.getEnclosingMethod().getName());
+ logger.info("[Conn] prepareStatement 3 NOT_IMPLEMENTED "+sql );
return null;
}
@Override
public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
- Logger.getLogger(SF_JDBC_DRIVER_NAME).info(Object.class.getEnclosingMethod().getName());
+ logger.info("[Conn] prepareStatement 4 NOT_IMPLEMENTED "+sql );
return null;
}
@Override
public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
- Logger.getLogger(SF_JDBC_DRIVER_NAME).info(Object.class.getEnclosingMethod().getName());
+ logger.info("[Conn] prepareStatement 5 NOT_IMPLEMENTED "+sql );
return null;
}
@@ -309,7 +358,8 @@ public SQLXML createSQLXML() throws SQLException {
@Override
public boolean isValid(int timeout) throws SQLException {
// TODO Auto-generated method stub
- return false;
+ logger.info("[Conn] isValid NOT_IMPLEMENTED ");
+ return true;
}
@Override
@@ -352,7 +402,7 @@ public Struct createStruct(String typeName, Object[] attributes) throws SQLExcep
@Override
public void setSchema(String schema) throws SQLException {
// TODO Auto-generated method stub
-
+ logger.info("[Conn] setSchema NOT_IMPLEMENTED ");
}
@Override
@@ -372,4 +422,5 @@ public int getNetworkTimeout() throws SQLException {
// TODO Auto-generated method stub
return 0;
}
+
}
diff --git a/sf-jdbc-driver/src/main/java/com/ascendix/jdbc/salesforce/connection/ForceService.java b/sf-jdbc-driver/src/main/java/com/ascendix/jdbc/salesforce/connection/ForceService.java
index cc07f38..c824b77 100644
--- a/sf-jdbc-driver/src/main/java/com/ascendix/jdbc/salesforce/connection/ForceService.java
+++ b/sf-jdbc-driver/src/main/java/com/ascendix/jdbc/salesforce/connection/ForceService.java
@@ -74,6 +74,7 @@ private static PartnerConnection createConnectionBySessionId(ForceConnectionInfo
private static PartnerConnection createConnectionByUserCredential(ForceConnectionInfo info)
throws ConnectionException {
+
ConnectorConfig partnerConfig = new ConnectorConfig();
partnerConfig.setUsername(info.getUserName());
partnerConfig.setPassword(info.getPassword());
@@ -87,7 +88,7 @@ private static PartnerConnection createConnectionByUserCredential(ForceConnectio
try {
info.setSandbox(false);
partnerConfig.setAuthEndpoint(buildAuthEndpoint(info));
- connection = Connector.newConnection(partnerConfig);
+ connection = Connector.newConnection(partnerConfig);
} catch (ConnectionException ce) {
info.setSandbox(true);
partnerConfig.setAuthEndpoint(buildAuthEndpoint(info));
diff --git a/sf-jdbc-driver/src/main/java/com/ascendix/jdbc/salesforce/delegates/PartnerService.java b/sf-jdbc-driver/src/main/java/com/ascendix/jdbc/salesforce/delegates/PartnerService.java
index a13477d..1267ad7 100644
--- a/sf-jdbc-driver/src/main/java/com/ascendix/jdbc/salesforce/delegates/PartnerService.java
+++ b/sf-jdbc-driver/src/main/java/com/ascendix/jdbc/salesforce/delegates/PartnerService.java
@@ -3,26 +3,22 @@
import com.ascendix.jdbc.salesforce.metadata.Column;
import com.ascendix.jdbc.salesforce.metadata.Table;
import com.ascendix.jdbc.salesforce.statement.FieldDef;
-import com.sforce.soap.partner.DescribeGlobalResult;
-import com.sforce.soap.partner.DescribeGlobalSObjectResult;
-import com.sforce.soap.partner.DescribeSObjectResult;
-import com.sforce.soap.partner.Field;
-import com.sforce.soap.partner.PartnerConnection;
-import com.sforce.soap.partner.QueryResult;
+import com.sforce.soap.partner.*;
+import com.sforce.soap.partner.sobject.SObject;
import com.sforce.ws.ConnectionException;
import com.sforce.ws.bind.XmlObject;
import org.apache.commons.collections4.IteratorUtils;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
+import java.util.*;
+import java.util.logging.Logger;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
public class PartnerService {
+ private static final String SF_JDBC_DRIVER_NAME = "SF JDBC driver";
+ private static final Logger logger = Logger.getLogger(SF_JDBC_DRIVER_NAME);
+
private PartnerConnection partnerConnection;
private List sObjectTypesCache;
@@ -31,17 +27,22 @@ public PartnerService(PartnerConnection partnerConnection) {
}
public List getTables() {
+ logger.info("[PartnerService] getTables IMPLEMENTED ");
List sObjects = getSObjectsDescription();
- return sObjects.stream()
+ List tables = sObjects.stream()
.map(this::convertToTable)
.collect(Collectors.toList());
+ logger.info("[PartnerService] getTables tables count="+tables.size());
+ return tables;
}
public DescribeSObjectResult describeSObject(String sObjectType) throws ConnectionException {
+ logger.info("[PartnerService] describeSObject "+sObjectType);
return partnerConnection.describeSObject(sObjectType);
}
private Table convertToTable(DescribeSObjectResult so) {
+ logger.info("[PartnerService] convertToTable "+so.getName());
List fields = Arrays.asList(so.getFields());
List columns = fields.stream()
.map(this::convertToColumn)
@@ -83,6 +84,7 @@ private List getSObjectTypes() throws ConnectionException {
sObjectTypesCache = Arrays.stream(sobs)
.map(DescribeGlobalSObjectResult::getName)
.collect(Collectors.toList());
+ logger.info("[PartnerService] getSObjectTypes count="+sObjectTypesCache.size());
}
return sObjectTypesCache;
@@ -127,40 +129,58 @@ private List> toBatches(List objects, int batchSize) {
}
public List query(String soql, List expectedSchema) throws ConnectionException {
+ logger.info("[PartnerService] query "+soql);
List resultRows = Collections.synchronizedList(new LinkedList<>());
QueryResult queryResult = null;
do {
queryResult = queryResult == null ? partnerConnection.query(soql)
: partnerConnection.queryMore(queryResult.getQueryLocator());
- resultRows.addAll(removeServiceInfo(Arrays.asList(queryResult.getRecords())));
+
+ List rows = Arrays.asList(queryResult.getRecords());
+ // extract the root entity name
+ Object rootEntityName = rows.stream().filter(xmlo -> "type".equals(xmlo.getName().getLocalPart())).findFirst().map(XmlObject::getValue).orElse(null);
+ String parentName = null;
+ resultRows.addAll(removeServiceInfo(rows, parentName, rootEntityName==null ? null : (String)rootEntityName));
} while (!queryResult.isDone());
return PartnerResultToCrtesianTable.expand(resultRows, expectedSchema);
}
- private List removeServiceInfo(Iterator rows) {
- return removeServiceInfo(IteratorUtils.toList(rows));
- }
-
- private List removeServiceInfo(List rows) {
+ private List removeServiceInfo(List rows, String parentName, String rootEntityName) {
return rows.stream()
.filter(this::isDataObjectType)
- .map(this::removeServiceInfo)
+ .map(row -> removeServiceInfo(row, parentName, rootEntityName))
.collect(Collectors.toList());
}
- private List removeServiceInfo(XmlObject row) {
+ private List removeServiceInfo(XmlObject row, String parentName, String rootEntityName) {
return IteratorUtils.toList(row.getChildren()).stream()
.filter(this::isDataObjectType)
.skip(1) // Removes duplicate Id from SF Partner API response
// (https://developer.salesforce.com/forums/?id=906F00000008kciIAA)
- .map(field -> isNestedResultset(field)
- ? removeServiceInfo(field.getChildren())
- : toForceResultField(field))
+ .flatMap(field -> translateField(field, parentName, rootEntityName))
.collect(Collectors.toList());
}
- private ForceResultField toForceResultField(XmlObject field) {
+ private Stream translateField(XmlObject field, String parentName, String rootEntityName) {
+ Stream.Builder outStream = Stream.builder();
+
+ String fieldType = field.getXmlType() != null ? field.getXmlType().getLocalPart() : null;
+ if ("sObject".equalsIgnoreCase(fieldType)) {
+ List childFields = removeServiceInfo(field, field.getName().getLocalPart(), rootEntityName);
+ childFields.forEach(outStream::add);
+ } else {
+ if (isNestedResultset(field)) {
+ outStream.add(removeServiceInfo(IteratorUtils.toList(field.getChildren()), field.getName().getLocalPart(), rootEntityName));
+ } else {
+ outStream.add(toForceResultField(field, parentName, rootEntityName));
+ }
+ }
+ return outStream.build();
+ }
+
+
+ private ForceResultField toForceResultField(XmlObject field, String parentName, String rootEntityName) {
String fieldType = field.getXmlType() != null ? field.getXmlType().getLocalPart() : null;
if ("sObject".equalsIgnoreCase(fieldType)) {
List children = new ArrayList<>();
@@ -168,6 +188,9 @@ private ForceResultField toForceResultField(XmlObject field) {
field = children.get(2);
}
String name = field.getName().getLocalPart();
+ if (parentName != null && (rootEntityName == null || !rootEntityName.equals(parentName))) {
+ name = parentName+"."+name;
+ }
Object value = field.getValue();
return new ForceResultField(null, fieldType, name, value);
}
@@ -179,7 +202,47 @@ private boolean isNestedResultset(XmlObject object) {
private final static List SOAP_RESPONSE_SERVICE_OBJECT_TYPES = Arrays.asList("type", "done", "queryLocator",
"size");
- private boolean isDataObjectType(XmlObject object) {
- return !SOAP_RESPONSE_SERVICE_OBJECT_TYPES.contains(object.getName().getLocalPart());
+ private boolean isDataObjectType(XmlObject obj) {
+ return !SOAP_RESPONSE_SERVICE_OBJECT_TYPES.contains(obj.getName().getLocalPart());
+ }
+
+ public SaveResult[] createRecords(String entityName, List