tableNames = new ArrayList<>(columnCount);
450 |
451 | // Get the columns and their meta data.
452 | // NOTE: columnIndex for rsmd.getXXX methods STARTS AT 1 NOT 0
453 | for (int i = 1; i <= columnCount; i++) {
454 | Column c = new Column(rsmd.getColumnLabel(i),
455 | rsmd.getColumnType(i), rsmd.getColumnTypeName(i));
456 | c.setWidth(c.getLabel().length());
457 | c.setTypeCategory(whichCategory(c.getType()));
458 | columns.add(c);
459 |
460 | if (!tableNames.contains(rsmd.getTableName(i))) {
461 | tableNames.add(rsmd.getTableName(i));
462 | }
463 | }
464 |
465 | // Go through each row, get values of each column and adjust
466 | // column widths.
467 | int rowCount = 0;
468 | while (rs.next()) {
469 |
470 | System.out.println("row: " + rowCount);
471 |
472 | // NOTE: columnIndex for rs.getXXX methods STARTS AT 1 NOT 0
473 | for (int i = 0; i < columnCount; i++) {
474 | Column c = columns.get(i);
475 | String value;
476 | int category = c.getTypeCategory();
477 |
478 | if (category == CATEGORY_OTHER) {
479 |
480 | // Use generic SQL type name instead of the actual value
481 | // for column types BLOB, BINARY etc.
482 | value = "(" + c.getTypeName() + ")";
483 |
484 | } else {
485 | value = rs.getString(i + 1) == null ? "NULL" : rs.getString(i + 1);
486 | }
487 | switch (category) {
488 | case CATEGORY_DOUBLE:
489 |
490 | // For real numbers, format the string value to have 3 digits
491 | // after the point. THIS IS TOTALLY ARBITRARY and can be
492 | // improved to be CONFIGURABLE.
493 | if (!value.equals("NULL")) {
494 | Double dValue = rs.getDouble(i + 1);
495 | value = String.format("%.3f", dValue);
496 | }
497 | break;
498 |
499 | case CATEGORY_STRING:
500 |
501 | // Left justify the text columns
502 | c.justifyLeft();
503 |
504 | // and apply the width limit
505 | if (value.length() > maxStringColWidth) {
506 | value = value.substring(0, maxStringColWidth - 3) + "...";
507 | }
508 | break;
509 | }
510 |
511 | // Adjust the column width
512 | c.setWidth(value.length() > c.getWidth() ? value.length() : c.getWidth());
513 | c.addValue(value);
514 | } // END of for loop columnCount
515 | rowCount++;
516 |
517 | } // END of while (rs.next)
518 |
519 | /*
520 | At this point we have gone through meta data, get the
521 | columns and created all Column objects, iterated over the
522 | ResultSet rows, populated the column values and adjusted
523 | the column widths.
524 |
525 | We cannot start printing just yet because we have to prepare
526 | a row separator String.
527 | */
528 |
529 | // For the fun of it, I will use StringBuilder
530 | StringBuilder strToPrint = new StringBuilder();
531 | StringBuilder rowSeparator = new StringBuilder();
532 |
533 | /*
534 | Prepare column labels to print as well as the row separator.
535 | It should look something like this:
536 | +--------+------------+------------+-----------+ (row separator)
537 | | EMP_NO | BIRTH_DATE | FIRST_NAME | LAST_NAME | (labels row)
538 | +--------+------------+------------+-----------+ (row separator)
539 | */
540 |
541 | // Iterate over columns
542 | for (Column c : columns) {
543 | int width = c.getWidth();
544 |
545 | // Center the column label
546 | String toPrint;
547 | String name = c.getLabel();
548 | int diff = width - name.length();
549 |
550 | if ((diff % 2) == 1) {
551 | // diff is not divisible by 2, add 1 to width (and diff)
552 | // so that we can have equal padding to the left and right
553 | // of the column label.
554 | width++;
555 | diff++;
556 | c.setWidth(width);
557 | }
558 |
559 | int paddingSize = diff / 2; // InteliJ says casting to int is redundant.
560 |
561 | // Cool String repeater code thanks to user102008 at stackoverflow.com
562 | // (http://tinyurl.com/7x9qtyg) "Simple way to repeat a string in java"
563 | String padding = new String(new char[paddingSize]).replace("\0", " ");
564 |
565 | toPrint = "| " + padding + name + padding + " ";
566 | // END centering the column label
567 |
568 | strToPrint.append(toPrint);
569 |
570 | rowSeparator.append("+");
571 | rowSeparator.append(new String(new char[width + 2]).replace("\0", "-"));
572 | }
573 |
574 | String lineSeparator = System.getProperty("line.separator");
575 |
576 | // Is this really necessary ??
577 | lineSeparator = lineSeparator == null ? "\n" : lineSeparator;
578 |
579 | rowSeparator.append("+").append(lineSeparator);
580 |
581 | strToPrint.append("|").append(lineSeparator);
582 | strToPrint.insert(0, rowSeparator);
583 | strToPrint.append(rowSeparator);
584 |
585 | StringJoiner sj = new StringJoiner(", ");
586 | for (String name : tableNames) {
587 | sj.add(name);
588 | }
589 |
590 | String info = "Printing " + rowCount;
591 | info += rowCount > 1 ? " rows from " : " row from ";
592 | info += tableNames.size() > 1 ? "tables " : "table ";
593 | info += sj.toString();
594 |
595 | System.out.println(info);
596 |
597 | // Print out the formatted column labels
598 | System.out.print(strToPrint.toString());
599 |
600 | String format;
601 |
602 | // Print out the rows
603 | for (int i = 0; i < rowCount; i++) {
604 | for (Column c : columns) {
605 |
606 | // This should form a format string like: "%-60s"
607 | format = String.format("| %%%s%ds ", c.getJustifyFlag(), c.getWidth());
608 | System.out.print(
609 | String.format(format, c.getValue(i))
610 | );
611 | }
612 |
613 | System.out.println("|");
614 | System.out.print(rowSeparator);
615 | }
616 |
617 | System.out.println();
618 |
619 | /*
620 | Hopefully this should have printed something like this:
621 | +--------+------------+------------+-----------+--------+-------------+
622 | | EMP_NO | BIRTH_DATE | FIRST_NAME | LAST_NAME | GENDER | HIRE_DATE |
623 | +--------+------------+------------+-----------+--------+-------------+
624 | | 10001 | 1953-09-02 | Georgi | Facello | M | 1986-06-26 |
625 | +--------+------------+------------+-----------+--------+-------------+
626 | | 10002 | 1964-06-02 | Bezalel | Simmel | F | 1985-11-21 |
627 | +--------+------------+------------+-----------+--------+-------------+
628 | */
629 |
630 | } catch (SQLException e) {
631 | System.err.println("SQL exception in DBTablePrinter. Message:");
632 | System.err.println(e.getMessage());
633 | }
634 | }
635 |
636 | /**
637 | * Takes a generic SQL type and returns the category this type
638 | * belongs to. Types are categorized according to print formatting
639 | * needs:
640 | *
641 | * Integers should not be truncated so column widths should
642 | * be adjusted without a column width limit. Text columns should be
643 | * left justified and can be truncated to a max. column width etc...
644 | *
645 | * See also:
647 | * java.sql.Types
648 | *
649 | * @param type Generic SQL type
650 | * @return The category this type belongs to
651 | */
652 | private static int whichCategory(int type) {
653 | switch (type) {
654 | case Types.BIGINT:
655 | case Types.TINYINT:
656 | case Types.SMALLINT:
657 | case Types.INTEGER:
658 | return CATEGORY_INTEGER;
659 |
660 | case Types.REAL:
661 | case Types.DOUBLE:
662 | case Types.DECIMAL:
663 | return CATEGORY_DOUBLE;
664 |
665 | case Types.DATE:
666 | case Types.TIME:
667 | case Types.TIME_WITH_TIMEZONE:
668 | case Types.TIMESTAMP:
669 | case Types.TIMESTAMP_WITH_TIMEZONE:
670 | return CATEGORY_DATETIME;
671 |
672 | case Types.BOOLEAN:
673 | return CATEGORY_BOOLEAN;
674 |
675 | case Types.VARCHAR:
676 | case Types.NVARCHAR:
677 | case Types.LONGVARCHAR:
678 | case Types.LONGNVARCHAR:
679 | case Types.CHAR:
680 | case Types.NCHAR:
681 | return CATEGORY_STRING;
682 |
683 | default:
684 | return CATEGORY_OTHER;
685 | }
686 | }
687 | }
688 |
--------------------------------------------------------------------------------
/sf-jdbc-driver/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | salesforce-jdbc
8 | com.ascendix.salesforce
9 | 1.2-SNAPSHOT
10 |
11 | sf-jdbc-driver
12 |
13 |
14 |
15 |
16 | com.ascendix.salesforce
17 | sf-auth-client
18 |
19 |
20 | org.mule.tools
21 | salesforce-soql-parser
22 |
23 |
24 | com.force.api
25 | force-partner-api
26 |
27 |
28 | org.mapdb
29 | mapdb
30 |
31 |
32 | org.apache.commons
33 | commons-lang3
34 |
35 |
36 | org.antlr
37 | antlr
38 |
39 |
40 | org.apache.commons
41 | commons-collections4
42 |
43 |
44 |
45 |
46 | junit
47 | junit
48 | test
49 |
50 |
51 | com.opencsv
52 | opencsv
53 | test
54 |
55 |
56 | com.thoughtworks.xstream
57 | xstream
58 | test
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | single-jar
67 |
68 |
69 |
70 | org.apache.maven.plugins
71 | maven-assembly-plugin
72 |
73 |
74 | package
75 |
76 | single
77 |
78 |
79 |
80 |
81 |
82 | jar-with-dependencies
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/sf-jdbc-driver/src/main/java/com/ascendix/jdbc/salesforce/ForceDriver.java:
--------------------------------------------------------------------------------
1 | package com.ascendix.jdbc.salesforce;
2 |
3 | import com.ascendix.jdbc.salesforce.connection.ForceConnection;
4 | import com.ascendix.jdbc.salesforce.connection.ForceConnectionInfo;
5 | import com.ascendix.jdbc.salesforce.connection.ForceService;
6 | import com.sforce.soap.partner.PartnerConnection;
7 | import com.sforce.ws.ConnectionException;
8 | import lombok.extern.slf4j.Slf4j;
9 |
10 | import java.io.ByteArrayInputStream;
11 | import java.io.IOException;
12 | import java.io.InputStream;
13 | import java.nio.charset.StandardCharsets;
14 | import java.sql.Connection;
15 | import java.sql.Driver;
16 | import java.sql.DriverManager;
17 | import java.sql.DriverPropertyInfo;
18 | import java.sql.SQLException;
19 | import java.sql.SQLFeatureNotSupportedException;
20 | import java.util.Properties;
21 | import java.util.logging.Logger;
22 | import java.util.regex.Matcher;
23 | import java.util.regex.Pattern;
24 |
25 |
26 | @Slf4j
27 | public class ForceDriver implements Driver {
28 |
29 | private static final String ACCEPTABLE_URL = "jdbc:ascendix:salesforce";
30 | private static final Pattern URL_PATTERN = Pattern.compile("\\A" + ACCEPTABLE_URL + "://(.*)");
31 | private static final Pattern URL_HAS_AUTHORIZATION_SEGMENT = Pattern.compile("\\A" + ACCEPTABLE_URL + "://([^:]+):([^@]+)@([^?]*)([?](.*))?");
32 | private static final Pattern PARAM_STANDARD_PATTERN = Pattern.compile("(([^=]+)=([^&]*)&?)");
33 |
34 | static {
35 | try {
36 | DriverManager.registerDriver(new ForceDriver());
37 | } catch (Exception e) {
38 | throw new RuntimeException("Failed register ForceDriver: " + e.getMessage(), e);
39 | }
40 | }
41 |
42 | @Override
43 | public Connection connect(String url, Properties properties) throws SQLException {
44 | if (!acceptsURL(url)) {
45 | /*
46 | * According to JDBC spec:
47 | * > The driver should return "null" if it realizes it is the wrong kind of driver to connect to the given URL.
48 | * > This will be common, as when the JDBC driver manager is asked to connect to a given URL it passes the URL to each loaded driver in turn.
49 | *
50 | * Source: https://docs.oracle.com/javase/8/docs/api/java/sql/Driver.html#connect-java.lang.String-java.util.Properties-
51 | */
52 | return null;
53 | }
54 | try {
55 | Properties connStringProps = getConnStringProperties(url);
56 | properties.putAll(connStringProps);
57 | ForceConnectionInfo info = new ForceConnectionInfo();
58 | info.setUserName(properties.getProperty("user"));
59 | info.setClientName(properties.getProperty("client"));
60 | info.setPassword(properties.getProperty("password"));
61 | info.setSessionId(properties.getProperty("sessionId"));
62 | info.setSandbox(resolveSandboxProperty(properties));
63 | info.setHttps(resolveBooleanProperty(properties, "https", true));
64 | info.setApiVersion(resolveStringProperty(properties, "api", ForceService.DEFAULT_API_VERSION));
65 | info.setLoginDomain(resolveStringProperty(properties, "loginDomain", ForceService.DEFAULT_LOGIN_DOMAIN));
66 |
67 | PartnerConnection partnerConnection = ForceService.createPartnerConnection(info);
68 | return new ForceConnection(partnerConnection);
69 | } catch (ConnectionException | IOException e) {
70 | throw new SQLException(e);
71 | }
72 | }
73 |
74 | private static Boolean resolveSandboxProperty(Properties properties) {
75 | String sandbox = properties.getProperty("sandbox");
76 | if (sandbox != null) {
77 | return Boolean.valueOf(sandbox);
78 | }
79 | String loginDomain = properties.getProperty("loginDomain");
80 | if (loginDomain != null) {
81 | return loginDomain.contains("test");
82 | }
83 | return null;
84 | }
85 |
86 | private static Boolean resolveBooleanProperty(Properties properties, String propertyName, boolean defaultValue) {
87 | String boolValue = properties.getProperty(propertyName);
88 | if (boolValue != null) {
89 | return Boolean.valueOf(boolValue);
90 | }
91 | return defaultValue;
92 | }
93 |
94 | private static String resolveStringProperty(Properties properties, String propertyName, String defaultValue) {
95 | String boolValue = properties.getProperty(propertyName);
96 | if (boolValue != null) {
97 | return boolValue;
98 | }
99 | return defaultValue;
100 | }
101 |
102 |
103 | protected Properties getConnStringProperties(String urlString) throws IOException {
104 | Properties result = new Properties();
105 | String urlProperties = null;
106 |
107 | Matcher stdMatcher = URL_PATTERN.matcher(urlString);
108 | Matcher authMatcher = URL_HAS_AUTHORIZATION_SEGMENT.matcher(urlString);
109 |
110 | if (authMatcher.matches()) {
111 | result.put("user", authMatcher.group(1));
112 | result.put("password", authMatcher.group(2));
113 | result.put("loginDomain", authMatcher.group(3));
114 | if (authMatcher.groupCount() > 4 && authMatcher.group(5) != null) {
115 | // has some other parameters - parse them from standard URL format like
116 | // ?param1=value1¶m2=value2
117 | String parameters = authMatcher.group(5);
118 | Matcher matcher = PARAM_STANDARD_PATTERN.matcher(parameters);
119 | while(matcher.find()) {
120 | String param = matcher.group(2);
121 | String value = 3 >= matcher.groupCount() ? matcher.group(3) : null;
122 | result.put(param, value);
123 | }
124 |
125 | }
126 | } else if (stdMatcher.matches()) {
127 | urlProperties = stdMatcher.group(1);
128 | urlProperties = urlProperties.replaceAll(";", "\n");
129 | }
130 |
131 | if (urlProperties != null) {
132 | try (InputStream in = new ByteArrayInputStream(urlProperties.getBytes(StandardCharsets.UTF_8))) {
133 | result.load(in);
134 | }
135 | }
136 |
137 | return result;
138 | }
139 |
140 | @Override
141 | public boolean acceptsURL(String url) {
142 | return url != null && url.startsWith(ACCEPTABLE_URL);
143 | }
144 |
145 | @Override
146 | public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) {
147 | return new DriverPropertyInfo[]{};
148 | }
149 |
150 | @Override
151 | public int getMajorVersion() {
152 | return 1;
153 | }
154 |
155 | @Override
156 | public int getMinorVersion() {
157 | return 1;
158 | }
159 |
160 | @Override
161 | public boolean jdbcCompliant() {
162 | return false;
163 | }
164 |
165 | @Override
166 | public Logger getParentLogger() throws SQLFeatureNotSupportedException {
167 | throw new SQLFeatureNotSupportedException();
168 | }
169 |
170 | }
171 |
--------------------------------------------------------------------------------
/sf-jdbc-driver/src/main/java/com/ascendix/jdbc/salesforce/connection/ForceConnection.java:
--------------------------------------------------------------------------------
1 | package com.ascendix.jdbc.salesforce.connection;
2 |
3 | import com.ascendix.jdbc.salesforce.statement.ForcePreparedStatement;
4 | import com.ascendix.jdbc.salesforce.metadata.ForceDatabaseMetaData;
5 | import com.sforce.soap.partner.PartnerConnection;
6 |
7 | import java.sql.Array;
8 | import java.sql.Blob;
9 | import java.sql.CallableStatement;
10 | import java.sql.Clob;
11 | import java.sql.Connection;
12 | import java.sql.DatabaseMetaData;
13 | import java.sql.NClob;
14 | import java.sql.PreparedStatement;
15 | import java.sql.SQLClientInfoException;
16 | import java.sql.SQLException;
17 | import java.sql.SQLWarning;
18 | import java.sql.SQLXML;
19 | import java.sql.Savepoint;
20 | import java.sql.Statement;
21 | import java.sql.Struct;
22 | import java.util.HashMap;
23 | import java.util.Map;
24 | import java.util.Properties;
25 | import java.util.concurrent.Executor;
26 | import java.util.logging.Logger;
27 |
28 | public class ForceConnection implements Connection {
29 |
30 | private final PartnerConnection partnerConnection;
31 | private final DatabaseMetaData metadata;
32 | private static final String SF_JDBC_DRIVER_NAME = "SF JDBC driver";
33 | private static final Logger logger = Logger.getLogger(SF_JDBC_DRIVER_NAME);
34 |
35 | private Map connectionCache = new HashMap<>();
36 | Properties clientInfo = new Properties();
37 |
38 | public ForceConnection(PartnerConnection partnerConnection) {
39 | this.partnerConnection = partnerConnection;
40 | this.metadata = new ForceDatabaseMetaData(this);
41 | }
42 |
43 | public PartnerConnection getPartnerConnection() {
44 | return partnerConnection;
45 | }
46 |
47 | public DatabaseMetaData getMetaData() {
48 | return metadata;
49 | }
50 |
51 | @Override
52 | public PreparedStatement prepareStatement(String soql) {
53 | return new ForcePreparedStatement(this, soql);
54 | }
55 |
56 | @Override
57 | public String getSchema() {
58 | return "Salesforce";
59 | }
60 |
61 | public Map getCache() {
62 | return connectionCache;
63 | }
64 |
65 | @Override
66 | public T unwrap(Class iface) {
67 | // TODO Auto-generated method stub
68 | return null;
69 | }
70 |
71 | @Override
72 | public boolean isWrapperFor(Class> iface) {
73 | // TODO Auto-generated method stub
74 | return false;
75 | }
76 |
77 | @Override
78 | public Statement createStatement() {
79 | logger.info("[Conn] createStatement 1 IMPLEMENTED ");
80 | return null;
81 | }
82 |
83 | @Override
84 | public CallableStatement prepareCall(String sql) {
85 | logger.info("[Conn] prepareCall NOT_IMPLEMENTED "+sql);
86 | return null;
87 | }
88 |
89 | @Override
90 | public String nativeSQL(String sql) {
91 | logger.info("[Conn] nativeSQL NOT_IMPLEMENTED "+sql);
92 | return null;
93 | }
94 |
95 | @Override
96 | public void setAutoCommit(boolean autoCommit) {
97 | // TODO Auto-generated method stub
98 |
99 | }
100 |
101 | @Override
102 | public boolean getAutoCommit() throws SQLException {
103 | // TODO Auto-generated method stub
104 | return false;
105 | }
106 |
107 | @Override
108 | public void commit() throws SQLException {
109 | // TODO Auto-generated method stub
110 |
111 | }
112 |
113 | @Override
114 | public void rollback() throws SQLException {
115 | // TODO Auto-generated method stub
116 |
117 | }
118 |
119 | @Override
120 | public void close() throws SQLException {
121 | // TODO Auto-generated method stub
122 |
123 | }
124 |
125 | @Override
126 | public boolean isClosed() throws SQLException {
127 | // TODO Auto-generated method stub
128 | return false;
129 | }
130 |
131 | @Override
132 | public void setReadOnly(boolean readOnly) throws SQLException {
133 | // TODO Auto-generated method stub
134 |
135 | }
136 |
137 | @Override
138 | public boolean isReadOnly() throws SQLException {
139 | // TODO Auto-generated method stub
140 | return false;
141 | }
142 |
143 | @Override
144 | public void setCatalog(String catalog) throws SQLException {
145 | // TODO Auto-generated method stub
146 |
147 | }
148 |
149 | @Override
150 | public String getCatalog() throws SQLException {
151 | // TODO Auto-generated method stub
152 | return null;
153 | }
154 |
155 | @Override
156 | public void setTransactionIsolation(int level) throws SQLException {
157 | // TODO Auto-generated method stub
158 |
159 | }
160 |
161 | @Override
162 | public int getTransactionIsolation() throws SQLException {
163 | // TODO Auto-generated method stub
164 | return 0;
165 | }
166 |
167 | @Override
168 | public SQLWarning getWarnings() throws SQLException {
169 | // TODO Auto-generated method stub
170 | return null;
171 | }
172 |
173 | @Override
174 | public void clearWarnings() throws SQLException {
175 | // TODO Auto-generated method stub
176 |
177 | }
178 |
179 | @Override
180 | public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
181 | Logger.getLogger(SF_JDBC_DRIVER_NAME).info(Object.class.getEnclosingMethod().getName());
182 | return null;
183 | }
184 |
185 | @Override
186 | public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
187 | throws SQLException {
188 | Logger.getLogger(SF_JDBC_DRIVER_NAME).info(Object.class.getEnclosingMethod().getName());
189 | return null;
190 | }
191 |
192 | @Override
193 | public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
194 | Logger.getLogger(SF_JDBC_DRIVER_NAME).info(Object.class.getEnclosingMethod().getName());
195 | return null;
196 | }
197 |
198 | @Override
199 | public Map> getTypeMap() throws SQLException {
200 | Logger.getLogger(SF_JDBC_DRIVER_NAME).info(Object.class.getEnclosingMethod().getName());
201 | return null;
202 | }
203 |
204 | @Override
205 | public void setTypeMap(Map> map) throws SQLException {
206 | // TODO Auto-generated method stub
207 |
208 | }
209 |
210 | @Override
211 | public void setHoldability(int holdability) throws SQLException {
212 | // TODO Auto-generated method stub
213 |
214 | }
215 |
216 | @Override
217 | public int getHoldability() throws SQLException {
218 | // TODO Auto-generated method stub
219 | return 0;
220 | }
221 |
222 | @Override
223 | public Savepoint setSavepoint() throws SQLException {
224 | // TODO Auto-generated method stub
225 | return null;
226 | }
227 |
228 | @Override
229 | public Savepoint setSavepoint(String name) throws SQLException {
230 | // TODO Auto-generated method stub
231 | return null;
232 | }
233 |
234 | @Override
235 | public void rollback(Savepoint savepoint) throws SQLException {
236 | // TODO Auto-generated method stub
237 |
238 | }
239 |
240 | @Override
241 | public void releaseSavepoint(Savepoint savepoint) throws SQLException {
242 | // TODO Auto-generated method stub
243 |
244 | }
245 |
246 | @Override
247 | public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
248 | throws SQLException {
249 | Logger.getLogger(SF_JDBC_DRIVER_NAME).info(Object.class.getEnclosingMethod().getName());
250 | return null;
251 | }
252 |
253 | @Override
254 | public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency,
255 | int resultSetHoldability) throws SQLException {
256 | Logger.getLogger(SF_JDBC_DRIVER_NAME).info(Object.class.getEnclosingMethod().getName());
257 | return null;
258 | }
259 |
260 | @Override
261 | public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency,
262 | int resultSetHoldability) throws SQLException {
263 | Logger.getLogger(SF_JDBC_DRIVER_NAME).info(Object.class.getEnclosingMethod().getName());
264 | return null;
265 | }
266 |
267 | @Override
268 | public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
269 | Logger.getLogger(SF_JDBC_DRIVER_NAME).info(Object.class.getEnclosingMethod().getName());
270 | return null;
271 | }
272 |
273 | @Override
274 | public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
275 | Logger.getLogger(SF_JDBC_DRIVER_NAME).info(Object.class.getEnclosingMethod().getName());
276 | return null;
277 | }
278 |
279 | @Override
280 | public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
281 | Logger.getLogger(SF_JDBC_DRIVER_NAME).info(Object.class.getEnclosingMethod().getName());
282 | return null;
283 | }
284 |
285 | @Override
286 | public Clob createClob() throws SQLException {
287 | // TODO Auto-generated method stub
288 | return null;
289 | }
290 |
291 | @Override
292 | public Blob createBlob() throws SQLException {
293 | // TODO Auto-generated method stub
294 | return null;
295 | }
296 |
297 | @Override
298 | public NClob createNClob() throws SQLException {
299 | // TODO Auto-generated method stub
300 | return null;
301 | }
302 |
303 | @Override
304 | public SQLXML createSQLXML() throws SQLException {
305 | // TODO Auto-generated method stub
306 | return null;
307 | }
308 |
309 | @Override
310 | public boolean isValid(int timeout) throws SQLException {
311 | // TODO Auto-generated method stub
312 | return false;
313 | }
314 |
315 | @Override
316 | public void setClientInfo(String name, String value) throws SQLClientInfoException {
317 | // TODO Auto-generated method stub
318 | logger.info("[Conn] setClientInfo 1 IMPLEMENTED "+name+"="+value);
319 | clientInfo.setProperty(name, value);
320 | }
321 |
322 | @Override
323 | public void setClientInfo(Properties properties) throws SQLClientInfoException {
324 | logger.info("[Conn] setClientInfo 2 IMPLEMENTED properties<>");
325 | properties.stringPropertyNames().forEach(propName -> clientInfo.setProperty(propName, properties.getProperty(propName)));
326 | }
327 |
328 | @Override
329 | public String getClientInfo(String name) throws SQLException {
330 | logger.info("[Conn] getClientInfo 1 IMPLEMENTED for '"+name+"'");
331 | return clientInfo.getProperty(name);
332 | }
333 |
334 | @Override
335 | public Properties getClientInfo() throws SQLException {
336 | logger.info("[Conn] getClientInfo 2 IMPLEMENTED ");
337 | return clientInfo;
338 | }
339 |
340 | @Override
341 | public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
342 | // TODO Auto-generated method stub
343 | return null;
344 | }
345 |
346 | @Override
347 | public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
348 | // TODO Auto-generated method stub
349 | return null;
350 | }
351 |
352 | @Override
353 | public void setSchema(String schema) throws SQLException {
354 | // TODO Auto-generated method stub
355 |
356 | }
357 |
358 | @Override
359 | public void abort(Executor executor) throws SQLException {
360 | // TODO Auto-generated method stub
361 |
362 | }
363 |
364 | @Override
365 | public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
366 | // TODO Auto-generated method stub
367 |
368 | }
369 |
370 | @Override
371 | public int getNetworkTimeout() throws SQLException {
372 | // TODO Auto-generated method stub
373 | return 0;
374 | }
375 | }
376 |
--------------------------------------------------------------------------------
/sf-jdbc-driver/src/main/java/com/ascendix/jdbc/salesforce/connection/ForceConnectionInfo.java:
--------------------------------------------------------------------------------
1 | package com.ascendix.jdbc.salesforce.connection;
2 |
3 | import lombok.Data;
4 | import lombok.NoArgsConstructor;
5 |
6 | @Data
7 | @NoArgsConstructor
8 | public class ForceConnectionInfo {
9 |
10 | private String userName;
11 | private String password;
12 | private String sessionId;
13 | private Boolean sandbox;
14 | private Boolean https = true;
15 | private String apiVersion = ForceService.DEFAULT_API_VERSION;
16 | private String loginDomain;
17 | private String clientName;
18 | }
19 |
--------------------------------------------------------------------------------
/sf-jdbc-driver/src/main/java/com/ascendix/jdbc/salesforce/connection/ForceService.java:
--------------------------------------------------------------------------------
1 | package com.ascendix.jdbc.salesforce.connection;
2 |
3 | import com.ascendix.salesforce.oauth.ForceOAuthClient;
4 | import com.sforce.soap.partner.Connector;
5 | import com.sforce.soap.partner.PartnerConnection;
6 | import com.sforce.ws.ConnectionException;
7 | import com.sforce.ws.ConnectorConfig;
8 | import lombok.experimental.UtilityClass;
9 | import lombok.extern.slf4j.Slf4j;
10 | import org.apache.commons.io.FileUtils;
11 | import org.apache.commons.lang3.StringUtils;
12 | import org.mapdb.DB;
13 | import org.mapdb.DBMaker;
14 | import org.mapdb.HTreeMap;
15 | import org.mapdb.Serializer;
16 |
17 | import java.util.concurrent.TimeUnit;
18 |
19 | @UtilityClass
20 | @Slf4j
21 | public class ForceService {
22 |
23 | public static final String DEFAULT_LOGIN_DOMAIN = "login.salesforce.com";
24 | private static final String SANDBOX_LOGIN_DOMAIN = "test.salesforce.com";
25 | private static final long CONNECTION_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
26 | private static final long READ_TIMEOUT = TimeUnit.SECONDS.toMillis(30);
27 | public static final String DEFAULT_API_VERSION = "50.0";
28 | public static final int EXPIRE_AFTER_CREATE = 60;
29 | public static final int EXPIRE_STORE_SIZE = 16;
30 |
31 |
32 | private static final DB cacheDb = DBMaker.tempFileDB().closeOnJvmShutdown().make();
33 |
34 | private static HTreeMap partnerUrlCache = cacheDb
35 | .hashMap("PartnerUrlCache", Serializer.STRING, Serializer.STRING)
36 | .expireAfterCreate(EXPIRE_AFTER_CREATE, TimeUnit.MINUTES)
37 | .expireStoreSize(EXPIRE_STORE_SIZE * FileUtils.ONE_MB)
38 | .create();
39 |
40 |
41 | private static String getPartnerUrl(String accessToken, boolean sandbox) {
42 | return partnerUrlCache.computeIfAbsent(accessToken, s -> getPartnerUrlFromUserInfo(accessToken, sandbox));
43 | }
44 |
45 | private static String getPartnerUrlFromUserInfo(String accessToken, boolean sandbox) {
46 | return new ForceOAuthClient(CONNECTION_TIMEOUT, READ_TIMEOUT).getUserInfo(accessToken, sandbox).getPartnerUrl();
47 | }
48 |
49 | public static PartnerConnection createPartnerConnection(ForceConnectionInfo info) throws ConnectionException {
50 | return info.getSessionId() != null ? createConnectionBySessionId(info) : createConnectionByUserCredential(info);
51 | }
52 |
53 | private static PartnerConnection createConnectionBySessionId(ForceConnectionInfo info) throws ConnectionException {
54 | ConnectorConfig partnerConfig = new ConnectorConfig();
55 | partnerConfig.setSessionId(info.getSessionId());
56 |
57 | if (info.getSandbox() != null) {
58 | partnerConfig.setServiceEndpoint(ForceService.getPartnerUrl(info.getSessionId(), info.getSandbox()));
59 | return Connector.newConnection(partnerConfig);
60 | }
61 |
62 | try {
63 | partnerConfig.setServiceEndpoint(ForceService.getPartnerUrl(info.getSessionId(), false));
64 | return Connector.newConnection(partnerConfig);
65 | } catch (RuntimeException re) {
66 | try {
67 | partnerConfig.setServiceEndpoint(ForceService.getPartnerUrl(info.getSessionId(), true));
68 | return Connector.newConnection(partnerConfig);
69 | } catch (RuntimeException r) {
70 | throw new ConnectionException(r.getMessage());
71 | }
72 | }
73 | }
74 |
75 | private static PartnerConnection createConnectionByUserCredential(ForceConnectionInfo info)
76 | throws ConnectionException {
77 | ConnectorConfig partnerConfig = new ConnectorConfig();
78 | partnerConfig.setUsername(info.getUserName());
79 | partnerConfig.setPassword(info.getPassword());
80 |
81 | PartnerConnection connection;
82 |
83 | if (info.getSandbox() != null) {
84 | partnerConfig.setAuthEndpoint(buildAuthEndpoint(info));
85 | connection = Connector.newConnection(partnerConfig);
86 | } else {
87 | try {
88 | info.setSandbox(false);
89 | partnerConfig.setAuthEndpoint(buildAuthEndpoint(info));
90 | connection = Connector.newConnection(partnerConfig);
91 | } catch (ConnectionException ce) {
92 | info.setSandbox(true);
93 | partnerConfig.setAuthEndpoint(buildAuthEndpoint(info));
94 | connection = Connector.newConnection(partnerConfig);
95 | }
96 | }
97 | if (connection != null && StringUtils.isNotBlank(info.getClientName())) {
98 | connection.setCallOptions(info.getClientName(), null);
99 | }
100 | return connection;
101 | }
102 |
103 | private static String buildAuthEndpoint(ForceConnectionInfo info) {
104 | String protocol = info.getHttps() ? "https" : "http";
105 | String domain = info.getSandbox() ? SANDBOX_LOGIN_DOMAIN : info.getLoginDomain() != null ? info.getLoginDomain() : DEFAULT_LOGIN_DOMAIN;
106 | return String.format("%s://%s/services/Soap/u/%s", protocol, domain, info.getApiVersion());
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/sf-jdbc-driver/src/main/java/com/ascendix/jdbc/salesforce/delegates/ForceResultField.java:
--------------------------------------------------------------------------------
1 | package com.ascendix.jdbc.salesforce.delegates;
2 |
3 | public class ForceResultField {
4 |
5 | public static final String NESTED_RESULT_SET_FIELD_TYPE = "nestedResultSet";
6 |
7 | private String entityType;
8 | private String name;
9 | private Object value;
10 | private String fieldType;
11 |
12 | public ForceResultField(String entityType, String fieldType, String name, Object value) {
13 |
14 | super();
15 | this.entityType = entityType;
16 | this.name = name;
17 | this.value = value;
18 | this.fieldType = fieldType;
19 | }
20 |
21 | public String getEntityType() {
22 | return entityType;
23 | }
24 |
25 | public String getName() {
26 | return name;
27 | }
28 |
29 | public Object getValue() {
30 | return value;
31 | }
32 |
33 | public String getFullName() {
34 | return entityType != null ? entityType + "." + name : name;
35 | }
36 |
37 | @Override
38 | public String toString() {
39 | return "SfResultField [entityType=" + entityType + ", name=" + name + ", value=" + value + "]";
40 | }
41 |
42 | @Override
43 | public int hashCode() {
44 | final int prime = 31;
45 | int result = 1;
46 | result = prime * result + ((entityType == null) ? 0 : entityType.hashCode());
47 | result = prime * result + ((name == null) ? 0 : name.hashCode());
48 | result = prime * result + ((value == null) ? 0 : value.hashCode());
49 | return result;
50 | }
51 |
52 | @Override
53 | public boolean equals(Object obj) {
54 | if (this == obj)
55 | return true;
56 | if (obj == null)
57 | return false;
58 | if (getClass() != obj.getClass())
59 | return false;
60 | ForceResultField other = (ForceResultField) obj;
61 | if (entityType == null) {
62 | if (other.entityType != null)
63 | return false;
64 | } else if (!entityType.equals(other.entityType))
65 | return false;
66 | if (name == null) {
67 | if (other.name != null)
68 | return false;
69 | } else if (!name.equals(other.name))
70 | return false;
71 | if (value == null) {
72 | if (other.value != null)
73 | return false;
74 | } else if (!value.equals(other.value))
75 | return false;
76 | return true;
77 | }
78 |
79 | public String getFieldType() {
80 | return fieldType;
81 | }
82 |
83 | public void setValue(Object value) {
84 | this.value = value;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/sf-jdbc-driver/src/main/java/com/ascendix/jdbc/salesforce/delegates/PartnerResultToCrtesianTable.java:
--------------------------------------------------------------------------------
1 | package com.ascendix.jdbc.salesforce.delegates;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Arrays;
5 | import java.util.Collection;
6 | import java.util.Collections;
7 | import java.util.List;
8 | import java.util.stream.Collectors;
9 |
10 | @SuppressWarnings({"rawtypes", "unchecked"})
11 | public class PartnerResultToCrtesianTable {
12 |
13 | private List schema;
14 |
15 | private PartnerResultToCrtesianTable(List schema) {
16 | this.schema = schema;
17 | }
18 |
19 | public static List expand(List list, List schema) {
20 | PartnerResultToCrtesianTable expander = new PartnerResultToCrtesianTable(schema);
21 | return expander.expandOn(list, 0, 0);
22 | }
23 |
24 | private List expandOn(List rows, int columnPosition, int schemaPosititon) {
25 | return rows.stream()
26 | .map(row -> expandRow(row, columnPosition, schemaPosititon))
27 | .flatMap(Collection::stream)
28 | .collect(Collectors.toList());
29 | }
30 |
31 | private List expandRow(List row, int columnPosition, int schemaPosititon) {
32 | List result = new ArrayList<>();
33 | if (schemaPosititon > schema.size() - 1) {
34 | result.add(row);
35 | return result;
36 | } else if (schema.get(schemaPosititon) instanceof List) {
37 | int nestedListSize = ((List) schema.get(schemaPosititon)).size();
38 | Object value = row.get(columnPosition);
39 | List nestedList = value instanceof List ? (List) value : Collections.emptyList();
40 | if (nestedList.isEmpty()) {
41 | result.add(expandRow(row, Collections.nCopies(nestedListSize, null), columnPosition));
42 | } else {
43 | nestedList.forEach(item -> result.add(expandRow(row, item, columnPosition)));
44 | }
45 | return expandOn(result, columnPosition + nestedListSize, schemaPosititon + 1);
46 | } else {
47 | result.add(row);
48 | return expandOn(result, columnPosition + 1, schemaPosititon + 1);
49 | }
50 |
51 | }
52 |
53 | private static List expandRow(List row, Object nestedItem, int position) {
54 | List nestedItemsToInsert = nestedItem instanceof List ? (List) nestedItem : Arrays.asList(nestedItem);
55 | List newRow = new ArrayList<>(row.subList(0, position));
56 | newRow.addAll(nestedItemsToInsert);
57 | newRow.addAll(row.subList(position + 1, row.size()));
58 | return newRow;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/sf-jdbc-driver/src/main/java/com/ascendix/jdbc/salesforce/delegates/PartnerService.java:
--------------------------------------------------------------------------------
1 | package com.ascendix.jdbc.salesforce.delegates;
2 |
3 | import com.ascendix.jdbc.salesforce.metadata.Column;
4 | import com.ascendix.jdbc.salesforce.metadata.Table;
5 | import com.ascendix.jdbc.salesforce.statement.FieldDef;
6 | import com.sforce.soap.partner.DescribeGlobalResult;
7 | import com.sforce.soap.partner.DescribeGlobalSObjectResult;
8 | import com.sforce.soap.partner.DescribeSObjectResult;
9 | import com.sforce.soap.partner.Field;
10 | import com.sforce.soap.partner.PartnerConnection;
11 | import com.sforce.soap.partner.QueryResult;
12 | import com.sforce.ws.ConnectionException;
13 | import com.sforce.ws.bind.XmlObject;
14 | import org.apache.commons.collections4.IteratorUtils;
15 |
16 | import java.util.ArrayList;
17 | import java.util.Arrays;
18 | import java.util.Collections;
19 | import java.util.Iterator;
20 | import java.util.LinkedList;
21 | import java.util.List;
22 | import java.util.stream.Collectors;
23 |
24 | public class PartnerService {
25 |
26 | private PartnerConnection partnerConnection;
27 | private List sObjectTypesCache;
28 |
29 | public PartnerService(PartnerConnection partnerConnection) {
30 | this.partnerConnection = partnerConnection;
31 | }
32 |
33 | public List getTables() {
34 | List sObjects = getSObjectsDescription();
35 | return sObjects.stream()
36 | .map(this::convertToTable)
37 | .collect(Collectors.toList());
38 | }
39 |
40 | public DescribeSObjectResult describeSObject(String sObjectType) throws ConnectionException {
41 | return partnerConnection.describeSObject(sObjectType);
42 | }
43 |
44 | private Table convertToTable(DescribeSObjectResult so) {
45 | List fields = Arrays.asList(so.getFields());
46 | List columns = fields.stream()
47 | .map(this::convertToColumn)
48 | .collect(Collectors.toList());
49 | return new Table(so.getName(), null, columns);
50 | }
51 |
52 | private Column convertToColumn(Field field) {
53 | try {
54 | Column column = new Column(field.getName(), getType(field));
55 | column.setNillable(false);
56 | column.setCalculated(field.isCalculated() || field.isAutoNumber());
57 | String[] referenceTos = field.getReferenceTo();
58 | if (referenceTos != null) {
59 | for (String referenceTo : referenceTos) {
60 | if (getSObjectTypes().contains(referenceTo)) {
61 | column.setReferencedTable(referenceTo);
62 | column.setReferencedColumn("Id");
63 | }
64 | }
65 | }
66 | return column;
67 | } catch (ConnectionException e) {
68 | throw new RuntimeException(e);
69 | }
70 | }
71 |
72 | private String getType(Field field) {
73 | String s = field.getType().toString();
74 | if (s.startsWith("_")) {
75 | s = s.substring("_".length());
76 | }
77 | return s.equalsIgnoreCase("double") ? "decimal" : s;
78 | }
79 |
80 | private List getSObjectTypes() throws ConnectionException {
81 | if (sObjectTypesCache == null) {
82 | DescribeGlobalSObjectResult[] sobs = partnerConnection.describeGlobal().getSobjects();
83 | sObjectTypesCache = Arrays.stream(sobs)
84 | .map(DescribeGlobalSObjectResult::getName)
85 | .collect(Collectors.toList());
86 | }
87 | return sObjectTypesCache;
88 |
89 | }
90 |
91 | private List getSObjectsDescription() {
92 | DescribeGlobalResult describeGlobals = describeGlobal();
93 | List tableNames = Arrays.stream(describeGlobals.getSobjects())
94 | .map(DescribeGlobalSObjectResult::getName)
95 | .collect(Collectors.toList());
96 | List> tableNamesBatched = toBatches(tableNames, 100);
97 | return tableNamesBatched.stream()
98 | .flatMap(batch -> describeSObjects(batch).stream())
99 | .collect(Collectors.toList());
100 | }
101 |
102 | private DescribeGlobalResult describeGlobal() {
103 | try {
104 | return partnerConnection.describeGlobal();
105 | } catch (ConnectionException e) {
106 | throw new RuntimeException(e);
107 | }
108 | }
109 |
110 | private List describeSObjects(List batch) {
111 | DescribeSObjectResult[] result;
112 | try {
113 | result = partnerConnection.describeSObjects(batch.toArray(new String[0]));
114 | return Arrays.asList(result);
115 | } catch (ConnectionException e) {
116 | throw new RuntimeException(e);
117 | }
118 | }
119 |
120 | private List> toBatches(List objects, int batchSize) {
121 | List> result = new ArrayList<>();
122 | for (int fromIndex = 0; fromIndex < objects.size(); fromIndex += batchSize) {
123 | int toIndex = Math.min(fromIndex + batchSize, objects.size());
124 | result.add(objects.subList(fromIndex, toIndex));
125 | }
126 | return result;
127 | }
128 |
129 | public List query(String soql, List expectedSchema) throws ConnectionException {
130 | List resultRows = Collections.synchronizedList(new LinkedList<>());
131 | QueryResult queryResult = null;
132 | do {
133 | queryResult = queryResult == null ? partnerConnection.query(soql)
134 | : partnerConnection.queryMore(queryResult.getQueryLocator());
135 | resultRows.addAll(removeServiceInfo(Arrays.asList(queryResult.getRecords())));
136 | } while (!queryResult.isDone());
137 |
138 | return PartnerResultToCrtesianTable.expand(resultRows, expectedSchema);
139 | }
140 |
141 | private List removeServiceInfo(Iterator rows) {
142 | return removeServiceInfo(IteratorUtils.toList(rows));
143 | }
144 |
145 | private List removeServiceInfo(List rows) {
146 | return rows.stream()
147 | .filter(this::isDataObjectType)
148 | .map(this::removeServiceInfo)
149 | .collect(Collectors.toList());
150 | }
151 |
152 | private List removeServiceInfo(XmlObject row) {
153 | return IteratorUtils.toList(row.getChildren()).stream()
154 | .filter(this::isDataObjectType)
155 | .skip(1) // Removes duplicate Id from SF Partner API response
156 | // (https://developer.salesforce.com/forums/?id=906F00000008kciIAA)
157 | .map(field -> isNestedResultset(field)
158 | ? removeServiceInfo(field.getChildren())
159 | : toForceResultField(field))
160 | .collect(Collectors.toList());
161 | }
162 |
163 | private ForceResultField toForceResultField(XmlObject field) {
164 | String fieldType = field.getXmlType() != null ? field.getXmlType().getLocalPart() : null;
165 | if ("sObject".equalsIgnoreCase(fieldType)) {
166 | List children = new ArrayList<>();
167 | field.getChildren().forEachRemaining(children::add);
168 | field = children.get(2);
169 | }
170 | String name = field.getName().getLocalPart();
171 | Object value = field.getValue();
172 | return new ForceResultField(null, fieldType, name, value);
173 | }
174 |
175 | private boolean isNestedResultset(XmlObject object) {
176 | return object.getXmlType() != null && "QueryResult".equals(object.getXmlType().getLocalPart());
177 | }
178 |
179 | private final static List SOAP_RESPONSE_SERVICE_OBJECT_TYPES = Arrays.asList("type", "done", "queryLocator",
180 | "size");
181 |
182 | private boolean isDataObjectType(XmlObject object) {
183 | return !SOAP_RESPONSE_SERVICE_OBJECT_TYPES.contains(object.getName().getLocalPart());
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/sf-jdbc-driver/src/main/java/com/ascendix/jdbc/salesforce/metadata/Column.java:
--------------------------------------------------------------------------------
1 | package com.ascendix.jdbc.salesforce.metadata;
2 |
3 | import java.io.Serializable;
4 |
5 | public class Column implements Serializable {
6 |
7 | private Table table;
8 | private String name;
9 | private String type;
10 | private String referencedTable;
11 | private String referencedColumn;
12 |
13 | private Integer length;
14 | private boolean nillable;
15 | private String comments;
16 | private boolean calculated;
17 |
18 | public Column(String name, String type) {
19 | this.name = name;
20 | this.type = type;
21 | }
22 |
23 | public Table getTable() {
24 | return table;
25 | }
26 |
27 | public void setTable(Table table) {
28 | this.table = table;
29 | }
30 |
31 | public String getName() {
32 | return name;
33 | }
34 |
35 | public String getType() {
36 | return type;
37 | }
38 |
39 | public String getReferencedTable() {
40 | return referencedTable;
41 | }
42 |
43 | public String getReferencedColumn() {
44 | return referencedColumn;
45 | }
46 |
47 | public Integer getLength() {
48 | return length;
49 | }
50 |
51 | public void setLength(Integer length) {
52 | this.length = length;
53 | }
54 |
55 | public boolean isNillable() {
56 | return nillable;
57 | }
58 |
59 | public void setNillable(boolean nillable) {
60 | this.nillable = nillable;
61 | }
62 |
63 | public void setReferencedTable(String referencedTable) {
64 | this.referencedTable = referencedTable;
65 | }
66 |
67 | public void setReferencedColumn(String referencedColumn) {
68 | this.referencedColumn = referencedColumn;
69 | }
70 |
71 | public String getComments() {
72 | return comments;
73 | }
74 |
75 | public void setComments(String comments) {
76 | this.comments = comments;
77 | }
78 |
79 | public boolean isCalculated() {
80 | return calculated;
81 | }
82 |
83 | public void setCalculated(boolean calculated) {
84 | this.calculated = calculated;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/sf-jdbc-driver/src/main/java/com/ascendix/jdbc/salesforce/metadata/ColumnMap.java:
--------------------------------------------------------------------------------
1 | package com.ascendix.jdbc.salesforce.metadata;
2 |
3 | import java.io.Serializable;
4 | import java.util.ArrayList;
5 |
6 | public class ColumnMap implements Serializable {
7 |
8 | private static final long serialVersionUID = 2705233366870541749L;
9 |
10 | private ArrayList columnNames = new ArrayList<>();
11 | private ArrayList values = new ArrayList<>();
12 | private int columnPosition = 0;
13 |
14 | public V put(K key, V value) {
15 | columnNames.add(columnPosition, key);
16 | values.add(columnPosition, value);
17 | columnPosition++;
18 | return value;
19 | }
20 |
21 | public V get(K key) {
22 | int index = columnNames.indexOf(key);
23 | return index != -1 ? values.get(index) : null;
24 | }
25 |
26 | /**
27 | * Get a column name by index, starting at 1, that represents the insertion
28 | * order into the map.
29 | */
30 | public V getByIndex(int index) {
31 | return values.get(index - 1);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/sf-jdbc-driver/src/main/java/com/ascendix/jdbc/salesforce/metadata/Table.java:
--------------------------------------------------------------------------------
1 | package com.ascendix.jdbc.salesforce.metadata;
2 |
3 | import java.io.Serializable;
4 | import java.util.List;
5 |
6 | public class Table implements Serializable {
7 |
8 | private String name;
9 | private String comments;
10 | private List columns;
11 |
12 | public Table(String name, String comments, List columns) {
13 | this.name = name;
14 | this.comments = comments;
15 | this.columns = columns;
16 | for (Column c : columns) {
17 | c.setTable(this);
18 | }
19 | }
20 |
21 | public String getName() {
22 | return name;
23 | }
24 |
25 | public String getComments() {
26 | return comments;
27 | }
28 |
29 | public List getColumns() {
30 | return columns;
31 | }
32 |
33 | public Column findColumn(String columnName) {
34 | return columns.stream()
35 | .filter(column -> columnName.equals(column.getName()))
36 | .findFirst()
37 | .orElse(null);
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/sf-jdbc-driver/src/main/java/com/ascendix/jdbc/salesforce/resultset/CachedResultSet.java:
--------------------------------------------------------------------------------
1 | package com.ascendix.jdbc.salesforce.resultset;
2 |
3 | import com.ascendix.jdbc.salesforce.metadata.ColumnMap;
4 |
5 | import javax.sql.rowset.serial.SerialBlob;
6 | import java.io.InputStream;
7 | import java.io.Reader;
8 | import java.io.Serializable;
9 | import java.math.BigDecimal;
10 | import java.net.URL;
11 | import java.sql.Array;
12 | import java.sql.Blob;
13 | import java.sql.Clob;
14 | import java.sql.Date;
15 | import java.sql.NClob;
16 | import java.sql.Ref;
17 | import java.sql.ResultSet;
18 | import java.sql.ResultSetMetaData;
19 | import java.sql.RowId;
20 | import java.sql.SQLException;
21 | import java.sql.SQLWarning;
22 | import java.sql.SQLXML;
23 | import java.sql.Statement;
24 | import java.sql.Time;
25 | import java.sql.Timestamp;
26 | import java.text.ParseException;
27 | import java.text.SimpleDateFormat;
28 | import java.util.Arrays;
29 | import java.util.Base64;
30 | import java.util.Calendar;
31 | import java.util.GregorianCalendar;
32 | import java.util.List;
33 | import java.util.Map;
34 | import java.util.Optional;
35 | import java.util.function.Function;
36 |
37 | public class CachedResultSet implements ResultSet, Serializable {
38 |
39 | private static final long serialVersionUID = 1L;
40 |
41 | private transient Integer index;
42 | private List> rows;
43 | private ResultSetMetaData metadata;
44 |
45 | public CachedResultSet(List> rows) {
46 | this.rows = rows;
47 | }
48 |
49 | public CachedResultSet(List> rows, ResultSetMetaData metadata) {
50 | this(rows);
51 | this.metadata = metadata;
52 | }
53 |
54 | public CachedResultSet(ColumnMap singleRow) {
55 | this(Arrays.asList(singleRow));
56 | }
57 |
58 | public Object getObject(String columnName) throws SQLException {
59 | return rows.get(getIndex()).get(columnName.toUpperCase());
60 | }
61 |
62 | public Object getObject(int columnIndex) throws SQLException {
63 | return rows.get(getIndex()).getByIndex(columnIndex);
64 | }
65 |
66 | private int getIndex() {
67 | if (index == null) {
68 | index = -1;
69 | }
70 | return index;
71 | }
72 |
73 | private void setIndex(int i) {
74 | index = i;
75 | }
76 |
77 | private void increaseIndex() {
78 | index = getIndex() + 1;
79 | }
80 |
81 | public String getString(String columnName) throws SQLException {
82 | return (String) getObject(columnName);
83 | }
84 |
85 | public String getString(int columnIndex) throws SQLException {
86 | return (String) getObject(columnIndex);
87 | }
88 |
89 | public boolean first() throws SQLException {
90 | if (rows.size() > 0) {
91 | setIndex(0);
92 | return true;
93 | } else {
94 | return false;
95 | }
96 | }
97 |
98 | public boolean last() throws SQLException {
99 | if (rows.size() > 0) {
100 | setIndex(rows.size() - 1);
101 | return true;
102 | } else {
103 | return false;
104 | }
105 | }
106 |
107 | public boolean next() throws SQLException {
108 | if (rows.size() > 0) {
109 | increaseIndex();
110 | return getIndex() < rows.size();
111 | } else {
112 | return false;
113 | }
114 | }
115 |
116 | public boolean isAfterLast() throws SQLException {
117 | return rows.size() > 0 && getIndex() == rows.size();
118 | }
119 |
120 | public boolean isBeforeFirst() throws SQLException {
121 | return rows.size() > 0 && getIndex() == -1;
122 | }
123 |
124 | public boolean isFirst() throws SQLException {
125 | return rows.size() > 0 && getIndex() == 0;
126 | }
127 |
128 | public boolean isLast() throws SQLException {
129 | return rows.size() > 0 && getIndex() == rows.size() - 1;
130 | }
131 |
132 | public ResultSetMetaData getMetaData() throws SQLException {
133 | return metadata != null ? metadata : new CachedResultSetMetaData();
134 | }
135 |
136 | public void setFetchSize(int rows) throws SQLException {
137 | }
138 |
139 | public Date getDate(int columnIndex, Calendar cal) throws SQLException {
140 | throw new UnsupportedOperationException("Not implemented yet.");
141 | }
142 |
143 | public Date getDate(String columnName, Calendar cal) throws SQLException {
144 | throw new UnsupportedOperationException("Not implemented yet.");
145 | }
146 |
147 | private class ColumnValueParser {
148 |
149 | private Function conversion;
150 |
151 | public ColumnValueParser(Function parser) {
152 | this.conversion = parser;
153 | }
154 |
155 | public Optional parse(int columnIndex) {
156 | Object value = rows.get(getIndex()).getByIndex(columnIndex);
157 | return parse(value);
158 | }
159 |
160 | public Optional parse(String columnName) {
161 | Object value = rows.get(getIndex()).get(columnName.toUpperCase());
162 | return parse(value);
163 | }
164 |
165 | private Optional parse(Object o) {
166 | if (o == null) return Optional.empty();
167 | if (!(o instanceof String)) return (Optional) Optional.of(o);
168 | return Optional.of(conversion.apply((String) o));
169 | }
170 |
171 | }
172 |
173 | public BigDecimal getBigDecimal(int columnIndex) throws SQLException {
174 | return new ColumnValueParser<>(BigDecimal::new)
175 | .parse(columnIndex)
176 | .orElse(null);
177 | }
178 |
179 | public BigDecimal getBigDecimal(String columnName) throws SQLException {
180 | return new ColumnValueParser<>(BigDecimal::new)
181 | .parse(columnName)
182 | .orElse(null);
183 | }
184 |
185 | protected java.util.Date parseDate(String dateRepr) {
186 | try {
187 | return new SimpleDateFormat("yyyy-MM-dd").parse(dateRepr);
188 | } catch (ParseException e) {
189 | throw new RuntimeException(e);
190 | }
191 | }
192 |
193 | public Date getDate(int columnIndex) throws SQLException {
194 | return new ColumnValueParser<>(this::parseDate)
195 | .parse(columnIndex)
196 | .map(d -> new java.sql.Date(d.getTime()))
197 | .orElse(null);
198 | }
199 |
200 | public Date getDate(String columnName) throws SQLException {
201 | return new ColumnValueParser<>(this::parseDate)
202 | .parse(columnName)
203 | .map(d -> new java.sql.Date(d.getTime()))
204 | .orElse(null);
205 | }
206 |
207 | private java.util.Date parseDateTime(String dateRepr) {
208 | try {
209 | return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX").parse(dateRepr);
210 | } catch (ParseException e) {
211 | throw new RuntimeException(e);
212 | }
213 | }
214 |
215 | public Timestamp getTimestamp(int columnIndex) throws SQLException {
216 | Object value = rows.get(getIndex()).getByIndex(columnIndex);
217 | if (value instanceof GregorianCalendar) {
218 | return new java.sql.Timestamp(((GregorianCalendar) value).getTime().getTime());
219 | } else {
220 | return new ColumnValueParser<>(this::parseDateTime)
221 | .parse(columnIndex)
222 | .map(d -> new java.sql.Timestamp(d.getTime()))
223 | .orElse(null);
224 | }
225 | }
226 |
227 | public Timestamp getTimestamp(String columnName) throws SQLException {
228 | Object value = rows.get(getIndex()).get(columnName);
229 | if (value instanceof GregorianCalendar) {
230 | return new java.sql.Timestamp(((GregorianCalendar) value).getTime().getTime());
231 | } else {
232 | return new ColumnValueParser<>((v) -> parseDateTime(v))
233 | .parse(columnName)
234 | .map(d -> new java.sql.Timestamp(d.getTime()))
235 | .orElse(null);
236 | }
237 | }
238 |
239 | public Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException {
240 | throw new UnsupportedOperationException("Not implemented yet.");
241 | }
242 |
243 | public Timestamp getTimestamp(String columnName, Calendar cal) throws SQLException {
244 | throw new UnsupportedOperationException("Not implemented yet.");
245 | }
246 |
247 | private java.util.Date parseTime(String dateRepr) {
248 | try {
249 | return new SimpleDateFormat("HH:mm:ss.SSSX").parse(dateRepr);
250 | } catch (ParseException e) {
251 | throw new RuntimeException(e);
252 | }
253 | }
254 |
255 | public Time getTime(String columnName) throws SQLException {
256 | return new ColumnValueParser<>(this::parseTime)
257 | .parse(columnName)
258 | .map(d -> new Time(d.getTime()))
259 | .orElse(null);
260 | }
261 |
262 | public Time getTime(int columnIndex) throws SQLException {
263 | return new ColumnValueParser<>(this::parseTime)
264 | .parse(columnIndex)
265 | .map(d -> new Time(d.getTime()))
266 | .orElse(null);
267 | }
268 |
269 | public BigDecimal getBigDecimal(int columnIndex, int scale) {
270 | Optional result = new ColumnValueParser<>(BigDecimal::new)
271 | .parse(columnIndex);
272 | result.ifPresent(v -> v.setScale(scale));
273 | return result.orElse(null);
274 | }
275 |
276 | public BigDecimal getBigDecimal(String columnName, int scale) {
277 | Optional result = new ColumnValueParser<>(BigDecimal::new)
278 | .parse(columnName);
279 | result.ifPresent(v -> v.setScale(scale));
280 | return result.orElse(null);
281 | }
282 |
283 | public float getFloat(int columnIndex) throws SQLException {
284 | return new ColumnValueParser<>(Float::new)
285 | .parse(columnIndex)
286 | .orElse(0f);
287 | }
288 |
289 | public float getFloat(String columnName) throws SQLException {
290 | return new ColumnValueParser<>(Float::new)
291 | .parse(columnName)
292 | .orElse(0f);
293 | }
294 |
295 | public double getDouble(int columnIndex) throws SQLException {
296 | return new ColumnValueParser<>(Double::new)
297 | .parse(columnIndex)
298 | .orElse(0d);
299 | }
300 |
301 | public double getDouble(String columnName) throws SQLException {
302 | return new ColumnValueParser<>(Double::new)
303 | .parse(columnName)
304 | .orElse(0d);
305 | }
306 |
307 | public long getLong(String columnName) throws SQLException {
308 | return new ColumnValueParser<>(Long::new)
309 | .parse(columnName)
310 | .orElse(0L);
311 | }
312 |
313 | public long getLong(int columnIndex) throws SQLException {
314 | return new ColumnValueParser(Long::new)
315 | .parse(columnIndex)
316 | .orElse(0L);
317 | }
318 |
319 | public int getInt(String columnName) throws SQLException {
320 | return new ColumnValueParser<>(Integer::new)
321 | .parse(columnName)
322 | .orElse(0);
323 | }
324 |
325 | public int getInt(int columnIndex) throws SQLException {
326 | return new ColumnValueParser<>(Integer::new)
327 | .parse(columnIndex)
328 | .orElse(0);
329 | }
330 |
331 | public short getShort(String columnName) throws SQLException {
332 | return new ColumnValueParser<>(Short::new)
333 | .parse(columnName)
334 | .orElse((short) 0);
335 | }
336 |
337 | public short getShort(int columnIndex) throws SQLException {
338 | return new ColumnValueParser<>(Short::new)
339 | .parse(columnIndex)
340 | .orElse((short) 0);
341 | }
342 |
343 | public InputStream getBinaryStream(int columnIndex) throws SQLException {
344 | throw new UnsupportedOperationException("Not implemented yet.");
345 | }
346 |
347 | public InputStream getBinaryStream(String columnName) throws SQLException {
348 | throw new UnsupportedOperationException("Not implemented yet.");
349 | }
350 |
351 | private Blob createBlob(byte[] data) {
352 | try {
353 | return new SerialBlob(data);
354 | } catch (SQLException e) {
355 | throw new RuntimeException(e);
356 | }
357 | }
358 |
359 | public Blob getBlob(int columnIndex) throws SQLException {
360 | return new ColumnValueParser<>((v) -> Base64.getDecoder().decode(v))
361 | .parse(columnIndex)
362 | .map(this::createBlob)
363 | .orElse(null);
364 | }
365 |
366 | public Blob getBlob(String columnName) throws SQLException {
367 | return new ColumnValueParser<>((v) -> Base64.getDecoder().decode(v))
368 | .parse(columnName)
369 | .map(this::createBlob)
370 | .orElse(null);
371 | }
372 |
373 | public boolean getBoolean(int columnIndex) throws SQLException {
374 | return new ColumnValueParser<>(Boolean::new)
375 | .parse(columnIndex)
376 | .orElse(false);
377 | }
378 |
379 | public boolean getBoolean(String columnName) throws SQLException {
380 | return new ColumnValueParser<>(Boolean::new)
381 | .parse(columnName)
382 | .orElse(false);
383 | }
384 |
385 | public byte getByte(int columnIndex) throws SQLException {
386 | return new ColumnValueParser<>(Byte::new)
387 | .parse(columnIndex)
388 | .orElse((byte) 0);
389 | }
390 |
391 | public byte getByte(String columnName) throws SQLException {
392 | return new ColumnValueParser<>(Byte::new)
393 | .parse(columnName)
394 | .orElse((byte) 0);
395 | }
396 |
397 | public byte[] getBytes(int columnIndex) throws SQLException {
398 | throw new UnsupportedOperationException("Not implemented yet.");
399 | }
400 |
401 | public byte[] getBytes(String columnName) throws SQLException {
402 | throw new UnsupportedOperationException("Not implemented yet.");
403 | }
404 |
405 | //
406 | // Not implemented below here
407 | //
408 |
409 | public boolean absolute(int row) throws SQLException {
410 | return false;
411 | }
412 |
413 | public void afterLast() throws SQLException {
414 | System.out.println("after last check");
415 | }
416 |
417 | public void beforeFirst() throws SQLException {
418 | }
419 |
420 | public void cancelRowUpdates() throws SQLException {
421 | }
422 |
423 | public void clearWarnings() throws SQLException {
424 |
425 | }
426 |
427 | public void close() throws SQLException {
428 |
429 | }
430 |
431 | public void deleteRow() throws SQLException {
432 |
433 | }
434 |
435 | public int findColumn(String columnName) throws SQLException {
436 |
437 | return 0;
438 | }
439 |
440 | public Array getArray(int i) throws SQLException {
441 |
442 | return null;
443 | }
444 |
445 | public Array getArray(String colName) throws SQLException {
446 |
447 | return null;
448 | }
449 |
450 | public InputStream getAsciiStream(int columnIndex) throws SQLException {
451 |
452 | return null;
453 | }
454 |
455 | public InputStream getAsciiStream(String columnName) throws SQLException {
456 |
457 | return null;
458 | }
459 |
460 | public Reader getCharacterStream(int columnIndex) throws SQLException {
461 |
462 | return null;
463 | }
464 |
465 | public Reader getCharacterStream(String columnName) throws SQLException {
466 |
467 | return null;
468 | }
469 |
470 | public Clob getClob(int i) throws SQLException {
471 |
472 | return null;
473 | }
474 |
475 | public Clob getClob(String colName) throws SQLException {
476 |
477 | return null;
478 | }
479 |
480 | public int getConcurrency() throws SQLException {
481 |
482 | return 0;
483 | }
484 |
485 | public String getCursorName() throws SQLException {
486 |
487 | return null;
488 | }
489 |
490 | public int getFetchDirection() throws SQLException {
491 |
492 | return 0;
493 | }
494 |
495 | public int getFetchSize() throws SQLException {
496 |
497 | return 0;
498 | }
499 |
500 | public Object getObject(int i, Map> map)
501 | throws SQLException {
502 |
503 | return null;
504 | }
505 |
506 | public Object getObject(String colName, Map> map)
507 | throws SQLException {
508 |
509 | return null;
510 | }
511 |
512 | public Ref getRef(int i) throws SQLException {
513 |
514 | return null;
515 | }
516 |
517 | public Ref getRef(String colName) throws SQLException {
518 |
519 | return null;
520 | }
521 |
522 | public int getRow() throws SQLException {
523 |
524 | return 0;
525 | }
526 |
527 | public Statement getStatement() throws SQLException {
528 |
529 | return null;
530 | }
531 |
532 | public Time getTime(int columnIndex, Calendar cal) throws SQLException {
533 |
534 | return null;
535 | }
536 |
537 | public Time getTime(String columnName, Calendar cal) throws SQLException {
538 |
539 | return null;
540 | }
541 |
542 | public int getType() throws SQLException {
543 |
544 | return 0;
545 | }
546 |
547 | public URL getURL(int columnIndex) throws SQLException {
548 |
549 | return null;
550 | }
551 |
552 | public URL getURL(String columnName) throws SQLException {
553 |
554 | return null;
555 | }
556 |
557 | public InputStream getUnicodeStream(int columnIndex) throws SQLException {
558 |
559 | return null;
560 | }
561 |
562 | public InputStream getUnicodeStream(String columnName) throws SQLException {
563 |
564 | return null;
565 | }
566 |
567 | public SQLWarning getWarnings() throws SQLException {
568 |
569 | return null;
570 | }
571 |
572 | public void insertRow() throws SQLException {
573 | }
574 |
575 | public void moveToCurrentRow() throws SQLException {
576 | }
577 |
578 | public void moveToInsertRow() throws SQLException {
579 | }
580 |
581 | public boolean previous() throws SQLException {
582 |
583 | return false;
584 | }
585 |
586 | public void refreshRow() throws SQLException {
587 | }
588 |
589 | public boolean relative(int rows) throws SQLException {
590 |
591 | return false;
592 | }
593 |
594 | public boolean rowDeleted() throws SQLException {
595 |
596 | return false;
597 | }
598 |
599 | public boolean rowInserted() throws SQLException {
600 |
601 | return false;
602 | }
603 |
604 | public boolean rowUpdated() throws SQLException {
605 |
606 | return false;
607 | }
608 |
609 | public void setFetchDirection(int direction) throws SQLException {
610 | }
611 |
612 | public void updateArray(int columnIndex, Array x) throws SQLException {
613 | }
614 |
615 | public void updateArray(String columnName, Array x) throws SQLException {
616 | }
617 |
618 | public void updateAsciiStream(int columnIndex, InputStream x, int length)
619 | throws SQLException {
620 | }
621 |
622 | public void updateAsciiStream(String columnName, InputStream x, int length)
623 | throws SQLException {
624 | }
625 |
626 | public void updateBigDecimal(int columnIndex, BigDecimal x)
627 | throws SQLException {
628 | }
629 |
630 | public void updateBigDecimal(String columnName, BigDecimal x)
631 | throws SQLException {
632 | }
633 |
634 | public void updateBinaryStream(int columnIndex, InputStream x, int length)
635 | throws SQLException {
636 | }
637 |
638 | public void updateBinaryStream(String columnName, InputStream x, int length)
639 | throws SQLException {
640 | }
641 |
642 | public void updateBlob(int columnIndex, Blob x) throws SQLException {
643 | }
644 |
645 | public void updateBlob(String columnName, Blob x) throws SQLException {
646 | }
647 |
648 | public void updateBoolean(int columnIndex, boolean x) throws SQLException {
649 | }
650 |
651 | public void updateBoolean(String columnName, boolean x) throws SQLException {
652 | }
653 |
654 | public void updateByte(int columnIndex, byte x) throws SQLException {
655 | }
656 |
657 | public void updateByte(String columnName, byte x) throws SQLException {
658 | }
659 |
660 | public void updateBytes(int columnIndex, byte[] x) throws SQLException {
661 | }
662 |
663 | public void updateBytes(String columnName, byte[] x) throws SQLException {
664 | }
665 |
666 | public void updateCharacterStream(int columnIndex, Reader x, int length)
667 | throws SQLException {
668 | }
669 |
670 | public void updateCharacterStream(String columnName, Reader reader,
671 | int length) throws SQLException {
672 | }
673 |
674 | public void updateClob(int columnIndex, Clob x) throws SQLException {
675 | }
676 |
677 | public void updateClob(String columnName, Clob x) throws SQLException {
678 | }
679 |
680 | public void updateDate(int columnIndex, Date x) throws SQLException {
681 | }
682 |
683 | public void updateDate(String columnName, Date x) throws SQLException {
684 | }
685 |
686 | public void updateDouble(int columnIndex, double x) throws SQLException {
687 | }
688 |
689 | public void updateDouble(String columnName, double x) throws SQLException {
690 | }
691 |
692 | public void updateFloat(int columnIndex, float x) throws SQLException {
693 | }
694 |
695 | public void updateFloat(String columnName, float x) throws SQLException {
696 | }
697 |
698 | public void updateInt(int columnIndex, int x) throws SQLException {
699 | }
700 |
701 | public void updateInt(String columnName, int x) throws SQLException {
702 | }
703 |
704 | public void updateLong(int columnIndex, long x) throws SQLException {
705 | }
706 |
707 | public void updateLong(String columnName, long x) throws SQLException {
708 | }
709 |
710 | public void updateNull(int columnIndex) throws SQLException {
711 | }
712 |
713 | public void updateNull(String columnName) throws SQLException {
714 | }
715 |
716 | public void updateObject(int columnIndex, Object x) throws SQLException {
717 | }
718 |
719 | public void updateObject(String columnName, Object x) throws SQLException {
720 | }
721 |
722 | public void updateObject(int columnIndex, Object x, int scale)
723 | throws SQLException {
724 | }
725 |
726 | public void updateObject(String columnName, Object x, int scale)
727 | throws SQLException {
728 | }
729 |
730 | public void updateRef(int columnIndex, Ref x) throws SQLException {
731 | }
732 |
733 | public void updateRef(String columnName, Ref x) throws SQLException {
734 | }
735 |
736 | public void updateRow() throws SQLException {
737 | }
738 |
739 | public void updateShort(int columnIndex, short x) throws SQLException {
740 | }
741 |
742 | public void updateShort(String columnName, short x) throws SQLException {
743 | }
744 |
745 | public void updateString(int columnIndex, String x) throws SQLException {
746 | }
747 |
748 | public void updateString(String columnName, String x) throws SQLException {
749 | }
750 |
751 | public void updateTime(int columnIndex, Time x) throws SQLException {
752 | }
753 |
754 | public void updateTime(String columnName, Time x) throws SQLException {
755 | }
756 |
757 | public void updateTimestamp(int columnIndex, Timestamp x)
758 | throws SQLException {
759 | }
760 |
761 | public void updateTimestamp(String columnName, Timestamp x)
762 | throws SQLException {
763 | }
764 |
765 | public boolean wasNull() throws SQLException {
766 |
767 | return false;
768 | }
769 |
770 | public T unwrap(Class iface) throws SQLException {
771 |
772 | return null;
773 | }
774 |
775 | public boolean isWrapperFor(Class> iface) throws SQLException {
776 |
777 | return false;
778 | }
779 |
780 | public RowId getRowId(int columnIndex) throws SQLException {
781 |
782 | return null;
783 | }
784 |
785 | public RowId getRowId(String columnLabel) throws SQLException {
786 |
787 | return null;
788 | }
789 |
790 | public void updateRowId(int columnIndex, RowId x) throws SQLException {
791 | }
792 |
793 | public void updateRowId(String columnLabel, RowId x) throws SQLException {
794 | }
795 |
796 | public int getHoldability() throws SQLException {
797 |
798 | return 0;
799 | }
800 |
801 | public boolean isClosed() throws SQLException {
802 |
803 | return false;
804 | }
805 |
806 | public void updateNString(int columnIndex, String nString) throws SQLException {
807 | }
808 |
809 | public void updateNString(String columnLabel, String nString) throws SQLException {
810 | }
811 |
812 | public void updateNClob(int columnIndex, NClob nClob) throws SQLException {
813 | }
814 |
815 | public void updateNClob(String columnLabel, NClob nClob) throws SQLException {
816 | }
817 |
818 | public NClob getNClob(int columnIndex) throws SQLException {
819 |
820 | return null;
821 | }
822 |
823 | public NClob getNClob(String columnLabel) throws SQLException {
824 |
825 | return null;
826 | }
827 |
828 | public SQLXML getSQLXML(int columnIndex) throws SQLException {
829 |
830 | return null;
831 | }
832 |
833 | public SQLXML getSQLXML(String columnLabel) throws SQLException {
834 |
835 | return null;
836 | }
837 |
838 | public void updateSQLXML(int columnIndex, SQLXML xmlObject) throws SQLException {
839 | }
840 |
841 | public void updateSQLXML(String columnLabel, SQLXML xmlObject) throws SQLException {
842 | }
843 |
844 | public String getNString(int columnIndex) throws SQLException {
845 |
846 | return null;
847 | }
848 |
849 | public String getNString(String columnLabel) throws SQLException {
850 |
851 | return null;
852 | }
853 |
854 | public Reader getNCharacterStream(int columnIndex) throws SQLException {
855 |
856 | return null;
857 | }
858 |
859 | public Reader getNCharacterStream(String columnLabel) throws SQLException {
860 |
861 | return null;
862 | }
863 |
864 | public void updateNCharacterStream(int columnIndex, Reader x, long length) throws SQLException {
865 | }
866 |
867 | public void updateNCharacterStream(String columnLabel, Reader reader, long length) throws SQLException {
868 | }
869 |
870 | public void updateAsciiStream(int columnIndex, InputStream x, long length) throws SQLException {
871 | }
872 |
873 | public void updateBinaryStream(int columnIndex, InputStream x, long length) throws SQLException {
874 | }
875 |
876 | public void updateCharacterStream(int columnIndex, Reader x, long length) throws SQLException {
877 | }
878 |
879 | public void updateAsciiStream(String columnLabel, InputStream x, long length) throws SQLException {
880 | }
881 |
882 | public void updateBinaryStream(String columnLabel, InputStream x, long length) throws SQLException {
883 | }
884 |
885 | public void updateCharacterStream(String columnLabel, Reader reader, long length) throws SQLException {
886 | }
887 |
888 | public void updateBlob(int columnIndex, InputStream inputStream, long length) throws SQLException {
889 | }
890 |
891 | public void updateBlob(String columnLabel, InputStream inputStream, long length) throws SQLException {
892 | }
893 |
894 | public void updateClob(int columnIndex, Reader reader, long length) throws SQLException {
895 | }
896 |
897 | public void updateClob(String columnLabel, Reader reader, long length) throws SQLException {
898 | }
899 |
900 | public void updateNClob(int columnIndex, Reader reader, long length) throws SQLException {
901 | }
902 |
903 | public void updateNClob(String columnLabel, Reader reader, long length) throws SQLException {
904 | }
905 |
906 | public void updateNCharacterStream(int columnIndex, Reader x) throws SQLException {
907 | }
908 |
909 | public void updateNCharacterStream(String columnLabel, Reader reader) throws SQLException {
910 | }
911 |
912 | public void updateAsciiStream(int columnIndex, InputStream x) throws SQLException {
913 | }
914 |
915 | public void updateBinaryStream(int columnIndex, InputStream x) throws SQLException {
916 | }
917 |
918 | public void updateCharacterStream(int columnIndex, Reader x) throws SQLException {
919 | }
920 |
921 | public void updateAsciiStream(String columnLabel, InputStream x) throws SQLException {
922 | }
923 |
924 | public void updateBinaryStream(String columnLabel, InputStream x) throws SQLException {
925 | }
926 |
927 | public void updateCharacterStream(String columnLabel, Reader reader) throws SQLException {
928 | }
929 |
930 | public void updateBlob(int columnIndex, InputStream inputStream) throws SQLException {
931 | }
932 |
933 | public void updateBlob(String columnLabel, InputStream inputStream) throws SQLException {
934 | }
935 |
936 | public void updateClob(int columnIndex, Reader reader) throws SQLException {
937 | }
938 |
939 | public void updateClob(String columnLabel, Reader reader) throws SQLException {
940 | }
941 |
942 | public void updateNClob(int columnIndex, Reader reader) throws SQLException {
943 | }
944 |
945 | public void updateNClob(String columnLabel, Reader reader) throws SQLException {
946 | }
947 |
948 | @Override
949 | public T getObject(int columnIndex, Class type) throws SQLException {
950 | // TODO Auto-generated method stub
951 | return null;
952 | }
953 |
954 | @Override
955 | public T getObject(String columnLabel, Class type) throws SQLException {
956 | // TODO Auto-generated method stub
957 | return null;
958 | }
959 | }
960 |
--------------------------------------------------------------------------------
/sf-jdbc-driver/src/main/java/com/ascendix/jdbc/salesforce/resultset/CachedResultSetMetaData.java:
--------------------------------------------------------------------------------
1 | package com.ascendix.jdbc.salesforce.resultset;
2 |
3 | import java.sql.ResultSetMetaData;
4 | import java.sql.SQLException;
5 |
6 | public class CachedResultSetMetaData implements ResultSetMetaData {
7 |
8 | public String getCatalogName(int column) throws SQLException {
9 | return "";
10 | }
11 |
12 | //
13 | // Not implemented below here
14 | //
15 |
16 | public String getColumnClassName(int column) throws SQLException {
17 |
18 | return null;
19 | }
20 |
21 | public int getColumnCount() throws SQLException {
22 |
23 | return 0;
24 | }
25 |
26 | public int getColumnDisplaySize(int column) throws SQLException {
27 |
28 | return 0;
29 | }
30 |
31 | public String getColumnLabel(int column) throws SQLException {
32 |
33 | return null;
34 | }
35 |
36 | public String getColumnName(int column) throws SQLException {
37 |
38 | return null;
39 | }
40 |
41 | public int getColumnType(int column) throws SQLException {
42 |
43 | return 0;
44 | }
45 |
46 | public String getColumnTypeName(int column) throws SQLException {
47 |
48 | return null;
49 | }
50 |
51 | public int getPrecision(int column) throws SQLException {
52 |
53 | return 0;
54 | }
55 |
56 | public int getScale(int column) throws SQLException {
57 |
58 | return 0;
59 | }
60 |
61 | public String getSchemaName(int column) throws SQLException {
62 |
63 | return null;
64 | }
65 |
66 | public String getTableName(int column) throws SQLException {
67 |
68 | return null;
69 | }
70 |
71 | public boolean isAutoIncrement(int column) throws SQLException {
72 |
73 | return false;
74 | }
75 |
76 | public boolean isCaseSensitive(int column) throws SQLException {
77 |
78 | return false;
79 | }
80 |
81 | public boolean isCurrency(int column) throws SQLException {
82 |
83 | return false;
84 | }
85 |
86 | public boolean isDefinitelyWritable(int column) throws SQLException {
87 |
88 | return false;
89 | }
90 |
91 | public int isNullable(int column) throws SQLException {
92 |
93 | return 0;
94 | }
95 |
96 | public boolean isReadOnly(int column) throws SQLException {
97 |
98 | return false;
99 | }
100 |
101 | public boolean isSearchable(int column) throws SQLException {
102 |
103 | return false;
104 | }
105 |
106 | public boolean isSigned(int column) throws SQLException {
107 |
108 | return false;
109 | }
110 |
111 | public boolean isWritable(int column) throws SQLException {
112 |
113 | return false;
114 | }
115 |
116 | public T unwrap(Class iface) throws SQLException {
117 |
118 | return null;
119 | }
120 |
121 | public boolean isWrapperFor(Class> iface) throws SQLException {
122 |
123 | return false;
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/sf-jdbc-driver/src/main/java/com/ascendix/jdbc/salesforce/statement/FieldDef.java:
--------------------------------------------------------------------------------
1 | package com.ascendix.jdbc.salesforce.statement;
2 |
3 | public class FieldDef {
4 |
5 | private String name;
6 | private String type;
7 |
8 | public FieldDef(String name, String type) {
9 | this.name = name;
10 | this.type = type;
11 | }
12 |
13 | public String getName() {
14 | return name;
15 | }
16 |
17 | public String getType() {
18 | return type;
19 | }
20 |
21 | @Override
22 | public int hashCode() {
23 | final int prime = 31;
24 | int result = 1;
25 | result = prime * result + ((name == null) ? 0 : name.hashCode());
26 | result = prime * result + ((type == null) ? 0 : type.hashCode());
27 | return result;
28 | }
29 |
30 | @Override
31 | public boolean equals(Object obj) {
32 | if (this == obj)
33 | return true;
34 | if (obj == null)
35 | return false;
36 | if (getClass() != obj.getClass())
37 | return false;
38 | FieldDef other = (FieldDef) obj;
39 | if (name == null) {
40 | if (other.name != null)
41 | return false;
42 | } else if (!name.equals(other.name))
43 | return false;
44 | if (type == null) {
45 | if (other.type != null)
46 | return false;
47 | } else if (!type.equals(other.type))
48 | return false;
49 | return true;
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/sf-jdbc-driver/src/main/java/com/ascendix/jdbc/salesforce/statement/ParameterMetadataImpl.java:
--------------------------------------------------------------------------------
1 | package com.ascendix.jdbc.salesforce.statement;
2 |
3 | import org.apache.commons.lang3.StringUtils;
4 |
5 | import java.sql.ParameterMetaData;
6 | import java.sql.SQLException;
7 | import java.sql.Types;
8 | import java.util.ArrayList;
9 | import java.util.Collections;
10 | import java.util.List;
11 |
12 | public class ParameterMetadataImpl implements ParameterMetaData {
13 |
14 | private List parameters = new ArrayList<>();
15 |
16 | public ParameterMetadataImpl(List parameters, String query) {
17 | super();
18 | this.parameters.addAll(parameters);
19 | int paramsCountInQuery = StringUtils.countMatches(query, '?');
20 | if (this.parameters.size() < paramsCountInQuery) {
21 | this.parameters.addAll(Collections.nCopies(paramsCountInQuery - this.parameters.size(), new Object()));
22 | }
23 | }
24 |
25 | @Override
26 | public T unwrap(Class iface) throws SQLException {
27 | // TODO Auto-generated method stub
28 | return null;
29 | }
30 |
31 | @Override
32 | public boolean isWrapperFor(Class> iface) throws SQLException {
33 | // TODO Auto-generated method stub
34 | return false;
35 | }
36 |
37 | @Override
38 | public int getParameterCount() throws SQLException {
39 | return parameters.size();
40 | }
41 |
42 | @Override
43 | public int isNullable(int param) throws SQLException {
44 | return ParameterMetaData.parameterNullable;
45 | }
46 |
47 | @Override
48 | public boolean isSigned(int param) throws SQLException {
49 | return parameters.get(param + 1).getClass().isInstance(Number.class);
50 | }
51 |
52 | @Override
53 | public int getPrecision(int param) throws SQLException {
54 | return 0;
55 | }
56 |
57 | @Override
58 | public int getScale(int param) throws SQLException {
59 | return 0;
60 | }
61 |
62 | @Override
63 | public int getParameterType(int param) throws SQLException {
64 | return Types.NVARCHAR;
65 | }
66 |
67 | @Override
68 | public String getParameterTypeName(int param) throws SQLException {
69 | return "varchar";
70 | }
71 |
72 | @Override
73 | public String getParameterClassName(int param) throws SQLException {
74 | return parameters.get(param + 1).getClass().getName();
75 | }
76 |
77 | @Override
78 | public int getParameterMode(int param) throws SQLException {
79 | return ParameterMetaData.parameterModeIn;
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/sf-jdbc-driver/src/main/java/com/ascendix/jdbc/salesforce/statement/SoqlQueryAnalyzer.java:
--------------------------------------------------------------------------------
1 | package com.ascendix.jdbc.salesforce.statement;
2 |
3 | import com.sforce.soap.partner.ChildRelationship;
4 | import com.sforce.soap.partner.DescribeSObjectResult;
5 | import com.sforce.soap.partner.Field;
6 | import org.mule.tools.soql.SOQLDataBaseVisitor;
7 | import org.mule.tools.soql.SOQLParserHelper;
8 | import org.mule.tools.soql.query.SOQLQuery;
9 | import org.mule.tools.soql.query.SOQLSubQuery;
10 | import org.mule.tools.soql.query.clause.FromClause;
11 | import org.mule.tools.soql.query.from.ObjectSpec;
12 | import org.mule.tools.soql.query.select.FieldSpec;
13 | import org.mule.tools.soql.query.select.FunctionCallSpec;
14 |
15 | import java.util.ArrayList;
16 | import java.util.Arrays;
17 | import java.util.HashMap;
18 | import java.util.List;
19 | import java.util.Map;
20 | import java.util.function.Function;
21 |
22 | public class SoqlQueryAnalyzer {
23 |
24 | private String soql;
25 | private Function objectDescriptor;
26 | private Map describedObjectsCache;
27 | private SOQLQuery queryData;
28 |
29 | public SoqlQueryAnalyzer(String soql, Function objectDescriptor) {
30 | this(soql, objectDescriptor, new HashMap<>());
31 | }
32 |
33 | public SoqlQueryAnalyzer(String soql, Function objectDescriptor, Map describedObjectsCache) {
34 | this.soql = soql;
35 | this.objectDescriptor = objectDescriptor;
36 | this.describedObjectsCache = describedObjectsCache;
37 | }
38 |
39 | private List fieldDefinitions;
40 |
41 | private class SelectSpecVisitor extends SOQLDataBaseVisitor {
42 |
43 | @Override
44 | public Void visitFieldSpec(FieldSpec fieldSpec) {
45 | String name = fieldSpec.getFieldName();
46 | String alias = fieldSpec.getAlias() != null ? fieldSpec.getAlias() : name;
47 | List prefixNames = new ArrayList<>(fieldSpec.getObjectPrefixNames());
48 | FieldDef result = createFieldDef(name, alias, prefixNames);
49 | fieldDefinitions.add(result);
50 | return null;
51 | }
52 |
53 | private FieldDef createFieldDef(String name, String alias, List prefixNames) {
54 | List fieldPrefixes = new ArrayList<>(prefixNames);
55 | String fromObject = getFromObjectName();
56 | if (!fieldPrefixes.isEmpty() && fieldPrefixes.get(0).equalsIgnoreCase(fromObject)) {
57 | fieldPrefixes.remove(0);
58 | }
59 | while (!fieldPrefixes.isEmpty()) {
60 | String referenceName = fieldPrefixes.get(0);
61 | Field reference = findField(referenceName, describeObject(fromObject), fld -> fld.getRelationshipName());
62 | fromObject = reference.getReferenceTo()[0];
63 | fieldPrefixes.remove(0);
64 | }
65 | String type = findField(name, describeObject(fromObject), fld -> fld.getName()).getType().name();
66 | FieldDef result = new FieldDef(alias, type);
67 | return result;
68 | }
69 |
70 | private final List FUNCTIONS_HAS_INT_RESULT = Arrays.asList("COUNT", "COUNT_DISTINCT", "CALENDAR_MONTH",
71 | "CALENDAR_QUARTER", "CALENDAR_YEAR", "DAY_IN_MONTH", "DAY_IN_WEEK", "DAY_IN_YEAR", "DAY_ONLY", "FISCAL_MONTH",
72 | "FISCAL_QUARTER", "FISCAL_YEAR", "HOUR_IN_DAY", "WEEK_IN_MONTH", "WEEK_IN_YEAR");
73 |
74 | @Override
75 | public Void visitFunctionCallSpec(FunctionCallSpec functionCallSpec) {
76 | String alias = functionCallSpec.getAlias() != null ? functionCallSpec.getAlias() : functionCallSpec.getFunctionName();
77 | if (FUNCTIONS_HAS_INT_RESULT.contains(functionCallSpec.getFunctionName().toUpperCase())) {
78 | fieldDefinitions.add(new FieldDef(alias, "int"));
79 | } else {
80 | org.mule.tools.soql.query.data.Field param = (org.mule.tools.soql.query.data.Field) functionCallSpec.getFunctionParameters().get(0);
81 | FieldDef result = createFieldDef(param.getFieldName(), alias, param.getObjectPrefixNames());
82 | fieldDefinitions.add(result);
83 | }
84 | return null;
85 | }
86 |
87 | @Override
88 | public Void visitSOQLSubQuery(SOQLSubQuery soqlSubQuery) {
89 | String subquerySoql = soqlSubQuery.toSOQLText().replaceAll("\\A\\s*\\(|\\)\\s*$", "");
90 | SOQLQuery subquery = SOQLParserHelper.createSOQLData(subquerySoql);
91 | String relationshipName = subquery.getFromClause().getMainObjectSpec().getObjectName();
92 | ChildRelationship relatedFrom = Arrays.stream(describeObject(getFromObjectName()).getChildRelationships())
93 | .filter(rel -> relationshipName.equalsIgnoreCase(rel.getRelationshipName()))
94 | .findFirst()
95 | .orElseThrow(() -> new IllegalArgumentException("Unresolved relationship in subquery \"" + subquerySoql + "\""));
96 | String fromObject = relatedFrom.getChildSObject();
97 | subquery.setFromClause(new FromClause(new ObjectSpec(fromObject, null)));
98 |
99 | SoqlQueryAnalyzer subqueryAnalyzer = new SoqlQueryAnalyzer(subquery.toSOQLText(), objectDescriptor, describedObjectsCache);
100 | fieldDefinitions.add(new ArrayList(subqueryAnalyzer.getFieldDefinitions()));
101 | return null;
102 | }
103 |
104 | }
105 |
106 | public List getFieldDefinitions() {
107 | if (fieldDefinitions == null) {
108 | fieldDefinitions = new ArrayList<>();
109 | SelectSpecVisitor visitor = new SelectSpecVisitor();
110 | getQueryData().getSelectSpecs()
111 | .forEach(spec -> spec.accept(visitor));
112 | }
113 | return fieldDefinitions;
114 | }
115 |
116 | private Field findField(String name, DescribeSObjectResult objectDesc, Function nameFetcher) {
117 | return Arrays.stream(objectDesc.getFields())
118 | .filter(field -> name.equals(nameFetcher.apply(field)))
119 | .findFirst()
120 | .orElseThrow(() -> new IllegalArgumentException("Unknown field name \"" + name + "\" in object \"" + objectDesc.getName() + "\""));
121 | }
122 |
123 | private DescribeSObjectResult describeObject(String fromObjectName) {
124 | if (!describedObjectsCache.containsKey(fromObjectName)) {
125 | DescribeSObjectResult description = objectDescriptor.apply(fromObjectName);
126 | describedObjectsCache.put(fromObjectName, description);
127 | return description;
128 | } else {
129 | return describedObjectsCache.get(fromObjectName);
130 | }
131 | }
132 |
133 | protected String getFromObjectName() {
134 | return getQueryData().getFromClause().getMainObjectSpec().getObjectName();
135 | }
136 |
137 | private SOQLQuery getQueryData() {
138 | if (queryData == null) {
139 | queryData = SOQLParserHelper.createSOQLData(soql);
140 | }
141 | return queryData;
142 | }
143 |
144 | }
145 |
--------------------------------------------------------------------------------
/sf-jdbc-driver/src/test/java/com/ascendix/jdbc/salesforce/ForceDriverTest.java:
--------------------------------------------------------------------------------
1 | package com.ascendix.jdbc.salesforce;
2 |
3 | import org.junit.Before;
4 | import org.junit.Test;
5 |
6 | import java.io.IOException;
7 | import java.sql.Connection;
8 | import java.sql.SQLException;
9 | import java.util.Properties;
10 |
11 | import static org.junit.Assert.*;
12 |
13 | public class ForceDriverTest {
14 |
15 | private ForceDriver driver;
16 |
17 | @Before
18 | public void setUp() {
19 | driver = new ForceDriver();
20 | }
21 |
22 | @Test
23 | public void testGetConnStringProperties() throws IOException {
24 | Properties actuals = driver.getConnStringProperties("jdbc:ascendix:salesforce://prop1=val1;prop2=val2");
25 |
26 | assertEquals(2, actuals.size());
27 | assertEquals("val1", actuals.getProperty("prop1"));
28 | assertEquals("val2", actuals.getProperty("prop2"));
29 | }
30 |
31 | @Test
32 | public void testGetConnStringProperties_WhenNoValue() throws IOException {
33 | Properties actuals = driver.getConnStringProperties("jdbc:ascendix:salesforce://prop1=val1; prop2; prop3 = val3");
34 |
35 | assertEquals(3, actuals.size());
36 | assertTrue(actuals.containsKey("prop2"));
37 | assertEquals("", actuals.getProperty("prop2"));
38 | }
39 |
40 | @Test
41 | public void testConnect_WhenWrongURL() throws SQLException {
42 | Connection connection = driver.connect("jdbc:mysql://localhost/test", new Properties());
43 |
44 | assertNull(connection);
45 | }
46 |
47 | @Test
48 | public void testGetConnStringProperties_StandartUrlFormat() throws IOException {
49 | Properties actuals = driver.getConnStringProperties("jdbc:ascendix:salesforce://test@test.ru:aaaa!aaa@login.salesforce.ru");
50 |
51 | assertEquals(3, actuals.size());
52 | assertTrue(actuals.containsKey("user"));
53 | assertEquals("test@test.ru", actuals.getProperty("user"));
54 | assertEquals("aaaa!aaa", actuals.getProperty("password"));
55 | assertEquals("login.salesforce.ru", actuals.getProperty("loginDomain"));
56 | }
57 |
58 | @Test
59 | public void testGetConnStringProperties_StandartUrlFormatHttpsApi() throws IOException {
60 | Properties actuals = driver.getConnStringProperties("jdbc:ascendix:salesforce://test@test.ru:aaaa!aaa@login.salesforce.ru?https=false&api=48.0");
61 |
62 | assertEquals(5, actuals.size());
63 | assertTrue(actuals.containsKey("user"));
64 | assertEquals("test@test.ru", actuals.getProperty("user"));
65 | assertEquals("aaaa!aaa", actuals.getProperty("password"));
66 | assertEquals("login.salesforce.ru", actuals.getProperty("loginDomain"));
67 | assertEquals("false", actuals.getProperty("https"));
68 | assertEquals("48.0", actuals.getProperty("api"));
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/sf-jdbc-driver/src/test/java/com/ascendix/jdbc/salesforce/delegates/PartnerResultToCrtesianTableTest.java:
--------------------------------------------------------------------------------
1 | package com.ascendix.jdbc.salesforce.delegates;
2 |
3 | import com.ascendix.jdbc.salesforce.delegates.PartnerResultToCrtesianTable;
4 | import org.junit.Test;
5 |
6 | import java.util.ArrayList;
7 | import java.util.Arrays;
8 | import java.util.List;
9 |
10 | import static org.junit.Assert.assertEquals;
11 | import static org.junit.Assert.assertTrue;
12 |
13 |
14 | public class PartnerResultToCrtesianTableTest {
15 |
16 | @Test
17 | public void testExpandSimple() {
18 | List schema = Arrays.asList(new Object(), new Object(), new Object(), new Object());
19 |
20 | List expected = Arrays.asList(
21 | (List) Arrays.asList(1, 2, 3, 4)
22 | );
23 |
24 | List actual = PartnerResultToCrtesianTable.expand(expected, schema);
25 |
26 | assertEquals(actual, expected);
27 | }
28 |
29 | @Test
30 | public void testExpandWhenNothingToExpand() {
31 | List schema = Arrays.asList(new Object(), new Object(), new Object(), new Object());
32 |
33 | List expected = Arrays.asList(
34 | Arrays.asList(1, 2, 3, 4),
35 | Arrays.asList("1", "2", "3", "4"),
36 | Arrays.asList("11", "12", "13", "14"),
37 | Arrays.asList("21", "22", "23", "24")
38 | );
39 |
40 | List actual = PartnerResultToCrtesianTable.expand(expected, schema);
41 |
42 | assertEquals(actual, expected);
43 | }
44 |
45 | @Test
46 | public void testExpandWhenOneNestedList() {
47 | List schema = Arrays.asList(new Object(), Arrays.asList(new Object(), new Object(), new Object()), new Object(), new Object());
48 |
49 | List list = Arrays.asList(
50 | (List) Arrays.asList("1", Arrays.asList("21", "22", "23"), "3", "4")
51 | );
52 |
53 | List expected = Arrays.asList(
54 | Arrays.asList("1", "21", "3", "4"),
55 | Arrays.asList("1", "22", "3", "4"),
56 | Arrays.asList("1", "23", "3", "4")
57 | );
58 |
59 | List actual = PartnerResultToCrtesianTable.expand(list, schema);
60 |
61 | assertEquals(actual, expected);
62 | }
63 |
64 | @Test
65 | public void testExpandWhenTwoNestedListAndOneRow() {
66 | List schema = Arrays.asList(new Object(), Arrays.asList(new Object(), new Object()), new Object(), Arrays.asList(new Object(), new Object()));
67 |
68 | List list = Arrays.asList(
69 | (List) Arrays.asList(11, Arrays.asList(Arrays.asList(1, 2), Arrays.asList(3, 4)), 12, Arrays.asList(Arrays.asList(5, 6), Arrays.asList(7, 8)))
70 | );
71 |
72 | List expected = Arrays.asList(
73 | Arrays.asList(11, 1, 2, 12, 5, 6),
74 | Arrays.asList(11, 3, 4, 12, 5, 6),
75 | Arrays.asList(11, 1, 2, 12, 7, 8),
76 | Arrays.asList(11, 3, 4, 12, 7, 8)
77 | );
78 |
79 | List actual = PartnerResultToCrtesianTable.expand(list, schema);
80 |
81 | assertEquals(expected.size(), actual.size());
82 | for (List l : expected) {
83 | assertTrue(actual.contains(l));
84 | }
85 | }
86 |
87 | @Test
88 | public void testExpandWhenOneNestedListAndTwoRows() {
89 | List schema = Arrays.asList(new Object(), Arrays.asList(new Object(), new Object()), new Object(), new Object());
90 |
91 | List list = Arrays.asList(
92 | Arrays.asList(11, Arrays.asList(Arrays.asList(1, 2), Arrays.asList(3, 4)), 12, 13),
93 | Arrays.asList(20, Arrays.asList(Arrays.asList(21, 22), Arrays.asList(23, 24), Arrays.asList(25, 26)), 41, 42)
94 | );
95 |
96 | List expected = Arrays.asList(
97 | Arrays.asList(11, 1, 2, 12, 13),
98 | Arrays.asList(11, 3, 4, 12, 13),
99 | Arrays.asList(20, 21, 22, 41, 42),
100 | Arrays.asList(20, 23, 24, 41, 42),
101 | Arrays.asList(20, 25, 26, 41, 42)
102 | );
103 |
104 | List actual = PartnerResultToCrtesianTable.expand(list, schema);
105 |
106 | assertEquals(actual, expected);
107 | }
108 |
109 | @Test
110 | public void testExpandWhenOneNestedListIsEmpty() {
111 | List schema = Arrays.asList(new Object(), Arrays.asList(new Object(), new Object()), new Object(), new Object());
112 |
113 | List list = Arrays.asList(
114 | (List) Arrays.asList(11, new ArrayList(), 12, 13)
115 | );
116 |
117 | List expected = Arrays.asList(
118 | (List) Arrays.asList(11, null, null, 12, 13)
119 | );
120 |
121 | List actual = PartnerResultToCrtesianTable.expand(list, schema);
122 |
123 | assertEquals(actual, expected);
124 | }
125 |
126 | @Test
127 | public void testExpandWhenNestedListIsEmpty() {
128 | List schema = Arrays.asList(new Object(), Arrays.asList(new Object(), new Object()));
129 |
130 | List list = Arrays.asList(
131 | (List) Arrays.asList(11, new Object())
132 | );
133 |
134 | List expected = Arrays.asList(
135 | (List) Arrays.asList(11, null, null)
136 | );
137 |
138 | List actual = PartnerResultToCrtesianTable.expand(list, schema);
139 |
140 | assertEquals(actual, expected);
141 | }
142 |
143 | }
144 |
--------------------------------------------------------------------------------
/sf-jdbc-driver/src/test/java/com/ascendix/jdbc/salesforce/metadata/ForceDatabaseMetaDataTest.java:
--------------------------------------------------------------------------------
1 | package com.ascendix.jdbc.salesforce.metadata;
2 |
3 | import com.ascendix.jdbc.salesforce.metadata.ForceDatabaseMetaData;
4 | import org.junit.Test;
5 |
6 | import java.sql.Types;
7 |
8 | import static org.junit.Assert.assertEquals;
9 |
10 | public class ForceDatabaseMetaDataTest {
11 |
12 | @Test
13 | public void testLookupTypeInfo() {
14 | ForceDatabaseMetaData.TypeInfo actual = ForceDatabaseMetaData.lookupTypeInfo("int");
15 |
16 | assertEquals("int", actual.typeName);
17 | assertEquals(Types.INTEGER, actual.sqlDataType);
18 | }
19 |
20 | @Test
21 | public void testLookupTypeInfo_IfTypeUnknown() {
22 | ForceDatabaseMetaData.TypeInfo actual = ForceDatabaseMetaData.lookupTypeInfo("my strange type");
23 |
24 | assertEquals("other", actual.typeName);
25 | assertEquals(Types.OTHER, actual.sqlDataType);
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/sf-jdbc-driver/src/test/java/com/ascendix/jdbc/salesforce/resultset/CachedResultSetTest.java:
--------------------------------------------------------------------------------
1 | package com.ascendix.jdbc.salesforce.resultset;
2 |
3 | import com.ascendix.jdbc.salesforce.metadata.ColumnMap;
4 | import com.ascendix.jdbc.salesforce.resultset.CachedResultSet;
5 | import org.junit.Before;
6 | import org.junit.Test;
7 |
8 | import java.util.Calendar;
9 | import java.util.Date;
10 |
11 | import static org.junit.Assert.assertEquals;
12 |
13 | public class CachedResultSetTest {
14 |
15 | private CachedResultSet cachedResultSet;
16 |
17 | @Before
18 | public void setUp() {
19 | ColumnMap columnMap = new ColumnMap<>();
20 | cachedResultSet = new CachedResultSet(columnMap);
21 | }
22 |
23 | @Test
24 | public void testParseDate() {
25 | Date actual = cachedResultSet.parseDate("2017-06-23");
26 |
27 | Calendar calendar = Calendar.getInstance();
28 | calendar.setTime(actual);
29 |
30 | assertEquals(2017, calendar.get(Calendar.YEAR));
31 | assertEquals(Calendar.JUNE, calendar.get(Calendar.MONTH));
32 | assertEquals(23, calendar.get(Calendar.DAY_OF_MONTH));
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/sf-jdbc-driver/src/test/java/com/ascendix/jdbc/salesforce/statement/ForcePreparedStatementTest.java:
--------------------------------------------------------------------------------
1 | package com.ascendix.jdbc.salesforce.statement;
2 |
3 | import com.ascendix.jdbc.salesforce.statement.ForcePreparedStatement;
4 | import org.junit.Test;
5 |
6 | import java.math.BigDecimal;
7 | import java.text.SimpleDateFormat;
8 | import java.util.GregorianCalendar;
9 | import java.util.List;
10 |
11 | import static org.junit.Assert.assertEquals;
12 | import static org.junit.Assert.assertNull;
13 |
14 | public class ForcePreparedStatementTest {
15 |
16 | @Test
17 | public void testGetParamClass() {
18 | assertEquals(String.class, ForcePreparedStatement.getParamClass("test"));
19 | assertEquals(Long.class, ForcePreparedStatement.getParamClass(1L));
20 | assertEquals(Object.class, ForcePreparedStatement.getParamClass(new SimpleDateFormat()));
21 | assertNull(ForcePreparedStatement.getParamClass(null));
22 | }
23 |
24 | @Test
25 | public void testToSoqlStringParam() {
26 | assertEquals("'\\''", ForcePreparedStatement.toSoqlStringParam("'"));
27 | assertEquals("'\\\\'", ForcePreparedStatement.toSoqlStringParam("\\"));
28 | assertEquals("'\\';DELETE DATABASE \\\\a'", ForcePreparedStatement.toSoqlStringParam("';DELETE DATABASE \\a"));
29 | }
30 |
31 | @Test
32 | public void testConvertToSoqlParam() {
33 | assertEquals("123.45", ForcePreparedStatement.convertToSoqlParam(123.45));
34 | assertEquals("123.45", ForcePreparedStatement.convertToSoqlParam(123.45f));
35 | assertEquals("123", ForcePreparedStatement.convertToSoqlParam(123L));
36 | assertEquals("123.45", ForcePreparedStatement.convertToSoqlParam(new BigDecimal("123.45")));
37 | assertEquals("2017-03-06T12:34:56", ForcePreparedStatement.convertToSoqlParam(new GregorianCalendar(2017, 2, 6, 12, 34, 56).getTime()));
38 | assertEquals("'\\'test\\'\\\\'", ForcePreparedStatement.convertToSoqlParam("'test'\\"));
39 | assertEquals("NULL", ForcePreparedStatement.convertToSoqlParam(null));
40 | }
41 |
42 | @Test
43 | public void testAddParameter() {
44 | ForcePreparedStatement statement = new ForcePreparedStatement(null, "");
45 | statement.addParameter(1, "one");
46 | statement.addParameter(3, "two");
47 |
48 | List actuals = statement.getParameters();
49 |
50 | assertEquals(3, actuals.size());
51 | assertEquals("one", actuals.get(0));
52 | assertEquals("two", actuals.get(2));
53 | assertNull(actuals.get(1));
54 | }
55 |
56 | @Test
57 | public void testSetParams() {
58 | ForcePreparedStatement statement = new ForcePreparedStatement(null, "");
59 | String query = "SELECT Something FROM Anything WERE name = ? AND age > ?";
60 | statement.addParameter(1, "one");
61 | statement.addParameter(2, 123);
62 |
63 | String actual = statement.setParams(query);
64 |
65 | assertEquals("SELECT Something FROM Anything WERE name = 'one' AND age > 123", actual);
66 | }
67 |
68 |
69 | @Test
70 | public void testGetCacheMode() {
71 | ForcePreparedStatement statement = new ForcePreparedStatement(null, "");
72 |
73 | assertEquals(ForcePreparedStatement.CacheMode.SESSION, statement.getCacheMode("CACHE SESSION select name from Account"));
74 | assertEquals(ForcePreparedStatement.CacheMode.GLOBAL, statement.getCacheMode(" Cache global select name from Account"));
75 | assertEquals(ForcePreparedStatement.CacheMode.NO_CACHE, statement.getCacheMode("select name from Account"));
76 | assertEquals(ForcePreparedStatement.CacheMode.NO_CACHE, statement.getCacheMode(" Cache unknown select name from Account"));
77 | }
78 |
79 | @Test
80 | public void removeCacheHints() {
81 | ForcePreparedStatement statement = new ForcePreparedStatement(null, "");
82 | assertEquals(" select name from Account", statement.removeCacheHints(" Cache global select name from Account"));
83 | }
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/sf-jdbc-driver/src/test/java/com/ascendix/jdbc/salesforce/statement/SoqlQueryAnalyzerTest.java:
--------------------------------------------------------------------------------
1 | package com.ascendix.jdbc.salesforce.statement;
2 |
3 | import com.sforce.soap.partner.DescribeSObjectResult;
4 | import com.thoughtworks.xstream.XStream;
5 | import org.junit.Test;
6 |
7 | import java.io.IOException;
8 | import java.nio.file.Files;
9 | import java.nio.file.Paths;
10 | import java.util.Arrays;
11 | import java.util.List;
12 | import java.util.stream.Collectors;
13 |
14 | import static org.junit.Assert.assertEquals;
15 |
16 | public class SoqlQueryAnalyzerTest {
17 |
18 | @Test
19 | public void testGetFieldNames_SimpleQuery() {
20 | SoqlQueryAnalyzer analyzer = new SoqlQueryAnalyzer(" select Id ,Name \r\nfrom Account\r\n where something = 'nothing' ", n -> this.describeSObject(n));
21 | List expecteds = Arrays.asList("Id", "Name");
22 | List actuals = listFlatFieldNames(analyzer);
23 |
24 | assertEquals(expecteds, actuals);
25 | }
26 |
27 | private List listFlatFieldNames(SoqlQueryAnalyzer analyzer) {
28 | return listFlatFieldDefinitions(analyzer.getFieldDefinitions()).stream()
29 | .map(FieldDef::getName)
30 | .collect(Collectors.toList());
31 | }
32 |
33 | @Test
34 | public void testGetFieldNames_SelectWithReferences() {
35 | SoqlQueryAnalyzer analyzer = new SoqlQueryAnalyzer(" select Id , Account.Name \r\nfrom Contact\r\n where something = 'nothing' ", n -> this.describeSObject(n));
36 | List expecteds = Arrays.asList("Id", "Name");
37 | List actuals = listFlatFieldNames(analyzer);
38 |
39 | assertEquals(expecteds, actuals);
40 | }
41 |
42 | @Test
43 | public void testGetFieldNames_SelectWithAggregateAliased() {
44 | SoqlQueryAnalyzer analyzer = new SoqlQueryAnalyzer(" select Id , Account.Name, count(id) aggrAlias1\r\nfrom Contact\r\n where something = 'nothing' ", n -> this.describeSObject(n));
45 | List expecteds = Arrays.asList("Id", "Name", "aggrAlias1");
46 | List actuals = listFlatFieldNames(analyzer);
47 |
48 | assertEquals(expecteds, actuals);
49 | }
50 |
51 | @Test
52 | public void testGetFieldNames_SelectWithAggregate() {
53 | SoqlQueryAnalyzer analyzer = new SoqlQueryAnalyzer(" select Id , Account.Name, count(id)\r\nfrom Contact\r\n where something = 'nothing' ", n -> this.describeSObject(n));
54 | List expecteds = Arrays.asList("Id", "Name", "count");
55 | List actuals = listFlatFieldNames(analyzer);
56 |
57 | assertEquals(expecteds, actuals);
58 | }
59 |
60 | @Test
61 | public void testGetFromObjectName() {
62 | SoqlQueryAnalyzer analyzer = new SoqlQueryAnalyzer(" select Id , Account.Name \r\nfrom Contact\r\n where something = 'nothing' ", n -> this.describeSObject(n));
63 | String expected = "Contact";
64 | String actual = analyzer.getFromObjectName();
65 |
66 | assertEquals(expected, actual);
67 | }
68 |
69 | private List listFlatFieldDefinitions(List> fieldDefs) {
70 | return (List) fieldDefs.stream()
71 | .flatMap(def -> def instanceof List
72 | ? ((List) def).stream()
73 | : Arrays.asList(def).stream())
74 | .collect(Collectors.toList());
75 | }
76 |
77 | @Test
78 | public void testGetSimpleFieldDefinitions() {
79 | SoqlQueryAnalyzer analyzer = new SoqlQueryAnalyzer("SELECT Id, Name FROM Account", n -> this.describeSObject(n));
80 |
81 | List actuals = listFlatFieldDefinitions(analyzer.getFieldDefinitions());
82 | assertEquals(2, actuals.size());
83 | assertEquals("Id", actuals.get(0).getName());
84 | assertEquals("id", actuals.get(0).getType());
85 |
86 | assertEquals("Name", actuals.get(1).getName());
87 | assertEquals("string", actuals.get(1).getType());
88 | }
89 |
90 | @Test
91 | public void testGetReferenceFieldDefinitions() {
92 | SoqlQueryAnalyzer analyzer = new SoqlQueryAnalyzer("SELECT Account.Name FROM Contact", n -> this.describeSObject(n));
93 |
94 | List actuals = listFlatFieldDefinitions(analyzer.getFieldDefinitions());
95 | assertEquals(1, actuals.size());
96 | assertEquals("Name", actuals.get(0).getName());
97 | assertEquals("string", actuals.get(0).getType());
98 | }
99 |
100 | @Test
101 | public void testGetAggregateFieldDefinition() {
102 | SoqlQueryAnalyzer analyzer = new SoqlQueryAnalyzer("SELECT MIN(Name) FROM Contact", n -> this.describeSObject(n));
103 |
104 | List actuals = listFlatFieldDefinitions(analyzer.getFieldDefinitions());
105 | assertEquals(1, actuals.size());
106 | assertEquals("MIN", actuals.get(0).getName());
107 | assertEquals("string", actuals.get(0).getType());
108 | }
109 |
110 | @Test
111 | public void testGetAggregateFieldDefinitionWithoutParameter() {
112 | SoqlQueryAnalyzer analyzer = new SoqlQueryAnalyzer("SELECT Count() FROM Contact", n -> this.describeSObject(n));
113 |
114 | List actuals = listFlatFieldDefinitions(analyzer.getFieldDefinitions());
115 | assertEquals(1, actuals.size());
116 | assertEquals("Count", actuals.get(0).getName());
117 | assertEquals("int", actuals.get(0).getType());
118 | }
119 |
120 | @Test
121 | public void testGetSimpleFieldWithQualifier() {
122 | SoqlQueryAnalyzer analyzer = new SoqlQueryAnalyzer("SELECT Contact.Id FROM Contact", n -> this.describeSObject(n));
123 |
124 | List actuals = listFlatFieldDefinitions(analyzer.getFieldDefinitions());
125 | assertEquals(1, actuals.size());
126 | assertEquals("Id", actuals.get(0).getName());
127 | assertEquals("id", actuals.get(0).getType());
128 | }
129 |
130 | @Test
131 | public void testGetNamedAggregateFieldDefinitions() {
132 | SoqlQueryAnalyzer analyzer = new SoqlQueryAnalyzer("SELECT count(Name) nameCount FROM Account", n -> this.describeSObject(n));
133 |
134 | List actuals = listFlatFieldDefinitions(analyzer.getFieldDefinitions());
135 |
136 | assertEquals(1, actuals.size());
137 | assertEquals("nameCount", actuals.get(0).getName());
138 | assertEquals("int", actuals.get(0).getType());
139 | }
140 |
141 | private DescribeSObjectResult describeSObject(String sObjectType) {
142 | try {
143 | String xml = new String(Files.readAllBytes(Paths.get("src/test/resources/" + sObjectType + "_desription.xml")));
144 | XStream xstream = new XStream();
145 | return (DescribeSObjectResult) xstream.fromXML(xml);
146 | } catch (IOException e) {
147 | throw new RuntimeException(e);
148 | }
149 | }
150 |
151 | @Test
152 | public void testFetchFieldDefinitions_WithIncludedSeslect() {
153 | SoqlQueryAnalyzer analyzer = new SoqlQueryAnalyzer("SELECT Name, (SELECT Id, max(LastName) maxLastName FROM Contacts), Id FROM Account", n -> this.describeSObject(n));
154 |
155 | List actuals = analyzer.getFieldDefinitions();
156 |
157 | assertEquals(3, actuals.size());
158 | FieldDef fieldDef = (FieldDef) actuals.get(0);
159 | assertEquals("Name", fieldDef.getName());
160 | assertEquals("string", fieldDef.getType());
161 |
162 | List suqueryDef = (List) actuals.get(1);
163 | fieldDef = (FieldDef) suqueryDef.get(0);
164 | assertEquals("Id", fieldDef.getName());
165 | assertEquals("id", fieldDef.getType());
166 |
167 | fieldDef = (FieldDef) suqueryDef.get(1);
168 | assertEquals("maxLastName", fieldDef.getName());
169 | assertEquals("string", fieldDef.getType());
170 |
171 | fieldDef = (FieldDef) actuals.get(2);
172 | assertEquals("Id", fieldDef.getName());
173 | assertEquals("id", fieldDef.getType());
174 | }
175 |
176 | }
177 |
--------------------------------------------------------------------------------
/sf-jdbc-driver/src/test/java/util/DBTablePrinter.java:
--------------------------------------------------------------------------------
1 | /*
2 | Database Table Printer
3 | Copyright (C) 2014 Hami Galip Torun
4 |
5 | Email: hamitorun@e-fabrika.net
6 | Project Home: https://github.com/htorun/dbtableprinter
7 |
8 | This program is free software: you can redistribute it and/or modify
9 | it under the terms of the GNU General Public License as published by
10 | the Free Software Foundation, either version 3 of the License, or
11 | (at your option) any later version.
12 |
13 | This program is distributed in the hope that it will be useful,
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | GNU General Public License for more details.
17 |
18 | You should have received a copy of the GNU General Public License
19 | along with this program. If not, see .
20 | */
21 |
22 | /*
23 | This is my first Java program that does something more or less
24 | useful. It is part of my effort to learn Java, how to use
25 | an IDE (IntelliJ IDEA 13.1.15 in this case), how to apply an
26 | open source license and how to use Git and GitHub (https://github.com)
27 | for version control and publishing an open source software.
28 |
29 | Hami
30 | */
31 |
32 | package net.efabrika.util;
33 |
34 | import java.sql.Connection;
35 | import java.sql.ResultSet;
36 | import java.sql.ResultSetMetaData;
37 | import java.sql.SQLException;
38 | import java.sql.Statement;
39 | import java.sql.Types;
40 | import java.util.ArrayList;
41 | import java.util.List;
42 | import java.util.StringJoiner;
43 |
44 | /**
45 | * Just a utility to print rows from a given DB table or a
46 | * ResultSet
to standard out, formatted to look
47 | * like a table with rows and columns with borders.
48 | *
49 | * Stack Overflow website
50 | * (stackoverflow.com )
51 | * was the primary source of inspiration and help to put this
52 | * code together. Especially the questions and answers of
53 | * the following people were very useful:
54 | *
55 | * Question:
56 | * How to display or
57 | * print the contents of a database table as is
58 | * People: sky scraper
59 | *
60 | * Question:
61 | * System.out.println()
62 | * from database into a table
63 | * People: Simon Cottrill, Tony Toews, Costis Aivali, Riggy, corsiKa
64 | *
65 | * Question:
66 | * Simple way to repeat
67 | * a string in java
68 | * People: Everybody who contributed but especially user102008
69 | */
70 | public class DBTablePrinter {
71 |
72 | /**
73 | * Default maximum number of rows to query and print.
74 | */
75 | private static final int DEFAULT_MAX_ROWS = 10;
76 |
77 | /**
78 | * Default maximum width for text columns
79 | * (like a VARCHAR
) column.
80 | */
81 | private static final int DEFAULT_MAX_TEXT_COL_WIDTH = 150;
82 |
83 | /**
84 | * Column type category for CHAR
, VARCHAR
85 | * and similar text columns.
86 | */
87 | public static final int CATEGORY_STRING = 1;
88 |
89 | /**
90 | * Column type category for TINYINT
, SMALLINT
,
91 | * INT
and BIGINT
columns.
92 | */
93 | public static final int CATEGORY_INTEGER = 2;
94 |
95 | /**
96 | * Column type category for REAL
, DOUBLE
,
97 | * and DECIMAL
columns.
98 | */
99 | public static final int CATEGORY_DOUBLE = 3;
100 |
101 | /**
102 | * Column type category for date and time related columns like
103 | * DATE
, TIME
, TIMESTAMP
etc.
104 | */
105 | public static final int CATEGORY_DATETIME = 4;
106 |
107 | /**
108 | * Column type category for BOOLEAN
columns.
109 | */
110 | public static final int CATEGORY_BOOLEAN = 5;
111 |
112 | /**
113 | * Column type category for types for which the type name
114 | * will be printed instead of the content, like BLOB
,
115 | * BINARY
, ARRAY
etc.
116 | */
117 | public static final int CATEGORY_OTHER = 0;
118 |
119 | /**
120 | * Represents a database table column.
121 | */
122 | private static class Column {
123 |
124 | /**
125 | * Column label.
126 | */
127 | private String label;
128 |
129 | /**
130 | * Generic SQL type of the column as defined in
131 | *
133 | * java.sql.Types
134 | * .
135 | */
136 | private int type;
137 |
138 | /**
139 | * Generic SQL type name of the column as defined in
140 | *
142 | * java.sql.Types
143 | * .
144 | */
145 | private String typeName;
146 |
147 | /**
148 | * Width of the column that will be adjusted according to column label
149 | * and values to be printed.
150 | */
151 | private int width = 0;
152 |
153 | /**
154 | * Column values from each row of a ResultSet
.
155 | */
156 | private List values = new ArrayList<>();
157 |
158 | /**
159 | * Flag for text justification using String.format
.
160 | * Empty string (""
) to justify right,
161 | * dash (-
) to justify left.
162 | *
163 | * @see #justifyLeft()
164 | */
165 | private String justifyFlag = "";
166 |
167 | /**
168 | * Column type category. The columns will be categorised according
169 | * to their column types and specific needs to print them correctly.
170 | */
171 | private int typeCategory = 0;
172 |
173 | /**
174 | * Constructs a new Column
with a column label,
175 | * generic SQL type and type name (as defined in
176 | *
178 | * java.sql.Types
179 | * )
180 | *
181 | * @param label Column label or name
182 | * @param type Generic SQL type
183 | * @param typeName Generic SQL type name
184 | */
185 | public Column(String label, int type, String typeName) {
186 | this.label = label;
187 | this.type = type;
188 | this.typeName = typeName;
189 | }
190 |
191 | /**
192 | * Returns the column label
193 | *
194 | * @return Column label
195 | */
196 | public String getLabel() {
197 | return label;
198 | }
199 |
200 | /**
201 | * Returns the generic SQL type of the column
202 | *
203 | * @return Generic SQL type
204 | */
205 | public int getType() {
206 | return type;
207 | }
208 |
209 | /**
210 | * Returns the generic SQL type name of the column
211 | *
212 | * @return Generic SQL type name
213 | */
214 | public String getTypeName() {
215 | return typeName;
216 | }
217 |
218 | /**
219 | * Returns the width of the column
220 | *
221 | * @return Column width
222 | */
223 | public int getWidth() {
224 | return width;
225 | }
226 |
227 | /**
228 | * Sets the width of the column to width
229 | *
230 | * @param width Width of the column
231 | */
232 | public void setWidth(int width) {
233 | this.width = width;
234 | }
235 |
236 | /**
237 | * Adds a String
representation (value
)
238 | * of a value to this column object's {@link #values} list.
239 | * These values will come from each row of a
240 | *
242 | * ResultSet
243 | * of a database query.
244 | *
245 | * @param value The column value to add to {@link #values}
246 | */
247 | public void addValue(String value) {
248 | values.add(value);
249 | }
250 |
251 | /**
252 | * Returns the column value at row index i
.
253 | * Note that the index starts at 0 so that getValue(0)
254 | * will get the value for this column from the first row
255 | * of a
257 | * ResultSet .
258 | *
259 | * @param i The index of the column value to get
260 | * @return The String representation of the value
261 | */
262 | public String getValue(int i) {
263 | return values.get(i);
264 | }
265 |
266 | /**
267 | * Returns the value of the {@link #justifyFlag}. The column
268 | * values will be printed using String.format
and
269 | * this flag will be used to right or left justify the text.
270 | *
271 | * @return The {@link #justifyFlag} of this column
272 | * @see #justifyLeft()
273 | */
274 | public String getJustifyFlag() {
275 | return justifyFlag;
276 | }
277 |
278 | /**
279 | * Sets {@link #justifyFlag} to "-"
so that
280 | * the column value will be left justified when printed with
281 | * String.format
. Typically numbers will be right
282 | * justified and text will be left justified.
283 | */
284 | public void justifyLeft() {
285 | this.justifyFlag = "-";
286 | }
287 |
288 | /**
289 | * Returns the generic SQL type category of the column
290 | *
291 | * @return The {@link #typeCategory} of the column
292 | */
293 | public int getTypeCategory() {
294 | return typeCategory;
295 | }
296 |
297 | /**
298 | * Sets the {@link #typeCategory} of the column
299 | *
300 | * @param typeCategory The type category
301 | */
302 | public void setTypeCategory(int typeCategory) {
303 | this.typeCategory = typeCategory;
304 | }
305 | }
306 |
307 | /**
308 | * Overloaded method that prints rows from table tableName
309 | * to standard out using the given database connection
310 | * conn
. Total number of rows will be limited to
311 | * {@link #DEFAULT_MAX_ROWS} and
312 | * {@link #DEFAULT_MAX_TEXT_COL_WIDTH} will be used to limit
313 | * the width of text columns (like a VARCHAR
column).
314 | *
315 | * @param conn Database connection object (java.sql.Connection)
316 | * @param tableName Name of the database table
317 | */
318 | public static void printTable(Connection conn, String tableName) {
319 | printTable(conn, tableName, DEFAULT_MAX_ROWS, DEFAULT_MAX_TEXT_COL_WIDTH);
320 | }
321 |
322 | /**
323 | * Overloaded method that prints rows from table tableName
324 | * to standard out using the given database connection
325 | * conn
. Total number of rows will be limited to
326 | * maxRows
and
327 | * {@link #DEFAULT_MAX_TEXT_COL_WIDTH} will be used to limit
328 | * the width of text columns (like a VARCHAR
column).
329 | *
330 | * @param conn Database connection object (java.sql.Connection)
331 | * @param tableName Name of the database table
332 | * @param maxRows Number of max. rows to query and print
333 | */
334 | public static void printTable(Connection conn, String tableName, int maxRows) {
335 | printTable(conn, tableName, maxRows, DEFAULT_MAX_TEXT_COL_WIDTH);
336 | }
337 |
338 | /**
339 | * Overloaded method that prints rows from table tableName
340 | * to standard out using the given database connection
341 | * conn
. Total number of rows will be limited to
342 | * maxRows
and
343 | * maxStringColWidth
will be used to limit
344 | * the width of text columns (like a VARCHAR
column).
345 | *
346 | * @param conn Database connection object (java.sql.Connection)
347 | * @param tableName Name of the database table
348 | * @param maxRows Number of max. rows to query and print
349 | * @param maxStringColWidth Max. width of text columns
350 | */
351 | public static void printTable(Connection conn, String tableName, int maxRows, int maxStringColWidth) {
352 | if (conn == null) {
353 | System.err.println("DBTablePrinter Error: No connection to database (Connection is null)!");
354 | return;
355 | }
356 | if (tableName == null) {
357 | System.err.println("DBTablePrinter Error: No table name (tableName is null)!");
358 | return;
359 | }
360 | if (tableName.length() == 0) {
361 | System.err.println("DBTablePrinter Error: Empty table name!");
362 | return;
363 | }
364 | if (maxRows < 1) {
365 | System.err.println("DBTablePrinter Info: Invalid max. rows number. Using default!");
366 | maxRows = DEFAULT_MAX_ROWS;
367 | }
368 |
369 | Statement stmt = null;
370 | ResultSet rs = null;
371 | try {
372 | if (conn.isClosed()) {
373 | System.err.println("DBTablePrinter Error: Connection is closed!");
374 | return;
375 | }
376 |
377 | String sqlSelectAll = "SELECT * FROM " + tableName + " LIMIT " + maxRows;
378 | stmt = conn.createStatement();
379 | rs = stmt.executeQuery(sqlSelectAll);
380 |
381 | printResultSet(rs, maxStringColWidth);
382 |
383 | } catch (SQLException e) {
384 | System.err.println("SQL exception in DBTablePrinter. Message:");
385 | System.err.println(e.getMessage());
386 | } finally {
387 | try {
388 | if (stmt != null) {
389 | stmt.close();
390 | }
391 | if (rs != null) {
392 | rs.close();
393 | }
394 | } catch (SQLException ignore) {
395 | // ignore
396 | }
397 | }
398 | }
399 |
400 | /**
401 | * Overloaded method to print rows of a
403 | * ResultSet to standard out using {@link #DEFAULT_MAX_TEXT_COL_WIDTH}
404 | * to limit the width of text columns.
405 | *
406 | * @param rs The ResultSet
to print
407 | */
408 | public static void printResultSet(ResultSet rs) {
409 | printResultSet(rs, DEFAULT_MAX_TEXT_COL_WIDTH);
410 | }
411 |
412 | /**
413 | * Overloaded method to print rows of a
415 | * ResultSet to standard out using maxStringColWidth
416 | * to limit the width of text columns.
417 | *
418 | * @param rs The ResultSet
to print
419 | * @param maxStringColWidth Max. width of text columns
420 | */
421 | public static void printResultSet(ResultSet rs, int maxStringColWidth) {
422 | try {
423 | if (rs == null) {
424 | System.err.println("DBTablePrinter Error: Result set is null!");
425 | return;
426 | }
427 | if (rs.isClosed()) {
428 | System.err.println("DBTablePrinter Error: Result Set is closed!");
429 | return;
430 | }
431 | if (maxStringColWidth < 1) {
432 | System.err.println("DBTablePrinter Info: Invalid max. varchar column width. Using default!");
433 | maxStringColWidth = DEFAULT_MAX_TEXT_COL_WIDTH;
434 | }
435 |
436 | // Get the meta data object of this ResultSet.
437 | ResultSetMetaData rsmd;
438 | rsmd = rs.getMetaData();
439 |
440 | // Total number of columns in this ResultSet
441 | int columnCount = rsmd.getColumnCount();
442 |
443 | // List of Column objects to store each columns of the ResultSet
444 | // and the String representation of their values.
445 | List columns = new ArrayList<>(columnCount);
446 |
447 | // List of table names. Can be more than one if it is a joined
448 | // table query
449 | List tableNames = new ArrayList<>(columnCount);
450 |
451 | // Get the columns and their meta data.
452 | // NOTE: columnIndex for rsmd.getXXX methods STARTS AT 1 NOT 0
453 | for (int i = 1; i <= columnCount; i++) {
454 | Column c = new Column(rsmd.getColumnLabel(i),
455 | rsmd.getColumnType(i), rsmd.getColumnTypeName(i));
456 | c.setWidth(c.getLabel().length());
457 | c.setTypeCategory(whichCategory(c.getType()));
458 | columns.add(c);
459 |
460 | if (!tableNames.contains(rsmd.getTableName(i))) {
461 | tableNames.add(rsmd.getTableName(i));
462 | }
463 | }
464 |
465 | // Go through each row, get values of each column and adjust
466 | // column widths.
467 | int rowCount = 0;
468 | while (rs.next()) {
469 |
470 | System.out.println("row: " + rowCount);
471 |
472 | // NOTE: columnIndex for rs.getXXX methods STARTS AT 1 NOT 0
473 | for (int i = 0; i < columnCount; i++) {
474 | Column c = columns.get(i);
475 | String value;
476 | int category = c.getTypeCategory();
477 |
478 | if (category == CATEGORY_OTHER) {
479 |
480 | // Use generic SQL type name instead of the actual value
481 | // for column types BLOB, BINARY etc.
482 | value = "(" + c.getTypeName() + ")";
483 |
484 | } else {
485 | value = rs.getString(i + 1) == null ? "NULL" : rs.getString(i + 1);
486 | }
487 | switch (category) {
488 | case CATEGORY_DOUBLE:
489 |
490 | // For real numbers, format the string value to have 3 digits
491 | // after the point. THIS IS TOTALLY ARBITRARY and can be
492 | // improved to be CONFIGURABLE.
493 | if (!value.equals("NULL")) {
494 | Double dValue = rs.getDouble(i + 1);
495 | value = String.format("%.3f", dValue);
496 | }
497 | break;
498 |
499 | case CATEGORY_STRING:
500 |
501 | // Left justify the text columns
502 | c.justifyLeft();
503 |
504 | // and apply the width limit
505 | if (value.length() > maxStringColWidth) {
506 | value = value.substring(0, maxStringColWidth - 3) + "...";
507 | }
508 | break;
509 | }
510 |
511 | // Adjust the column width
512 | c.setWidth(value.length() > c.getWidth() ? value.length() : c.getWidth());
513 | c.addValue(value);
514 | } // END of for loop columnCount
515 | rowCount++;
516 |
517 | } // END of while (rs.next)
518 |
519 | /*
520 | At this point we have gone through meta data, get the
521 | columns and created all Column objects, iterated over the
522 | ResultSet rows, populated the column values and adjusted
523 | the column widths.
524 |
525 | We cannot start printing just yet because we have to prepare
526 | a row separator String.
527 | */
528 |
529 | // For the fun of it, I will use StringBuilder
530 | StringBuilder strToPrint = new StringBuilder();
531 | StringBuilder rowSeparator = new StringBuilder();
532 |
533 | /*
534 | Prepare column labels to print as well as the row separator.
535 | It should look something like this:
536 | +--------+------------+------------+-----------+ (row separator)
537 | | EMP_NO | BIRTH_DATE | FIRST_NAME | LAST_NAME | (labels row)
538 | +--------+------------+------------+-----------+ (row separator)
539 | */
540 |
541 | // Iterate over columns
542 | for (Column c : columns) {
543 | int width = c.getWidth();
544 |
545 | // Center the column label
546 | String toPrint;
547 | String name = c.getLabel();
548 | int diff = width - name.length();
549 |
550 | if ((diff % 2) == 1) {
551 | // diff is not divisible by 2, add 1 to width (and diff)
552 | // so that we can have equal padding to the left and right
553 | // of the column label.
554 | width++;
555 | diff++;
556 | c.setWidth(width);
557 | }
558 |
559 | int paddingSize = diff / 2; // InteliJ says casting to int is redundant.
560 |
561 | // Cool String repeater code thanks to user102008 at stackoverflow.com
562 | // (http://tinyurl.com/7x9qtyg) "Simple way to repeat a string in java"
563 | String padding = new String(new char[paddingSize]).replace("\0", " ");
564 |
565 | toPrint = "| " + padding + name + padding + " ";
566 | // END centering the column label
567 |
568 | strToPrint.append(toPrint);
569 |
570 | rowSeparator.append("+");
571 | rowSeparator.append(new String(new char[width + 2]).replace("\0", "-"));
572 | }
573 |
574 | String lineSeparator = System.getProperty("line.separator");
575 |
576 | // Is this really necessary ??
577 | lineSeparator = lineSeparator == null ? "\n" : lineSeparator;
578 |
579 | rowSeparator.append("+").append(lineSeparator);
580 |
581 | strToPrint.append("|").append(lineSeparator);
582 | strToPrint.insert(0, rowSeparator);
583 | strToPrint.append(rowSeparator);
584 |
585 | StringJoiner sj = new StringJoiner(", ");
586 | for (String name : tableNames) {
587 | sj.add(name);
588 | }
589 |
590 | String info = "Printing " + rowCount;
591 | info += rowCount > 1 ? " rows from " : " row from ";
592 | info += tableNames.size() > 1 ? "tables " : "table ";
593 | info += sj.toString();
594 |
595 | System.out.println(info);
596 |
597 | // Print out the formatted column labels
598 | System.out.print(strToPrint.toString());
599 |
600 | String format;
601 |
602 | // Print out the rows
603 | for (int i = 0; i < rowCount; i++) {
604 | for (Column c : columns) {
605 |
606 | // This should form a format string like: "%-60s"
607 | format = String.format("| %%%s%ds ", c.getJustifyFlag(), c.getWidth());
608 | System.out.print(
609 | String.format(format, c.getValue(i))
610 | );
611 | }
612 |
613 | System.out.println("|");
614 | System.out.print(rowSeparator);
615 | }
616 |
617 | System.out.println();
618 |
619 | /*
620 | Hopefully this should have printed something like this:
621 | +--------+------------+------------+-----------+--------+-------------+
622 | | EMP_NO | BIRTH_DATE | FIRST_NAME | LAST_NAME | GENDER | HIRE_DATE |
623 | +--------+------------+------------+-----------+--------+-------------+
624 | | 10001 | 1953-09-02 | Georgi | Facello | M | 1986-06-26 |
625 | +--------+------------+------------+-----------+--------+-------------+
626 | | 10002 | 1964-06-02 | Bezalel | Simmel | F | 1985-11-21 |
627 | +--------+------------+------------+-----------+--------+-------------+
628 | */
629 |
630 | } catch (SQLException e) {
631 | System.err.println("SQL exception in DBTablePrinter. Message:");
632 | System.err.println(e.getMessage());
633 | }
634 | }
635 |
636 | /**
637 | * Takes a generic SQL type and returns the category this type
638 | * belongs to. Types are categorized according to print formatting
639 | * needs:
640 | *
641 | * Integers should not be truncated so column widths should
642 | * be adjusted without a column width limit. Text columns should be
643 | * left justified and can be truncated to a max. column width etc...
644 | *
645 | * See also:
647 | * java.sql.Types
648 | *
649 | * @param type Generic SQL type
650 | * @return The category this type belongs to
651 | */
652 | private static int whichCategory(int type) {
653 | switch (type) {
654 | case Types.BIGINT:
655 | case Types.TINYINT:
656 | case Types.SMALLINT:
657 | case Types.INTEGER:
658 | return CATEGORY_INTEGER;
659 |
660 | case Types.REAL:
661 | case Types.DOUBLE:
662 | case Types.DECIMAL:
663 | return CATEGORY_DOUBLE;
664 |
665 | case Types.DATE:
666 | case Types.TIME:
667 | case Types.TIME_WITH_TIMEZONE:
668 | case Types.TIMESTAMP:
669 | case Types.TIMESTAMP_WITH_TIMEZONE:
670 | return CATEGORY_DATETIME;
671 |
672 | case Types.BOOLEAN:
673 | return CATEGORY_BOOLEAN;
674 |
675 | case Types.VARCHAR:
676 | case Types.NVARCHAR:
677 | case Types.LONGVARCHAR:
678 | case Types.LONGNVARCHAR:
679 | case Types.CHAR:
680 | case Types.NCHAR:
681 | return CATEGORY_STRING;
682 |
683 | default:
684 | return CATEGORY_OTHER;
685 | }
686 | }
687 | }
688 |
--------------------------------------------------------------------------------