diff --git a/pom.xml b/pom.xml index 05b921635949c..377f5b34e31bc 100644 --- a/pom.xml +++ b/pom.xml @@ -76,81 +76,79 @@ - presto-accumulo - presto-accumulo-iterators - presto-array presto-atop - presto-base-jdbc - presto-benchmark - presto-benchmark-driver - presto-benchto-benchmarks - presto-bigquery - presto-blackhole - presto-cassandra - presto-cli - presto-client - presto-docs - presto-druid - presto-elasticsearch - presto-example-http - presto-geospatial - presto-geospatial-toolkit - presto-google-sheets - presto-hive - presto-hive-hadoop2 - presto-iceberg - presto-jdbc + presto-spi + presto-array presto-jmx + presto-record-decoder presto-kafka presto-kinesis - presto-kudu - presto-local-file - presto-main - presto-matching + presto-redis + presto-accumulo-iterators + presto-accumulo + presto-cassandra + presto-blackhole presto-memory - presto-memory-context - presto-memsql - presto-ml - presto-mongodb - presto-mysql - presto-oracle presto-orc presto-parquet - presto-parser - presto-password-authenticators + presto-rcfile + presto-hive + presto-hive-hadoop2 + presto-teradata-functions + presto-example-http + presto-local-file + presto-tpch + presto-tpcds + presto-raptor-legacy + presto-base-jdbc + presto-mysql + presto-memsql presto-phoenix - presto-pinot - presto-plugin-toolkit presto-postgresql + presto-redshift + presto-sqlserver + presto-mongodb + presto-client + presto-parser + presto-main + presto-server-main + presto-ml + presto-geospatial + presto-geospatial-toolkit + presto-benchmark + presto-testing + presto-tests presto-product-tests presto-product-tests-launcher - presto-prometheus - presto-proxy - presto-raptor-legacy - presto-rcfile - presto-record-decoder - presto-redis - presto-redshift - presto-resource-group-managers + presto-jdbc + presto-cli + presto-benchmark-driver + presto-salesforce presto-server - presto-server-main presto-server-rpm + presto-docs + presto-verifier + presto-testing-server-launcher + presto-plugin-toolkit + presto-resource-group-managers + presto-password-authenticators presto-session-property-managers - presto-spi - presto-sqlserver - presto-teradata-functions - presto-test-jdbc-compatibility-old-driver - presto-test-jdbc-compatibility-old-server - presto-testing - presto-testing-kafka - presto-testng-services - presto-tests - presto-thrift + presto-benchto-benchmarks presto-thrift-api presto-thrift-testing-server - presto-tpcds - presto-tpch - presto-verifier + presto-thrift + presto-matching + presto-memory-context + presto-prometheus + presto-proxy + presto-kudu + presto-elasticsearch + presto-iceberg + presto-google-sheets + presto-bigquery + presto-pinot + presto-oracle + presto-druid diff --git a/presto-base-jdbc/src/main/java/io/prestosql/plugin/jdbc/BaseJdbcClient.java b/presto-base-jdbc/src/main/java/io/prestosql/plugin/jdbc/BaseJdbcClient.java index c3fff09c93803..80445e5721ca4 100644 --- a/presto-base-jdbc/src/main/java/io/prestosql/plugin/jdbc/BaseJdbcClient.java +++ b/presto-base-jdbc/src/main/java/io/prestosql/plugin/jdbc/BaseJdbcClient.java @@ -982,7 +982,6 @@ private static String escapeNamePattern(String name, String escape) { requireNonNull(name, "name is null"); requireNonNull(escape, "escape is null"); - checkArgument(!escape.isEmpty(), "Escape string must not be empty"); checkArgument(!escape.equals("_"), "Escape string must not be '_'"); checkArgument(!escape.equals("%"), "Escape string must not be '%'"); name = name.replace(escape, escape + escape); diff --git a/presto-docs/src/main/sphinx/connector.rst b/presto-docs/src/main/sphinx/connector.rst index 76a76872cc33f..bcc70d5989638 100644 --- a/presto-docs/src/main/sphinx/connector.rst +++ b/presto-docs/src/main/sphinx/connector.rst @@ -33,6 +33,7 @@ from different data sources. Prometheus Redis Redshift + Salesforce SQL Server System Thrift diff --git a/presto-docs/src/main/sphinx/connector/salesforce.rst b/presto-docs/src/main/sphinx/connector/salesforce.rst new file mode 100644 index 0000000000000..b155553a5370c --- /dev/null +++ b/presto-docs/src/main/sphinx/connector/salesforce.rst @@ -0,0 +1,53 @@ +==================== +Salesforce Connector +==================== + +The Salesforce connector allows querying and creating tables in an +external Salesforce instance. This can be used to join data between +different systems like Salesforce and Hive, or between two different +Salesforce instances. + +Configuration +------------- + +To configure the Salesforce connector, create a catalog properties file +in ``etc/catalog`` named, for example, ``salesforce.properties``, to +mount the Salesforce connector as the ``salesforce`` catalog. +Create the file with the following contents, replacing the +connection properties as appropriate for your setup: + +.. code-block:: none + + connector.name=salesforce + connection-url=jdbc:salesforce:// + connection-user=admin + connection-password=secret + salesforce.security-token=abc + +Querying Salesforce +------------------- + +The Salesforce connector provides single a schema named ``salesforce``. + + SHOW TABLES FROM salesforce.salesforce; + +You can see a list of the columns in the ``account`` table in the ``salesforce`` database +using either of the following:: + + DESCRIBE salesforce.salesforce.account; + SHOW COLUMNS FROM salesforce.salesforce.account; + +Finally, you can access the ``account`` table:: + + SELECT * FROM salesforce.salesforce.account; + +If you used a different name for your catalog properties file, use +that catalog name instead of ``salesforce`` in the above examples. + +Salesforce Connector Limitations +-------------------------------- + +At this time this connector is read-only. Furthermore, it fetches data +using the Salesforce synchronous API, which offers limited performance. + +Queries on the information schema can be especially expensive. diff --git a/presto-salesforce/pom.xml b/presto-salesforce/pom.xml new file mode 100644 index 0000000000000..b9a3bf66f5b1a --- /dev/null +++ b/presto-salesforce/pom.xml @@ -0,0 +1,216 @@ + + + 4.0.0 + + + io.prestosql + presto-root + 339-SNAPSHOT + + + presto-salesforce + Presto - Salesforce Connector + presto-plugin + + + ${project.parent.basedir} + true + + 1.25.0 + 3.8 + 4.1 + + 4.12 + 1.3 + + 3.7.0 + 2.5 + 2.21.0 + 3.0.1 + 3.1.0 + 3.1.0 + 2.8.2 + 2.0 + 43.0.0 + 3.0.5 + 4.2 + 1.2.2 + + + + + + mulesoft-releases + MuleSoft Releases Repository + http://repository.mulesoft.org/releases/ + default + + + + + + io.prestosql + presto-base-jdbc + + + + io.airlift + configuration + + + + com.google.guava + guava + + + + com.google.inject + guice + + + + javax.inject + javax.inject + + + + + io.prestosql + presto-spi + provided + + + + io.airlift + slice + provided + + + + com.fasterxml.jackson.core + jackson-annotations + provided + + + + org.openjdk.jol + jol-core + provided + + + + + org.testng + testng + test + + + + io.prestosql + presto-main + test + + + + + com.google.oauth-client + google-oauth-client + ${google-oauth-client.version} + + + org.apache.httpcomponents + httpclient + + + + + + com.google.http-client + google-http-client-jackson2 + ${google-oauth-client.version} + + + + org.mule.tools + salesforce-soql-parser + 2.0 + + + com.force.api + force-partner-api + ${force-partner-api.version} + + + org.mapdb + mapdb + ${mapdb.version} + + + org.apache.commons + commons-lang3 + ${commons-lang3.version} + + + org.apache.commons + commons-collections4 + ${commons-collections4.version} + + + commons-io + commons-io + 2.4 + + + com.google.http-client + google-http-client + 1.25.0 + + + com.force.api + force-wsc + 47.0.0 + + + + + junit + junit + ${junit.version} + test + + + com.opencsv + opencsv + ${opencsv.version} + test + + + com.thoughtworks.xstream + xstream + ${xstream.version} + test + + + io.airlift + log + + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + + + org.antlr:ST4 + + + + + + + + diff --git a/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/SalesforceClient.java b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/SalesforceClient.java new file mode 100644 index 0000000000000..569bd839eafbc --- /dev/null +++ b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/SalesforceClient.java @@ -0,0 +1,166 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce; + +import io.prestosql.plugin.jdbc.BaseJdbcClient; +import io.prestosql.plugin.jdbc.BaseJdbcConfig; +import io.prestosql.plugin.jdbc.ColumnMapping; +import io.prestosql.plugin.jdbc.ConnectionFactory; +import io.prestosql.plugin.jdbc.JdbcColumnHandle; +import io.prestosql.plugin.jdbc.JdbcSplit; +import io.prestosql.plugin.jdbc.JdbcTableHandle; +import io.prestosql.plugin.jdbc.JdbcTypeHandle; +import io.prestosql.plugin.jdbc.ObjectReadFunction; +import io.prestosql.plugin.jdbc.ObjectWriteFunction; +import io.prestosql.plugin.jdbc.SliceReadFunction; +import io.prestosql.spi.PrestoException; +import io.prestosql.spi.block.Block; +import io.prestosql.spi.block.BlockBuilder; +import io.prestosql.spi.connector.ConnectorSession; +import io.prestosql.spi.type.ArrayType; +import io.prestosql.spi.type.Type; +import io.prestosql.spi.type.VarcharType; + +import javax.inject.Inject; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; +import java.util.List; +import java.util.Optional; +import java.util.function.BiFunction; + +import static io.airlift.slice.Slices.utf8Slice; +import static io.prestosql.plugin.jdbc.ColumnMapping.objectMapping; +import static io.prestosql.plugin.jdbc.JdbcErrorCode.JDBC_ERROR; +import static io.prestosql.plugin.jdbc.StandardColumnMappings.doubleColumnMapping; +import static io.prestosql.plugin.jdbc.StandardColumnMappings.varcharColumnMapping; +import static io.prestosql.plugin.jdbc.StandardColumnMappings.varcharWriteFunction; +import static io.prestosql.spi.type.VarcharType.createUnboundedVarcharType; +import static io.prestosql.spi.type.VarcharType.createVarcharType; + +public class SalesforceClient + extends BaseJdbcClient +{ + private final SalesforceConfig salesforceConfig; + + @Inject + public SalesforceClient(BaseJdbcConfig baseConfig, SalesforceConfig salesforceConfig, ConnectionFactory connectionFactory) + { + super(baseConfig, "", connectionFactory); + + this.salesforceConfig = salesforceConfig; + } + + @Override + public PreparedStatement getPreparedStatement(Connection connection, String sql) + throws SQLException + { + connection.setAutoCommit(false); + PreparedStatement statement = connection.prepareStatement(sql); + + statement.setFetchSize(salesforceConfig.getFetchSize().orElse(2000)); + + return statement; + } + + @Override + protected Optional> limitFunction() + { + return Optional.of((sql, limit) -> sql + " LIMIT " + limit); + } + + @Override + protected String getTableSchemaName(ResultSet resultSet) + throws SQLException + { + return "salesforce"; + } + + @Override + public Optional toPrestoType(ConnectorSession session, Connection connection, JdbcTypeHandle typeHandle) + { + int columnSize = typeHandle.getColumnSize(); + + String jdbcTypeName = typeHandle.getJdbcTypeName() + .orElseThrow(() -> new PrestoException(JDBC_ERROR, "Type name is missing: " + typeHandle)); + + Optional mapping = getForcedMappingToVarchar(typeHandle); + if (mapping.isPresent()) { + return mapping; + } + + if (jdbcTypeName.equals("multipicklist")) { + VarcharType type = createVarcharType(typeHandle.getColumnSize()); + + if (typeHandle.getColumnSize() == 0 || typeHandle.getColumnSize() > VarcharType.MAX_LENGTH) { + type = createUnboundedVarcharType(); + } + + return Optional.of(objectMapping(new ArrayType(type), multiPicklistReadFunction(type), multiPicklistWriteFunction(type))); + } + + switch (typeHandle.getJdbcType()) { + case Types.VARCHAR: + case Types.NVARCHAR: + case Types.LONGVARCHAR: + case Types.LONGNVARCHAR: + if (columnSize == 0 || typeHandle.getColumnSize() > VarcharType.MAX_LENGTH) { + return Optional.of(varcharColumnMapping(createUnboundedVarcharType())); + } + return Optional.of(varcharColumnMapping(createVarcharType(columnSize))); + case Types.NUMERIC: + case Types.DECIMAL: + return Optional.of(doubleColumnMapping()); + case Types.OTHER: + return Optional.of(ColumnMapping.sliceMapping(VarcharType.VARCHAR, otherReadFunction(), varcharWriteFunction())); + default: + return super.toPrestoType(session, connection, typeHandle); + } + } + + @Override + public PreparedStatement buildSql(ConnectorSession session, Connection connection, JdbcSplit split, JdbcTableHandle table, List columns) + throws SQLException + { + return new SoqlQueryBuilder(identifierQuote).buildSql(this, session, connection, table.getCatalogName(), table.getSchemaName(), table.getTableName(), columns, table.getConstraint(), split.getAdditionalPredicate(), tryApplyLimit(table.getLimit())); + } + + private static ObjectReadFunction multiPicklistReadFunction(Type type) + { + return ObjectReadFunction.of(Block.class, (resultSet, columnIndex) -> { + BlockBuilder builder = createUnboundedVarcharType().createBlockBuilder(null, 1); + + for (String value : resultSet.getString(columnIndex).split(";")) { + type.writeSlice(builder, utf8Slice(value)); + } + + return builder.build(); + }); + } + + private static ObjectWriteFunction multiPicklistWriteFunction(Type type) + { + return ObjectWriteFunction.of(Block.class, (statement, index, block) -> { + // Not implemented + }); + } + + public static SliceReadFunction otherReadFunction() + { + return (resultSet, columnIndex) -> utf8Slice(resultSet.getObject(columnIndex).toString()); + } +} diff --git a/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/SalesforceClientModule.java b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/SalesforceClientModule.java new file mode 100644 index 0000000000000..e7d52a083f3f6 --- /dev/null +++ b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/SalesforceClientModule.java @@ -0,0 +1,67 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce; + +import com.google.inject.Binder; +import com.google.inject.Module; +import com.google.inject.Provides; +import com.google.inject.Scopes; +import com.google.inject.Singleton; +import io.prestosql.plugin.jdbc.BaseJdbcConfig; +import io.prestosql.plugin.jdbc.ConnectionFactory; +import io.prestosql.plugin.jdbc.DriverConnectionFactory; +import io.prestosql.plugin.jdbc.ForBaseJdbc; +import io.prestosql.plugin.jdbc.JdbcClient; +import io.prestosql.plugin.jdbc.TypeHandlingJdbcConfig; +import io.prestosql.plugin.jdbc.credential.CredentialProvider; +import io.prestosql.plugin.salesforce.driver.ForceDriver; +import io.prestosql.plugin.salesforce.driver.ForceModule; + +import java.util.Properties; + +import static io.airlift.configuration.ConfigBinder.configBinder; + +public class SalesforceClientModule + implements Module +{ + @Override + public void configure(Binder binder) + { + binder.install(new ForceModule()); + + binder.bind(JdbcClient.class) + .annotatedWith(ForBaseJdbc.class) + .to(SalesforceClient.class) + .in(Scopes.SINGLETON); + + configBinder(binder).bindConfig(BaseJdbcConfig.class); + configBinder(binder).bindConfig(TypeHandlingJdbcConfig.class); + configBinder(binder).bindConfig(SalesforceConfig.class); + } + + @Singleton + @Provides + @ForBaseJdbc + public ConnectionFactory getConnectionFactory( + BaseJdbcConfig baseConfig, + SalesforceConfig salesforceConfig, + CredentialProvider credentialProvider) + { + Properties properties = new Properties(); + + salesforceConfig.getSecurityToken().ifPresent(token -> properties.setProperty("securityToken", token)); + + return new DriverConnectionFactory(new ForceDriver(), baseConfig.getConnectionUrl(), properties, credentialProvider); + } +} diff --git a/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/SalesforceConfig.java b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/SalesforceConfig.java new file mode 100644 index 0000000000000..cc6249338f01b --- /dev/null +++ b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/SalesforceConfig.java @@ -0,0 +1,50 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce; + +import io.airlift.configuration.Config; +import io.airlift.configuration.ConfigSecuritySensitive; + +import java.util.Optional; + +public class SalesforceConfig +{ + private String securityToken; + private Integer fetchSize; + + @Config("salesforce.security-token") + @ConfigSecuritySensitive + public SalesforceConfig setSecurityToken(String securityToken) + { + this.securityToken = securityToken; + return this; + } + + @Config("salesforce.fetch-size") + public SalesforceConfig setFetchSize(Integer fetchSize) + { + this.fetchSize = fetchSize; + return this; + } + + public Optional getSecurityToken() + { + return Optional.ofNullable(securityToken); + } + + public Optional getFetchSize() + { + return Optional.ofNullable(fetchSize); + } +} diff --git a/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/SalesforcePlugin.java b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/SalesforcePlugin.java new file mode 100644 index 0000000000000..7061ade414461 --- /dev/null +++ b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/SalesforcePlugin.java @@ -0,0 +1,25 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce; + +import io.prestosql.plugin.jdbc.JdbcPlugin; + +public class SalesforcePlugin + extends JdbcPlugin +{ + public SalesforcePlugin() + { + super("salesforce", new SalesforceClientModule()); + } +} diff --git a/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/SoqlQueryBuilder.java b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/SoqlQueryBuilder.java new file mode 100644 index 0000000000000..4d7a1b28bc60e --- /dev/null +++ b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/SoqlQueryBuilder.java @@ -0,0 +1,298 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce; + +import com.google.common.base.Joiner; +import com.google.common.base.VerifyException; +import com.google.common.collect.ImmutableList; +import io.airlift.log.Logger; +import io.airlift.slice.Slice; +import io.prestosql.plugin.jdbc.BooleanWriteFunction; +import io.prestosql.plugin.jdbc.DoubleWriteFunction; +import io.prestosql.plugin.jdbc.JdbcClient; +import io.prestosql.plugin.jdbc.JdbcColumnHandle; +import io.prestosql.plugin.jdbc.JdbcTypeHandle; +import io.prestosql.plugin.jdbc.LongWriteFunction; +import io.prestosql.plugin.jdbc.ObjectWriteFunction; +import io.prestosql.plugin.jdbc.SliceWriteFunction; +import io.prestosql.plugin.jdbc.WriteFunction; +import io.prestosql.spi.connector.ColumnHandle; +import io.prestosql.spi.connector.ConnectorSession; +import io.prestosql.spi.predicate.Domain; +import io.prestosql.spi.predicate.Range; +import io.prestosql.spi.predicate.TupleDomain; +import io.prestosql.spi.type.Type; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; + +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Strings.isNullOrEmpty; +import static com.google.common.collect.Iterables.getOnlyElement; +import static java.lang.String.format; +import static java.util.Collections.nCopies; +import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.joining; + +public class SoqlQueryBuilder +{ + private static final Logger log = Logger.get(SoqlQueryBuilder.class); + + // not all databases support booleans, so use 1=1 and 1=0 instead + private static final String ALWAYS_TRUE = "1=1"; + private static final String ALWAYS_FALSE = "1=0"; + + private final String identifierQuote; + + private static class TypeAndValue + { + private final Type type; + private final JdbcTypeHandle typeHandle; + private final Object value; + + public TypeAndValue(Type type, JdbcTypeHandle typeHandle, Object value) + { + this.type = requireNonNull(type, "type is null"); + this.typeHandle = requireNonNull(typeHandle, "typeHandle is null"); + this.value = requireNonNull(value, "value is null"); + } + + public Type getType() + { + return type; + } + + public JdbcTypeHandle getTypeHandle() + { + return typeHandle; + } + + public Object getValue() + { + return value; + } + } + + public SoqlQueryBuilder(String identifierQuote) + { + this.identifierQuote = requireNonNull(identifierQuote, "identifierQuote is null"); + } + + public PreparedStatement buildSql( + JdbcClient client, + ConnectorSession session, + Connection connection, + String catalog, + String schema, + String table, + List columns, + TupleDomain tupleDomain, + Optional additionalPredicate, + Function sqlFunction) + throws SQLException + { + StringBuilder sql = new StringBuilder(); + + String columnNames = columns.stream() + .map(JdbcColumnHandle::getColumnName) + .map(this::quote) + .collect(joining(", ")); + + sql.append("SELECT "); + sql.append(columnNames); + if (columns.isEmpty()) { + sql.append("null"); + } + + sql.append(" FROM "); + if (!isNullOrEmpty(catalog)) { + sql.append(quote(catalog)).append('.'); + } + if (!isNullOrEmpty(schema)) { + sql.append(quote(schema)).append('.'); + } + sql.append(quote(table)); + + List accumulator = new ArrayList<>(); + + List clauses = toConjuncts(client, session, connection, columns, tupleDomain, accumulator); + if (additionalPredicate.isPresent()) { + clauses = ImmutableList.builder() + .addAll(clauses) + .add(additionalPredicate.get()) + .build(); + } + if (!clauses.isEmpty()) { + sql.append(" WHERE ") + .append(Joiner.on(" AND ").join(clauses)); + } + + String query = sqlFunction.apply(sql.toString()); + log.debug("Preparing query: %s", query); + PreparedStatement statement = client.getPreparedStatement(connection, query); + + for (int i = 0; i < accumulator.size(); i++) { + TypeAndValue typeAndValue = accumulator.get(i); + int parameterIndex = i + 1; + Type type = typeAndValue.getType(); + WriteFunction writeFunction = client.toPrestoType(session, connection, typeAndValue.getTypeHandle()) + .orElseThrow(() -> new VerifyException(format("Unsupported type %s with handle %s", type, typeAndValue.getTypeHandle()))) + .getWriteFunction(); + Class javaType = type.getJavaType(); + Object value = typeAndValue.getValue(); + if (javaType == boolean.class) { + ((BooleanWriteFunction) writeFunction).set(statement, parameterIndex, (boolean) value); + } + else if (javaType == long.class) { + ((LongWriteFunction) writeFunction).set(statement, parameterIndex, (long) value); + } + else if (javaType == double.class) { + ((DoubleWriteFunction) writeFunction).set(statement, parameterIndex, (double) value); + } + else if (javaType == Slice.class) { + ((SliceWriteFunction) writeFunction).set(statement, parameterIndex, (Slice) value); + } + else { + ((ObjectWriteFunction) writeFunction).set(statement, parameterIndex, value); + } + } + + return statement; + } + + private static Domain pushDownDomain(JdbcClient client, ConnectorSession session, Connection connection, JdbcColumnHandle column, Domain domain) + { + return client.toPrestoType(session, connection, column.getJdbcTypeHandle()) + .orElseThrow(() -> new IllegalStateException(format("Unsupported type %s with handle %s", column.getColumnType(), column.getJdbcTypeHandle()))) + .getPushdownConverter().apply(domain); + } + + private List toConjuncts( + JdbcClient client, + ConnectorSession session, + Connection connection, + List columns, + TupleDomain tupleDomain, + List accumulator) + { + if (tupleDomain.isNone()) { + return ImmutableList.of(ALWAYS_FALSE); + } + ImmutableList.Builder builder = ImmutableList.builder(); + for (JdbcColumnHandle column : columns) { + Domain domain = tupleDomain.getDomains().get().get(column); + if (domain != null) { + domain = pushDownDomain(client, session, connection, column, domain); + builder.add(toPredicate(column.getColumnName(), domain, column, accumulator)); + } + } + return builder.build(); + } + + private String toPredicate(String columnName, Domain domain, JdbcColumnHandle column, List accumulator) + { + if (domain.getValues().isNone()) { + return domain.isNullAllowed() ? quote(columnName) + " = NULL" : ALWAYS_FALSE; + } + + if (domain.getValues().isAll()) { + return domain.isNullAllowed() ? ALWAYS_TRUE : quote(columnName) + " != NULL"; + } + + List disjuncts = new ArrayList<>(); + List singleValues = new ArrayList<>(); + for (Range range : domain.getValues().getRanges().getOrderedRanges()) { + checkState(!range.isAll()); // Already checked + if (range.isSingleValue()) { + singleValues.add(range.getLow().getValue()); + } + else { + List rangeConjuncts = new ArrayList<>(); + if (!range.getLow().isLowerUnbounded()) { + switch (range.getLow().getBound()) { + case ABOVE: + rangeConjuncts.add(toPredicate(columnName, ">", range.getLow().getValue(), column, accumulator)); + break; + case EXACTLY: + rangeConjuncts.add(toPredicate(columnName, ">=", range.getLow().getValue(), column, accumulator)); + break; + case BELOW: + throw new IllegalArgumentException("Low marker should never use BELOW bound"); + default: + throw new AssertionError("Unhandled bound: " + range.getLow().getBound()); + } + } + if (!range.getHigh().isUpperUnbounded()) { + switch (range.getHigh().getBound()) { + case ABOVE: + throw new IllegalArgumentException("High marker should never use ABOVE bound"); + case EXACTLY: + rangeConjuncts.add(toPredicate(columnName, "<=", range.getHigh().getValue(), column, accumulator)); + break; + case BELOW: + rangeConjuncts.add(toPredicate(columnName, "<", range.getHigh().getValue(), column, accumulator)); + break; + default: + throw new AssertionError("Unhandled bound: " + range.getHigh().getBound()); + } + } + // If rangeConjuncts is null, then the range was ALL, which should already have been checked for + checkState(!rangeConjuncts.isEmpty()); + disjuncts.add("(" + Joiner.on(" AND ").join(rangeConjuncts) + ")"); + } + } + + // Add back all of the possible single values either as an equality or an IN predicate + if (singleValues.size() == 1) { + disjuncts.add(toPredicate(columnName, "=", getOnlyElement(singleValues), column, accumulator)); + } + else if (singleValues.size() > 1) { + for (Object value : singleValues) { + bindValue(value, column, accumulator); + } + String values = Joiner.on(",").join(nCopies(singleValues.size(), "?")); + disjuncts.add(quote(columnName) + " IN (" + values + ")"); + } + + // Add nullability disjuncts + checkState(!disjuncts.isEmpty()); + if (domain.isNullAllowed()) { + disjuncts.add(quote(columnName) + " IS NULL"); + } + + return "(" + Joiner.on(" OR ").join(disjuncts) + ")"; + } + + private String toPredicate(String columnName, String operator, Object value, JdbcColumnHandle column, List accumulator) + { + bindValue(value, column, accumulator); + return quote(columnName) + " " + operator + " ?"; + } + + private String quote(String name) + { + return identifierQuote + name.replace(identifierQuote, identifierQuote + identifierQuote) + identifierQuote; + } + + private static void bindValue(Object value, JdbcColumnHandle column, List accumulator) + { + Type type = column.getColumnType(); + accumulator.add(new TypeAndValue(type, column.getJdbcTypeHandle(), value)); + } +} diff --git a/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/ForceDriver.java b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/ForceDriver.java new file mode 100644 index 0000000000000..c707c93df5a44 --- /dev/null +++ b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/ForceDriver.java @@ -0,0 +1,160 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce.driver; + +import com.sforce.soap.partner.PartnerConnection; +import com.sforce.ws.ConnectionException; +import io.prestosql.plugin.salesforce.driver.connection.ForceConnection; +import io.prestosql.plugin.salesforce.driver.connection.ForceConnectionInfo; +import io.prestosql.plugin.salesforce.driver.connection.ForceService; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.sql.Connection; +import java.sql.Driver; +import java.sql.DriverManager; +import java.sql.DriverPropertyInfo; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.Properties; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ForceDriver + implements Driver +{ + private static final String ACCEPTABLE_URL = "jdbc:salesforce"; + private static final Pattern URL_PATTERN = Pattern.compile("\\A" + ACCEPTABLE_URL + "://(.*)"); + private static final Pattern URL_HAS_AUTHORIZATION_SEGMENT = Pattern.compile("\\A" + ACCEPTABLE_URL + "://([^:]+):([^@]+)@.*"); + + private static Boolean resolveSandboxProperty(Properties properties) + { + String sandbox = properties.getProperty("sandbox"); + if (sandbox != null) { + return Boolean.valueOf(sandbox); + } + String loginDomain = properties.getProperty("loginDomain"); + if (loginDomain != null) { + return loginDomain.contains("test"); + } + return null; + } + + @Override + public Connection connect(String url, Properties properties) + throws SQLException + { + if (!acceptsURL(url)) { + /* + * According to JDBC spec: + * > The driver should return "null" if it realizes it is the wrong kind of driver to connect to the given URL. + * > 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. + * + * Source: https://docs.oracle.com/javase/8/docs/api/java/sql/Driver.html#connect-java.lang.String-java.util.Properties- + */ + return null; + } + try { + properties.putAll(getConnStringProperties(url)); + + ForceConnectionInfo info = new ForceConnectionInfo( + properties.getProperty("user"), + properties.getProperty("password"), + properties.getProperty("securityToken"), + properties.getProperty("sessionId"), + resolveSandboxProperty(properties)); + + PartnerConnection partnerConnection = ForceService.createPartnerConnection(info); + return new ForceConnection(partnerConnection); + } + catch (ConnectionException | IOException e) { + throw new SQLException(e); + } + } + + protected Properties getConnStringProperties(String url) + throws IOException + { + Properties result = new Properties(); + String urlProperties = null; + + Matcher stdMatcher = URL_PATTERN.matcher(url); + Matcher authMatcher = URL_HAS_AUTHORIZATION_SEGMENT.matcher(url); + + if (authMatcher.matches()) { + urlProperties = "user=" + authMatcher.group(1) + "\npassword=" + authMatcher.group(2); + } + else if (stdMatcher.matches()) { + urlProperties = stdMatcher.group(1); + urlProperties = urlProperties.replaceAll(";", "\n"); + } + + if (urlProperties != null) { + try (InputStream in = new ByteArrayInputStream(urlProperties.getBytes(StandardCharsets.UTF_8))) { + result.load(in); + } + } + + return result; + } + + @Override + public boolean acceptsURL(String url) + { + return url != null && url.startsWith(ACCEPTABLE_URL); + } + + @Override + public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) + { + return new DriverPropertyInfo[] {}; + } + + @Override + public int getMajorVersion() + { + return 1; + } + + @Override + public int getMinorVersion() + { + return 1; + } + + @Override + public boolean jdbcCompliant() + { + return false; + } + + @Override + public Logger getParentLogger() + throws SQLFeatureNotSupportedException + { + throw new SQLFeatureNotSupportedException(); + } + + static { + try { + DriverManager.registerDriver(new ForceDriver()); + } + catch (Exception e) { + throw new RuntimeException("Failed register ForceDriver: " + e.getMessage(), e); + } + } +} diff --git a/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/ForceModule.java b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/ForceModule.java new file mode 100644 index 0000000000000..919cd8dda405a --- /dev/null +++ b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/ForceModule.java @@ -0,0 +1,41 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce.driver; + +import com.google.inject.Binder; +import com.google.inject.Module; +import com.google.inject.Scopes; +import com.google.inject.TypeLiteral; +import io.prestosql.plugin.salesforce.driver.connection.ForceConnection; +import io.prestosql.plugin.salesforce.driver.metadata.ForceDatabaseMetaData; +import io.prestosql.plugin.salesforce.driver.metadata.ForceDatabaseMetadataCache; + +import java.util.HashMap; +import java.util.Map; + +public class ForceModule + implements Module +{ + @Override + public void configure(Binder binder) + { + binder.bind(new TypeLiteral>() + { + }).annotatedWith(ForceDatabaseMetadataCache.class).to(new TypeLiteral>() + { + }).in(Scopes.SINGLETON); + + binder.requestStaticInjection(ForceConnection.class); + } +} diff --git a/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/connection/ForceConnection.java b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/connection/ForceConnection.java new file mode 100644 index 0000000000000..98701209f1d1a --- /dev/null +++ b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/connection/ForceConnection.java @@ -0,0 +1,460 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce.driver.connection; + +import com.sforce.soap.partner.PartnerConnection; +import io.prestosql.plugin.salesforce.driver.metadata.ForceDatabaseMetaData; +import io.prestosql.plugin.salesforce.driver.metadata.ForceDatabaseMetadataCache; +import io.prestosql.plugin.salesforce.driver.statement.ForcePreparedStatement; + +import javax.inject.Inject; +import javax.inject.Provider; + +import java.sql.Array; +import java.sql.Blob; +import java.sql.CallableStatement; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.NClob; +import java.sql.PreparedStatement; +import java.sql.SQLClientInfoException; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.SQLXML; +import java.sql.Savepoint; +import java.sql.Statement; +import java.sql.Struct; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import static java.util.Objects.requireNonNull; + +public class ForceConnection + implements Connection +{ + private static final String SF_JDBC_DRIVER_NAME = "SF JDBC driver"; + @Inject @ForceDatabaseMetadataCache private static Provider> metadataCacheProvider; + private final PartnerConnection partnerConnection; + private ForceDatabaseMetaData metadata; + + private Map connectionCache = new HashMap<>(); + + public ForceConnection(PartnerConnection partnerConnection) + { + requireNonNull(partnerConnection); + + this.partnerConnection = partnerConnection; + + Map metadataCache = metadataCacheProvider.get(); + metadata = metadataCache.get(this.partnerConnection.getConfig().getUsername()); + + if (this.metadata == null) { + this.metadata = new ForceDatabaseMetaData(); + this.metadata.setConnection(this); + metadataCache.put(this.partnerConnection.getConfig().getUsername(), this.metadata); + } + + this.metadata.setConnection(this); + } + + public PartnerConnection getPartnerConnection() + { + return partnerConnection; + } + + @Override + public DatabaseMetaData getMetaData() + { + return metadata; + } + + @Override + public PreparedStatement prepareStatement(String soql) + { + return new ForcePreparedStatement(this, soql); + } + + @Override + public String getSchema() + { + return "Salesforce"; + } + + @Override + public void setSchema(String schema) + throws SQLException + { + throw new SQLException("Salesforce does not have a concept of schema."); + } + + @Override + public T unwrap(Class iface) + throws SQLException + { + if (isWrapperFor(iface)) { + return (T) this; + } + throw new SQLException("No wrapper for " + iface); + } + + @Override + public boolean isWrapperFor(Class iface) + { + return iface.isInstance(this); + } + + @Override + public Statement createStatement() + { + Logger.getLogger(SF_JDBC_DRIVER_NAME).info(Object.class.getEnclosingMethod().getName()); + return null; + } + + @Override + public CallableStatement prepareCall(String sql) + { + Logger.getLogger(SF_JDBC_DRIVER_NAME).info(Object.class.getEnclosingMethod().getName()); + return null; + } + + @Override + public String nativeSQL(String sql) + { + Logger.getLogger(SF_JDBC_DRIVER_NAME).info(Object.class.getEnclosingMethod().getName()); + return null; + } + + @Override + public boolean getAutoCommit() + { + // TODO Auto-generated method stub + return false; + } + + @Override + public void setAutoCommit(boolean autoCommit) + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public void commit() + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public void rollback() + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public void close() + throws SQLException + { + // TODO Auto-generated method stub + } + + @Override + public boolean isClosed() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isReadOnly() + throws SQLException + { + // Always return true because the driver does not support writing yet. + return true; + } + + @Override + public void setReadOnly(boolean readOnly) + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public String getCatalog() + throws SQLException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public void setCatalog(String catalog) + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public int getTransactionIsolation() + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public void setTransactionIsolation(int level) + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public SQLWarning getWarnings() + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public void clearWarnings() + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public Statement createStatement(int resultSetType, int resultSetConcurrency) + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public Map> getTypeMap() + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public void setTypeMap(Map> map) + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public int getHoldability() + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public void setHoldability(int holdability) + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public Savepoint setSavepoint() + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public Savepoint setSavepoint(String name) + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public void rollback(Savepoint savepoint) + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public void releaseSavepoint(Savepoint savepoint) + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public PreparedStatement prepareStatement(String sql, int[] columnIndexes) + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public PreparedStatement prepareStatement(String sql, String[] columnNames) + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public Clob createClob() + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public Blob createBlob() + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public NClob createNClob() + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public SQLXML createSQLXML() + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public boolean isValid(int timeout) + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public void setClientInfo(String name, String value) + throws SQLClientInfoException + { + throw new SQLClientInfoException(); + } + + @Override + public String getClientInfo(String name) + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public Properties getClientInfo() + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public void setClientInfo(Properties properties) + throws SQLClientInfoException + { + throw new SQLClientInfoException(); + } + + @Override + public Array createArrayOf(String typeName, Object[] elements) + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public Struct createStruct(String typeName, Object[] attributes) + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public void abort(Executor executor) + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public void setNetworkTimeout(Executor executor, int milliseconds) + throws SQLException + { + throw new SQLException(Object.class.getEnclosingMethod().getName() + " is not implemented by this driver"); + } + + @Override + public int getNetworkTimeout() + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } +} diff --git a/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/connection/ForceConnectionInfo.java b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/connection/ForceConnectionInfo.java new file mode 100644 index 0000000000000..50e86c13dd58a --- /dev/null +++ b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/connection/ForceConnectionInfo.java @@ -0,0 +1,82 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce.driver.connection; + +public class ForceConnectionInfo +{ + private String userName; + private String password; + private String sessionId; + private Boolean sandbox; + private String securityToken; + + public ForceConnectionInfo(String userName, String password, String securityToken, String sessionId, Boolean sandbox) + { + this.userName = userName; + this.password = password; + this.securityToken = securityToken; + this.sessionId = sessionId; + this.sandbox = sandbox; + } + + public String getUserName() + { + return userName; + } + + public void setUserName(String userName) + { + this.userName = userName; + } + + public String getPassword() + { + return password; + } + + public void setPassword(String password) + { + this.password = password; + } + + public String getSessionId() + { + return sessionId; + } + + public void setSessionId(String sessionId) + { + this.sessionId = sessionId; + } + + public Boolean getSandbox() + { + return sandbox; + } + + public void setSandbox(Boolean sandbox) + { + this.sandbox = sandbox; + } + + public void setSecurityToken(String securityToken) + { + this.securityToken = securityToken; + } + + public String getSecurityToken() + { + return securityToken; + } +} diff --git a/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/connection/ForceService.java b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/connection/ForceService.java new file mode 100644 index 0000000000000..7f33c1c020058 --- /dev/null +++ b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/connection/ForceService.java @@ -0,0 +1,123 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce.driver.connection; + +import com.sforce.soap.partner.Connector; +import com.sforce.soap.partner.PartnerConnection; +import com.sforce.ws.ConnectionException; +import com.sforce.ws.ConnectorConfig; +import io.prestosql.plugin.salesforce.driver.oauth.ForceOAuthClient; +import org.apache.commons.io.FileUtils; +import org.mapdb.DB; +import org.mapdb.DBMaker; +import org.mapdb.HTreeMap; +import org.mapdb.Serializer; + +import java.util.concurrent.TimeUnit; + +public final class ForceService +{ + public static final int EXPIRE_AFTER_CREATE = 60; + public static final int EXPIRE_STORE_SIZE = 16; + private static final String DEFAULT_LOGIN_DOMAIN = "login.salesforce.com"; + private static final String SANDBOX_LOGIN_DOMAIN = "test.salesforce.com"; + private static final long CONNECTION_TIMEOUT = TimeUnit.SECONDS.toMillis(10); + private static final long READ_TIMEOUT = TimeUnit.SECONDS.toMillis(30); + private static final String DEFAULT_API_VERSION = "43.0"; + private static final DB cacheDb = DBMaker.tempFileDB().closeOnJvmShutdown().make(); + + private static HTreeMap partnerUrlCache = cacheDb.hashMap("PartnerUrlCache", Serializer.STRING, Serializer.STRING).expireAfterCreate(EXPIRE_AFTER_CREATE, TimeUnit.MINUTES).expireStoreSize(EXPIRE_STORE_SIZE * FileUtils.ONE_MB).create(); + + private ForceService() {} + + private static String getPartnerUrl(String accessToken, boolean sandbox) + { + return partnerUrlCache.computeIfAbsent(accessToken, s -> getPartnerUrlFromUserInfo(accessToken, sandbox)); + } + + private static String getPartnerUrlFromUserInfo(String accessToken, boolean sandbox) + { + return new ForceOAuthClient(CONNECTION_TIMEOUT, READ_TIMEOUT).getUserInfo(accessToken, sandbox).getPartnerUrl(); + } + + public static PartnerConnection createPartnerConnection(ForceConnectionInfo info) + throws ConnectionException + { + return info.getSessionId() != null ? createConnectionBySessionId(info) : createConnectionByUserCredential(info); + } + + private static PartnerConnection createConnectionBySessionId(ForceConnectionInfo info) + throws ConnectionException + { + ConnectorConfig partnerConfig = new ConnectorConfig(); + partnerConfig.setSessionId(info.getSessionId()); + + if (info.getSandbox() != null) { + partnerConfig.setServiceEndpoint(ForceService.getPartnerUrl(info.getSessionId(), info.getSandbox())); + return Connector.newConnection(partnerConfig); + } + + try { + partnerConfig.setServiceEndpoint(ForceService.getPartnerUrl(info.getSessionId(), false)); + return Connector.newConnection(partnerConfig); + } + catch (RuntimeException re) { + try { + partnerConfig.setServiceEndpoint(ForceService.getPartnerUrl(info.getSessionId(), true)); + return Connector.newConnection(partnerConfig); + } + catch (RuntimeException r) { + throw new ConnectionException(r.getMessage()); + } + } + } + + private static PartnerConnection createConnectionByUserCredential(ForceConnectionInfo info) + throws ConnectionException + { + ConnectorConfig partnerConfig = new ConnectorConfig(); + partnerConfig.setUsername(info.getUserName()); + + if (info.getSecurityToken() != null) { + partnerConfig.setPassword(info.getPassword() + info.getSecurityToken()); + } + else { + partnerConfig.setPassword(info.getPassword()); + } + + if (info.getSandbox() != null) { + partnerConfig.setAuthEndpoint(buildAuthEndpoint(info.getSandbox())); + return Connector.newConnection(partnerConfig); + } + + try { + partnerConfig.setAuthEndpoint(buildAuthEndpoint(false)); + return Connector.newConnection(partnerConfig); + } + catch (ConnectionException ce) { + partnerConfig.setAuthEndpoint(buildAuthEndpoint(true)); + return Connector.newConnection(partnerConfig); + } + } + + private static String buildAuthEndpoint(boolean sandbox) + { + if (sandbox) { + return String.format("https://%s/services/Soap/u/%s", SANDBOX_LOGIN_DOMAIN, DEFAULT_API_VERSION); + } + else { + return String.format("https://%s/services/Soap/u/%s", DEFAULT_LOGIN_DOMAIN, DEFAULT_API_VERSION); + } + } +} diff --git a/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/delegates/ForceResultField.java b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/delegates/ForceResultField.java new file mode 100644 index 0000000000000..ede0bd34b8a22 --- /dev/null +++ b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/delegates/ForceResultField.java @@ -0,0 +1,90 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce.driver.delegates; + +import java.util.Objects; + +public class ForceResultField +{ + public static final String NESTED_RESULT_SET_FIELD_TYPE = "nestedResultSet"; + + private String entityType; + private String name; + private Object value; + private String fieldType; + + public ForceResultField(String entityType, String fieldType, String name, Object value) + { + super(); + this.entityType = entityType; + this.name = name; + this.value = value; + this.fieldType = fieldType; + } + + public String getEntityType() + { + return entityType; + } + + public String getName() + { + return name; + } + + public Object getValue() + { + return value; + } + + public void setValue(Object value) + { + this.value = value; + } + + public String getFullName() + { + return entityType != null ? entityType + "." + name : name; + } + + @Override + public String toString() + { + return "SfResultField [entityType=" + entityType + ", name=" + name + ", value=" + value + "]"; + } + + @Override + public int hashCode() + { + return Objects.hash(entityType, name, value); + } + + @Override + public boolean equals(Object obj) + { + if (obj.getClass() != ForceResultField.class) { + return false; + } + + ForceResultField other = (ForceResultField) obj; + return Objects.equals(entityType, other.entityType) && + Objects.equals(name, other.name) && + Objects.equals(value, other.value); + } + + public String getFieldType() + { + return fieldType; + } +} diff --git a/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/delegates/PartnerResultToCartesianTable.java b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/delegates/PartnerResultToCartesianTable.java new file mode 100644 index 0000000000000..17216d45c3a30 --- /dev/null +++ b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/delegates/PartnerResultToCartesianTable.java @@ -0,0 +1,77 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce.driver.delegates; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +@SuppressWarnings({"rawtypes", "unchecked"}) +public class PartnerResultToCartesianTable +{ + private List schema; + + private PartnerResultToCartesianTable(List schema) + { + this.schema = schema; + } + + public static List expand(List list, List schema) + { + PartnerResultToCartesianTable expander = new PartnerResultToCartesianTable(schema); + return expander.expandOn(list, 0, 0); + } + + private static List expandRow(List row, Object nestedItem, int position) + { + List nestedItemsToInsert = nestedItem instanceof List ? (List) nestedItem : Arrays.asList(nestedItem); + List newRow = new ArrayList<>(row.subList(0, position)); + newRow.addAll(nestedItemsToInsert); + newRow.addAll(row.subList(position + 1, row.size())); + return newRow; + } + + private List expandOn(List rows, int columnPosition, int schemaPosititon) + { + return rows.parallelStream().map(row -> expandRow(row, columnPosition, schemaPosititon)).flatMap(Collection::stream).collect(Collectors.toList()); + } + + private List expandRow(List row, int columnPosition, int schemaPosititon) + { + List result = new ArrayList<>(); + if (schemaPosititon > schema.size() - 1) { + result.add(row); + return result; + } + else if (schema.get(schemaPosititon) instanceof List) { + int nestedListSize = ((List) schema.get(schemaPosititon)).size(); + Object value = row.get(columnPosition); + List nestedList = value instanceof List ? (List) value : Collections.emptyList(); + if (nestedList.isEmpty()) { + result.add(expandRow(row, Collections.nCopies(nestedListSize, null), columnPosition)); + } + else { + nestedList.forEach(item -> result.add(expandRow(row, item, columnPosition))); + } + return expandOn(result, columnPosition + nestedListSize, schemaPosititon + 1); + } + else { + result.add(row); + return expandOn(result, columnPosition + 1, schemaPosititon + 1); + } + } +} diff --git a/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/delegates/PartnerService.java b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/delegates/PartnerService.java new file mode 100644 index 0000000000000..2652ba27f7d26 --- /dev/null +++ b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/delegates/PartnerService.java @@ -0,0 +1,225 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce.driver.delegates; + +import com.sforce.soap.partner.DescribeGlobalResult; +import com.sforce.soap.partner.DescribeGlobalSObjectResult; +import com.sforce.soap.partner.DescribeSObjectResult; +import com.sforce.soap.partner.Field; +import com.sforce.soap.partner.PartnerConnection; +import com.sforce.soap.partner.QueryResult; +import com.sforce.ws.ConnectionException; +import com.sforce.ws.bind.XmlObject; +import io.prestosql.plugin.salesforce.driver.metadata.Column; +import io.prestosql.plugin.salesforce.driver.metadata.Table; +import io.prestosql.plugin.salesforce.driver.statement.FieldDef; +import org.apache.commons.collections4.IteratorUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.stream.Collectors; + +public class PartnerService +{ + private static final List SOAP_RESPONSE_SERVICE_OBJECT_TYPES = Arrays.asList("type", "done", "queryLocator", "size"); + private PartnerConnection partnerConnection; + private List sObjectTypesCache; + + public PartnerService(PartnerConnection partnerConnection) + { + this.partnerConnection = partnerConnection; + } + + public List getTables() + { + List sObjects = getSObjectsDescription(); + return sObjects.stream().map(this::convertToTable).collect(Collectors.toList()); + } + + public Table getTable(String tableName) + throws ConnectionException + { + DescribeSObjectResult describeSObjectResult = partnerConnection.describeSObject(tableName); + return convertToTable(describeSObjectResult); + } + + public DescribeSObjectResult describeSObject(String sObjectType) + throws ConnectionException + { + return partnerConnection.describeSObject(sObjectType); + } + + private Table convertToTable(DescribeSObjectResult so) + { + List fields = Arrays.asList(so.getFields()); + List columns = fields.stream().map(this::convertToColumn).collect(Collectors.toList()); + return new Table(so.getName(), null, columns); + } + + private Column convertToColumn(Field field) + { + try { + Column column = new Column(field.getName(), getType(field)); + column.setLength(field.getLength()); + column.setNillable(field.getNillable()); + column.setCalculated(field.isCalculated() || field.isAutoNumber()); + String[] referenceTos = field.getReferenceTo(); + if (referenceTos != null) { + for (String referenceTo : referenceTos) { + if (getSObjectTypes().contains(referenceTo)) { + column.setReferencedTable(referenceTo); + column.setReferencedColumn("Id"); + } + } + } + return column; + } + catch (ConnectionException e) { + throw new RuntimeException(e); + } + } + + private String getType(Field field) + { + String s = field.getType().toString(); + if (s.startsWith("_")) { + s = s.substring("_".length()); + } + return s.equalsIgnoreCase("double") ? "decimal" : s; + } + + private List getSObjectTypes() + throws ConnectionException + { + if (sObjectTypesCache == null) { + DescribeGlobalSObjectResult[] sobs = partnerConnection.describeGlobal().getSobjects(); + sObjectTypesCache = Arrays.stream(sobs).map(DescribeGlobalSObjectResult::getName).collect(Collectors.toList()); + } + return sObjectTypesCache; + } + + private List getSObjectsDescription() + { + DescribeGlobalResult describeGlobals = describeGlobal(); + List tableNames = Arrays.stream(describeGlobals.getSobjects()).parallel().map(DescribeGlobalSObjectResult::getName).collect(Collectors.toList()); + List> tableNamesBatched = toBatches(tableNames, 100); + return tableNamesBatched.parallelStream().flatMap(batch -> describeSObjects(batch).parallelStream()).collect(Collectors.toList()); + } + + private DescribeGlobalResult describeGlobal() + { + try { + return partnerConnection.describeGlobal(); + } + catch (ConnectionException e) { + throw new RuntimeException(e); + } + } + + private List describeSObjects(List batch) + { + DescribeSObjectResult[] result; + try { + result = partnerConnection.describeSObjects(batch.toArray(new String[0])); + return Arrays.asList(result); + } + catch (ConnectionException e) { + throw new RuntimeException(e); + } + } + + private List> toBatches(List objects, int batchSize) + { + List> result = new ArrayList<>(); + for (int fromIndex = 0; fromIndex < objects.size(); fromIndex += batchSize) { + int toIndex = Math.min(fromIndex + batchSize, objects.size()); + result.add(objects.subList(fromIndex, toIndex)); + } + return result; + } + + public List queryAll(String soql, List expectedSchema) + throws ConnectionException + { + List resultRows = Collections.synchronizedList(new ArrayList<>()); + QueryResult queryResult = null; + do { + queryResult = queryResult == null ? partnerConnection.query(soql) : partnerConnection.queryMore(queryResult.getQueryLocator()); + resultRows.addAll(removeServiceInfo(Arrays.asList(queryResult.getRecords()))); + } + while (!queryResult.isDone()); + + return PartnerResultToCartesianTable.expand(resultRows, expectedSchema); + } + + public QueryResult query(String soql) + throws ConnectionException + { + return partnerConnection.query(soql); + } + + public QueryResult queryMore(QueryResult queryResult) + throws ConnectionException, NoSuchElementException + { + if (queryResult.isDone()) { + throw new NoSuchElementException(); + } + + return partnerConnection.queryMore(queryResult.getQueryLocator()); + } + + private List removeServiceInfo(Iterator rows) + { + return removeServiceInfo(IteratorUtils.toList(rows)); + } + + private List removeServiceInfo(List rows) + { + return rows.stream().filter(this::isDataObjectType).map(this::removeServiceInfo).collect(Collectors.toList()); + } + + private List removeServiceInfo(XmlObject row) + { + return IteratorUtils.toList(row.getChildren()).stream().filter(this::isDataObjectType).skip(1) // Removes duplicate Id from SF Partner API response + // (https://developer.salesforce.com/forums/?id=906F00000008kciIAA) + .map(field -> isNestedResultset(field) ? removeServiceInfo(field.getChildren()) : toForceResultField(field)).collect(Collectors.toList()); + } + + private ForceResultField toForceResultField(XmlObject field) + { + String fieldType = field.getXmlType() != null ? field.getXmlType().getLocalPart() : null; + if ("sObject".equalsIgnoreCase(fieldType)) { + List children = new ArrayList<>(); + field.getChildren().forEachRemaining(children::add); + field = children.get(2); + } + String name = field.getName().getLocalPart(); + Object value = field.getValue(); + return new ForceResultField(null, fieldType, name, value); + } + + private boolean isNestedResultset(XmlObject object) + { + return object.getXmlType() != null && "QueryResult".equals(object.getXmlType().getLocalPart()); + } + + private boolean isDataObjectType(XmlObject object) + { + return !SOAP_RESPONSE_SERVICE_OBJECT_TYPES.contains(object.getName().getLocalPart()); + } +} diff --git a/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/metadata/Column.java b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/metadata/Column.java new file mode 100644 index 0000000000000..266aa7b4e40a5 --- /dev/null +++ b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/metadata/Column.java @@ -0,0 +1,117 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce.driver.metadata; + +import java.io.Serializable; + +public class Column + implements Serializable +{ + private Table table; + private String name; + private String type; + private String referencedTable; + private String referencedColumn; + + private Integer length; + private boolean nillable; + private String comments; + private boolean calculated; + + public Column(String name, String type) + { + this.name = name; + this.type = type; + } + + public Table getTable() + { + return table; + } + + public void setTable(Table table) + { + this.table = table; + } + + public String getName() + { + return name; + } + + public String getType() + { + return type; + } + + public String getReferencedTable() + { + return referencedTable; + } + + public void setReferencedTable(String referencedTable) + { + this.referencedTable = referencedTable; + } + + public String getReferencedColumn() + { + return referencedColumn; + } + + public void setReferencedColumn(String referencedColumn) + { + this.referencedColumn = referencedColumn; + } + + public Integer getLength() + { + return length; + } + + public void setLength(Integer length) + { + this.length = length; + } + + public boolean isNillable() + { + return nillable; + } + + public void setNillable(boolean nillable) + { + this.nillable = nillable; + } + + public String getComments() + { + return comments; + } + + public void setComments(String comments) + { + this.comments = comments; + } + + public boolean isCalculated() + { + return calculated; + } + + public void setCalculated(boolean calculated) + { + this.calculated = calculated; + } +} diff --git a/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/metadata/ColumnMap.java b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/metadata/ColumnMap.java new file mode 100644 index 0000000000000..4aaf649256f20 --- /dev/null +++ b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/metadata/ColumnMap.java @@ -0,0 +1,50 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce.driver.metadata; + +import java.io.Serializable; +import java.util.ArrayList; + +public class ColumnMap + implements Serializable +{ + private static final long serialVersionUID = 2705233366870541749L; + + private ArrayList columnNames = new ArrayList<>(); + private ArrayList values = new ArrayList<>(); + private int columnPosition; + + public V put(K key, V value) + { + columnNames.add(columnPosition, key); + values.add(columnPosition, value); + columnPosition++; + return value; + } + + public V get(K key) + { + int index = columnNames.indexOf(key); + return index != -1 ? values.get(index) : null; + } + + /** + * Get a column name by index, starting at 1, that represents the insertion + * order into the map. + */ + public V getByIndex(int index) + { + return values.get(index - 1); + } +} diff --git a/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/metadata/ForceDatabaseMetaData.java b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/metadata/ForceDatabaseMetaData.java new file mode 100644 index 0000000000000..d4321fcf86923 --- /dev/null +++ b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/metadata/ForceDatabaseMetaData.java @@ -0,0 +1,1651 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce.driver.metadata; + +import com.google.common.collect.Lists; +import io.prestosql.plugin.salesforce.driver.connection.ForceConnection; +import io.prestosql.plugin.salesforce.driver.delegates.PartnerService; +import io.prestosql.plugin.salesforce.driver.resultset.ForceResultSet; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import java.io.Serializable; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.RowIdLifetime; +import java.sql.SQLException; +import java.sql.Types; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +@Singleton +public class ForceDatabaseMetaData + implements DatabaseMetaData, Serializable +{ + private static final String DEFAULT_SCHEMA = "Salesforce"; + private static final TypeInfo OTHER_TYPE_INFO = new TypeInfo("other", Types.OTHER, 0x7fffffff, 0, 0, 0); + private static final TypeInfo[] TYPE_INFO_DATA = { + new TypeInfo("id", Types.VARCHAR, 0x7fffffff, 0, 0, 0), new TypeInfo("masterrecord", Types.VARCHAR, 0x7fffffff, 0, 0, 0), + new TypeInfo("reference", Types.VARCHAR, 0x7fffffff, 0, 0, 0), new TypeInfo("string", Types.VARCHAR, 0x7fffffff, 0, 0, 0), + new TypeInfo("encryptedstring", Types.VARCHAR, 0x7fffffff, 0, 0, 0), new TypeInfo("email", Types.VARCHAR, 0x7fffffff, 0, 0, 0), + new TypeInfo("phone", Types.VARCHAR, 0x7fffffff, 0, 0, 0), new TypeInfo("url", Types.VARCHAR, 0x7fffffff, 0, 0, 0), + new TypeInfo("textarea", Types.LONGVARCHAR, 0x7fffffff, 0, 0, 0), new TypeInfo("base64", Types.BLOB, 0x7fffffff, 0, 0, 0), + new TypeInfo("boolean", Types.BOOLEAN, 1, 0, 0, 0), new TypeInfo("_boolean", Types.BOOLEAN, 1, 0, 0, 0), new TypeInfo("byte", Types.VARBINARY, 10, 0, 0, 10), + new TypeInfo("_byte", Types.VARBINARY, 10, 0, 0, 10), new TypeInfo("int", Types.INTEGER, 10, 0, 0, 10), new TypeInfo("_int", Types.INTEGER, 10, 0, 0, 10), + new TypeInfo("decimal", Types.DECIMAL, 17, -324, 306, 10), new TypeInfo("double", Types.DOUBLE, 17, -324, 306, 10), + new TypeInfo("_double", Types.DOUBLE, 17, -324, 306, 10), new TypeInfo("percent", Types.DOUBLE, 17, -324, 306, 10), + new TypeInfo("currency", Types.DOUBLE, 17, -324, 306, 10), new TypeInfo("date", Types.DATE, 10, 0, 0, 0), new TypeInfo("time", Types.TIME, 10, 0, 0, 0), + new TypeInfo("datetime", Types.TIMESTAMP, 10, 0, 0, 0), new TypeInfo("picklist", Types.VARCHAR, 0, 0, 0, 0), new TypeInfo("multipicklist", Types.ARRAY, 0, 0, 0, 0), + new TypeInfo("combobox", Types.ARRAY, 0, 0, 0, 0), new TypeInfo("anyType", Types.OTHER, 0x7fffffff, 0, 0, 0)}; + private transient PartnerService partnerService; + private transient ForceConnection connection; + private Map tablesCache; + private int counter; + + @Inject + public ForceDatabaseMetaData() + { + } + + public static TypeInfo lookupTypeInfo(String forceTypeName) + { + String typeName = forceTypeName.replaceFirst("\\A_+", ""); + return Arrays.stream(TYPE_INFO_DATA).filter(entry -> typeName.equals(entry.typeName)).findAny().orElse(OTHER_TYPE_INFO); + } + + @Override + public ResultSet getTables(String catalog, String schemaPattern, String tableNamePattern, String[] types) + { + List
tables; + + if (tableNamePattern != null) { + tables = Lists.newArrayList(getTables().get(tableNamePattern)); + } + else { + tables = Lists.newArrayList(getTables().values()); + } + + List> maps = new ArrayList<>(); + for (Table table : tables) { + ColumnMap map = new ColumnMap<>(); + map.put("TABLE_CAT", null); + map.put("TABLE_SCHEM", null); + map.put("TABLE_NAME", table.getName()); + map.put("TABLE_TYPE", "TABLE"); + map.put("REMARKS", table.getComments()); + map.put("TYPE_CAT", null); + map.put("TYPE_SCHEM", null); + map.put("TYPE_NAME", null); + map.put("SELF_REFERENCING_COL_NAME", null); + map.put("REF_GENERATION", null); + maps.add(map); + } + return new ForceResultSet(maps); + } + + private Map getTables() + { + if (tablesCache == null) { + tablesCache = partnerService.getTables().stream().collect(Collectors.toMap(x -> x.getName().toLowerCase(Locale.getDefault()), x -> x)); + } + return tablesCache; + } + + @Override + public ResultSet getColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) + { + List
tables; + + if (tableNamePattern != null) { + tables = Lists.newArrayList(getTables().get(tableNamePattern.toLowerCase(Locale.getDefault()))); + } + else { + tables = Lists.newArrayList(getTables().values()); + } + + AtomicInteger ordinal = new AtomicInteger(1); + + return new ForceResultSet(tables.stream().filter(table -> tableNamePattern == null || table.getName().equals(tableNamePattern)).flatMap(table -> table.getColumns().stream()).filter(column -> columnNamePattern == null || column.getName().equals(columnNamePattern)).map(column -> new ColumnMap() + {{ + TypeInfo typeInfo = lookupTypeInfo(column.getType()); + put("TABLE_CAT", null); + put("TABLE_SCHEM", null); + put("TABLE_NAME", column.getTable().getName()); + put("COLUMN_NAME", column.getName()); + put("DATA_TYPE", typeInfo != null ? typeInfo.sqlDataType : Types.OTHER); + put("TYPE_NAME", column.getType()); + put("COLUMN_SIZE", column.getLength()); + put("BUFFER_LENGTH", 0); + put("DECIMAL_DIGITS", 0); + put("NUM_PREC_RADIX", typeInfo != null ? typeInfo.radix : 10); + put("NULLABLE", 0); + put("REMARKS", column.getComments()); + put("COLUMN_DEF", null); + put("SQL_DATA_TYPE", null); + put("SQL_DATETIME_SUB", null); + put("CHAR_OCTET_LENGTH", 0); + put("ORDINAL_POSITION", ordinal.getAndIncrement()); + put("IS_NULLABLE", ""); + put("SCOPE_CATLOG", null); + put("SCOPE_SCHEMA", null); + put("SCOPE_TABLE", null); + put("SOURCE_DATA_TYPE", column.getType()); + put("NULLABLE", column.isNillable() ? DatabaseMetaData.columnNullable : DatabaseMetaData.columnNoNulls); + }}).collect(Collectors.toList())); + } + + @Override + public ResultSet getSchemas() + throws SQLException + { + ColumnMap row = new ColumnMap<>(); + row.put("TABLE_SCHEM", DEFAULT_SCHEMA); + row.put("TABLE_CATALOG", null); + row.put("IS_DEFAULT", true); + return new ForceResultSet(Collections.singletonList(row)); + } + + @Override + public ResultSet getPrimaryKeys(String catalog, String schema, String tableName) + throws SQLException + { + List> maps = new ArrayList<>(); + for (Table table : getTables().values()) { + if (table.getName().equals(tableName)) { + for (Column column : table.getColumns()) { + if (column.getName().equalsIgnoreCase("Id")) { + ColumnMap map = new ColumnMap<>(); + map.put("TABLE_CAT", null); + map.put("TABLE_SCHEM", null); + map.put("TABLE_NAME", table.getName()); + map.put("COLUMN_NAME", "" + column.getName()); + map.put("KEY_SEQ", 0); + map.put("PK_NAME", "FakePK" + counter); + maps.add(map); + } + } + } + } + return new ForceResultSet(maps); + } + + @Override + public ResultSet getImportedKeys(String catalog, String schema, String tableName) + throws SQLException + { + List> maps = new ArrayList<>(); + for (Table table : getTables().values()) { + if (table.getName().equals(tableName)) { + for (Column column : table.getColumns()) { + if (column.getReferencedTable() != null && column.getReferencedColumn() != null) { + ColumnMap map = new ColumnMap<>(); + map.put("PKTABLE_CAT", null); + map.put("PKTABLE_SCHEM", null); + map.put("PKTABLE_NAME", column.getReferencedTable()); + map.put("PKCOLUMN_NAME", column.getReferencedColumn()); + map.put("FKTABLE_CAT", null); + map.put("FKTABLE_SCHEM", null); + map.put("FKTABLE_NAME", tableName); + map.put("FKCOLUMN_NAME", column.getName()); + map.put("KEY_SEQ", counter); + map.put("UPDATE_RULE", 0); + map.put("DELETE_RULE", 0); + map.put("FK_NAME", "FakeFK" + counter); + map.put("PK_NAME", "FakePK" + counter); + map.put("DEFERRABILITY", 0); + counter++; + maps.add(map); + } + } + } + } + return new ForceResultSet(maps); + } + + @Override + public ResultSet getIndexInfo(String catalog, String schema, String tableName, boolean unique, boolean approximate) + { + List> maps = new ArrayList<>(); + for (Table table : getTables().values()) { + if (table.getName().equals(tableName)) { + for (Column column : table.getColumns()) { + if (column.getName().equalsIgnoreCase("Id")) { + ColumnMap map = new ColumnMap<>(); + map.put("TABLE_CAT", null); + map.put("TABLE_SCHEM", null); + map.put("TABLE_NAME", table.getName()); + map.put("NON_UNIQUE", true); + map.put("INDEX_QUALIFIER", null); + map.put("INDEX_NAME", "FakeIndex" + counter++); + map.put("TYPE", DatabaseMetaData.tableIndexOther); + map.put("ORDINAL_POSITION", counter); + map.put("COLUMN_NAME", "Id"); + map.put("ASC_OR_DESC", "A"); + map.put("CARDINALITY", 1); + map.put("PAGES", 1); + map.put("FILTER_CONDITION", null); + + maps.add(map); + } + } + } + } + return new ForceResultSet(maps); + } + + @SuppressWarnings("unchecked") + @Override + public ResultSet getCatalogs() + throws SQLException + { + return new ForceResultSet(Collections.emptyList()); + } + + @Override + public ResultSet getTypeInfo() + throws SQLException + { + List> rows = new ArrayList<>(); + for (TypeInfo typeInfo : TYPE_INFO_DATA) { + ColumnMap row = new ColumnMap<>(); + row.put("TYPE_NAME", typeInfo.typeName); + row.put("DATA_TYPE", typeInfo.sqlDataType); + row.put("PRECISION", typeInfo.precision); + row.put("LITERAL_PREFIX", null); + row.put("LITERAL_SUFFIX", null); + row.put("CREATE_PARAMS", null); + row.put("NULLABLE", 1); + row.put("CASE_SENSITIVE", 0); + row.put("SEARCHABLE", 3); + row.put("UNSIGNED_ATTRIBUTE", false); + row.put("FIXED_PREC_SCALE", false); + row.put("AUTO_INCREMENT", false); + row.put("LOCAL_TYPE_NAME", typeInfo.typeName); + row.put("MINIMUM_SCALE", typeInfo.minScale); + row.put("MAXIMUM_SCALE", typeInfo.maxScale); + row.put("SQL_DATA_TYPE", typeInfo.sqlDataType); + row.put("SQL_DATETIME_SUB", null); + row.put("NUM_PREC_RADIX", typeInfo.radix); + row.put("TYPE_SUB", 1); + + rows.add(row); + } + return new ForceResultSet(rows); + } + + @Override + public T unwrap(Class iface) + throws SQLException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean isWrapperFor(Class iface) + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean allProceduresAreCallable() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean allTablesAreSelectable() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public String getURL() + throws SQLException + { + // TODO Auto-generated method stub + return ""; + } + + @Override + public String getUserName() + throws SQLException + { + // TODO Auto-generated method stub + return ""; + } + + @Override + public boolean isReadOnly() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean nullsAreSortedHigh() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean nullsAreSortedLow() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean nullsAreSortedAtStart() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean nullsAreSortedAtEnd() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public String getDatabaseProductName() + throws SQLException + { + return "Ascendix JDBC driver for Salesforce"; + } + + @Override + public String getDatabaseProductVersion() + throws SQLException + { + return "39"; + } + + @Override + public String getDriverName() + throws SQLException + { + return "Ascendix JDBC driver for Salesforce"; + } + + @Override + public String getDriverVersion() + throws SQLException + { + return "1.1"; + } + + @Override + public int getDriverMajorVersion() + { + return 1; + } + + @Override + public int getDriverMinorVersion() + { + return 0; + } + + @Override + public boolean usesLocalFiles() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean usesLocalFilePerTable() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsMixedCaseIdentifiers() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean storesUpperCaseIdentifiers() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean storesLowerCaseIdentifiers() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean storesMixedCaseIdentifiers() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsMixedCaseQuotedIdentifiers() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean storesUpperCaseQuotedIdentifiers() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean storesLowerCaseQuotedIdentifiers() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean storesMixedCaseQuotedIdentifiers() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public String getIdentifierQuoteString() + throws SQLException + { + // TODO Auto-generated method stub + return ""; + } + + @Override + public String getSQLKeywords() + throws SQLException + { + // TODO Auto-generated method stub + return ""; + } + + @Override + public String getNumericFunctions() + throws SQLException + { + // TODO Auto-generated method stub + return ""; + } + + @Override + public String getStringFunctions() + throws SQLException + { + // TODO Auto-generated method stub + return ""; + } + + @Override + public String getSystemFunctions() + throws SQLException + { + // TODO Auto-generated method stub + return ""; + } + + @Override + public String getTimeDateFunctions() + throws SQLException + { + // TODO Auto-generated method stub + return ""; + } + + @Override + public String getSearchStringEscape() + throws SQLException + { + // TODO Auto-generated method stub + return ""; + } + + @Override + public String getExtraNameCharacters() + throws SQLException + { + // TODO Auto-generated method stub + return ""; + } + + @Override + public boolean supportsAlterTableWithAddColumn() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsAlterTableWithDropColumn() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsColumnAliasing() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean nullPlusNonNullIsNull() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsConvert() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsConvert(int fromType, int toType) + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsTableCorrelationNames() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsDifferentTableCorrelationNames() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsExpressionsInOrderBy() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsOrderByUnrelated() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsGroupBy() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsGroupByUnrelated() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsGroupByBeyondSelect() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsLikeEscapeClause() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsMultipleResultSets() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsMultipleTransactions() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsNonNullableColumns() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsMinimumSQLGrammar() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsCoreSQLGrammar() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsExtendedSQLGrammar() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsANSI92EntryLevelSQL() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsANSI92IntermediateSQL() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsANSI92FullSQL() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsIntegrityEnhancementFacility() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsOuterJoins() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsFullOuterJoins() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsLimitedOuterJoins() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public String getSchemaTerm() + throws SQLException + { + // TODO Auto-generated method stub + return ""; + } + + @Override + public String getProcedureTerm() + throws SQLException + { + // TODO Auto-generated method stub + return ""; + } + + @Override + public String getCatalogTerm() + throws SQLException + { + // TODO Auto-generated method stub + return ""; + } + + @Override + public boolean isCatalogAtStart() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public String getCatalogSeparator() + throws SQLException + { + // TODO Auto-generated method stub + return ""; + } + + @Override + public boolean supportsSchemasInDataManipulation() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsSchemasInProcedureCalls() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsSchemasInTableDefinitions() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsSchemasInIndexDefinitions() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsSchemasInPrivilegeDefinitions() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsCatalogsInDataManipulation() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsCatalogsInProcedureCalls() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsCatalogsInTableDefinitions() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsCatalogsInIndexDefinitions() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsCatalogsInPrivilegeDefinitions() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsPositionedDelete() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsPositionedUpdate() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsSelectForUpdate() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsStoredProcedures() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsSubqueriesInComparisons() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsSubqueriesInExists() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsSubqueriesInIns() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsSubqueriesInQuantifieds() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsCorrelatedSubqueries() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsUnion() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsUnionAll() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsOpenCursorsAcrossCommit() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsOpenCursorsAcrossRollback() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsOpenStatementsAcrossCommit() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsOpenStatementsAcrossRollback() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public int getMaxBinaryLiteralLength() + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getMaxCharLiteralLength() + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getMaxColumnNameLength() + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getMaxColumnsInGroupBy() + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getMaxColumnsInIndex() + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getMaxColumnsInOrderBy() + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getMaxColumnsInSelect() + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getMaxColumnsInTable() + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getMaxConnections() + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getMaxCursorNameLength() + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getMaxIndexLength() + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getMaxSchemaNameLength() + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getMaxProcedureNameLength() + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getMaxCatalogNameLength() + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getMaxRowSize() + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public boolean doesMaxRowSizeIncludeBlobs() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public int getMaxStatementLength() + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getMaxStatements() + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getMaxTableNameLength() + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getMaxTablesInSelect() + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getMaxUserNameLength() + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getDefaultTransactionIsolation() + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public boolean supportsTransactions() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsTransactionIsolationLevel(int level) + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsDataDefinitionAndDataManipulationTransactions() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsDataManipulationTransactionsOnly() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean dataDefinitionCausesTransactionCommit() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean dataDefinitionIgnoredInTransactions() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public ResultSet getProcedures(String catalog, String schemaPattern, String procedureNamePattern) + throws SQLException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public ResultSet getProcedureColumns(String catalog, String schemaPattern, String procedureNamePattern, String columnNamePattern) + throws SQLException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public ResultSet getTableTypes() + throws SQLException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public ResultSet getColumnPrivileges(String catalog, String schema, String table, String columnNamePattern) + throws SQLException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public ResultSet getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern) + throws SQLException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public ResultSet getBestRowIdentifier(String catalog, String schema, String table, int scope, boolean nullable) + throws SQLException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public ResultSet getVersionColumns(String catalog, String schema, String table) + throws SQLException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public ResultSet getExportedKeys(String catalog, String schema, String table) + throws SQLException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public ResultSet getCrossReference(String parentCatalog, String parentSchema, String parentTable, String foreignCatalog, String foreignSchema, String foreignTable) + throws SQLException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean supportsResultSetType(int type) + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsResultSetConcurrency(int type, int concurrency) + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean ownUpdatesAreVisible(int type) + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean ownDeletesAreVisible(int type) + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean ownInsertsAreVisible(int type) + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean othersUpdatesAreVisible(int type) + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean othersDeletesAreVisible(int type) + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean othersInsertsAreVisible(int type) + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean updatesAreDetected(int type) + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean deletesAreDetected(int type) + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean insertsAreDetected(int type) + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsBatchUpdates() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public ResultSet getUDTs(String catalog, String schemaPattern, String typeNamePattern, int[] types) + throws SQLException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public Connection getConnection() + throws SQLException + { + // TODO Auto-generated method stub + return connection; + } + + public void setConnection(ForceConnection connection) + { + this.connection = connection; + this.partnerService = new PartnerService(connection.getPartnerConnection()); + } + + @Override + public boolean supportsSavepoints() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsNamedParameters() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsMultipleOpenResults() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsGetGeneratedKeys() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public ResultSet getSuperTypes(String catalog, String schemaPattern, String typeNamePattern) + throws SQLException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public ResultSet getSuperTables(String catalog, String schemaPattern, String tableNamePattern) + throws SQLException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public ResultSet getAttributes(String catalog, String schemaPattern, String typeNamePattern, String attributeNamePattern) + throws SQLException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean supportsResultSetHoldability(int holdability) + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public int getResultSetHoldability() + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getDatabaseMajorVersion() + throws SQLException + { + return 39; + } + + @Override + public int getDatabaseMinorVersion() + throws SQLException + { + return 0; + } + + @Override + public int getJDBCMajorVersion() + throws SQLException + { + return 4; + } + + @Override + public int getJDBCMinorVersion() + throws SQLException + { + return 0; + } + + @Override + public int getSQLStateType() + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public boolean locatorsUpdateCopy() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsStatementPooling() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public RowIdLifetime getRowIdLifetime() + throws SQLException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public ResultSet getSchemas(String catalog, String schemaPattern) + throws SQLException + { + return getSchemas(); + } + + @Override + public boolean supportsStoredFunctionsUsingCallSyntax() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean autoCommitFailureClosesAllResultSets() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public ResultSet getClientInfoProperties() + throws SQLException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public ResultSet getFunctions(String catalog, String schemaPattern, String functionNamePattern) + throws SQLException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public ResultSet getFunctionColumns(String catalog, String schemaPattern, String functionNamePattern, String columnNamePattern) + throws SQLException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public ResultSet getPseudoColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) + throws SQLException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean generatedKeyAlwaysReturned() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + public static class TypeInfo + { + public String typeName; + public int sqlDataType; + public int precision; + public int minScale; + public int maxScale; + public int radix; + + public TypeInfo(String typeName, int sqlDataType, int precision, int minScale, int maxScale, int radix) + { + this.typeName = typeName; + this.sqlDataType = sqlDataType; + this.precision = precision; + this.minScale = minScale; + this.maxScale = maxScale; + this.radix = radix; + } + } +} diff --git a/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/metadata/ForceDatabaseMetadataCache.java b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/metadata/ForceDatabaseMetadataCache.java new file mode 100644 index 0000000000000..1c07769233ae3 --- /dev/null +++ b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/metadata/ForceDatabaseMetadataCache.java @@ -0,0 +1,31 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce.driver.metadata; + +import com.google.inject.BindingAnnotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@BindingAnnotation +@Target({FIELD, PARAMETER, METHOD}) +@Retention(RUNTIME) +public @interface ForceDatabaseMetadataCache +{ +} diff --git a/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/metadata/Table.java b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/metadata/Table.java new file mode 100644 index 0000000000000..e1c7b94810e71 --- /dev/null +++ b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/metadata/Table.java @@ -0,0 +1,55 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce.driver.metadata; + +import java.io.Serializable; +import java.util.List; + +public class Table + implements Serializable +{ + private String name; + private String comments; + private List columns; + + public Table(String name, String comments, List columns) + { + this.name = name; + this.comments = comments; + this.columns = columns; + for (Column c : columns) { + c.setTable(this); + } + } + + public String getName() + { + return name; + } + + public String getComments() + { + return comments; + } + + public List getColumns() + { + return columns; + } + + public Column findColumn(String columnName) + { + return columns.stream().filter(column -> columnName.equals(column.getName())).findFirst().orElse(null); + } +} diff --git a/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/oauth/BadOAuthTokenException.java b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/oauth/BadOAuthTokenException.java new file mode 100644 index 0000000000000..41b3ed40aa2f2 --- /dev/null +++ b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/oauth/BadOAuthTokenException.java @@ -0,0 +1,23 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce.driver.oauth; + +public class BadOAuthTokenException + extends RuntimeException +{ + public BadOAuthTokenException(String message) + { + super(message); + } +} diff --git a/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/oauth/ForceClientException.java b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/oauth/ForceClientException.java new file mode 100644 index 0000000000000..45f577aedaeb6 --- /dev/null +++ b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/oauth/ForceClientException.java @@ -0,0 +1,28 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce.driver.oauth; + +public class ForceClientException + extends RuntimeException +{ + public ForceClientException(String message) + { + super(message); + } + + public ForceClientException(String message, Throwable cause) + { + super(message, cause); + } +} diff --git a/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/oauth/ForceOAuthClient.java b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/oauth/ForceOAuthClient.java new file mode 100644 index 0000000000000..91f6c1cd64c89 --- /dev/null +++ b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/oauth/ForceOAuthClient.java @@ -0,0 +1,154 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce.driver.oauth; + +import com.google.api.client.auth.oauth2.BearerToken; +import com.google.api.client.auth.oauth2.Credential; +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpBackOffIOExceptionHandler; +import com.google.api.client.http.HttpBackOffUnsuccessfulResponseHandler; +import com.google.api.client.http.HttpIOExceptionHandler; +import com.google.api.client.http.HttpRequestFactory; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.HttpResponseException; +import com.google.api.client.http.HttpStatusCodes; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.JsonFactory; +import com.google.api.client.json.jackson2.JacksonFactory; +import com.google.api.client.util.BackOff; +import com.google.api.client.util.ExponentialBackOff; +import org.apache.commons.lang3.StringUtils; + +import java.io.IOException; + +import static java.lang.Math.toIntExact; + +public class ForceOAuthClient +{ + private static final String LOGIN_URL = "https://login.salesforce.com/services/oauth2/userinfo"; + private static final String TEST_LOGIN_URL = "https://test.salesforce.com/services/oauth2/userinfo"; + private static final String API_VERSION = "43"; + + private static final String BAD_TOKEN_SF_ERROR_CODE = "Bad_OAuth_Token"; + private static final String MISSING_TOKEN_SF_ERROR_CODE = "Missing_OAuth_Token"; + private static final String WRONG_ORG_SF_ERROR_CODE = "Wrong_Org"; + private static final String BAD_ID_SF_ERROR_CODE = "Bad_Id"; + private static final String INTERNAL_SERVER_ERROR_SF_ERROR_CODE = "Internal Error"; + private static final int MAX_RETRIES = 5; + + private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport(); + private static final JsonFactory JSON_FACTORY = new JacksonFactory(); + + private final long connectTimeout; + private final long readTimeout; + + public ForceOAuthClient(long connectTimeout, long readTimeout) + { + this.connectTimeout = connectTimeout; + this.readTimeout = readTimeout; + } + + private static void extractPartnerUrl(ForceUserInfo userInfo) + { + if (userInfo.getUrls() == null || !userInfo.getUrls().containsKey("partner")) { + throw new IllegalStateException("User info doesn't contain partner URL: " + userInfo.getUrls()); + } + userInfo.setPartnerUrl(userInfo.getUrls().get("partner").replace("{version}", API_VERSION)); + } + + private static void extractInstance(ForceUserInfo userInfo) + { + String profileUrl = userInfo.getPartnerUrl(); + + if (StringUtils.isBlank(profileUrl)) { + return; + } + + profileUrl = profileUrl.replace("https://", ""); + + String instance = StringUtils.split(profileUrl, '.')[0]; + userInfo.setInstance(instance); + } + + public ForceUserInfo getUserInfo(String accessToken, boolean sandbox) + { + GenericUrl loginUrl = new GenericUrl(sandbox ? TEST_LOGIN_URL : LOGIN_URL); + HttpRequestFactory requestFactory = buildHttpRequestFactory(accessToken); + int tryCount = 0; + while (true) { + try { + HttpResponse result = requestFactory.buildGetRequest(loginUrl).execute(); + ForceUserInfo forceUserInfo = result.parseAs(ForceUserInfo.class); + extractPartnerUrl(forceUserInfo); + extractInstance(forceUserInfo); + + return forceUserInfo; + } + catch (HttpResponseException e) { + if (isForceInternalError(e) && tryCount < MAX_RETRIES) { + tryCount++; + continue; //try one more time + } + if (isBadTokenError(e)) { + throw new BadOAuthTokenException("Bad OAuth Token: " + accessToken); + } + throw new ForceClientException("Response error: " + e.getStatusCode() + " " + e.getContent()); + } + catch (IOException e) { + throw new ForceClientException("IO error: " + e.getMessage(), e); + } + } + } + + private HttpRequestFactory buildHttpRequestFactory(String accessToken) + { + Credential credential = new Credential(BearerToken.authorizationHeaderAccessMethod()).setAccessToken(accessToken); + + return HTTP_TRANSPORT.createRequestFactory(request -> { + request.setConnectTimeout(toIntExact(connectTimeout)); + request.setReadTimeout(toIntExact(readTimeout)); + request.setParser(JSON_FACTORY.createJsonObjectParser()); + request.setInterceptor(credential); + request.setUnsuccessfulResponseHandler(buildUnsuccessfulResponseHandler()); + request.setIOExceptionHandler(buildIOExceptionHandler()); + request.setNumberOfRetries(MAX_RETRIES); + }); + } + + private boolean isBadTokenError(HttpResponseException e) + { + return ((e.getStatusCode() == HttpStatusCodes.STATUS_CODE_FORBIDDEN) && StringUtils.equalsAnyIgnoreCase(e.getContent(), BAD_TOKEN_SF_ERROR_CODE, MISSING_TOKEN_SF_ERROR_CODE, WRONG_ORG_SF_ERROR_CODE)) || (e.getStatusCode() == HttpStatusCodes.STATUS_CODE_NOT_FOUND && StringUtils.equalsIgnoreCase(e.getContent(), BAD_ID_SF_ERROR_CODE)); + } + + private boolean isForceInternalError(HttpResponseException e) + { + return e.getStatusCode() == HttpStatusCodes.STATUS_CODE_NOT_FOUND && StringUtils.equalsIgnoreCase(e.getContent(), INTERNAL_SERVER_ERROR_SF_ERROR_CODE); + } + + private BackOff getBackOff() + { + return new ExponentialBackOff.Builder().setInitialIntervalMillis(500).setMaxElapsedTimeMillis(30000).setMaxIntervalMillis(10000).setMultiplier(1.5).setRandomizationFactor(0.5).build(); + } + + private HttpBackOffUnsuccessfulResponseHandler buildUnsuccessfulResponseHandler() + { + return new HttpBackOffUnsuccessfulResponseHandler(getBackOff()); + } + + private HttpIOExceptionHandler buildIOExceptionHandler() + { + return new HttpBackOffIOExceptionHandler(getBackOff()); + } +} diff --git a/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/oauth/ForceUserInfo.java b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/oauth/ForceUserInfo.java new file mode 100644 index 0000000000000..d9442bfa5d903 --- /dev/null +++ b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/oauth/ForceUserInfo.java @@ -0,0 +1,143 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce.driver.oauth; + +import com.google.api.client.util.Key; + +import java.util.Map; + +public class ForceUserInfo +{ + @Key("user_id") private String userId; + @Key("organization_id") private String organizationId; + @Key("preferred_username") private String preferredUsername; + @Key("nickname") private String nickName; + private String name; + private String email; + @Key("zoneinfo") private String timeZone; + @Key("locale") private String locale; + private String instance; + private String partnerUrl; + @Key("urls") private Map urls; + + public String getUserId() + { + return userId; + } + + public void setUserId(String userId) + { + this.userId = userId; + } + + public String getOrganizationId() + { + return organizationId; + } + + public void setOrganizationId(String organizationId) + { + this.organizationId = organizationId; + } + + public String getPreferredUsername() + { + return preferredUsername; + } + + public void setPreferredUsername(String preferredUsername) + { + this.preferredUsername = preferredUsername; + } + + public String getNickName() + { + return nickName; + } + + public void setNickName(String nickName) + { + this.nickName = nickName; + } + + public String getName() + { + return name; + } + + public void setName(String name) + { + this.name = name; + } + + public String getEmail() + { + return email; + } + + public void setEmail(String email) + { + this.email = email; + } + + public String getTimeZone() + { + return timeZone; + } + + public void setTimeZone(String timeZone) + { + this.timeZone = timeZone; + } + + public String getLocale() + { + return locale; + } + + public void setLocale(String locale) + { + this.locale = locale; + } + + public String getInstance() + { + return instance; + } + + public void setInstance(String instance) + { + this.instance = instance; + } + + public String getPartnerUrl() + { + return partnerUrl; + } + + public void setPartnerUrl(String partnerUrl) + { + this.partnerUrl = partnerUrl; + } + + public Map getUrls() + { + return urls; + } + + public void setUrls(Map urls) + { + this.urls = urls; + } +} diff --git a/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/resultset/ForceResultSet.java b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/resultset/ForceResultSet.java new file mode 100644 index 0000000000000..bfbf21ac94ee7 --- /dev/null +++ b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/resultset/ForceResultSet.java @@ -0,0 +1,1437 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce.driver.resultset; + +import com.sforce.ws.ConnectionException; +import io.prestosql.plugin.salesforce.driver.delegates.PartnerService; +import io.prestosql.plugin.salesforce.driver.metadata.ColumnMap; +import io.prestosql.plugin.salesforce.driver.statement.ForcePreparedStatement; +import io.prestosql.plugin.salesforce.driver.statement.QueryWrapper; + +import javax.sql.rowset.serial.SerialBlob; + +import java.io.InputStream; +import java.io.Reader; +import java.io.Serializable; +import java.math.BigDecimal; +import java.net.URL; +import java.sql.Array; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Date; +import java.sql.NClob; +import java.sql.Ref; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.RowId; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.SQLXML; +import java.sql.Time; +import java.sql.Timestamp; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Base64; +import java.util.Calendar; +import java.util.Deque; +import java.util.GregorianCalendar; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.function.Function; + +public class ForceResultSet + implements ResultSet, Serializable +{ + private static final long serialVersionUID = 1L; + + private transient Integer index; + private ResultSetMetaData metadata; + private PartnerService service; + private Deque> rows = new LinkedList<>(); + private ColumnMap currentRecord; + private QueryWrapper queryWrapper; + private int lastColumnIndex; + + public ForceResultSet(List> columnMaps) + { + this.rows.addAll(columnMaps); + } + + public ForceResultSet(QueryWrapper queryWrapper) + { + this.queryWrapper = queryWrapper; + } + + @Override + public Object getObject(String columnName) + throws SQLException + { + return currentRecord.get(columnName.toUpperCase()); + } + + @Override + public Object getObject(int columnIndex) + throws SQLException + { + lastColumnIndex = columnIndex; + return currentRecord.getByIndex(columnIndex); + } + + @Override + public String getString(String columnName) + throws SQLException + { + return (String) getObject(columnName); + } + + @Override + public String getString(int columnIndex) + throws SQLException + { + return (String) getObject(columnIndex); + } + + @Override + public boolean next() + throws SQLException + { + try { + currentRecord = rows.pop(); + return true; + } + catch (NoSuchElementException e) { + try { + if (queryWrapper == null) { + return false; + } + + rows.addAll(queryWrapper.next()); + currentRecord = rows.pop(); + return true; + } + catch (NoSuchElementException e2) { + return false; + } + catch (ConnectionException e2) { + ForcePreparedStatement.rethrowAsNonChecked(e2); + } + } + + return false; + } + + @Override + public ResultSetMetaData getMetaData() + throws SQLException + { + return metadata != null ? metadata : new ForceResultSetMetaData(); + } + + @Override + public Date getDate(int columnIndex, Calendar cal) + throws SQLException + { + throw new UnsupportedOperationException("Not implemented yet."); + } + + @Override + public Date getDate(String columnName, Calendar cal) + throws SQLException + { + throw new UnsupportedOperationException("Not implemented yet."); + } + + @Override + public BigDecimal getBigDecimal(int columnIndex) + throws SQLException + { + return new ColumnValueParser<>(BigDecimal::new).parse(columnIndex).orElse(null); + } + + @Override + public BigDecimal getBigDecimal(String columnName) + throws SQLException + { + return new ColumnValueParser<>(BigDecimal::new).parse(columnName).orElse(null); + } + + @Override + public boolean isBeforeFirst() + throws SQLException + { + return false; + } + + @Override + public boolean isAfterLast() + throws SQLException + { + return false; + } + + @Override + public boolean isFirst() + throws SQLException + { + return false; + } + + @Override + public boolean isLast() + throws SQLException + { + return false; + } + + protected java.util.Date parseDate(String dateRepr) + { + try { + return new SimpleDateFormat("yyyy-MM-dd").parse(dateRepr); + } + catch (ParseException e) { + throw new RuntimeException(e); + } + } + + @Override + public Date getDate(int columnIndex) + throws SQLException + { + return new ColumnValueParser<>(this::parseDate).parse(columnIndex).map(d -> new java.sql.Date(d.getTime())).orElse(null); + } + + @Override + public Date getDate(String columnName) + throws SQLException + { + return new ColumnValueParser<>(this::parseDate).parse(columnName).map(d -> new java.sql.Date(d.getTime())).orElse(null); + } + + private java.util.Date parseDateTime(String dateRepr) + { + try { + return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX").parse(dateRepr); + } + catch (ParseException e) { + throw new RuntimeException(e); + } + } + + @Override + public Timestamp getTimestamp(int columnIndex) + throws SQLException + { + Object value = currentRecord.getByIndex(columnIndex); + if (value instanceof GregorianCalendar) { + return new java.sql.Timestamp(((GregorianCalendar) value).getTime().getTime()); + } + else { + return new ColumnValueParser<>(this::parseDateTime).parse(columnIndex).map(d -> new java.sql.Timestamp(d.getTime())).orElse(null); + } + } + + @Override + public Timestamp getTimestamp(String columnName) + throws SQLException + { + Object value = currentRecord.get(columnName); + if (value instanceof GregorianCalendar) { + return new java.sql.Timestamp(((GregorianCalendar) value).getTime().getTime()); + } + else { + return new ColumnValueParser<>((v) -> parseDateTime(v)).parse(columnName).map(d -> new java.sql.Timestamp(d.getTime())).orElse(null); + } + } + + @Override + public Timestamp getTimestamp(int columnIndex, Calendar cal) + throws SQLException + { + throw new UnsupportedOperationException("Not implemented yet."); + } + + @Override + public Timestamp getTimestamp(String columnName, Calendar cal) + throws SQLException + { + throw new UnsupportedOperationException("Not implemented yet."); + } + + private java.util.Date parseTime(String dateRepr) + { + try { + return new SimpleDateFormat("HH:mm:ss.SSSX").parse(dateRepr); + } + catch (ParseException e) { + throw new RuntimeException(e); + } + } + + @Override + public Time getTime(String columnName) + throws SQLException + { + return new ColumnValueParser<>(this::parseTime).parse(columnName).map(d -> new Time(d.getTime())).orElse(null); + } + + @Override + public Time getTime(int columnIndex) + throws SQLException + { + return new ColumnValueParser<>(this::parseTime).parse(columnIndex).map(d -> new Time(d.getTime())).orElse(null); + } + + @Override + public BigDecimal getBigDecimal(int columnIndex, int scale) + { + Optional result = new ColumnValueParser<>(BigDecimal::new).parse(columnIndex); + result.ifPresent(v -> v.setScale(scale)); + return result.orElse(null); + } + + @Override + public BigDecimal getBigDecimal(String columnName, int scale) + { + Optional result = new ColumnValueParser<>(BigDecimal::new).parse(columnName); + result.ifPresent(v -> v.setScale(scale)); + return result.orElse(null); + } + + @Override + public float getFloat(int columnIndex) + throws SQLException + { + return new ColumnValueParser<>(Float::new).parse(columnIndex).orElse(0f); + } + + @Override + public float getFloat(String columnName) + throws SQLException + { + return new ColumnValueParser<>(Float::new).parse(columnName).orElse(0f); + } + + @Override + public double getDouble(int columnIndex) + throws SQLException + { + return new ColumnValueParser<>(Double::new).parse(columnIndex).orElse(0d); + } + + @Override + public double getDouble(String columnName) + throws SQLException + { + return new ColumnValueParser<>(Double::new).parse(columnName).orElse(0d); + } + + @Override + public long getLong(String columnName) + throws SQLException + { + return new ColumnValueParser<>(Long::new).parse(columnName).orElse(0L); + } + + @Override + public long getLong(int columnIndex) + throws SQLException + { + return new ColumnValueParser(Long::new).parse(columnIndex).orElse(0L); + } + + @Override + public int getInt(String columnName) + throws SQLException + { + return new ColumnValueParser<>(Integer::new).parse(columnName).orElse(0); + } + + @Override + public int getInt(int columnIndex) + throws SQLException + { + return new ColumnValueParser<>(Integer::new).parse(columnIndex).orElse(0); + } + + @Override + public short getShort(String columnName) + throws SQLException + { + return new ColumnValueParser<>(Short::new).parse(columnName).orElse((short) 0); + } + + @Override + public short getShort(int columnIndex) + throws SQLException + { + return new ColumnValueParser<>(Short::new).parse(columnIndex).orElse((short) 0); + } + + @Override + public InputStream getBinaryStream(int columnIndex) + throws SQLException + { + throw new UnsupportedOperationException("Not implemented yet."); + } + + @Override + public InputStream getBinaryStream(String columnName) + throws SQLException + { + throw new UnsupportedOperationException("Not implemented yet."); + } + + private Blob createBlob(byte[] data) + { + try { + return new SerialBlob(data); + } + catch (SQLException e) { + throw new RuntimeException(e); + } + } + + @Override + public Blob getBlob(int columnIndex) + throws SQLException + { + return new ColumnValueParser<>((v) -> Base64.getDecoder().decode(v)).parse(columnIndex).map(this::createBlob).orElse(null); + } + + @Override + public Blob getBlob(String columnName) + throws SQLException + { + return new ColumnValueParser<>((v) -> Base64.getDecoder().decode(v)).parse(columnName).map(this::createBlob).orElse(null); + } + + @Override + public boolean getBoolean(int columnIndex) + throws SQLException + { + return new ColumnValueParser<>(Boolean::new).parse(columnIndex).orElse(false); + } + + @Override + public boolean getBoolean(String columnName) + throws SQLException + { + return new ColumnValueParser<>(Boolean::new).parse(columnName).orElse(false); + } + + @Override + public byte getByte(int columnIndex) + throws SQLException + { + return new ColumnValueParser<>(Byte::new).parse(columnIndex).orElse((byte) 0); + } + + @Override + public byte getByte(String columnName) + throws SQLException + { + return new ColumnValueParser<>(Byte::new).parse(columnName).orElse((byte) 0); + } + + @Override + public byte[] getBytes(int columnIndex) + throws SQLException + { + throw new UnsupportedOperationException("Not implemented yet."); + } + + @Override + public byte[] getBytes(String columnName) + throws SQLException + { + throw new UnsupportedOperationException("Not implemented yet."); + } + + @Override + public boolean absolute(int row) + throws SQLException + { + return false; + } + + @Override + public void afterLast() + throws SQLException + { + System.out.println("after last check"); + } + + // + // Not implemented below here + // + + @Override + public boolean first() + throws SQLException + { + return false; + } + + @Override + public boolean last() + throws SQLException + { + return false; + } + + @Override + public void beforeFirst() + throws SQLException + { + } + + @Override + public void cancelRowUpdates() + throws SQLException + { + } + + @Override + public void clearWarnings() + throws SQLException + { + } + + @Override + public void close() + throws SQLException + { + } + + @Override + public void deleteRow() + throws SQLException + { + } + + @Override + public int findColumn(String columnName) + throws SQLException + { + return 0; + } + + @Override + public Array getArray(int i) + throws SQLException + { + return null; + } + + @Override + public Array getArray(String colName) + throws SQLException + { + return null; + } + + @Override + public InputStream getAsciiStream(int columnIndex) + throws SQLException + { + return null; + } + + @Override + public InputStream getAsciiStream(String columnName) + throws SQLException + { + return null; + } + + @Override + public Reader getCharacterStream(int columnIndex) + throws SQLException + { + return null; + } + + @Override + public Reader getCharacterStream(String columnName) + throws SQLException + { + return null; + } + + @Override + public Clob getClob(int i) + throws SQLException + { + return null; + } + + @Override + public Clob getClob(String colName) + throws SQLException + { + return null; + } + + @Override + public int getConcurrency() + throws SQLException + { + return 0; + } + + @Override + public String getCursorName() + throws SQLException + { + return null; + } + + @Override + public int getFetchDirection() + throws SQLException + { + return 0; + } + + @Override + public void setFetchDirection(int direction) + throws SQLException + { + } + + @Override + public int getFetchSize() + throws SQLException + { + return 0; + } + + @Override + public void setFetchSize(int rows) + throws SQLException + { + } + + @Override + public Object getObject(int i, Map> map) + throws SQLException + { + return null; + } + + @Override + public Object getObject(String colName, Map> map) + throws SQLException + { + return null; + } + + @Override + public Ref getRef(int i) + throws SQLException + { + return null; + } + + @Override + public Ref getRef(String colName) + throws SQLException + { + return null; + } + + @Override + public int getRow() + throws SQLException + { + return 0; + } + + @Override + public ForcePreparedStatement getStatement() + throws SQLException + { + return null; + } + + @Override + public Time getTime(int columnIndex, Calendar cal) + throws SQLException + { + return null; + } + + @Override + public Time getTime(String columnName, Calendar cal) + throws SQLException + { + return null; + } + + @Override + public int getType() + throws SQLException + { + return 0; + } + + @Override + public URL getURL(int columnIndex) + throws SQLException + { + return null; + } + + @Override + public URL getURL(String columnName) + throws SQLException + { + return null; + } + + @Override + public InputStream getUnicodeStream(int columnIndex) + throws SQLException + { + return null; + } + + @Override + public InputStream getUnicodeStream(String columnName) + throws SQLException + { + return null; + } + + @Override + public SQLWarning getWarnings() + throws SQLException + { + return null; + } + + @Override + public void insertRow() + throws SQLException + { + } + + @Override + public void moveToCurrentRow() + throws SQLException + { + } + + @Override + public void moveToInsertRow() + throws SQLException + { + } + + @Override + public boolean previous() + throws SQLException + { + return false; + } + + @Override + public void refreshRow() + throws SQLException + { + } + + @Override + public boolean relative(int rows) + throws SQLException + { + return false; + } + + @Override + public boolean rowDeleted() + throws SQLException + { + return false; + } + + @Override + public boolean rowInserted() + throws SQLException + { + return false; + } + + @Override + public boolean rowUpdated() + throws SQLException + { + return false; + } + + @Override + public void updateArray(int columnIndex, Array x) + throws SQLException + { + } + + @Override + public void updateArray(String columnName, Array x) + throws SQLException + { + } + + @Override + public void updateAsciiStream(int columnIndex, InputStream x, int length) + throws SQLException + { + } + + @Override + public void updateAsciiStream(String columnName, InputStream x, int length) + throws SQLException + { + } + + @Override + public void updateBigDecimal(int columnIndex, BigDecimal x) + throws SQLException + { + } + + @Override + public void updateBigDecimal(String columnName, BigDecimal x) + throws SQLException + { + } + + @Override + public void updateBinaryStream(int columnIndex, InputStream x, int length) + throws SQLException + { + } + + @Override + public void updateBinaryStream(String columnName, InputStream x, int length) + throws SQLException + { + } + + @Override + public void updateBlob(int columnIndex, Blob x) + throws SQLException + { + } + + @Override + public void updateBlob(String columnName, Blob x) + throws SQLException + { + } + + @Override + public void updateBoolean(int columnIndex, boolean x) + throws SQLException + { + } + + @Override + public void updateBoolean(String columnName, boolean x) + throws SQLException + { + } + + @Override + public void updateByte(int columnIndex, byte x) + throws SQLException + { + } + + @Override + public void updateByte(String columnName, byte x) + throws SQLException + { + } + + @Override + public void updateBytes(int columnIndex, byte[] x) + throws SQLException + { + } + + @Override + public void updateBytes(String columnName, byte[] x) + throws SQLException + { + } + + @Override + public void updateCharacterStream(int columnIndex, Reader x, int length) + throws SQLException + { + } + + @Override + public void updateCharacterStream(String columnName, Reader reader, int length) + throws SQLException + { + } + + @Override + public void updateClob(int columnIndex, Clob x) + throws SQLException + { + } + + @Override + public void updateClob(String columnName, Clob x) + throws SQLException + { + } + + @Override + public void updateDate(int columnIndex, Date x) + throws SQLException + { + } + + @Override + public void updateDate(String columnName, Date x) + throws SQLException + { + } + + @Override + public void updateDouble(int columnIndex, double x) + throws SQLException + { + } + + @Override + public void updateDouble(String columnName, double x) + throws SQLException + { + } + + @Override + public void updateFloat(int columnIndex, float x) + throws SQLException + { + } + + @Override + public void updateFloat(String columnName, float x) + throws SQLException + { + } + + @Override + public void updateInt(int columnIndex, int x) + throws SQLException + { + } + + @Override + public void updateInt(String columnName, int x) + throws SQLException + { + } + + @Override + public void updateLong(int columnIndex, long x) + throws SQLException + { + } + + @Override + public void updateLong(String columnName, long x) + throws SQLException + { + } + + @Override + public void updateNull(int columnIndex) + throws SQLException + { + } + + @Override + public void updateNull(String columnName) + throws SQLException + { + } + + @Override + public void updateObject(int columnIndex, Object x) + throws SQLException + { + } + + @Override + public void updateObject(String columnName, Object x) + throws SQLException + { + } + + @Override + public void updateObject(int columnIndex, Object x, int scale) + throws SQLException + { + } + + @Override + public void updateObject(String columnName, Object x, int scale) + throws SQLException + { + } + + @Override + public void updateRef(int columnIndex, Ref x) + throws SQLException + { + } + + @Override + public void updateRef(String columnName, Ref x) + throws SQLException + { + } + + @Override + public void updateRow() + throws SQLException + { + } + + @Override + public void updateShort(int columnIndex, short x) + throws SQLException + { + } + + @Override + public void updateShort(String columnName, short x) + throws SQLException + { + } + + @Override + public void updateString(int columnIndex, String x) + throws SQLException + { + } + + @Override + public void updateString(String columnName, String x) + throws SQLException + { + } + + @Override + public void updateTime(int columnIndex, Time x) + throws SQLException + { + } + + @Override + public void updateTime(String columnName, Time x) + throws SQLException + { + } + + @Override + public void updateTimestamp(int columnIndex, Timestamp x) + throws SQLException + { + } + + @Override + public void updateTimestamp(String columnName, Timestamp x) + throws SQLException + { + } + + @Override + public boolean wasNull() + throws SQLException + { + return getObject(lastColumnIndex) == null; + } + + @Override + public T unwrap(Class iface) + throws SQLException + { + return null; + } + + @Override + public boolean isWrapperFor(Class iface) + throws SQLException + { + return false; + } + + @Override + public RowId getRowId(int columnIndex) + throws SQLException + { + return null; + } + + @Override + public RowId getRowId(String columnLabel) + throws SQLException + { + return null; + } + + @Override + public void updateRowId(int columnIndex, RowId x) + throws SQLException + { + } + + @Override + public void updateRowId(String columnLabel, RowId x) + throws SQLException + { + } + + @Override + public int getHoldability() + throws SQLException + { + return 0; + } + + @Override + public boolean isClosed() + throws SQLException + { + return false; + } + + @Override + public void updateNString(int columnIndex, String nString) + throws SQLException + { + } + + @Override + public void updateNString(String columnLabel, String nString) + throws SQLException + { + } + + @Override + public void updateNClob(int columnIndex, NClob nClob) + throws SQLException + { + } + + @Override + public void updateNClob(String columnLabel, NClob nClob) + throws SQLException + { + } + + @Override + public NClob getNClob(int columnIndex) + throws SQLException + { + return null; + } + + @Override + public NClob getNClob(String columnLabel) + throws SQLException + { + return null; + } + + @Override + public SQLXML getSQLXML(int columnIndex) + throws SQLException + { + return null; + } + + @Override + public SQLXML getSQLXML(String columnLabel) + throws SQLException + { + return null; + } + + @Override + public void updateSQLXML(int columnIndex, SQLXML xmlObject) + throws SQLException + { + } + + @Override + public void updateSQLXML(String columnLabel, SQLXML xmlObject) + throws SQLException + { + } + + @Override + public String getNString(int columnIndex) + throws SQLException + { + return null; + } + + @Override + public String getNString(String columnLabel) + throws SQLException + { + return null; + } + + @Override + public Reader getNCharacterStream(int columnIndex) + throws SQLException + { + return null; + } + + @Override + public Reader getNCharacterStream(String columnLabel) + throws SQLException + { + return null; + } + + @Override + public void updateNCharacterStream(int columnIndex, Reader x, long length) + throws SQLException + { + } + + @Override + public void updateNCharacterStream(String columnLabel, Reader reader, long length) + throws SQLException + { + } + + @Override + public void updateAsciiStream(int columnIndex, InputStream x, long length) + throws SQLException + { + } + + @Override + public void updateBinaryStream(int columnIndex, InputStream x, long length) + throws SQLException + { + } + + @Override + public void updateCharacterStream(int columnIndex, Reader x, long length) + throws SQLException + { + } + + @Override + public void updateAsciiStream(String columnLabel, InputStream x, long length) + throws SQLException + { + } + + @Override + public void updateBinaryStream(String columnLabel, InputStream x, long length) + throws SQLException + { + } + + @Override + public void updateCharacterStream(String columnLabel, Reader reader, long length) + throws SQLException + { + } + + @Override + public void updateBlob(int columnIndex, InputStream inputStream, long length) + throws SQLException + { + } + + @Override + public void updateBlob(String columnLabel, InputStream inputStream, long length) + throws SQLException + { + } + + @Override + public void updateClob(int columnIndex, Reader reader, long length) + throws SQLException + { + } + + @Override + public void updateClob(String columnLabel, Reader reader, long length) + throws SQLException + { + } + + @Override + public void updateNClob(int columnIndex, Reader reader, long length) + throws SQLException + { + } + + @Override + public void updateNClob(String columnLabel, Reader reader, long length) + throws SQLException + { + } + + @Override + public void updateNCharacterStream(int columnIndex, Reader x) + throws SQLException + { + } + + @Override + public void updateNCharacterStream(String columnLabel, Reader reader) + throws SQLException + { + } + + @Override + public void updateAsciiStream(int columnIndex, InputStream x) + throws SQLException + { + } + + @Override + public void updateBinaryStream(int columnIndex, InputStream x) + throws SQLException + { + } + + @Override + public void updateCharacterStream(int columnIndex, Reader x) + throws SQLException + { + } + + @Override + public void updateAsciiStream(String columnLabel, InputStream x) + throws SQLException + { + } + + @Override + public void updateBinaryStream(String columnLabel, InputStream x) + throws SQLException + { + } + + @Override + public void updateCharacterStream(String columnLabel, Reader reader) + throws SQLException + { + } + + @Override + public void updateBlob(int columnIndex, InputStream inputStream) + throws SQLException + { + } + + @Override + public void updateBlob(String columnLabel, InputStream inputStream) + throws SQLException + { + } + + @Override + public void updateClob(int columnIndex, Reader reader) + throws SQLException + { + } + + @Override + public void updateClob(String columnLabel, Reader reader) + throws SQLException + { + } + + @Override + public void updateNClob(int columnIndex, Reader reader) + throws SQLException + { + } + + @Override + public void updateNClob(String columnLabel, Reader reader) + throws SQLException + { + } + + @Override + public T getObject(int columnIndex, Class type) + throws SQLException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public T getObject(String columnLabel, Class type) + throws SQLException + { + // TODO Auto-generated method stub + return null; + } + + private class ColumnValueParser + { + private Function conversion; + + public ColumnValueParser(Function parser) + { + this.conversion = parser; + } + + public Optional parse(int columnIndex) + { + Object value = currentRecord.getByIndex(columnIndex); + return parse(value); + } + + public Optional parse(String columnName) + { + Object value = currentRecord.get(columnName.toUpperCase()); + return parse(value); + } + + private Optional parse(Object o) + { + if (o == null) { + return Optional.empty(); + } + if (!(o instanceof String)) { + return (Optional) Optional.of(o); + } + return Optional.of(conversion.apply((String) o)); + } + } +} diff --git a/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/resultset/ForceResultSetMetaData.java b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/resultset/ForceResultSetMetaData.java new file mode 100644 index 0000000000000..cd16635d5976f --- /dev/null +++ b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/resultset/ForceResultSetMetaData.java @@ -0,0 +1,186 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce.driver.resultset; + +import java.sql.ResultSetMetaData; +import java.sql.SQLException; + +public class ForceResultSetMetaData + implements ResultSetMetaData +{ + @Override + public String getCatalogName(int column) + throws SQLException + { + return ""; + } + + // + // Not implemented below here + // + + @Override + public String getColumnClassName(int column) + throws SQLException + { + return null; + } + + @Override + public int getColumnCount() + throws SQLException + { + return 0; + } + + @Override + public int getColumnDisplaySize(int column) + throws SQLException + { + return 0; + } + + @Override + public String getColumnLabel(int column) + throws SQLException + { + return null; + } + + @Override + public String getColumnName(int column) + throws SQLException + { + return null; + } + + @Override + public int getColumnType(int column) + throws SQLException + { + return 0; + } + + @Override + public String getColumnTypeName(int column) + throws SQLException + { + return null; + } + + @Override + public int getPrecision(int column) + throws SQLException + { + return 0; + } + + @Override + public int getScale(int column) + throws SQLException + { + return 0; + } + + @Override + public String getSchemaName(int column) + throws SQLException + { + return null; + } + + @Override + public String getTableName(int column) + throws SQLException + { + return null; + } + + @Override + public boolean isAutoIncrement(int column) + throws SQLException + { + return false; + } + + @Override + public boolean isCaseSensitive(int column) + throws SQLException + { + return false; + } + + @Override + public boolean isCurrency(int column) + throws SQLException + { + return false; + } + + @Override + public boolean isDefinitelyWritable(int column) + throws SQLException + { + return false; + } + + @Override + public int isNullable(int column) + throws SQLException + { + return 0; + } + + @Override + public boolean isReadOnly(int column) + throws SQLException + { + return false; + } + + @Override + public boolean isSearchable(int column) + throws SQLException + { + return false; + } + + @Override + public boolean isSigned(int column) + throws SQLException + { + return false; + } + + @Override + public boolean isWritable(int column) + throws SQLException + { + return false; + } + + @Override + public T unwrap(Class iface) + throws SQLException + { + return null; + } + + @Override + public boolean isWrapperFor(Class iface) + throws SQLException + { + return false; + } +} diff --git a/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/statement/FieldDef.java b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/statement/FieldDef.java new file mode 100644 index 0000000000000..5f60bcfbac8e1 --- /dev/null +++ b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/statement/FieldDef.java @@ -0,0 +1,60 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce.driver.statement; + +import java.util.Objects; + +import static java.util.Objects.requireNonNull; + +public class FieldDef +{ + private final String name; + private final String type; + + public FieldDef(String name, String type) + { + requireNonNull(name); + requireNonNull(type); + + this.name = name; + this.type = type; + } + + public String getName() + { + return name; + } + + public String getType() + { + return type; + } + + @Override + public int hashCode() + { + return Objects.hash(name, type); + } + + @Override + public boolean equals(Object obj) + { + if (obj.getClass() != FieldDef.class) { + return false; + } + + FieldDef other = (FieldDef) obj; + return Objects.equals(name, other.name) && Objects.equals(type, other.type); + } +} diff --git a/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/statement/ForceParameterMetadata.java b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/statement/ForceParameterMetadata.java new file mode 100644 index 0000000000000..ad049efe38c74 --- /dev/null +++ b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/statement/ForceParameterMetadata.java @@ -0,0 +1,118 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce.driver.statement; + +import org.apache.commons.lang3.StringUtils; + +import java.sql.ParameterMetaData; +import java.sql.SQLException; +import java.sql.Types; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class ForceParameterMetadata + implements ParameterMetaData +{ + private List parameters = new ArrayList<>(); + + public ForceParameterMetadata(List parameters, String query) + { + super(); + this.parameters.addAll(parameters); + int paramsCountInQuery = StringUtils.countMatches(query, '?'); + if (this.parameters.size() < paramsCountInQuery) { + this.parameters.addAll(Collections.nCopies(paramsCountInQuery - this.parameters.size(), new Object())); + } + } + + @Override + public T unwrap(Class iface) + throws SQLException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean isWrapperFor(Class iface) + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public int getParameterCount() + throws SQLException + { + return parameters.size(); + } + + @Override + public int isNullable(int param) + throws SQLException + { + return ParameterMetaData.parameterNullable; + } + + @Override + public boolean isSigned(int param) + throws SQLException + { + return parameters.get(param + 1).getClass().isInstance(Number.class); + } + + @Override + public int getPrecision(int param) + throws SQLException + { + return 0; + } + + @Override + public int getScale(int param) + throws SQLException + { + return 0; + } + + @Override + public int getParameterType(int param) + throws SQLException + { + return Types.NVARCHAR; + } + + @Override + public String getParameterTypeName(int param) + throws SQLException + { + return "varchar"; + } + + @Override + public String getParameterClassName(int param) + throws SQLException + { + return parameters.get(param + 1).getClass().getName(); + } + + @Override + public int getParameterMode(int param) + throws SQLException + { + return ParameterMetaData.parameterModeIn; + } +} diff --git a/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/statement/ForcePreparedStatement.java b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/statement/ForcePreparedStatement.java new file mode 100644 index 0000000000000..3a3e0525d3612 --- /dev/null +++ b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/statement/ForcePreparedStatement.java @@ -0,0 +1,1025 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce.driver.statement; + +import com.sforce.soap.partner.QueryResult; +import com.sforce.ws.ConnectionException; +import io.prestosql.plugin.salesforce.driver.connection.ForceConnection; +import io.prestosql.plugin.salesforce.driver.delegates.ForceResultField; +import io.prestosql.plugin.salesforce.driver.delegates.PartnerService; +import io.prestosql.plugin.salesforce.driver.metadata.ColumnMap; +import io.prestosql.plugin.salesforce.driver.metadata.ForceDatabaseMetaData; +import io.prestosql.plugin.salesforce.driver.resultset.ForceResultSet; +import org.apache.commons.lang3.StringUtils; +import org.mule.tools.soql.exception.SOQLParsingException; + +import javax.sql.rowset.RowSetMetaDataImpl; + +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.net.URL; +import java.sql.Array; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.Date; +import java.sql.NClob; +import java.sql.ParameterMetaData; +import java.sql.PreparedStatement; +import java.sql.Ref; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.RowId; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.SQLXML; +import java.sql.Time; +import java.sql.Timestamp; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class ForcePreparedStatement + implements PreparedStatement +{ + private static final DateFormat SF_DATETIME_FORMATTER = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"); + private static Map, Function> paramConverters = new HashMap<>(); + private String soqlQuery; + private QueryResult queryResult; + private ForceConnection connection; + private PartnerService partnerService; + private ResultSetMetaData metadata; + private int fetchSize; + private int maxRows; + private List parameters = new ArrayList<>(); + private List fieldDefinitions; + private SoqlQueryAnalyzer queryAnalyzer; + + public ForcePreparedStatement(ForceConnection connection, String soql) + { + this.connection = connection; + this.soqlQuery = soql; + } + + public static RuntimeException rethrowAsNonChecked(Throwable throwable) + throws T + { + throw (T) throwable; // rely on vacuous cast + } + + protected static String toSoqlStringParam(Object param) + { + return "'" + param.toString().replaceAll("\\\\", "\\\\\\\\").replaceAll("'", "\\\\'") + "'"; + } + + protected static String convertToSoqlParam(Object paramValue) + { + Class paramClass = getParamClass(paramValue); + return paramConverters.get(paramClass).apply(paramValue); + } + + protected static Class getParamClass(Object paramValue) + { + Class paramClass = paramValue != null ? paramValue.getClass() : null; + if (!paramConverters.containsKey(paramClass)) { + paramClass = Object.class; + } + return paramClass; + } + + @Override + public ResultSet executeQuery() + throws SQLException + { + return query(); + } + + private ResultSet query() + throws SQLException + { + try { + String preparedSoql = prepareQuery(); + QueryWrapper queryWrapper = new QueryWrapper(preparedSoql, getFieldDefinitions(), getPartnerService()); + return new ForceResultSet(queryWrapper); + } + catch (ConnectionException | SOQLParsingException e) { + throw new SQLException(e); + } + } + + private String prepareQuery() + { + return setParams(soqlQuery); + } + + private ColumnMap convertToColumnMap(List record) + { + ColumnMap columnMap = new ColumnMap<>(); + record.stream().map(field -> field == null ? new ForceResultField(null, null, null, null) : field).forEach(field -> { + columnMap.put(field.getFullName(), field.getValue()); + }); + return columnMap; + } + + public List getParameters() + { + int paramsCountInQuery = StringUtils.countMatches(soqlQuery, '?'); + if (parameters.size() < paramsCountInQuery) { + parameters.addAll(Collections.nCopies(paramsCountInQuery - parameters.size(), null)); + } + return parameters; + } + + protected String setParams(String soql) + { + String result = soql; + for (Object param : getParameters()) { + String paramRepresentation = convertToSoqlParam(param); + result = result.replaceFirst("\\?", paramRepresentation); + } + return result; + } + + private ResultSetMetaData loadMetaData() + throws SQLException + { + try { + if (metadata == null) { + RowSetMetaDataImpl result = new RowSetMetaDataImpl(); + SoqlQueryAnalyzer queryAnalyzer = getQueryAnalyzer(); + List resultFieldDefinitions = flatten(getFieldDefinitions()); + int columnsCount = resultFieldDefinitions.size(); + result.setColumnCount(columnsCount); + for (int i = 1; i <= columnsCount; i++) { + FieldDef field = resultFieldDefinitions.get(i - 1); + result.setAutoIncrement(i, false); + result.setColumnName(i, field.getName()); + result.setColumnLabel(i, field.getName()); + String forceTypeName = field.getType(); + ForceDatabaseMetaData.TypeInfo typeInfo = ForceDatabaseMetaData.lookupTypeInfo(forceTypeName); + result.setColumnType(i, typeInfo.sqlDataType); + result.setColumnTypeName(i, typeInfo.typeName); + result.setPrecision(i, typeInfo.precision); + result.setSchemaName(i, "Salesforce"); + result.setTableName(i, queryAnalyzer.getFromObjectName()); + } + metadata = result; + } + return metadata; + } + catch (RuntimeException e) { + throw new SQLException(e.getCause() != null ? e.getCause() : e); + } + } + + private List flatten(List fieldDefinitions) + { + return (List) fieldDefinitions.stream().flatMap(def -> def instanceof List ? ((List) def).stream() : Stream.of(def)).collect(Collectors.toList()); + } + + private List getFieldDefinitions() + { + if (fieldDefinitions == null) { + fieldDefinitions = getQueryAnalyzer().getFieldDefinitions(); + } + return fieldDefinitions; + } + + private SoqlQueryAnalyzer getQueryAnalyzer() + { + if (queryAnalyzer == null) { + queryAnalyzer = new SoqlQueryAnalyzer(prepareQuery(), (objName) -> { + try { + return getPartnerService().describeSObject(objName); + } + catch (ConnectionException e) { + throw new RuntimeException(e); + } + }); + } + return queryAnalyzer; + } + + @Override + public ParameterMetaData getParameterMetaData() + throws SQLException + { + return new ForceParameterMetadata(parameters, soqlQuery); + } + + @Override + public ResultSetMetaData getMetaData() + throws SQLException + { + return loadMetaData(); + } + + public PartnerService getPartnerService() + throws ConnectionException + { + if (partnerService == null) { + partnerService = new PartnerService(connection.getPartnerConnection()); + } + return partnerService; + } + + @Override + public int getFetchSize() + throws SQLException + { + return fetchSize; + } + + @Override + public void setFetchSize(int rows) + throws SQLException + { + this.fetchSize = rows; + } + + @Override + public int getMaxRows() + throws SQLException + { + return maxRows; + } + + @Override + public void setMaxRows(int max) + throws SQLException + { + this.maxRows = max; + } + + @Override + public void setArray(int i, Array x) + throws SQLException + { + String methodName = this.getClass().getSimpleName() + "." + new Object() + { + }.getClass().getEnclosingMethod().getName(); + throw new UnsupportedOperationException("The " + methodName + " is not implemented yet."); + } + + @Override + public void setAsciiStream(int parameterIndex, InputStream x, int length) + throws SQLException + { + String methodName = this.getClass().getSimpleName() + "." + new Object() + { + }.getClass().getEnclosingMethod().getName(); + throw new UnsupportedOperationException("The " + methodName + " is not implemented yet."); + } + + @Override + public void setBigDecimal(int parameterIndex, BigDecimal x) + throws SQLException + { + addParameter(parameterIndex, x); + } + + protected void addParameter(int parameterIndex, Object x) + { + parameterIndex--; + if (parameters.size() < parameterIndex) { + parameters.addAll(Collections.nCopies(parameterIndex - parameters.size(), null)); + } + parameters.add(parameterIndex, x); + } + + @Override + public void setBinaryStream(int parameterIndex, InputStream x, int length) + throws SQLException + { + String methodName = this.getClass().getSimpleName() + "." + new Object() + { + }.getClass().getEnclosingMethod().getName(); + throw new UnsupportedOperationException("The " + methodName + " is not implemented yet."); + } + + @Override + public void setBlob(int i, Blob x) + throws SQLException + { + String methodName = this.getClass().getSimpleName() + "." + new Object() + { + }.getClass().getEnclosingMethod().getName(); + throw new UnsupportedOperationException("The " + methodName + " is not implemented yet."); + } + + @Override + public void setBoolean(int parameterIndex, boolean x) + throws SQLException + { + addParameter(parameterIndex, x); + } + + @Override + public void setByte(int parameterIndex, byte x) + throws SQLException + { + String methodName = this.getClass().getSimpleName() + "." + new Object() + { + }.getClass().getEnclosingMethod().getName(); + throw new UnsupportedOperationException("The " + methodName + " is not implemented yet."); + } + + @Override + public void setBytes(int parameterIndex, byte[] x) + throws SQLException + { + String methodName = this.getClass().getSimpleName() + "." + new Object() + { + }.getClass().getEnclosingMethod().getName(); + throw new UnsupportedOperationException("The " + methodName + " is not implemented yet."); + } + + @Override + public void setCharacterStream(int parameterIndex, Reader reader, int length) + throws SQLException + { + String methodName = this.getClass().getSimpleName() + "." + new Object() + { + }.getClass().getEnclosingMethod().getName(); + throw new UnsupportedOperationException("The " + methodName + " is not implemented yet."); + } + + @Override + public void setClob(int i, Clob x) + throws SQLException + { + String methodName = this.getClass().getSimpleName() + "." + new Object() + { + }.getClass().getEnclosingMethod().getName(); + throw new UnsupportedOperationException("The " + methodName + " is not implemented yet."); + } + + @Override + public void setDate(int parameterIndex, Date x) + throws SQLException + { + addParameter(parameterIndex, x); + } + + @Override + public void setDate(int parameterIndex, Date x, Calendar cal) + throws SQLException + { + String methodName = this.getClass().getSimpleName() + "." + new Object() + { + }.getClass().getEnclosingMethod().getName(); + throw new UnsupportedOperationException("The " + methodName + " is not implemented yet."); + } + + @Override + public void setDouble(int parameterIndex, double x) + throws SQLException + { + addParameter(parameterIndex, x); + } + + @Override + public void setFloat(int parameterIndex, float x) + throws SQLException + { + addParameter(parameterIndex, x); + } + + @Override + public void setInt(int parameterIndex, int x) + throws SQLException + { + addParameter(parameterIndex, x); + } + + @Override + public void setLong(int parameterIndex, long x) + throws SQLException + { + addParameter(parameterIndex, x); + } + + @Override + public void setNull(int parameterIndex, int sqlType) + throws SQLException + { + addParameter(parameterIndex, null); + } + + @Override + public void setNull(int paramIndex, int sqlType, String typeName) + throws SQLException + { + addParameter(paramIndex, null); + } + + @Override + public void setObject(int parameterIndex, Object x) + throws SQLException + { + String methodName = this.getClass().getSimpleName() + "." + new Object() + { + }.getClass().getEnclosingMethod().getName(); + throw new UnsupportedOperationException("The " + methodName + " is not implemented yet."); + } + + @Override + public void setObject(int parameterIndex, Object x, int targetSqlType) + throws SQLException + { + String methodName = this.getClass().getSimpleName() + "." + new Object() + { + }.getClass().getEnclosingMethod().getName(); + throw new UnsupportedOperationException("The " + methodName + " is not implemented yet."); + } + + @Override + public void setObject(int parameterIndex, Object x, int targetSqlType, int scale) + throws SQLException + { + String methodName = this.getClass().getSimpleName() + "." + new Object() + { + }.getClass().getEnclosingMethod().getName(); + throw new UnsupportedOperationException("The " + methodName + " is not implemented yet."); + } + + @Override + public void setRef(int i, Ref x) + throws SQLException + { + String methodName = this.getClass().getSimpleName() + "." + new Object() + { + }.getClass().getEnclosingMethod().getName(); + throw new UnsupportedOperationException("The " + methodName + " is not implemented yet."); + } + + @Override + public void setShort(int parameterIndex, short x) + throws SQLException + { + addParameter(parameterIndex, x); + } + + @Override + public void setString(int parameterIndex, String x) + throws SQLException + { + addParameter(parameterIndex, x); + } + + @Override + public void setTime(int parameterIndex, Time x) + throws SQLException + { + String methodName = this.getClass().getSimpleName() + "." + new Object() + { + }.getClass().getEnclosingMethod().getName(); + throw new UnsupportedOperationException("The " + methodName + " is not implemented yet."); + } + + @Override + public void setTime(int parameterIndex, Time x, Calendar cal) + throws SQLException + { + String methodName = this.getClass().getSimpleName() + "." + new Object() + { + }.getClass().getEnclosingMethod().getName(); + throw new UnsupportedOperationException("The " + methodName + " is not implemented yet."); + } + + @Override + public void setTimestamp(int parameterIndex, Timestamp x) + throws SQLException + { + addParameter(parameterIndex, x); + } + + @Override + public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) + throws SQLException + { + String methodName = this.getClass().getSimpleName() + "." + new Object() + { + }.getClass().getEnclosingMethod().getName(); + throw new UnsupportedOperationException("The " + methodName + " is not implemented yet."); + } + + @Override + public void setURL(int parameterIndex, URL x) + throws SQLException + { + String methodName = this.getClass().getSimpleName() + "." + new Object() + { + }.getClass().getEnclosingMethod().getName(); + throw new UnsupportedOperationException("The " + methodName + " is not implemented yet."); + } + + @Override + public void setUnicodeStream(int parameterIndex, InputStream x, int length) + throws SQLException + { + String methodName = this.getClass().getSimpleName() + "." + new Object() + { + }.getClass().getEnclosingMethod().getName(); + throw new UnsupportedOperationException("The " + methodName + " is not implemented yet."); + } + + @Override + public ResultSet executeQuery(String sql) + throws SQLException + { + throw new RuntimeException("Not yet implemented."); + } + + @Override + public int executeUpdate(String sql) + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + // Not required to implement below + + @Override + public void close() + throws SQLException + { + // TODO Auto-generated method stub + } + + @Override + public int getMaxFieldSize() + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public void setMaxFieldSize(int max) + throws SQLException + { + // TODO Auto-generated method stub + } + + @Override + public void setEscapeProcessing(boolean enable) + throws SQLException + { + // TODO Auto-generated method stub + } + + @Override + public int getQueryTimeout() + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public void setQueryTimeout(int seconds) + throws SQLException + { + // TODO Auto-generated method stub + } + + @Override + public void cancel() + throws SQLException + { + // TODO Auto-generated method stub + } + + @Override + public SQLWarning getWarnings() + throws SQLException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public void clearWarnings() + throws SQLException + { + // TODO Auto-generated method stub + } + + @Override + public void setCursorName(String name) + throws SQLException + { + // TODO Auto-generated method stub + } + + @Override + public boolean execute(String sql) + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public ResultSet getResultSet() + throws SQLException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getUpdateCount() + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public boolean getMoreResults() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public int getFetchDirection() + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public void setFetchDirection(int direction) + throws SQLException + { + // TODO Auto-generated method stub + } + + @Override + public int getResultSetConcurrency() + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getResultSetType() + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public void addBatch(String sql) + throws SQLException + { + // TODO Auto-generated method stub + } + + @Override + public void clearBatch() + throws SQLException + { + // TODO Auto-generated method stub + } + + @Override + public int[] executeBatch() + throws SQLException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public Connection getConnection() + throws SQLException + { + return connection; + } + + @Override + public boolean getMoreResults(int current) + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public ResultSet getGeneratedKeys() + throws SQLException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public int executeUpdate(String sql, int autoGeneratedKeys) + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int executeUpdate(String sql, int[] columnIndexes) + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int executeUpdate(String sql, String[] columnNames) + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public boolean execute(String sql, int autoGeneratedKeys) + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean execute(String sql, int[] columnIndexes) + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean execute(String sql, String[] columnNames) + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public int getResultSetHoldability() + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public boolean isClosed() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isPoolable() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public void setPoolable(boolean poolable) + throws SQLException + { + // TODO Auto-generated method stub + } + + @Override + public void closeOnCompletion() + throws SQLException + { + // TODO Auto-generated method stub + } + + @Override + public boolean isCloseOnCompletion() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public T unwrap(Class iface) + throws SQLException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean isWrapperFor(Class iface) + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public int executeUpdate() + throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public void clearParameters() + throws SQLException + { + // TODO Auto-generated method stub + } + + @Override + public boolean execute() + throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + @Override + public void addBatch() + throws SQLException + { + // TODO Auto-generated method stub + } + + @Override + public void setRowId(int parameterIndex, RowId x) + throws SQLException + { + // TODO Auto-generated method stub + } + + @Override + public void setNString(int parameterIndex, String value) + throws SQLException + { + // TODO Auto-generated method stub + } + + @Override + public void setNCharacterStream(int parameterIndex, Reader value, long length) + throws SQLException + { + // TODO Auto-generated method stub + } + + @Override + public void setNClob(int parameterIndex, NClob value) + throws SQLException + { + // TODO Auto-generated method stub + } + + @Override + public void setClob(int parameterIndex, Reader reader, long length) + throws SQLException + { + // TODO Auto-generated method stub + } + + @Override + public void setBlob(int parameterIndex, InputStream inputStream, long length) + throws SQLException + { + // TODO Auto-generated method stub + } + + @Override + public void setNClob(int parameterIndex, Reader reader, long length) + throws SQLException + { + // TODO Auto-generated method stub + } + + @Override + public void setSQLXML(int parameterIndex, SQLXML xmlObject) + throws SQLException + { + // TODO Auto-generated method stub + } + + @Override + public void setAsciiStream(int parameterIndex, InputStream x, long length) + throws SQLException + { + // TODO Auto-generated method stub + } + + @Override + public void setBinaryStream(int parameterIndex, InputStream x, long length) + throws SQLException + { + // TODO Auto-generated method stub + } + + @Override + public void setCharacterStream(int parameterIndex, Reader reader, long length) + throws SQLException + { + // TODO Auto-generated method stub + } + + @Override + public void setAsciiStream(int parameterIndex, InputStream x) + throws SQLException + { + // TODO Auto-generated method stub + } + + @Override + public void setBinaryStream(int parameterIndex, InputStream x) + throws SQLException + { + // TODO Auto-generated method stub + } + + @Override + public void setCharacterStream(int parameterIndex, Reader reader) + throws SQLException + { + // TODO Auto-generated method stub + } + + @Override + public void setNCharacterStream(int parameterIndex, Reader value) + throws SQLException + { + // TODO Auto-generated method stub + } + + @Override + public void setClob(int parameterIndex, Reader reader) + throws SQLException + { + // TODO Auto-generated method stub + } + + @Override + public void setBlob(int parameterIndex, InputStream inputStream) + throws SQLException + { + // TODO Auto-generated method stub + } + + @Override + public void setNClob(int parameterIndex, Reader reader) + throws SQLException + { + // TODO Auto-generated method stub + } + + static { + paramConverters.put(String.class, ForcePreparedStatement::toSoqlStringParam); + paramConverters.put(Object.class, ForcePreparedStatement::toSoqlStringParam); + paramConverters.put(Boolean.class, Object::toString); + paramConverters.put(Double.class, Object::toString); + paramConverters.put(BigDecimal.class, Object::toString); + paramConverters.put(Float.class, Object::toString); + paramConverters.put(Integer.class, Object::toString); + paramConverters.put(Long.class, Object::toString); + paramConverters.put(Short.class, Object::toString); + paramConverters.put(java.util.Date.class, SF_DATETIME_FORMATTER::format); + paramConverters.put(Timestamp.class, SF_DATETIME_FORMATTER::format); + paramConverters.put(null, p -> "NULL"); + } +} diff --git a/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/statement/QueryWrapper.java b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/statement/QueryWrapper.java new file mode 100644 index 0000000000000..e9ca5aae9c18d --- /dev/null +++ b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/statement/QueryWrapper.java @@ -0,0 +1,125 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce.driver.statement; + +import com.sforce.soap.partner.QueryResult; +import com.sforce.ws.ConnectionException; +import com.sforce.ws.bind.XmlObject; +import io.prestosql.plugin.salesforce.driver.delegates.ForceResultField; +import io.prestosql.plugin.salesforce.driver.delegates.PartnerResultToCartesianTable; +import io.prestosql.plugin.salesforce.driver.delegates.PartnerService; +import io.prestosql.plugin.salesforce.driver.metadata.ColumnMap; +import org.apache.commons.collections4.IteratorUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +public class QueryWrapper +{ + private static final List SOAP_RESPONSE_SERVICE_OBJECT_TYPES = Arrays.asList("type", "done", "queryLocator", "size"); + private final PartnerService service; + private List expectedSchema; + private String soql; + private Optional queryResult = Optional.empty(); + + private QueryWrapper(List expectedSchema, PartnerService service) + { + this.expectedSchema = expectedSchema; + this.service = service; + } + + QueryWrapper(String soql, List expectedSchema, PartnerService service) + { + this(expectedSchema, service); + this.soql = soql; + } + + public List> next() + throws ConnectionException + { + try { + if (!queryResult.isPresent()) { + queryResult = Optional.of(service.query(soql)); + } + else { + queryResult = Optional.of(service.queryMore(queryResult.get())); + } + } + catch (ConnectionException e) { + ForcePreparedStatement.rethrowAsNonChecked(e); + } + + List resultRows = Collections.synchronizedList(new ArrayList<>()); + resultRows.addAll(removeServiceInfo(Arrays.asList(queryResult.get().getRecords()))); + List cartesianTable = PartnerResultToCartesianTable.expand(resultRows, expectedSchema); + + List> maps = Collections.synchronizedList(new ArrayList<>()); + cartesianTable.forEach(record -> maps.add(convertToColumnMap(record))); + return maps; + } + + private ColumnMap convertToColumnMap(List record) + { + ColumnMap columnMap = new ColumnMap<>(); + record.stream().map(field -> field == null ? new ForceResultField(null, null, null, null) : field).forEach(field -> { + columnMap.put(field.getFullName(), field.getValue()); + }); + return columnMap; + } + + private List removeServiceInfo(Iterator rows) + { + return removeServiceInfo(IteratorUtils.toList(rows)); + } + + private List removeServiceInfo(List rows) + { + return rows.stream().filter(this::isDataObjectType).map(this::removeServiceInfo).collect(Collectors.toList()); + } + + private List removeServiceInfo(XmlObject row) + { + return IteratorUtils.toList(row.getChildren()).stream().filter(this::isDataObjectType).skip(1) // Removes duplicate Id from SF Partner API response + // (https://developer.salesforce.com/forums/?id=906F00000008kciIAA) + .map(field -> isNestedResultset(field) ? removeServiceInfo(field.getChildren()) : toForceResultField(field)).collect(Collectors.toList()); + } + + private boolean isNestedResultset(XmlObject object) + { + return object.getXmlType() != null && "QueryResult".equals(object.getXmlType().getLocalPart()); + } + + private ForceResultField toForceResultField(XmlObject field) + { + String fieldType = field.getXmlType() != null ? field.getXmlType().getLocalPart() : null; + if ("sObject".equalsIgnoreCase(fieldType)) { + List children = new ArrayList<>(); + field.getChildren().forEachRemaining(children::add); + field = children.get(2); + } + String name = field.getName().getLocalPart(); + Object value = field.getValue(); + return new ForceResultField(null, fieldType, name, value); + } + + private boolean isDataObjectType(XmlObject object) + { + return !SOAP_RESPONSE_SERVICE_OBJECT_TYPES.contains(object.getName().getLocalPart()); + } +} diff --git a/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/statement/SoqlQueryAnalyzer.java b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/statement/SoqlQueryAnalyzer.java new file mode 100644 index 0000000000000..f9b236cb56c0b --- /dev/null +++ b/presto-salesforce/src/main/java/io/prestosql/plugin/salesforce/driver/statement/SoqlQueryAnalyzer.java @@ -0,0 +1,159 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce.driver.statement; + +import com.sforce.soap.partner.ChildRelationship; +import com.sforce.soap.partner.DescribeSObjectResult; +import com.sforce.soap.partner.Field; +import org.mule.tools.soql.SOQLDataBaseVisitor; +import org.mule.tools.soql.SOQLParserHelper; +import org.mule.tools.soql.query.SOQLQuery; +import org.mule.tools.soql.query.SOQLSubQuery; +import org.mule.tools.soql.query.clause.FromClause; +import org.mule.tools.soql.query.from.ObjectSpec; +import org.mule.tools.soql.query.select.FieldSpec; +import org.mule.tools.soql.query.select.FunctionCallSpec; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +public class SoqlQueryAnalyzer +{ + private String soql; + private Function objectDescriptor; + private Map describedObjectsCache; + private SOQLQuery queryData; + private List fieldDefinitions; + + public SoqlQueryAnalyzer(String soql, Function objectDescriptor) + { + this(soql, objectDescriptor, new HashMap<>()); + } + + public SoqlQueryAnalyzer(String soql, Function objectDescriptor, Map describedObjectsCache) + { + this.soql = soql; + this.objectDescriptor = objectDescriptor; + this.describedObjectsCache = describedObjectsCache; + } + + public List getFieldDefinitions() + { + if (fieldDefinitions == null) { + fieldDefinitions = new ArrayList<>(); + SelectSpecVisitor visitor = new SelectSpecVisitor(); + getQueryData().getSelectSpecs().forEach(spec -> spec.accept(visitor)); + } + return fieldDefinitions; + } + + private Field findField(String name, DescribeSObjectResult objectDesc, Function nameFetcher) + { + return Arrays.stream(objectDesc.getFields()).filter(field -> name.equals(nameFetcher.apply(field))).findFirst().orElseThrow(() -> new IllegalArgumentException("Unknown field name \"" + name + "\" in object \"" + objectDesc.getName() + "\"")); + } + + private DescribeSObjectResult describeObject(String fromObjectName) + { + if (!describedObjectsCache.containsKey(fromObjectName)) { + DescribeSObjectResult description = objectDescriptor.apply(fromObjectName); + describedObjectsCache.put(fromObjectName, description); + return description; + } + else { + return describedObjectsCache.get(fromObjectName); + } + } + + protected String getFromObjectName() + { + return getQueryData().getFromClause().getMainObjectSpec().getObjectName(); + } + + private SOQLQuery getQueryData() + { + if (queryData == null) { + queryData = SOQLParserHelper.createSOQLData(soql); + } + return queryData; + } + + private class SelectSpecVisitor + extends SOQLDataBaseVisitor + { + private final List functionsHasIntResult = Arrays.asList("COUNT", "COUNT_DISTINCT", "CALENDAR_MONTH", "CALENDAR_QUARTER", "CALENDAR_YEAR", "DAY_IN_MONTH", "DAY_IN_WEEK", "DAY_IN_YEAR", "DAY_ONLY", "FISCAL_MONTH", "FISCAL_QUARTER", "FISCAL_YEAR", "HOUR_IN_DAY", "WEEK_IN_MONTH", "WEEK_IN_YEAR"); + + @Override + public Void visitFieldSpec(FieldSpec fieldSpec) + { + String name = fieldSpec.getFieldName(); + String alias = fieldSpec.getAlias() != null ? fieldSpec.getAlias() : name; + List prefixNames = new ArrayList<>(fieldSpec.getObjectPrefixNames()); + FieldDef result = createFieldDef(name, alias, prefixNames); + fieldDefinitions.add(result); + return null; + } + + private FieldDef createFieldDef(String name, String alias, List prefixNames) + { + List fieldPrefixes = new ArrayList<>(prefixNames); + String fromObject = getFromObjectName(); + if (!fieldPrefixes.isEmpty() && fieldPrefixes.get(0).equalsIgnoreCase(fromObject)) { + fieldPrefixes.remove(0); + } + while (!fieldPrefixes.isEmpty()) { + String referenceName = fieldPrefixes.get(0); + Field reference = findField(referenceName, describeObject(fromObject), fld -> fld.getRelationshipName()); + fromObject = reference.getReferenceTo()[0]; + fieldPrefixes.remove(0); + } + String type = findField(name, describeObject(fromObject), fld -> fld.getName()).getType().name(); + FieldDef result = new FieldDef(alias, type); + return result; + } + + @Override + public Void visitFunctionCallSpec(FunctionCallSpec functionCallSpec) + { + String alias = functionCallSpec.getAlias() != null ? functionCallSpec.getAlias() : functionCallSpec.getFunctionName(); + if (functionsHasIntResult.contains(functionCallSpec.getFunctionName().toUpperCase())) { + fieldDefinitions.add(new FieldDef(alias, "int")); + } + else { + org.mule.tools.soql.query.data.Field param = (org.mule.tools.soql.query.data.Field) functionCallSpec.getFunctionParameters().get(0); + FieldDef result = createFieldDef(param.getFieldName(), alias, param.getObjectPrefixNames()); + fieldDefinitions.add(result); + } + return null; + } + + @Override + public Void visitSOQLSubQuery(SOQLSubQuery soqlSubQuery) + { + String subquerySoql = soqlSubQuery.toSOQLText().replaceAll("\\A\\s*\\(|\\)\\s*$", ""); + SOQLQuery subquery = SOQLParserHelper.createSOQLData(subquerySoql); + String relationshipName = subquery.getFromClause().getMainObjectSpec().getObjectName(); + ChildRelationship relatedFrom = Arrays.stream(describeObject(getFromObjectName()).getChildRelationships()).filter(rel -> relationshipName.equalsIgnoreCase(rel.getRelationshipName())).findFirst().orElseThrow(() -> new IllegalArgumentException("Unresolved relationship in subquery \"" + subquerySoql + "\"")); + String fromObject = relatedFrom.getChildSObject(); + subquery.setFromClause(new FromClause(new ObjectSpec(fromObject, null))); + + SoqlQueryAnalyzer subqueryAnalyzer = new SoqlQueryAnalyzer(subquery.toSOQLText(), objectDescriptor, describedObjectsCache); + fieldDefinitions.add(new ArrayList(subqueryAnalyzer.getFieldDefinitions())); + return null; + } + } +} diff --git a/presto-salesforce/src/test/java/io/prestosql/plugin/salesforce/TestSalesforcePlugin.java b/presto-salesforce/src/test/java/io/prestosql/plugin/salesforce/TestSalesforcePlugin.java new file mode 100644 index 0000000000000..aedbf4a1f0003 --- /dev/null +++ b/presto-salesforce/src/test/java/io/prestosql/plugin/salesforce/TestSalesforcePlugin.java @@ -0,0 +1,33 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce; + +import com.google.common.collect.ImmutableMap; +import io.prestosql.spi.Plugin; +import io.prestosql.spi.connector.ConnectorFactory; +import io.prestosql.testing.TestingConnectorContext; +import org.testng.annotations.Test; + +import static com.google.common.collect.Iterables.getOnlyElement; + +public class TestSalesforcePlugin +{ + @Test + public void testCreateConnector() + { + Plugin plugin = new SalesforcePlugin(); + ConnectorFactory factory = getOnlyElement(plugin.getConnectorFactories()); + factory.create("test", ImmutableMap.of("connection-url", "test"), new TestingConnectorContext()); + } +} diff --git a/presto-salesforce/src/test/java/io/prestosql/plugin/salesforce/driver/ForceDriverTest.java b/presto-salesforce/src/test/java/io/prestosql/plugin/salesforce/driver/ForceDriverTest.java new file mode 100644 index 0000000000000..13f45bda07118 --- /dev/null +++ b/presto-salesforce/src/test/java/io/prestosql/plugin/salesforce/driver/ForceDriverTest.java @@ -0,0 +1,80 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce.driver; + +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Properties; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +public class ForceDriverTest +{ + private ForceDriver driver; + + @Before + public void setUp() + { + driver = new ForceDriver(); + } + + @Test + public void testGetConnStringProperties() + throws IOException + { + Properties actuals = driver.getConnStringProperties("jdbc:salesforce://prop1=val1;prop2=val2"); + + assertEquals(2, actuals.size()); + assertEquals("val1", actuals.getProperty("prop1")); + assertEquals("val2", actuals.getProperty("prop2")); + } + + @Test + public void testGetConnStringProperties_WhenNoValue() + throws IOException + { + Properties actuals = driver.getConnStringProperties("jdbc:salesforce://prop1=val1; prop2; prop3 = val3"); + + assertEquals(3, actuals.size()); + assertTrue(actuals.containsKey("prop2")); + assertEquals("", actuals.getProperty("prop2")); + } + + @Test + public void testConnect_WhenWrongURL() + throws SQLException + { + Connection connection = driver.connect("jdbc:mysql://localhost/test", new Properties()); + + assertNull(connection); + } + + @Test + public void testGetConnStringProperties_StandartUrlFormat() + throws IOException + { + Properties actuals = driver.getConnStringProperties("jdbc:salesforce://test@test.ru:aaaa!aaa@login.salesforce.ru"); + + assertEquals(2, actuals.size()); + assertTrue(actuals.containsKey("user")); + assertEquals("test@test.ru", actuals.getProperty("user")); + assertEquals("aaaa!aaa", actuals.getProperty("password")); + } +} diff --git a/presto-salesforce/src/test/java/io/prestosql/plugin/salesforce/driver/delegates/PartnerResultToCartesianTableTest.java b/presto-salesforce/src/test/java/io/prestosql/plugin/salesforce/driver/delegates/PartnerResultToCartesianTableTest.java new file mode 100644 index 0000000000000..a8e3a8b9c7ef7 --- /dev/null +++ b/presto-salesforce/src/test/java/io/prestosql/plugin/salesforce/driver/delegates/PartnerResultToCartesianTableTest.java @@ -0,0 +1,123 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce.driver.delegates; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class PartnerResultToCartesianTableTest +{ + @Test + public void testExpandSimple() + { + List schema = Arrays.asList(new Object(), new Object(), new Object(), new Object()); + + List expected = Arrays.asList((List) Arrays.asList(1, 2, 3, 4)); + + List actual = PartnerResultToCartesianTable.expand(expected, schema); + + assertEquals(actual, expected); + } + + @Test + public void testExpandWhenNothingToExpand() + { + List schema = Arrays.asList(new Object(), new Object(), new Object(), new Object()); + + List expected = Arrays.asList(Arrays.asList(1, 2, 3, 4), Arrays.asList("1", "2", "3", "4"), Arrays.asList("11", "12", "13", "14"), Arrays.asList("21", "22", "23", "24")); + + List actual = PartnerResultToCartesianTable.expand(expected, schema); + + assertEquals(actual, expected); + } + + @Test + public void testExpandWhenOneNestedList() + { + List schema = Arrays.asList(new Object(), Arrays.asList(new Object(), new Object(), new Object()), new Object(), new Object()); + + List list = Arrays.asList((List) Arrays.asList("1", Arrays.asList("21", "22", "23"), "3", "4")); + + List expected = Arrays.asList(Arrays.asList("1", "21", "3", "4"), Arrays.asList("1", "22", "3", "4"), Arrays.asList("1", "23", "3", "4")); + + List actual = PartnerResultToCartesianTable.expand(list, schema); + + assertEquals(actual, expected); + } + + @Test + public void testExpandWhenTwoNestedListAndOneRow() + { + List schema = Arrays.asList(new Object(), Arrays.asList(new Object(), new Object()), new Object(), Arrays.asList(new Object(), new Object())); + + List list = Arrays.asList((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)))); + + List expected = Arrays.asList(Arrays.asList(11, 1, 2, 12, 5, 6), Arrays.asList(11, 3, 4, 12, 5, 6), Arrays.asList(11, 1, 2, 12, 7, 8), Arrays.asList(11, 3, 4, 12, 7, 8)); + + List actual = PartnerResultToCartesianTable.expand(list, schema); + + assertEquals(expected.size(), actual.size()); + for (List l : expected) { + assertTrue(actual.contains(l)); + } + } + + @Test + public void testExpandWhenOneNestedListAndTwoRows() + { + List schema = Arrays.asList(new Object(), Arrays.asList(new Object(), new Object()), new Object(), new Object()); + + List list = Arrays.asList(Arrays.asList(11, Arrays.asList(Arrays.asList(1, 2), Arrays.asList(3, 4)), 12, 13), Arrays.asList(20, Arrays.asList(Arrays.asList(21, 22), Arrays.asList(23, 24), Arrays.asList(25, 26)), 41, 42)); + + List expected = Arrays.asList(Arrays.asList(11, 1, 2, 12, 13), Arrays.asList(11, 3, 4, 12, 13), Arrays.asList(20, 21, 22, 41, 42), Arrays.asList(20, 23, 24, 41, 42), Arrays.asList(20, 25, 26, 41, 42)); + + List actual = PartnerResultToCartesianTable.expand(list, schema); + + assertEquals(actual, expected); + } + + @Test + public void testExpandWhenOneNestedListIsEmpty() + { + List schema = Arrays.asList(new Object(), Arrays.asList(new Object(), new Object()), new Object(), new Object()); + + List list = Arrays.asList((List) Arrays.asList(11, new ArrayList(), 12, 13)); + + List expected = Arrays.asList((List) Arrays.asList(11, null, null, 12, 13)); + + List actual = PartnerResultToCartesianTable.expand(list, schema); + + assertEquals(actual, expected); + } + + @Test + public void testExpandWhenNestedListIsEmpty() + { + List schema = Arrays.asList(new Object(), Arrays.asList(new Object(), new Object())); + + List list = Arrays.asList((List) Arrays.asList(11, new Object())); + + List expected = Arrays.asList((List) Arrays.asList(11, null, null)); + + List actual = PartnerResultToCartesianTable.expand(list, schema); + + assertEquals(actual, expected); + } +} diff --git a/presto-salesforce/src/test/java/io/prestosql/plugin/salesforce/driver/metadata/ForceDatabaseMetaDataTest.java b/presto-salesforce/src/test/java/io/prestosql/plugin/salesforce/driver/metadata/ForceDatabaseMetaDataTest.java new file mode 100644 index 0000000000000..80f8426f7e01e --- /dev/null +++ b/presto-salesforce/src/test/java/io/prestosql/plugin/salesforce/driver/metadata/ForceDatabaseMetaDataTest.java @@ -0,0 +1,41 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce.driver.metadata; + +import org.junit.Test; + +import java.sql.Types; + +import static org.junit.Assert.assertEquals; + +public class ForceDatabaseMetaDataTest +{ + @Test + public void testLookupTypeInfo() + { + ForceDatabaseMetaData.TypeInfo actual = ForceDatabaseMetaData.lookupTypeInfo("int"); + + assertEquals("int", actual.typeName); + assertEquals(Types.INTEGER, actual.sqlDataType); + } + + @Test + public void testLookupTypeInfo_IfTypeUnknown() + { + ForceDatabaseMetaData.TypeInfo actual = ForceDatabaseMetaData.lookupTypeInfo("my strange type"); + + assertEquals("other", actual.typeName); + assertEquals(Types.OTHER, actual.sqlDataType); + } +} diff --git a/presto-salesforce/src/test/java/io/prestosql/plugin/salesforce/driver/resultset/ForceResultSetTest.java b/presto-salesforce/src/test/java/io/prestosql/plugin/salesforce/driver/resultset/ForceResultSetTest.java new file mode 100644 index 0000000000000..226e044d8aa8d --- /dev/null +++ b/presto-salesforce/src/test/java/io/prestosql/plugin/salesforce/driver/resultset/ForceResultSetTest.java @@ -0,0 +1,49 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce.driver.resultset; + +import io.prestosql.plugin.salesforce.driver.metadata.ColumnMap; +import org.junit.Before; +import org.junit.Test; + +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; + +import static org.junit.Assert.assertEquals; + +public class ForceResultSetTest +{ + private ForceResultSet forceResultSet; + + @Before + public void setUp() + { + ColumnMap columnMap = new ColumnMap<>(); + forceResultSet = new ForceResultSet(Collections.singletonList(columnMap)); + } + + @Test + public void testParseDate() + { + Date actual = forceResultSet.parseDate("2017-06-23"); + + Calendar calendar = Calendar.getInstance(); + calendar.setTime(actual); + + assertEquals(2017, calendar.get(Calendar.YEAR)); + assertEquals(Calendar.JUNE, calendar.get(Calendar.MONTH)); + assertEquals(23, calendar.get(Calendar.DAY_OF_MONTH)); + } +} diff --git a/presto-salesforce/src/test/java/io/prestosql/plugin/salesforce/driver/statement/ForcePreparedStatementTest.java b/presto-salesforce/src/test/java/io/prestosql/plugin/salesforce/driver/statement/ForcePreparedStatementTest.java new file mode 100644 index 0000000000000..751af36893807 --- /dev/null +++ b/presto-salesforce/src/test/java/io/prestosql/plugin/salesforce/driver/statement/ForcePreparedStatementTest.java @@ -0,0 +1,84 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce.driver.statement; + +import org.junit.Test; + +import java.math.BigDecimal; +import java.text.SimpleDateFormat; +import java.util.GregorianCalendar; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class ForcePreparedStatementTest +{ + @Test + public void testGetParamClass() + { + assertEquals(String.class, ForcePreparedStatement.getParamClass("test")); + assertEquals(Long.class, ForcePreparedStatement.getParamClass(1L)); + assertEquals(Object.class, ForcePreparedStatement.getParamClass(new SimpleDateFormat())); + assertNull(ForcePreparedStatement.getParamClass(null)); + } + + @Test + public void testToSoqlStringParam() + { + assertEquals("'\\''", ForcePreparedStatement.toSoqlStringParam("'")); + assertEquals("'\\\\'", ForcePreparedStatement.toSoqlStringParam("\\")); + assertEquals("'\\';DELETE DATABASE \\\\a'", ForcePreparedStatement.toSoqlStringParam("';DELETE DATABASE \\a")); + } + + @Test + public void testConvertToSoqlParam() + { + assertEquals("123.45", ForcePreparedStatement.convertToSoqlParam(123.45)); + assertEquals("123.45", ForcePreparedStatement.convertToSoqlParam(123.45f)); + assertEquals("123", ForcePreparedStatement.convertToSoqlParam(123L)); + assertEquals("123.45", ForcePreparedStatement.convertToSoqlParam(new BigDecimal("123.45"))); + assertEquals("2017-03-06T12:34:56-06:00", ForcePreparedStatement.convertToSoqlParam(new GregorianCalendar(2017, 2, 6, 12, 34, 56).getTime())); + assertEquals("'\\'test\\'\\\\'", ForcePreparedStatement.convertToSoqlParam("'test'\\")); + assertEquals("NULL", ForcePreparedStatement.convertToSoqlParam(null)); + } + + @Test + public void testAddParameter() + { + ForcePreparedStatement statement = new ForcePreparedStatement(null, ""); + statement.addParameter(1, "one"); + statement.addParameter(3, "two"); + + List actuals = statement.getParameters(); + + assertEquals(3, actuals.size()); + assertEquals("one", actuals.get(0)); + assertEquals("two", actuals.get(2)); + assertNull(actuals.get(1)); + } + + @Test + public void testSetParams() + { + ForcePreparedStatement statement = new ForcePreparedStatement(null, ""); + String query = "SELECT Something FROM Anything WERE name = ? AND age > ?"; + statement.addParameter(1, "one"); + statement.addParameter(2, 123); + + String actual = statement.setParams(query); + + assertEquals("SELECT Something FROM Anything WERE name = 'one' AND age > 123", actual); + } +} diff --git a/presto-salesforce/src/test/java/io/prestosql/plugin/salesforce/driver/statement/SoqlQueryAnalyzerTest.java b/presto-salesforce/src/test/java/io/prestosql/plugin/salesforce/driver/statement/SoqlQueryAnalyzerTest.java new file mode 100644 index 0000000000000..7c45eefd49ccf --- /dev/null +++ b/presto-salesforce/src/test/java/io/prestosql/plugin/salesforce/driver/statement/SoqlQueryAnalyzerTest.java @@ -0,0 +1,199 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prestosql.plugin.salesforce.driver.statement; + +import com.sforce.soap.partner.DescribeSObjectResult; +import com.thoughtworks.xstream.XStream; +import org.junit.Test; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertEquals; + +public class SoqlQueryAnalyzerTest +{ + @Test + public void testGetFieldNames_SimpleQuery() + { + SoqlQueryAnalyzer analyzer = new SoqlQueryAnalyzer(" select Id ,Name \r\nfrom Account\r\n where something = 'nothing' ", n -> this.describeSObject(n)); + List expecteds = Arrays.asList("Id", "Name"); + List actuals = listFlatFieldNames(analyzer); + + assertEquals(expecteds, actuals); + } + + private List listFlatFieldNames(SoqlQueryAnalyzer analyzer) + { + return listFlatFieldDefinitions(analyzer.getFieldDefinitions()).stream().map(FieldDef::getName).collect(Collectors.toList()); + } + + @Test + public void testGetFieldNames_SelectWithReferences() + { + SoqlQueryAnalyzer analyzer = new SoqlQueryAnalyzer(" select Id , Account.Name \r\nfrom Contact\r\n where something = 'nothing' ", n -> this.describeSObject(n)); + List expecteds = Arrays.asList("Id", "Name"); + List actuals = listFlatFieldNames(analyzer); + + assertEquals(expecteds, actuals); + } + + @Test + public void testGetFieldNames_SelectWithAggregateAliased() + { + SoqlQueryAnalyzer analyzer = new SoqlQueryAnalyzer(" select Id , Account.Name, count(id) aggrAlias1\r\nfrom Contact\r\n where something = 'nothing' ", n -> this.describeSObject(n)); + List expecteds = Arrays.asList("Id", "Name", "aggrAlias1"); + List actuals = listFlatFieldNames(analyzer); + + assertEquals(expecteds, actuals); + } + + @Test + public void testGetFieldNames_SelectWithAggregate() + { + SoqlQueryAnalyzer analyzer = new SoqlQueryAnalyzer(" select Id , Account.Name, count(id)\r\nfrom Contact\r\n where something = 'nothing' ", n -> this.describeSObject(n)); + List expecteds = Arrays.asList("Id", "Name", "count"); + List actuals = listFlatFieldNames(analyzer); + + assertEquals(expecteds, actuals); + } + + @Test + public void testGetFromObjectName() + { + SoqlQueryAnalyzer analyzer = new SoqlQueryAnalyzer(" select Id , Account.Name \r\nfrom Contact\r\n where something = 'nothing' ", n -> this.describeSObject(n)); + String expected = "Contact"; + String actual = analyzer.getFromObjectName(); + + assertEquals(expected, actual); + } + + private List listFlatFieldDefinitions(List fieldDefs) + { + return (List) fieldDefs.stream().flatMap(def -> def instanceof List ? ((List) def).stream() : Arrays.asList(def).stream()).collect(Collectors.toList()); + } + + @Test + public void testGetSimpleFieldDefinitions() + { + SoqlQueryAnalyzer analyzer = new SoqlQueryAnalyzer("SELECT Id, Name FROM Account", n -> this.describeSObject(n)); + + List actuals = listFlatFieldDefinitions(analyzer.getFieldDefinitions()); + assertEquals(2, actuals.size()); + assertEquals("Id", actuals.get(0).getName()); + assertEquals("id", actuals.get(0).getType()); + + assertEquals("Name", actuals.get(1).getName()); + assertEquals("string", actuals.get(1).getType()); + } + + @Test + public void testGetReferenceFieldDefinitions() + { + SoqlQueryAnalyzer analyzer = new SoqlQueryAnalyzer("SELECT Account.Name FROM Contact", n -> this.describeSObject(n)); + + List actuals = listFlatFieldDefinitions(analyzer.getFieldDefinitions()); + assertEquals(1, actuals.size()); + assertEquals("Name", actuals.get(0).getName()); + assertEquals("string", actuals.get(0).getType()); + } + + @Test + public void testGetAggregateFieldDefinition() + { + SoqlQueryAnalyzer analyzer = new SoqlQueryAnalyzer("SELECT MIN(Name) FROM Contact", n -> this.describeSObject(n)); + + List actuals = listFlatFieldDefinitions(analyzer.getFieldDefinitions()); + assertEquals(1, actuals.size()); + assertEquals("MIN", actuals.get(0).getName()); + assertEquals("string", actuals.get(0).getType()); + } + + @Test + public void testGetAggregateFieldDefinitionWithoutParameter() + { + SoqlQueryAnalyzer analyzer = new SoqlQueryAnalyzer("SELECT Count() FROM Contact", n -> this.describeSObject(n)); + + List actuals = listFlatFieldDefinitions(analyzer.getFieldDefinitions()); + assertEquals(1, actuals.size()); + assertEquals("Count", actuals.get(0).getName()); + assertEquals("int", actuals.get(0).getType()); + } + + @Test + public void testGetSimpleFieldWithQualifier() + { + SoqlQueryAnalyzer analyzer = new SoqlQueryAnalyzer("SELECT Contact.Id FROM Contact", n -> this.describeSObject(n)); + + List actuals = listFlatFieldDefinitions(analyzer.getFieldDefinitions()); + assertEquals(1, actuals.size()); + assertEquals("Id", actuals.get(0).getName()); + assertEquals("id", actuals.get(0).getType()); + } + + @Test + public void testGetNamedAggregateFieldDefinitions() + { + SoqlQueryAnalyzer analyzer = new SoqlQueryAnalyzer("SELECT count(Name) nameCount FROM Account", n -> this.describeSObject(n)); + + List actuals = listFlatFieldDefinitions(analyzer.getFieldDefinitions()); + + assertEquals(1, actuals.size()); + assertEquals("nameCount", actuals.get(0).getName()); + assertEquals("int", actuals.get(0).getType()); + } + + private DescribeSObjectResult describeSObject(String sObjectType) + { + try { + String xml = new String(Files.readAllBytes(Paths.get("src/test/resources/" + sObjectType + "Description.xml")), StandardCharsets.UTF_8); + XStream xstream = new XStream(); + return (DescribeSObjectResult) xstream.fromXML(xml); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Test + public void testFetchFieldDefinitions_WithIncludedSeslect() + { + SoqlQueryAnalyzer analyzer = new SoqlQueryAnalyzer("SELECT Name, (SELECT Id, max(LastName) maxLastName FROM Contacts), Id FROM Account", n -> this.describeSObject(n)); + + List actuals = analyzer.getFieldDefinitions(); + + assertEquals(3, actuals.size()); + FieldDef fieldDef = (FieldDef) actuals.get(0); + assertEquals("Name", fieldDef.getName()); + assertEquals("string", fieldDef.getType()); + + List suqueryDef = (List) actuals.get(1); + fieldDef = (FieldDef) suqueryDef.get(0); + assertEquals("Id", fieldDef.getName()); + assertEquals("id", fieldDef.getType()); + + fieldDef = (FieldDef) suqueryDef.get(1); + assertEquals("maxLastName", fieldDef.getName()); + assertEquals("string", fieldDef.getType()); + + fieldDef = (FieldDef) actuals.get(2); + assertEquals("Id", fieldDef.getName()); + assertEquals("id", fieldDef.getType()); + } +} diff --git a/presto-salesforce/src/test/resources/AccountDescription.xml b/presto-salesforce/src/test/resources/AccountDescription.xml new file mode 100644 index 0000000000000..328667d93954b --- /dev/null +++ b/presto-salesforce/src/test/resources/AccountDescription.xml @@ -0,0 +1,9122 @@ + + true + + + true + LARGE + true + false + true + View + true + 0M06100000001BHCAY + true + + + true + false + true + + + true + false + true + AcceptedEventRelation + true + false + true + AccountId + false + + false + + false + false + false + + + true + false + true + Account + true + false + true + ParentId + false + + false + + true + ChildAccounts + false + false + + + true + true + true + AccountContactRelation + true + false + true + AccountId + false + + false + + true + AccountContactRelations + false + false + + + true + true + true + AccountContactRole + true + false + true + AccountId + false + + false + + true + AccountContactRoles + false + false + + + true + true + true + AccountFeed + true + false + true + ParentId + false + + false + + true + Feeds + false + false + + + true + true + true + AccountHistory + true + false + true + AccountId + false + + false + + true + Histories + false + false + + + true + true + true + AccountPartner + true + false + true + AccountFromId + false + + false + + true + AccountPartnersFrom + false + false + + + true + true + true + AccountPartner + true + false + true + AccountToId + false + + false + + true + AccountPartnersTo + false + false + + + true + true + true + AccountShare + true + false + true + AccountId + false + + false + + true + Shares + false + false + + + true + true + true + ActivityHistory + true + false + true + AccountId + false + + false + + true + ActivityHistories + false + false + + + true + false + true + ActivityHistory + true + false + true + PrimaryAccountId + false + + false + + false + false + false + + + true + true + true + Asset + true + false + true + AccountId + false + + false + + true + Assets + false + false + + + true + true + true + AttachedContentDocument + true + false + true + LinkedEntityId + false + + false + + true + AttachedContentDocuments + false + false + + + true + true + true + AttachedContentNote + true + false + true + LinkedEntityId + false + + false + + true + AttachedContentNotes + false + false + + + true + true + true + Attachment + true + false + true + ParentId + false + + false + + true + Attachments + false + false + + + true + false + true + Case + true + false + true + AccountId + false + + false + + true + Cases + true + true + + + true + true + true + CombinedAttachment + true + false + true + ParentId + false + + false + + true + CombinedAttachments + false + false + + + true + true + true + Contact + true + false + true + AccountId + false + + false + + true + Contacts + false + false + + + true + true + true + ContentDistribution + true + false + true + RelatedRecordId + false + + false + + false + false + false + + + true + true + true + ContentDocumentLink + true + false + true + LinkedEntityId + false + + false + + true + ContentDocumentLinks + false + false + + + true + false + true + ContentVersion + true + false + true + FirstPublishLocationId + false + + false + + false + false + false + + + true + true + true + Contract + true + false + true + AccountId + false + + false + + true + Contracts + true + true + + + true + false + true + DeclinedEventRelation + true + false + true + AccountId + false + + false + + false + false + false + + + true + true + true + DuplicateRecordItem + true + false + true + RecordId + false + + false + + true + DuplicateRecordItems + false + false + + + true + false + true + EmailMessage + true + false + true + RelatedToId + false + + false + + true + Emails + false + false + + + true + true + true + EntitySubscription + true + false + true + ParentId + false + + false + + true + FeedSubscriptionsForEntity + false + false + + + true + false + true + Event + true + false + true + AccountId + false + + false + + false + false + false + + + true + true + true + Event + true + false + true + WhatId + false + + false + + true + Events + false + false + + + true + false + true + EventRelation + true + false + true + AccountId + false + + false + + false + false + false + + + true + true + true + EventRelation + true + false + true + RelationId + false + + false + + true + EventRelations + false + false + + + true + false + true + EventWhoRelation + true + false + true + AccountId + false + + false + + false + false + false + + + true + false + true + FeedComment + true + false + true + ParentId + false + + false + + false + false + false + + + true + true + true + FeedItem + true + false + true + ParentId + false + + false + + false + false + false + + + true + false + true + Lead + true + false + true + ConvertedAccountId + false + + false + + false + false + false + + + true + true + true + Note + true + false + true + ParentId + false + + false + + true + Notes + false + false + + + true + true + true + NoteAndAttachment + true + false + true + ParentId + false + + false + + true + NotesAndAttachments + false + false + + + true + true + true + OpenActivity + true + false + true + AccountId + false + + false + + true + OpenActivities + false + false + + + true + false + true + OpenActivity + true + false + true + PrimaryAccountId + false + + false + + false + false + false + + + true + true + true + Opportunity + true + false + true + AccountId + false + + false + + true + Opportunities + false + false + + + true + true + true + OpportunityPartner + true + false + true + AccountToId + false + + false + + true + OpportunityPartnersTo + false + false + + + true + true + true + Order + true + false + true + AccountId + false + + false + + true + Orders + true + true + + + true + false + true + OutgoingEmail + true + false + true + RelatedToId + false + + false + + false + false + false + + + true + true + true + Partner + true + false + true + AccountFromId + false + + false + + true + PartnersFrom + false + false + + + true + true + true + Partner + true + false + true + AccountToId + false + + false + + true + PartnersTo + false + false + + + true + true + true + ProcessInstance + true + false + true + TargetObjectId + false + + false + + true + ProcessInstances + false + false + + + true + false + true + ProcessInstanceHistory + true + false + true + TargetObjectId + false + + false + + true + ProcessSteps + false + false + + + true + false + true + Task + true + false + true + AccountId + false + + false + + false + false + false + + + true + true + true + Task + true + false + true + WhatId + false + + false + + true + Tasks + false + false + + + true + false + true + TaskRelation + true + false + true + AccountId + false + + false + + false + false + false + + + true + true + true + TaskRelation + true + false + true + RelationId + false + + false + + true + TaskRelations + false + false + + + true + false + true + TaskWhoRelation + true + false + true + AccountId + false + + false + + false + false + false + + + true + true + true + TopicAssignment + true + false + true + EntityId + false + + false + + true + TopicAssignments + false + false + + + true + false + true + UndecidedEventRelation + true + false + true + AccountId + false + + false + + false + false + false + + + true + false + true + User + true + false + true + AccountId + false + + false + + false + false + false + + + true + false + true + UserRole + true + false + true + PortalAccountId + false + + false + + false + false + false + + + true + true + true + ascendix_ciserv__AccountRelationship__c + true + false + true + ascendix_ciserv__Account__c + false + + false + + true + ascendix_ciserv__AccountRelationships__r + false + false + + + true + false + true + ascendix_ciserv__AdHocListMember__c + true + false + true + ascendix_ciserv__Account__c + false + + false + + true + ascendix_ciserv__AdHocListMembers__r + false + false + + + true + false + true + ascendix_ciserv__Commission__c + true + false + true + ascendix_ciserv__Account__c + false + + false + + true + ascendix_ciserv__Commissions__r + false + false + + + true + false + true + ascendix_ciserv__Deal__c + true + false + true + ascendix_ciserv__BuyerRep__c + false + + false + + true + ascendix_ciserv__DealsByBuyerRep__r + true + true + + + true + false + true + ascendix_ciserv__Deal__c + true + false + true + ascendix_ciserv__Buyer__c + false + + false + + true + ascendix_ciserv__DealsByBuyer__r + true + true + + + true + false + true + ascendix_ciserv__Deal__c + true + false + true + ascendix_ciserv__Client__c + false + + false + + true + ascendix_ciserv__DealsByClient__r + false + false + + + true + false + true + ascendix_ciserv__Deal__c + true + false + true + ascendix_ciserv__LeadBrokerCompany__c + false + + false + + true + ascendix_ciserv__DealsByLeadBrokerCompany__r + false + false + + + true + false + true + ascendix_ciserv__Deal__c + true + false + true + ascendix_ciserv__Lender__c + false + + false + + true + ascendix_ciserv__DealsByLender__r + false + false + + + true + false + true + ascendix_ciserv__Deal__c + true + false + true + ascendix_ciserv__ListingBrokerCompany__c + false + + false + + true + ascendix_ciserv__DealsByListingBrokerCompany__r + false + false + + + true + false + true + ascendix_ciserv__Deal__c + true + false + true + ascendix_ciserv__OwnerLandlord__c + false + + false + + true + ascendix_ciserv__DealsByOwnerLandlord__r + false + false + + + true + false + true + ascendix_ciserv__Deal__c + true + false + true + ascendix_ciserv__Seller__c + false + + false + + true + ascendix_ciserv__DealsBySeller__r + false + false + + + true + false + true + ascendix_ciserv__Deal__c + true + false + true + ascendix_ciserv__TenantRepBroker__c + false + + false + + true + ascendix_ciserv__DealsByTenantRepBroker__r + false + false + + + true + false + true + ascendix_ciserv__Deal__c + true + false + true + ascendix_ciserv__Tenant__c + false + + false + + true + ascendix_ciserv__DealsByTenant__r + false + false + + + true + false + true + ascendix_ciserv__Inquiry__c + true + false + true + ascendix_ciserv__ConvertedAccount__c + false + + false + + true + ascendix_ciserv__Inquiries__r + false + false + + + true + false + true + ascendix_ciserv__Lease__c + true + false + true + ascendix_ciserv__ListingBrokerCompany__c + false + + false + + true + ascendix_ciserv__LeasesByListingBrokerCompany__r + false + false + + + true + false + true + ascendix_ciserv__Lease__c + true + false + true + ascendix_ciserv__OwnerLandlord__c + false + + false + + true + ascendix_ciserv__LeasesByOwnerLandlord__r + false + false + + + true + false + true + ascendix_ciserv__Lease__c + true + false + true + ascendix_ciserv__TenantRepBroker__c + false + + false + + true + ascendix_ciserv__LeasesByTenantRepBroker__r + false + false + + + true + false + true + ascendix_ciserv__Lease__c + true + false + true + ascendix_ciserv__Tenant__c + false + + false + + true + ascendix_ciserv__LeasesByTenant__r + true + true + + + true + false + true + ascendix_ciserv__Listing__c + true + false + true + ascendix_ciserv__ListingBrokerCompany__c + false + + false + + true + ascendix_ciserv__ListingsByListingBrokerCompany__r + false + false + + + true + false + true + ascendix_ciserv__Listing__c + true + false + true + ascendix_ciserv__OwnerLandlord__c + false + + false + + true + ascendix_ciserv__ListingsByOwnerLandlord__r + false + false + + + true + false + true + ascendix_ciserv__Preference__c + true + false + true + ascendix_ciserv__Account__c + false + + false + + true + ascendix_ciserv__Preferences__r + false + false + + + true + false + true + ascendix_ciserv__Property__c + true + false + true + ascendix_ciserv__Developer__c + false + + false + + true + ascendix_ciserv__PropertiesByDeveloper__r + false + false + + + true + false + true + ascendix_ciserv__Property__c + true + false + true + ascendix_ciserv__ListingBrokerCompany__c + false + + false + + true + ascendix_ciserv__PropertiesByListingBrokerCompany__r + true + true + + + true + false + true + ascendix_ciserv__Property__c + true + false + true + ascendix_ciserv__OwnerLandlord__c + false + + false + + true + ascendix_ciserv__PropertiesByOwnerLandlord__r + false + false + + + true + false + true + ascendix_ciserv__Property__c + true + false + true + ascendix_ciserv__PropertyManager__c + false + + false + + true + ascendix_ciserv__PropertiesByPropertyManager__r + false + false + + + true + false + true + ascendix_ciserv__ProspectiveCustomer__c + true + false + true + ascendix_ciserv__Account__c + false + + false + + true + ascendix_ciserv__ProspectiveCustomersByAccount__r + true + true + + + true + false + true + ascendix_ciserv__Sale__c + true + false + true + ascendix_ciserv__BuyerRep__c + false + + false + + true + ascendix_ciserv__SalesByBuyerRep__r + false + false + + + true + false + true + ascendix_ciserv__Sale__c + true + false + true + ascendix_ciserv__Buyer__c + false + + false + + true + ascendix_ciserv__SalesByBuyer__r + false + false + + + true + false + true + ascendix_ciserv__Sale__c + true + false + true + ascendix_ciserv__Lender__c + false + + false + + true + ascendix_ciserv__SalesByLender__r + false + false + + + true + false + true + ascendix_ciserv__Sale__c + true + false + true + ascendix_ciserv__ListingBrokerCompany__c + false + + false + + true + ascendix_ciserv__SalesByListingBrokerCompany__r + false + false + + + true + false + true + ascendix_ciserv__Sale__c + true + false + true + ascendix_ciserv__Seller__c + false + + false + + true + ascendix_ciserv__SalesBySeller__r + false + false + + + true + false + true + ascendix_ciserv__Sale__c + true + false + true + ascendix_ciserv__SellingBroker__c + false + + false + + true + ascendix_ciserv__SalesBySellingBroker__r + false + false + + + true + false + true + ascendix_search__AdHocListMember__c + true + false + true + ascendix_search__Account__c + false + + false + + true + ascendix_search__AdHocListMembers__r + false + false + + + true + true + true + true + true + false + true + false + true + true + true + false + true + true + true + + + true + true + true + false + true + 18 + true + false + false + false + false + true + false + false + false + true + false + true + false + false + false + true + true + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + true + false + true + + true + 18 + false + false + true + Id + true + false + true + false + true + false + true + false + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + ID + true + true + true + id + true + false + true + false + false + false + + + true + false + true + false + true + 0 + true + false + false + false + false + true + false + false + false + true + false + true + false + true + false + false + true + true + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + IsDeleted + true + false + true + false + true + false + true + false + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + _boolean + true + true + true + _boolean + true + false + true + false + false + false + + + true + true + true + false + true + 18 + true + false + false + false + false + true + false + false + false + true + false + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 18 + false + false + true + MasterRecordId + true + false + true + false + true + true + true + false + false + + true + 0 + true + false + false + true + + Account + + true + MasterRecord + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + reference + true + false + true + false + false + false + + + true + true + true + false + true + 765 + true + false + false + false + false + true + false + true + Name + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + true + switchablepersonname + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 255 + false + false + true + Name + true + true + true + false + true + false + true + false + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + string + true + true + true + string + true + false + true + true + false + false + + + true + true + true + false + true + 120 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 40 + false + false + true + Type + true + false + true + false + true + true + true + true + true + + + true + true + true + false + true + + false + true + Appraiser + + + true + true + true + false + true + + false + true + Asset Manager + + + true + true + true + false + true + + false + true + Attorney + + + true + true + true + false + true + + false + true + Broker + + + true + true + true + false + true + + false + true + Client + + + true + true + true + false + true + + false + true + Competitor + + + true + true + true + false + true + + false + true + Consultant + + + true + true + true + false + true + + false + true + Contractor + + + true + true + true + false + true + + false + true + Developer + + + true + true + true + false + true + + false + true + Influencer + + + true + true + true + false + true + + false + true + Investor + + + true + true + true + false + true + + false + true + Leasing Agent + + + true + true + true + false + true + + false + true + Other + + + true + true + true + false + true + + false + true + Partner + + + true + true + true + false + true + + false + true + Property Manager + + + true + true + true + false + true + + false + true + Prospect + + + true + true + true + false + true + + false + true + Vendor + + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + picklist + true + false + true + true + false + false + + + true + true + true + false + true + 18 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 18 + false + false + true + ParentId + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + true + + Account + + true + Parent + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 765 + true + false + false + false + false + true + false + true + BillingAddress + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + true + plaintextarea + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 255 + false + false + true + BillingStreet + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + textarea + true + false + true + true + false + false + + + true + true + true + false + true + 120 + true + false + false + false + false + true + false + true + BillingAddress + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 40 + false + false + true + BillingCity + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 240 + true + false + false + false + false + true + false + true + BillingAddress + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 80 + false + false + true + BillingState + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 60 + true + false + false + false + false + true + false + true + BillingAddress + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 20 + false + false + true + BillingPostalCode + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 240 + true + false + false + false + false + true + false + true + BillingAddress + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 80 + false + false + true + BillingCountry + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 0 + true + false + false + false + false + true + false + true + BillingAddress + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + false + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + BillingLatitude + true + false + true + false + true + true + true + true + false + + true + 18 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 15 + true + _double + true + true + true + _double + true + false + true + true + false + false + + + true + true + true + false + true + 0 + true + false + false + false + false + true + false + true + BillingAddress + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + false + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + BillingLongitude + true + false + true + false + true + true + true + true + false + + true + 18 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 15 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 120 + true + false + false + false + false + true + false + true + BillingAddress + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 40 + false + false + true + BillingGeocodeAccuracy + true + false + true + false + true + true + true + true + true + + + true + true + true + false + true + + false + true + Address + + + true + true + true + false + true + + false + true + NearAddress + + + true + true + true + false + true + + false + true + Block + + + true + true + true + false + true + + false + true + Street + + + true + true + true + false + true + + false + true + ExtendedZip + + + true + true + true + false + true + + false + true + Zip + + + true + true + true + false + true + + false + true + Neighborhood + + + true + true + true + false + true + + false + true + City + + + true + true + true + false + true + + false + true + County + + + true + true + true + false + true + + false + true + State + + + true + true + true + false + true + + false + true + Unknown + + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + true + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + false + true + false + true + 0 + true + false + false + false + false + true + false + false + false + true + false + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + false + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + BillingAddress + true + false + true + false + true + true + true + true + false + + true + 0 + true + true + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + address + true + false + true + address + true + false + true + false + false + false + + + true + true + true + false + true + 765 + true + false + false + false + false + true + false + true + ShippingAddress + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + true + plaintextarea + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 255 + false + false + true + ShippingStreet + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 120 + true + false + false + false + false + true + false + true + ShippingAddress + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 40 + false + false + true + ShippingCity + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 240 + true + false + false + false + false + true + false + true + ShippingAddress + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 80 + false + false + true + ShippingState + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 60 + true + false + false + false + false + true + false + true + ShippingAddress + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 20 + false + false + true + ShippingPostalCode + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 240 + true + false + false + false + false + true + false + true + ShippingAddress + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 80 + false + false + true + ShippingCountry + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 0 + true + false + false + false + false + true + false + true + ShippingAddress + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + false + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + ShippingLatitude + true + false + true + false + true + true + true + true + false + + true + 18 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 15 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 0 + true + false + false + false + false + true + false + true + ShippingAddress + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + false + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + ShippingLongitude + true + false + true + false + true + true + true + true + false + + true + 18 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 15 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 120 + true + false + false + false + false + true + false + true + ShippingAddress + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 40 + false + false + true + ShippingGeocodeAccuracy + true + false + true + false + true + true + true + true + true + + + true + true + true + false + true + + false + true + Address + + + true + true + true + false + true + + false + true + NearAddress + + + true + true + true + false + true + + false + true + Block + + + true + true + true + false + true + + false + true + Street + + + true + true + true + false + true + + false + true + ExtendedZip + + + true + true + true + false + true + + false + true + Zip + + + true + true + true + false + true + + false + true + Neighborhood + + + true + true + true + false + true + + false + true + City + + + true + true + true + false + true + + false + true + County + + + true + true + true + false + true + + false + true + State + + + true + true + true + false + true + + false + true + Unknown + + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + true + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + false + true + false + true + 0 + true + false + false + false + false + true + false + false + false + true + false + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + false + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + ShippingAddress + true + false + true + false + true + true + true + true + false + + true + 0 + true + true + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + false + true + + true + false + true + false + false + false + + + true + true + true + false + true + 120 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 40 + false + false + true + Phone + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + phone + true + false + true + true + false + false + + + true + true + true + false + true + 120 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 40 + false + false + true + Fax + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 120 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 40 + false + false + true + AccountNumber + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 765 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 255 + false + false + true + Website + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + url + true + false + true + true + false + false + + + true + true + true + false + true + 765 + true + false + false + false + false + true + false + false + false + true + false + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + true + imageurl + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 255 + false + false + true + PhotoUrl + true + false + true + false + true + true + true + false + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + false + false + false + + + true + true + true + false + true + 60 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 20 + false + false + true + Sic + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 120 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 40 + false + false + true + Industry + true + false + true + false + true + true + true + true + true + + + true + true + true + false + true + + false + true + Agriculture + + + true + true + true + false + true + + false + true + Apparel + + + true + true + true + false + true + + false + true + Banking + + + true + true + true + false + true + + false + true + Biotechnology + + + true + true + true + false + true + + false + true + Chemicals + + + true + true + true + false + true + + false + true + Communications + + + true + true + true + false + true + + false + true + Construction + + + true + true + true + false + true + + false + true + Consulting + + + true + true + true + false + true + + false + true + Education + + + true + true + true + false + true + + false + true + Electronics + + + true + true + true + false + true + + false + true + Energy + + + true + true + true + false + true + + false + true + Engineering + + + true + true + true + false + true + + false + true + Entertainment + + + true + true + true + false + true + + false + true + Environmental + + + true + true + true + false + true + + false + true + Finance + + + true + true + true + false + true + + false + true + Food & Beverage + + + true + true + true + false + true + + false + true + Government + + + true + true + true + false + true + + false + true + Healthcare + + + true + true + true + false + true + + false + true + Hospitality + + + true + true + true + false + true + + false + true + Insurance + + + true + true + true + false + true + + false + true + Machinery + + + true + true + true + false + true + + false + true + Manufacturing + + + true + true + true + false + true + + false + true + Media + + + true + true + true + false + true + + false + true + Not For Profit + + + true + true + true + false + true + + false + true + Recreation + + + true + true + true + false + true + + false + true + Retail + + + true + true + true + false + true + + false + true + Shipping + + + true + true + true + false + true + + false + true + Technology + + + true + true + true + false + true + + false + true + Telecommunications + + + true + true + true + false + true + + false + true + Transportation + + + true + true + true + false + true + + false + true + Utilities + + + true + true + true + false + true + + false + true + Other + + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 0 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + false + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + AnnualRevenue + true + false + true + false + true + true + true + true + false + + true + 18 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + currency + true + false + true + true + false + false + + + true + true + true + false + true + 0 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 8 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + NumberOfEmployees + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + _int + true + true + true + _int + true + false + true + true + false + false + + + true + true + true + false + true + 120 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 40 + false + false + true + Ownership + true + false + true + false + true + true + true + true + true + + + true + true + true + false + true + + false + true + Public + + + true + true + true + false + true + + false + true + Private + + + true + true + true + false + true + + false + true + Subsidiary + + + true + true + true + false + true + + false + true + Other + + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 60 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 20 + false + false + true + TickerSymbol + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + false + true + false + true + 96000 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + true + plaintextarea + true + false + false + true + false + false + false + false + false + true + false + false + true + + true + 32000 + false + false + true + Description + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + false + true + + true + false + true + true + false + false + + + true + true + true + false + true + 120 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 40 + false + false + true + Rating + true + false + true + false + true + true + true + true + true + + + true + true + true + false + true + + false + true + Hot + + + true + true + true + false + true + + false + true + Warm + + + true + true + true + false + true + + false + true + Cold + + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 240 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 80 + false + false + true + Site + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 18 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + true + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 18 + false + false + true + OwnerId + true + false + true + false + true + false + true + false + false + + true + 0 + true + false + false + true + + User + + true + Owner + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 0 + true + false + false + false + false + true + false + false + false + true + false + true + false + false + false + true + true + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + false + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + CreatedDate + true + false + true + false + true + false + true + false + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + dateTime + true + true + true + datetime + true + false + true + false + false + false + + + true + true + true + false + true + 18 + true + false + false + false + false + true + false + false + false + true + false + true + false + false + false + true + true + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 18 + false + false + true + CreatedById + true + false + true + false + true + false + true + false + false + + true + 0 + true + false + false + true + + User + + true + CreatedBy + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + false + false + false + + + true + true + true + false + true + 0 + true + false + false + false + false + true + false + false + false + true + false + true + false + false + false + true + true + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + false + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + LastModifiedDate + true + false + true + false + true + false + true + false + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + false + false + false + + + true + true + true + false + true + 18 + true + false + false + false + false + true + false + false + false + true + false + true + false + false + false + true + true + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 18 + false + false + true + LastModifiedById + true + false + true + false + true + false + true + false + false + + true + 0 + true + false + false + true + + User + + true + LastModifiedBy + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + false + false + false + + + true + true + true + false + true + 0 + true + false + false + false + false + true + false + false + false + true + false + true + false + false + false + true + true + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + false + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + SystemModstamp + true + false + true + false + true + false + true + false + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + false + false + false + + + true + true + true + false + true + 0 + true + false + false + false + false + true + false + false + false + true + false + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + LastActivityDate + true + false + true + false + true + true + true + false + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + date + true + true + true + date + true + false + true + false + false + false + + + true + true + true + false + true + 0 + true + false + false + false + false + true + false + false + false + true + false + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + false + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + LastViewedDate + true + false + true + false + true + true + true + false + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + false + false + false + + + true + true + true + false + true + 0 + true + false + false + false + false + true + false + false + false + true + false + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + false + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + LastReferencedDate + true + false + true + false + true + true + true + false + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + false + false + false + + + true + true + true + false + true + 60 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 20 + false + false + true + Jigsaw + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 60 + true + false + false + false + false + true + false + false + false + true + false + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 20 + false + false + true + JigsawCompanyId + true + false + true + false + true + true + true + false + false + + true + 0 + true + false + false + false + + true + JigsawCompany + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + false + false + false + + + true + true + true + false + true + 120 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 40 + false + false + true + AccountSource + true + false + true + false + true + true + true + true + true + + + true + true + true + false + true + + false + true + Web + + + true + true + true + false + true + + false + true + Phone Inquiry + + + true + true + true + false + true + + false + true + Partner Referral + + + true + true + true + false + true + + false + true + Purchased List + + + true + true + true + false + true + + false + true + Other + + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 27 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 9 + false + false + true + DunsNumber + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 765 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 255 + false + false + true + Tradestyle + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 24 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 8 + false + false + true + NaicsCode + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 360 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 120 + false + false + true + NaicsDesc + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 12 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 4 + false + false + true + YearStarted + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 240 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 80 + false + false + true + SicDesc + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 18 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 18 + false + false + true + DandbCompanyId + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + true + + DandBCompany + + true + DandbCompany + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 765 + true + false + false + false + false + true + false + false + false + true + true + true + true + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 255 + false + false + true + ascendix_ciserv__CustomerPriority__c + true + false + true + false + true + true + true + true + true + + + true + true + true + false + true + + false + true + High + + + true + true + true + false + true + + false + true + Low + + + true + true + true + false + true + + false + true + Medium + + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 765 + true + false + false + false + false + true + false + false + false + true + true + true + true + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 255 + false + false + true + ascendix_ciserv__SLA__c + true + false + true + false + true + true + true + true + true + + + true + true + true + false + true + + false + true + Gold + + + true + true + true + false + true + + false + true + Silver + + + true + true + true + false + true + + false + true + Platinum + + + true + true + true + false + true + + false + true + Bronze + + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 765 + true + false + false + false + false + true + false + false + false + true + true + true + true + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 255 + false + false + true + ascendix_ciserv__Active__c + true + false + true + false + true + true + true + true + true + + + true + true + true + false + true + + false + true + No + + + true + true + true + false + true + + false + true + Yes + + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 0 + true + false + false + false + false + true + false + false + false + true + true + true + true + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + false + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + ascendix_ciserv__NumberofLocations__c + true + false + true + false + true + true + true + true + false + + true + 3 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 765 + true + false + false + false + false + true + false + false + false + true + true + true + true + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 255 + false + false + true + ascendix_ciserv__UpsellOpportunity__c + true + false + true + false + true + true + true + true + true + + + true + true + true + false + true + + false + true + Maybe + + + true + true + true + false + true + + false + true + No + + + true + true + true + false + true + + false + true + Yes + + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 30 + true + false + false + false + false + true + false + false + false + true + true + true + true + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 10 + false + false + true + ascendix_ciserv__SLASerialNumber__c + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 0 + true + false + false + false + false + true + false + false + false + true + true + true + true + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + ascendix_ciserv__SLAExpirationDate__c + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 765 + true + false + false + false + false + true + false + false + false + true + true + true + true + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + true + true + false + true + true + false + true + true + false + false + false + false + true + true + false + true + + true + 255 + false + false + true + ascendix_ciserv__SourceSystemNumber__c + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 765 + true + false + false + false + false + true + false + false + false + true + true + true + true + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 255 + false + false + true + ascendix_ciserv__SourceSystem__c + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + false + true + true + true + false + true + 001 + true + + true + Accounts + true + true + false + false + false + false + true + true + true + true + true + Account + false + + true + true + true + true + + + true + true + true + true + true + true + true + Master + true + 012000000000000AAA + + + true + true + true + true + true + true + true + true + true + + + true + + true + everything + + + true + + true + mine + + + true + + true + team + + + true + true + true + true + true + true + true + https://ascendixre-ci-dev-ed.my.salesforce.com/{ID} + true + https://ascendixre-ci-dev-ed.my.salesforce.com/{ID}/e + true + https://ascendixre-ci-dev-ed.my.salesforce.com/001/e + \ No newline at end of file diff --git a/presto-salesforce/src/test/resources/ContactDescription.xml b/presto-salesforce/src/test/resources/ContactDescription.xml new file mode 100644 index 0000000000000..9cf42ef4b261a --- /dev/null +++ b/presto-salesforce/src/test/resources/ContactDescription.xml @@ -0,0 +1,8258 @@ + + true + + + true + LARGE + true + false + true + View + true + 0M06100000001BICAY + true + + + true + false + true + + + true + false + true + AcceptedEventRelation + true + false + true + RelationId + false + + false + + true + AcceptedEventRelations + false + false + + + true + true + true + AccountContactRelation + true + false + true + ContactId + false + + false + + true + AccountContactRelations + false + false + + + true + true + true + AccountContactRole + true + false + true + ContactId + false + + false + + true + AccountContactRoles + false + false + + + true + false + true + ActivityHistory + true + false + true + PrimaryWhoId + false + + false + + false + false + false + + + true + true + true + ActivityHistory + true + false + true + WhoId + false + + false + + true + ActivityHistories + false + false + + + true + true + true + Asset + true + false + true + ContactId + false + + false + + true + Assets + false + false + + + true + true + true + AttachedContentDocument + true + false + true + LinkedEntityId + false + + false + + true + AttachedContentDocuments + false + false + + + true + true + true + AttachedContentNote + true + false + true + LinkedEntityId + false + + false + + true + AttachedContentNotes + false + false + + + true + true + true + Attachment + true + false + true + ParentId + false + + false + + true + Attachments + false + false + + + true + true + true + CampaignMember + true + false + true + ContactId + false + + false + + true + CampaignMembers + false + false + + + true + false + true + CampaignMember + true + false + true + LeadOrContactId + false + + false + + false + false + false + + + true + false + true + Case + true + false + true + ContactId + false + + false + + true + Cases + true + true + + + true + true + true + CaseContactRole + true + false + true + ContactId + false + + false + + true + CaseContactRoles + false + false + + + true + true + true + CaseTeamMember + true + false + true + MemberId + false + + false + + false + false + false + + + true + true + true + CaseTeamTemplateMember + true + false + true + MemberId + false + + false + + false + false + false + + + true + true + true + CombinedAttachment + true + false + true + ParentId + false + + false + + true + CombinedAttachments + false + false + + + true + false + true + Contact + true + false + true + ReportsToId + false + + false + + false + false + false + + + true + true + true + ContactFeed + true + false + true + ParentId + false + + false + + true + Feeds + false + false + + + true + true + true + ContactHistory + true + false + true + ContactId + false + + false + + true + Histories + false + false + + + true + true + true + ContactShare + true + false + true + ContactId + false + + false + + true + Shares + false + false + + + true + true + true + ContentDistribution + true + false + true + RelatedRecordId + false + + false + + false + false + false + + + true + true + true + ContentDocumentLink + true + false + true + LinkedEntityId + false + + false + + true + ContentDocumentLinks + false + false + + + true + false + true + ContentVersion + true + false + true + FirstPublishLocationId + false + + false + + false + false + false + + + true + false + true + Contract + true + false + true + CustomerSignedId + false + + false + + true + ContractsSigned + true + true + + + true + true + true + ContractContactRole + true + false + true + ContactId + false + + false + + true + ContractContactRoles + false + false + + + true + false + true + DeclinedEventRelation + true + false + true + RelationId + false + + false + + true + DeclinedEventRelations + false + false + + + true + true + true + DuplicateRecordItem + true + false + true + RecordId + false + + false + + true + DuplicateRecordItems + false + false + + + true + true + true + EmailMessageRelation + true + false + true + RelationId + false + + false + + true + EmailMessageRelations + false + false + + + true + true + true + EmailStatus + true + false + true + WhoId + false + + false + + true + EmailStatuses + false + false + + + true + true + true + EntitySubscription + true + false + true + ParentId + false + + false + + true + FeedSubscriptionsForEntity + false + false + + + true + true + true + Event + true + false + true + WhoId + false + + false + + true + Events + false + false + + + true + true + true + EventRelation + true + false + true + RelationId + false + + false + + true + EventRelations + false + false + + + true + false + true + EventWhoRelation + true + false + true + RelationId + false + + false + + true + EventWhoRelations + false + false + + + true + false + true + FeedComment + true + false + true + ParentId + false + + false + + false + false + false + + + true + true + true + FeedItem + true + false + true + ParentId + false + + false + + false + false + false + + + true + false + true + Lead + true + false + true + ConvertedContactId + false + + false + + false + false + false + + + true + true + true + Note + true + false + true + ParentId + false + + false + + true + Notes + false + false + + + true + true + true + NoteAndAttachment + true + false + true + ParentId + false + + false + + true + NotesAndAttachments + false + false + + + true + false + true + OpenActivity + true + false + true + PrimaryWhoId + false + + false + + false + false + false + + + true + true + true + OpenActivity + true + false + true + WhoId + false + + false + + true + OpenActivities + false + false + + + true + true + true + OpportunityContactRole + true + false + true + ContactId + false + + false + + true + OpportunityContactRoles + false + false + + + true + false + true + Order + true + false + true + BillToContactId + false + + false + + false + true + true + + + true + false + true + Order + true + false + true + CustomerAuthorizedById + false + + false + + false + true + true + + + true + false + true + Order + true + false + true + ShipToContactId + false + + false + + false + true + true + + + true + false + true + OutgoingEmail + true + false + true + WhoId + false + + false + + false + false + false + + + true + false + true + OutgoingEmailRelation + true + false + true + RelationId + false + + false + + true + OutgoingEmailRelations + false + false + + + true + true + true + ProcessInstance + true + false + true + TargetObjectId + false + + false + + true + ProcessInstances + false + false + + + true + false + true + ProcessInstanceHistory + true + false + true + TargetObjectId + false + + false + + true + ProcessSteps + false + false + + + true + false + true + SOSSession + true + false + true + ContactId + false + + false + + true + SOSSessions + false + false + + + true + true + true + Task + true + false + true + WhoId + false + + false + + true + Tasks + false + false + + + true + true + true + TaskRelation + true + false + true + RelationId + false + + false + + true + TaskRelations + false + false + + + true + false + true + TaskWhoRelation + true + false + true + RelationId + false + + false + + true + TaskWhoRelations + false + false + + + true + true + true + TopicAssignment + true + false + true + EntityId + false + + false + + true + TopicAssignments + false + false + + + true + false + true + UndecidedEventRelation + true + false + true + RelationId + false + + false + + true + UndecidedEventRelations + false + false + + + true + false + true + User + true + false + true + ContactId + false + + false + + false + true + true + + + true + false + true + ascendix_ciserv__AdHocListMember__c + true + false + true + ascendix_ciserv__Contact__c + false + + false + + true + ascendix_ciserv__AdHocListMembers__r + false + false + + + true + false + true + ascendix_ciserv__Commission__c + true + false + true + ascendix_ciserv__Contact__c + false + + false + + true + ascendix_ciserv__Commissions__r + false + false + + + true + true + true + ascendix_ciserv__ContactRelationship__c + true + false + true + ascendix_ciserv__Contact__c + false + + false + + true + ascendix_ciserv__ContactRelationships__r + false + false + + + true + false + true + ascendix_ciserv__Deal__c + true + false + true + ascendix_ciserv__BuyerContact__c + false + + false + + true + ascendix_ciserv__DealsByBuyerContact__r + true + true + + + true + false + true + ascendix_ciserv__Deal__c + true + false + true + ascendix_ciserv__BuyerRepContact__c + false + + false + + true + ascendix_ciserv__DealsByBuyerRepContact__r + false + false + + + true + false + true + ascendix_ciserv__Deal__c + true + false + true + ascendix_ciserv__ClientContact__c + false + + false + + true + ascendix_ciserv__DealsByClientContact__r + false + false + + + true + false + true + ascendix_ciserv__Deal__c + true + false + true + ascendix_ciserv__LeadBrokerContact__c + false + + false + + true + ascendix_ciserv__DealsByLeadBrokerContact__r + false + false + + + true + false + true + ascendix_ciserv__Deal__c + true + false + true + ascendix_ciserv__LenderContact__c + false + + false + + true + ascendix_ciserv__DealsByLenderContact__r + false + false + + + true + false + true + ascendix_ciserv__Deal__c + true + false + true + ascendix_ciserv__ListingBrokerContact__c + false + + false + + true + ascendix_ciserv__DealsByListingBrokerContact__r + false + false + + + true + false + true + ascendix_ciserv__Deal__c + true + false + true + ascendix_ciserv__OwnerLandlordContact__c + false + + false + + true + ascendix_ciserv__DealsByOwnerLandlordContact__r + false + false + + + true + false + true + ascendix_ciserv__Deal__c + true + false + true + ascendix_ciserv__SellerContact__c + false + + false + + true + ascendix_ciserv__DealsBySellerContact__r + false + false + + + true + false + true + ascendix_ciserv__Deal__c + true + false + true + ascendix_ciserv__TenantContact__c + false + + false + + true + ascendix_ciserv__DealsByTenantContact__r + false + false + + + true + false + true + ascendix_ciserv__Deal__c + true + false + true + ascendix_ciserv__TenantRepBrokerContact__c + false + + false + + true + ascendix_ciserv__DealsByTenantRepBrokerContact__r + false + false + + + true + false + true + ascendix_ciserv__Inquiry__c + true + false + true + ascendix_ciserv__ConvertedContact__c + false + + false + + true + ascendix_ciserv__Inquiries1__r + false + false + + + true + false + true + ascendix_ciserv__Lease__c + true + false + true + ascendix_ciserv__ListingBrokerContact__c + false + + false + + true + ascendix_ciserv__LeasesByListingBrokerContact__r + false + false + + + true + false + true + ascendix_ciserv__Lease__c + true + false + true + ascendix_ciserv__OwnerLandlordContact__c + false + + false + + true + ascendix_ciserv__LeasesByOwnerLandlordContact__r + false + false + + + true + false + true + ascendix_ciserv__Lease__c + true + false + true + ascendix_ciserv__TenantContact__c + false + + false + + true + ascendix_ciserv__LeasesByTenantContact__r + false + false + + + true + false + true + ascendix_ciserv__Lease__c + true + false + true + ascendix_ciserv__TenantRepBrokerContact__c + false + + false + + true + ascendix_ciserv__LeasesByTenantRepBrokerContact__r + false + false + + + true + false + true + ascendix_ciserv__Listing__c + true + false + true + ascendix_ciserv__ListingBrokerContact__c + false + + false + + true + ascendix_ciserv__ListingsByListingBrokerContact__r + false + false + + + true + false + true + ascendix_ciserv__Listing__c + true + false + true + ascendix_ciserv__OwnerLandlordContact__c + false + + false + + true + ascendix_ciserv__ListingsByOwnerLandlordContact__r + false + false + + + true + false + true + ascendix_ciserv__Preference__c + true + false + true + ascendix_ciserv__Contact__c + false + + false + + true + ascendix_ciserv__Preferences__r + false + false + + + true + false + true + ascendix_ciserv__Property__c + true + false + true + ascendix_ciserv__DeveloperContact__c + false + + false + + true + ascendix_ciserv__PropertiesByDeveloperContact__r + false + false + + + true + false + true + ascendix_ciserv__Property__c + true + false + true + ascendix_ciserv__ListingBrokerContact__c + false + + false + + true + ascendix_ciserv__PropertyByListingBrokerContact__r + false + false + + + true + false + true + ascendix_ciserv__Property__c + true + false + true + ascendix_ciserv__OwnerLandlordContact__c + false + + false + + true + ascendix_ciserv__PropertiesByOwnerLandlordContact__r + false + false + + + true + false + true + ascendix_ciserv__Property__c + true + false + true + ascendix_ciserv__PropertyManagerContact__c + false + + false + + true + ascendix_ciserv__PropertiesByPropertyManagerContact__r + false + false + + + true + false + true + ascendix_ciserv__ProspectiveCustomer__c + true + false + true + ascendix_ciserv__Contact__c + false + + false + + true + ascendix_ciserv__ProspectiveCustomersByContact__r + false + false + + + true + false + true + ascendix_ciserv__Sale__c + true + false + true + ascendix_ciserv__BuyerContact__c + false + + false + + true + ascendix_ciserv__SalesByBuyerContact__r + false + false + + + true + false + true + ascendix_ciserv__Sale__c + true + false + true + ascendix_ciserv__BuyerRepContact__c + false + + false + + true + ascendix_ciserv__SalesByBuyerRepContact__r + false + false + + + true + false + true + ascendix_ciserv__Sale__c + true + false + true + ascendix_ciserv__LenderContact__c + false + + false + + true + ascendix_ciserv__SalesByLenderContact__r + false + false + + + true + false + true + ascendix_ciserv__Sale__c + true + false + true + ascendix_ciserv__ListingBrokerContact__c + false + + false + + true + ascendix_ciserv__SalesByListingBrokerContact__r + false + false + + + true + false + true + ascendix_ciserv__Sale__c + true + false + true + ascendix_ciserv__SellerContact__c + false + + false + + true + ascendix_ciserv__SalesBySellerContact__r + false + false + + + true + false + true + ascendix_search__AdHocListMember__c + true + false + true + ascendix_search__Contact__c + false + + false + + true + ascendix_search__AdHocListMembers__r + false + false + + + true + true + true + true + true + false + true + false + true + true + true + false + true + true + true + + + true + true + true + false + true + 18 + true + false + false + false + false + true + false + false + false + true + false + true + false + false + false + true + true + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + true + false + true + + true + 18 + false + false + true + Id + true + false + true + false + true + false + true + false + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + ID + true + true + true + id + true + false + true + false + false + false + + + true + false + true + false + true + 0 + true + false + false + false + false + true + false + false + false + true + false + true + false + true + false + false + true + true + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + IsDeleted + true + false + true + false + true + false + true + false + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + _boolean + true + true + true + _boolean + true + false + true + false + false + false + + + true + true + true + false + true + 18 + true + false + false + false + false + true + false + false + false + true + false + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 18 + false + false + true + MasterRecordId + true + false + true + false + true + true + true + false + false + + true + 0 + true + false + false + true + + Contact + + true + MasterRecord + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + reference + true + false + true + false + false + false + + + true + true + true + false + true + 18 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 18 + false + false + true + AccountId + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + true + + Account + + true + Account + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 240 + true + false + false + false + false + true + false + true + Name + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + true + personname + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 80 + false + false + true + LastName + true + false + true + false + true + false + true + false + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + string + true + true + true + string + true + false + true + true + false + false + + + true + true + true + false + true + 120 + true + false + false + false + false + true + false + true + Name + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + true + personname + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 40 + false + false + true + FirstName + true + false + true + false + true + true + true + false + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 120 + true + false + false + false + false + true + false + true + Name + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + true + personname + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 40 + false + false + true + Salutation + true + false + true + false + true + true + true + false + true + + + true + true + true + false + true + + false + true + Mr. + + + true + true + true + false + true + + false + true + Ms. + + + true + true + true + false + true + + false + true + Mrs. + + + true + true + true + false + true + + false + true + Dr. + + + true + true + true + false + true + + false + true + Prof. + + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + picklist + true + false + true + true + false + false + + + true + true + true + false + true + 363 + true + false + false + false + false + true + false + false + false + true + false + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + true + personname + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 121 + false + false + true + Name + true + true + true + false + true + false + true + false + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + false + false + false + + + true + true + true + false + true + 765 + true + false + false + false + false + true + false + true + OtherAddress + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + true + plaintextarea + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 255 + false + false + true + OtherStreet + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + textarea + true + false + true + true + false + false + + + true + true + true + false + true + 120 + true + false + false + false + false + true + false + true + OtherAddress + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 40 + false + false + true + OtherCity + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 240 + true + false + false + false + false + true + false + true + OtherAddress + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 80 + false + false + true + OtherState + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 60 + true + false + false + false + false + true + false + true + OtherAddress + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 20 + false + false + true + OtherPostalCode + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 240 + true + false + false + false + false + true + false + true + OtherAddress + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 80 + false + false + true + OtherCountry + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 0 + true + false + false + false + false + true + false + true + OtherAddress + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + false + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + OtherLatitude + true + false + true + false + true + true + true + true + false + + true + 18 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 15 + true + _double + true + true + true + _double + true + false + true + true + false + false + + + true + true + true + false + true + 0 + true + false + false + false + false + true + false + true + OtherAddress + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + false + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + OtherLongitude + true + false + true + false + true + true + true + true + false + + true + 18 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 15 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 120 + true + false + false + false + false + true + false + true + OtherAddress + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 40 + false + false + true + OtherGeocodeAccuracy + true + false + true + false + true + true + true + true + true + + + true + true + true + false + true + + false + true + Address + + + true + true + true + false + true + + false + true + NearAddress + + + true + true + true + false + true + + false + true + Block + + + true + true + true + false + true + + false + true + Street + + + true + true + true + false + true + + false + true + ExtendedZip + + + true + true + true + false + true + + false + true + Zip + + + true + true + true + false + true + + false + true + Neighborhood + + + true + true + true + false + true + + false + true + City + + + true + true + true + false + true + + false + true + County + + + true + true + true + false + true + + false + true + State + + + true + true + true + false + true + + false + true + Unknown + + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + true + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + false + true + false + true + 0 + true + false + false + false + false + true + false + false + false + true + false + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + false + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + OtherAddress + true + false + true + false + true + true + true + true + false + + true + 0 + true + true + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + address + true + false + true + address + true + false + true + false + false + false + + + true + true + true + false + true + 765 + true + false + false + false + false + true + false + true + MailingAddress + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + true + plaintextarea + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 255 + false + false + true + MailingStreet + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 120 + true + false + false + false + false + true + false + true + MailingAddress + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 40 + false + false + true + MailingCity + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 240 + true + false + false + false + false + true + false + true + MailingAddress + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 80 + false + false + true + MailingState + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 60 + true + false + false + false + false + true + false + true + MailingAddress + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 20 + false + false + true + MailingPostalCode + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 240 + true + false + false + false + false + true + false + true + MailingAddress + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 80 + false + false + true + MailingCountry + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 0 + true + false + false + false + false + true + false + true + MailingAddress + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + false + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + MailingLatitude + true + false + true + false + true + true + true + true + false + + true + 18 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 15 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 0 + true + false + false + false + false + true + false + true + MailingAddress + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + false + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + MailingLongitude + true + false + true + false + true + true + true + true + false + + true + 18 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 15 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 120 + true + false + false + false + false + true + false + true + MailingAddress + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 40 + false + false + true + MailingGeocodeAccuracy + true + false + true + false + true + true + true + true + true + + + true + true + true + false + true + + false + true + Address + + + true + true + true + false + true + + false + true + NearAddress + + + true + true + true + false + true + + false + true + Block + + + true + true + true + false + true + + false + true + Street + + + true + true + true + false + true + + false + true + ExtendedZip + + + true + true + true + false + true + + false + true + Zip + + + true + true + true + false + true + + false + true + Neighborhood + + + true + true + true + false + true + + false + true + City + + + true + true + true + false + true + + false + true + County + + + true + true + true + false + true + + false + true + State + + + true + true + true + false + true + + false + true + Unknown + + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + true + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + false + true + false + true + 0 + true + false + false + false + false + true + false + false + false + true + false + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + false + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + MailingAddress + true + false + true + false + true + true + true + true + false + + true + 0 + true + true + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + false + true + + true + false + true + false + false + false + + + true + true + true + false + true + 120 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 40 + false + false + true + Phone + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + phone + true + false + true + true + false + false + + + true + true + true + false + true + 120 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 40 + false + false + true + Fax + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 120 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 40 + false + false + true + MobilePhone + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 120 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 40 + false + false + true + HomePhone + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 120 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 40 + false + false + true + OtherPhone + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 120 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 40 + false + false + true + AssistantPhone + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 18 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 18 + false + false + true + ReportsToId + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + true + + Contact + + true + ReportsTo + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 240 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + true + false + true + + true + 80 + false + false + true + Email + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + email + true + false + true + true + false + false + + + true + true + true + false + true + 384 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 128 + false + false + true + Title + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 240 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 80 + false + false + true + Department + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 120 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 40 + false + false + true + AssistantName + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 120 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 40 + false + false + true + LeadSource + true + false + true + false + true + true + true + true + true + + + true + true + true + false + true + + false + true + Web + + + true + true + true + false + true + + false + true + Phone Inquiry + + + true + true + true + false + true + + false + true + Partner Referral + + + true + true + true + false + true + + false + true + Purchased List + + + true + true + true + false + true + + false + true + Other + + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 0 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + Birthdate + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + date + true + true + true + date + true + false + true + true + false + false + + + true + false + true + false + true + 96000 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + true + plaintextarea + true + false + false + true + false + false + false + false + false + true + false + false + true + + true + 32000 + false + false + true + Description + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + false + true + + true + false + true + true + false + false + + + true + true + true + false + true + 18 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + true + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 18 + false + false + true + OwnerId + true + false + true + false + true + false + true + false + false + + true + 0 + true + false + false + true + + User + + true + Owner + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + false + true + false + true + 0 + true + false + false + false + false + true + false + false + false + true + true + true + false + true + false + false + true + true + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + HasOptedOutOfEmail + true + false + true + false + true + false + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + false + true + false + true + 0 + true + false + false + false + false + true + false + false + false + true + true + true + false + true + false + false + true + true + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + HasOptedOutOfFax + true + false + true + false + true + false + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + false + true + false + true + 0 + true + false + false + false + false + true + false + false + false + true + true + true + false + true + false + false + true + true + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + DoNotCall + true + false + true + false + true + false + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 0 + true + false + false + false + false + true + false + false + false + true + false + true + false + false + false + true + true + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + false + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + CreatedDate + true + false + true + false + true + false + true + false + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + dateTime + true + true + true + datetime + true + false + true + false + false + false + + + true + true + true + false + true + 18 + true + false + false + false + false + true + false + false + false + true + false + true + false + false + false + true + true + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 18 + false + false + true + CreatedById + true + false + true + false + true + false + true + false + false + + true + 0 + true + false + false + true + + User + + true + CreatedBy + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + false + false + false + + + true + true + true + false + true + 0 + true + false + false + false + false + true + false + false + false + true + false + true + false + false + false + true + true + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + false + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + LastModifiedDate + true + false + true + false + true + false + true + false + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + false + false + false + + + true + true + true + false + true + 18 + true + false + false + false + false + true + false + false + false + true + false + true + false + false + false + true + true + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 18 + false + false + true + LastModifiedById + true + false + true + false + true + false + true + false + false + + true + 0 + true + false + false + true + + User + + true + LastModifiedBy + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + false + false + false + + + true + true + true + false + true + 0 + true + false + false + false + false + true + false + false + false + true + false + true + false + false + false + true + true + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + false + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + SystemModstamp + true + false + true + false + true + false + true + false + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + false + false + false + + + true + true + true + false + true + 0 + true + false + false + false + false + true + false + false + false + true + false + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + LastActivityDate + true + false + true + false + true + true + true + false + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + false + false + false + + + true + true + true + false + true + 0 + true + false + false + false + false + true + false + false + false + true + false + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + false + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + LastCURequestDate + true + false + true + false + true + true + true + false + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + false + false + false + + + true + true + true + false + true + 0 + true + false + false + false + false + true + false + false + false + true + false + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + false + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + LastCUUpdateDate + true + false + true + false + true + true + true + false + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + false + false + false + + + true + true + true + false + true + 0 + true + false + false + false + false + true + false + false + false + true + false + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + false + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + LastViewedDate + true + false + true + false + true + true + true + false + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + false + false + false + + + true + true + true + false + true + 0 + true + false + false + false + false + true + false + false + false + true + false + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + false + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + LastReferencedDate + true + false + true + false + true + true + true + false + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + false + false + false + + + true + true + true + false + true + 765 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 255 + false + false + true + EmailBouncedReason + true + false + true + false + true + true + true + false + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 0 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + false + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + EmailBouncedDate + true + false + true + false + true + true + true + false + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + false + true + false + true + 0 + true + false + false + false + false + true + false + false + false + true + false + true + false + true + false + false + true + true + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 0 + false + false + true + IsEmailBounced + true + false + true + false + true + false + true + false + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + false + false + false + + + true + true + true + false + true + 765 + true + false + false + false + false + true + false + false + false + true + false + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + true + imageurl + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 255 + false + false + true + PhotoUrl + true + false + true + false + true + true + true + false + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + url + true + false + true + false + false + false + + + true + true + true + false + true + 60 + true + false + false + false + false + true + false + false + false + true + true + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 20 + false + false + true + Jigsaw + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 60 + true + false + false + false + false + true + false + false + false + true + false + true + false + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 20 + false + false + true + JigsawContactId + true + false + true + false + true + true + true + false + false + + true + 0 + true + false + false + false + + true + JigsawContact + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + false + false + false + + + true + true + true + false + true + 765 + true + false + false + false + false + true + false + false + false + true + true + true + true + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 255 + false + false + true + ascendix_ciserv__Level__c + true + false + true + false + true + true + true + true + true + + + true + true + true + false + true + + false + true + Secondary + + + true + true + true + false + true + + false + true + Tertiary + + + true + true + true + false + true + + false + true + Primary + + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 300 + true + false + false + false + false + true + false + false + false + true + true + true + true + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 100 + false + false + true + ascendix_ciserv__Languages__c + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 765 + true + false + false + false + false + true + false + false + false + true + true + true + true + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + true + true + false + true + true + false + true + true + false + false + false + false + true + true + false + true + + true + 255 + false + false + true + ascendix_ciserv__SourceSystemNumber__c + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + true + true + false + true + 765 + true + false + false + false + false + true + false + false + false + true + true + true + true + false + false + true + false + false + false + true + false + true + 0 + false + false + false + false + false + false + false + true + true + false + true + true + false + false + false + false + true + false + false + true + + true + 255 + false + false + true + ascendix_ciserv__SourceSystem__c + true + false + true + false + true + true + true + true + false + + true + 0 + true + false + false + false + + false + false + 0 + false + false + true + false + true + 0 + true + + true + true + true + + true + false + true + true + false + false + + + true + false + true + true + true + false + true + 003 + true + + true + Contacts + true + true + false + false + false + false + true + true + true + true + true + Contact + false + + true + true + true + true + + + true + true + true + true + true + true + true + Master + true + 012000000000000AAA + + + true + true + true + true + true + true + true + true + true + + + true + + true + everything + + + true + + true + mine + + + true + + true + team + + + true + true + true + true + true + true + true + https://ascendixre-ci-dev-ed.my.salesforce.com/{ID} + true + https://ascendixre-ci-dev-ed.my.salesforce.com/{ID}/e + true + https://ascendixre-ci-dev-ed.my.salesforce.com/003/e + \ No newline at end of file diff --git a/presto-server-main/etc/config.properties b/presto-server-main/etc/config.properties index c013971c221fa..28365847cc3c4 100644 --- a/presto-server-main/etc/config.properties +++ b/presto-server-main/etc/config.properties @@ -48,6 +48,8 @@ plugin.bundles=\ ../presto-thrift/pom.xml, \ ../presto-tpcds/pom.xml, \ ../presto-google-sheets/pom.xml, \ - ../presto-druid/pom.xml + ../presto-druid/pom.xml, \ + ../presto-google-sheets/pom.xml, \ + ../presto-salesforce/pom.xml node-scheduler.include-coordinator=true diff --git a/presto-server/src/main/provisio/presto.xml b/presto-server/src/main/provisio/presto.xml index 1ef267f816ccb..4017c094f79ef 100644 --- a/presto-server/src/main/provisio/presto.xml +++ b/presto-server/src/main/provisio/presto.xml @@ -241,6 +241,7 @@ + @@ -252,4 +253,10 @@ + + + + + +