All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.snowflake.client.jdbc.SnowflakeResultSetSerializableV1 Maven / Gradle / Ivy

There is a newer version: 3.18.0
Show newest version
/*
 * Copyright (c) 2012-2019 Snowflake Computing Inc. All rights reserved.
 */

package net.snowflake.client.jdbc;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import net.snowflake.client.core.ChunkDownloader;
import net.snowflake.client.core.HttpUtil;
import net.snowflake.client.core.MetaDataOfBinds;
import net.snowflake.client.core.OCSPMode;
import net.snowflake.client.core.ObjectMapperFactory;
import net.snowflake.client.core.QueryResultFormat;
import net.snowflake.client.core.SFArrowResultSet;
import net.snowflake.client.core.SFBaseResultSet;
import net.snowflake.client.core.SFResultSet;
import net.snowflake.client.core.SFResultSetMetaData;
import net.snowflake.client.core.SFSession;
import net.snowflake.client.core.SFSessionProperty;
import net.snowflake.client.core.SFStatementType;
import net.snowflake.client.core.SessionUtil;
import net.snowflake.client.jdbc.telemetry.NoOpTelemetryClient;
import net.snowflake.client.jdbc.telemetry.Telemetry;
import net.snowflake.client.log.ArgSupplier;
import net.snowflake.common.core.SFBinaryFormat;

import net.snowflake.client.log.SFLogger;
import net.snowflake.client.log.SFLoggerFactory;

import java.io.Serializable;
import java.io.IOException;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.TimeZone;

import net.snowflake.client.core.ResultUtil;
import net.snowflake.client.core.SFStatement;
import net.snowflake.common.core.SnowflakeDateTimeFormat;
import org.apache.arrow.memory.RootAllocator;

import static net.snowflake.client.core.SessionUtil.CLIENT_ENABLE_CONSERVATIVE_MEMORY_USAGE;
import static net.snowflake.client.core.SessionUtil.CLIENT_MEMORY_LIMIT;
import static net.snowflake.client.core.SessionUtil.CLIENT_PREFETCH_THREADS;
import static net.snowflake.client.core.SessionUtil.CLIENT_RESULT_CHUNK_SIZE;
import static net.snowflake.client.core.SessionUtil.DEFAULT_CLIENT_MEMORY_LIMIT;
import static net.snowflake.client.core.SessionUtil.DEFAULT_CLIENT_PREFETCH_THREADS;


/**
 * This object is an intermediate object between result JSON from GS and
 * ResultSet. Originally, it is created from result JSON. And it can
 * also be serializable. Logically, it stands for a part of ResultSet.
 * 

* A typical result JSON data section consists of the content of the first chunk * file and file metadata for the rest of chunk files e.g. URL, chunk size, etc. * So this object consists of one chunk data and a list of chunk file entries. * In actual cases, it may only include chunk data or chunk files entries. *

* This object is serializable, so it can be distributed to other threads or * worker nodes for distributed processing. */ public class SnowflakeResultSetSerializableV1 implements SnowflakeResultSetSerializable, Serializable { private static final long serialVersionUID = 1L; static final SFLogger logger = SFLoggerFactory.getLogger(SnowflakeResultSetSerializableV1.class); static final ObjectMapper mapper = ObjectMapperFactory.getObjectMapper(); /** * An Entity class to represent a chunk file metadata. */ static public class ChunkFileMetadata implements Serializable { private static final long serialVersionUID = 1L; String fileURL; int rowCount; int compressedByteSize; int uncompressedByteSize; public ChunkFileMetadata(String fileURL, int rowCount, int compressedByteSize, int uncompressedByteSize) { this.fileURL = fileURL; this.rowCount = rowCount; this.compressedByteSize = compressedByteSize; this.uncompressedByteSize = uncompressedByteSize; } public void setFileURL(String fileURL) { this.fileURL = fileURL; } public String getFileURL() { return fileURL; } public int getRowCount() { return rowCount; } public int getCompressedByteSize() { return compressedByteSize; } public int getUncompressedByteSize() { return uncompressedByteSize; } public String toString() { StringBuilder builder = new StringBuilder(1024); builder.append("RowCount: ").append(rowCount).append(", "); builder.append("CompressedSize: ").append(compressedByteSize).append(", "); builder.append("UnCompressedSize: ").append(uncompressedByteSize).append(", "); builder.append("fileURL: ").append(fileURL); return builder.toString(); } } // Below fields are for the data fields that this object wraps String firstChunkStringData; // For ARROW, it's BASE64-encoded arrow file. // For JSON, it's string data for the json. int firstChunkRowCount; // It is only used for JSON format result. int chunkFileCount; List chunkFileMetadatas = new ArrayList<>(); // below fields are used for building a ChunkDownloader which // uses http client to download chunk files boolean useJsonParserV2; int resultPrefetchThreads; String qrmk; Map chunkHeadersMap = new HashMap<>(); // Below fields are from session or statement SnowflakeConnectString snowflakeConnectionString; OCSPMode ocspMode; int networkTimeoutInMilli; boolean isResultColumnCaseInsensitive; int resultSetType; int resultSetConcurrency; int resultSetHoldability; // Below are some metadata fields parsed from the result JSON node String queryId; String finalDatabaseName; String finalSchemaName; String finalRoleName; String finalWarehouseName; SFStatementType statementType; boolean totalRowCountTruncated; Map parameters = new HashMap<>(); int columnCount; private List resultColumnMetadata = new ArrayList<>(); long resultVersion; int numberOfBinds; boolean arrayBindSupported; long sendResultTime; List metaDataOfBinds = new ArrayList<>(); QueryResultFormat queryResultFormat; // Below fields are transient, they are generated from parameters transient TimeZone timeZone; transient boolean honorClientTZForTimestampNTZ; transient SnowflakeDateTimeFormat timestampNTZFormatter; transient SnowflakeDateTimeFormat timestampLTZFormatter; transient SnowflakeDateTimeFormat timestampTZFormatter; transient SnowflakeDateTimeFormat dateFormatter; transient SnowflakeDateTimeFormat timeFormatter; transient SFBinaryFormat binaryFormatter; transient long memoryLimit; // Below fields are transient, they are generated on the fly. transient JsonNode firstChunkRowset = null; // only used for JSON result transient ChunkDownloader chunkDownloader = null; transient RootAllocator rootAllocator = null; // only used for ARROW result transient SFResultSetMetaData resultSetMetaData = null; /** * Default constructor. */ public SnowflakeResultSetSerializableV1() { } /** * This is copy constructor. *

* NOTE: The copy is NOT deep copy. * * @param toCopy the source object to be copied. */ private SnowflakeResultSetSerializableV1(SnowflakeResultSetSerializableV1 toCopy) { // Below fields are for the data fields that this object wraps this.firstChunkStringData = toCopy.firstChunkStringData; this.firstChunkRowCount = toCopy.firstChunkRowCount; this.chunkFileCount = toCopy.chunkFileCount; this.chunkFileMetadatas = toCopy.chunkFileMetadatas; // below fields are used for building a ChunkDownloader this.useJsonParserV2 = toCopy.useJsonParserV2; this.resultPrefetchThreads = toCopy.resultPrefetchThreads; this.qrmk = toCopy.qrmk; this.chunkHeadersMap = toCopy.chunkHeadersMap; // Below fields are from session or statement this.snowflakeConnectionString = toCopy.snowflakeConnectionString; this.ocspMode = toCopy.ocspMode; this.networkTimeoutInMilli = toCopy.networkTimeoutInMilli; this.isResultColumnCaseInsensitive = toCopy.isResultColumnCaseInsensitive; this.resultSetType = toCopy.resultSetType; this.resultSetConcurrency = toCopy.resultSetConcurrency; this.resultSetHoldability = toCopy.resultSetHoldability; // Below are some metadata fields parsed from the result JSON node this.queryId = toCopy.queryId; this.finalDatabaseName = toCopy.finalDatabaseName; this.finalSchemaName = toCopy.finalSchemaName; this.finalRoleName = toCopy.finalRoleName; this.finalWarehouseName = toCopy.finalWarehouseName; this.statementType = toCopy.statementType; this.totalRowCountTruncated = toCopy.totalRowCountTruncated; this.parameters = toCopy.parameters; this.columnCount = toCopy.columnCount; this.resultColumnMetadata = toCopy.resultColumnMetadata; this.resultVersion = toCopy.resultVersion; this.numberOfBinds = toCopy.numberOfBinds; this.arrayBindSupported = toCopy.arrayBindSupported; this.sendResultTime = toCopy.sendResultTime; this.metaDataOfBinds = toCopy.metaDataOfBinds; this.queryResultFormat = toCopy.queryResultFormat; // Below fields are transient, they are generated from parameters this.timeZone = toCopy.timeZone; this.honorClientTZForTimestampNTZ = toCopy.honorClientTZForTimestampNTZ; this.timestampNTZFormatter = toCopy.timestampNTZFormatter; this.timestampLTZFormatter = toCopy.timestampLTZFormatter; this.timestampTZFormatter = toCopy.timestampTZFormatter; this.dateFormatter = toCopy.dateFormatter; this.timeFormatter = toCopy.timeFormatter; this.binaryFormatter = toCopy.binaryFormatter; this.memoryLimit = toCopy.memoryLimit; // Below fields are transient, they are generated on the fly. this.firstChunkRowset = toCopy.firstChunkRowset; this.chunkDownloader = toCopy.chunkDownloader; this.rootAllocator = toCopy.rootAllocator; this.resultSetMetaData = toCopy.resultSetMetaData; } public void setRootAllocator(RootAllocator rootAllocator) { this.rootAllocator = rootAllocator; } public void setQueryResultFormat(QueryResultFormat queryResultFormat) { this.queryResultFormat = queryResultFormat; } public void setChunkFileCount(int chunkFileCount) { this.chunkFileCount = chunkFileCount; } public void setFristChunkStringData(String firstChunkStringData) { this.firstChunkStringData = firstChunkStringData; } public void setChunkDownloader(ChunkDownloader chunkDownloader) { this.chunkDownloader = chunkDownloader; } public long getUncompressedDataSize() { long totalSize = this.firstChunkStringData != null ? this.firstChunkStringData.length() : 0; for (ChunkFileMetadata entry : chunkFileMetadatas) { totalSize += entry.getUncompressedByteSize(); } return totalSize; } public SFResultSetMetaData getSFResultSetMetaData() { return resultSetMetaData; } public int getResultSetType() { return resultSetType; } public int getResultSetConcurrency() { return resultSetConcurrency; } public int getResultSetHoldability() { return resultSetHoldability; } public SnowflakeConnectString getSnowflakeConnectString() { return snowflakeConnectionString; } public OCSPMode getOCSPMode() { return ocspMode; } public String getQrmk() { return qrmk; } public int getNetworkTimeoutInMilli() { return networkTimeoutInMilli; } public int getResultPrefetchThreads() { return resultPrefetchThreads; } public boolean getUseJsonParserV2() { return useJsonParserV2; } public long getMemoryLimit() { return memoryLimit; } public Map getChunkHeadersMap() { return chunkHeadersMap; } public List getChunkFileMetadatas() { return chunkFileMetadatas; } public RootAllocator getRootAllocator() { return rootAllocator; } public QueryResultFormat getQueryResultFormat() { return queryResultFormat; } public int getChunkFileCount() { return chunkFileCount; } public boolean isArrayBindSupported() { return arrayBindSupported; } public String getQueryId() { return queryId; } public String getFinalDatabaseName() { return finalDatabaseName; } public String getFinalSchemaName() { return finalSchemaName; } public String getFinalRoleName() { return finalRoleName; } public String getFinalWarehouseName() { return finalWarehouseName; } public SFStatementType getStatementType() { return statementType; } public boolean isTotalRowCountTruncated() { return totalRowCountTruncated; } public Map getParameters() { return parameters; } public int getColumnCount() { return columnCount; } public List getResultColumnMetadata() { return resultColumnMetadata; } public JsonNode getAndClearFirstChunkRowset() { JsonNode firstChunkRowset = this.firstChunkRowset; this.firstChunkRowset = null; return firstChunkRowset; } public int getFirstChunkRowCount() { return firstChunkRowCount; } public long getResultVersion() { return resultVersion; } public int getNumberOfBinds() { return numberOfBinds; } public ChunkDownloader getChunkDownloader() { return chunkDownloader; } public SnowflakeDateTimeFormat getTimestampNTZFormatter() { return timestampNTZFormatter; } public SnowflakeDateTimeFormat getTimestampLTZFormatter() { return timestampLTZFormatter; } public SnowflakeDateTimeFormat getTimestampTZFormatter() { return timestampTZFormatter; } public SnowflakeDateTimeFormat getDateFormatter() { return dateFormatter; } public SnowflakeDateTimeFormat getTimeFormatter() { return timeFormatter; } public TimeZone getTimeZone() { return timeZone; } public boolean isHonorClientTZForTimestampNTZ() { return honorClientTZForTimestampNTZ; } public SFBinaryFormat getBinaryFormatter() { return binaryFormatter; } public long getSendResultTime() { return sendResultTime; } public List getMetaDataOfBinds() { return metaDataOfBinds; } public String getFirstChunkStringData() { return firstChunkStringData; } /** * A factory function to create SnowflakeResultSetSerializable object * from result JSON node. * * @param rootNode result JSON node received from GS * @param sfSession the Snowflake session * @param sfStatement the Snowflake statement * @return processed ResultSetSerializable object * @throws SnowflakeSQLException if failed to parse the result JSON node */ static public SnowflakeResultSetSerializableV1 create( JsonNode rootNode, SFSession sfSession, SFStatement sfStatement) throws SnowflakeSQLException { SnowflakeResultSetSerializableV1 resultSetSerializable = new SnowflakeResultSetSerializableV1(); logger.debug("Entering create()"); SnowflakeUtil.checkErrorAndThrowException(rootNode); // get the query id resultSetSerializable.queryId = rootNode.path("data").path("queryId").asText(); JsonNode databaseNode = rootNode.path("data").path("finalDatabaseName"); resultSetSerializable.finalDatabaseName = databaseNode.isNull() ? null : databaseNode.asText(); JsonNode schemaNode = rootNode.path("data").path("finalSchemaName"); resultSetSerializable.finalSchemaName = schemaNode.isNull() ? null : schemaNode.asText(); JsonNode roleNode = rootNode.path("data").path("finalRoleName"); resultSetSerializable.finalRoleName = roleNode.isNull() ? null : roleNode.asText(); JsonNode warehouseNode = rootNode.path("data").path("finalWarehouseName"); resultSetSerializable.finalWarehouseName = warehouseNode.isNull() ? null : warehouseNode.asText(); resultSetSerializable.statementType = SFStatementType.lookUpTypeById( rootNode.path("data").path("statementTypeId").asLong()); resultSetSerializable.totalRowCountTruncated = rootNode.path("data").path("totalTruncated").asBoolean(); logger.debug("query id: {}", resultSetSerializable.queryId); Optional queryResultFormat = QueryResultFormat .lookupByName(rootNode.path("data").path("queryResultFormat").asText()); resultSetSerializable.queryResultFormat = queryResultFormat.orElse(QueryResultFormat.JSON); // extract parameters resultSetSerializable.parameters = SessionUtil.getCommonParams(rootNode.path("data").path("parameters")); // initialize column metadata resultSetSerializable.columnCount = rootNode.path("data").path("rowtype").size(); for (int i = 0; i < resultSetSerializable.columnCount; i++) { JsonNode colNode = rootNode.path("data").path("rowtype").path(i); SnowflakeColumnMetadata columnMetadata = SnowflakeUtil.extractColumnMetadata( colNode, sfSession.isJdbcTreatDecimalAsInt()); resultSetSerializable.resultColumnMetadata.add(columnMetadata); logger.debug("Get column metadata: {}", (ArgSupplier) () -> columnMetadata.toString()); } // process the content of first chunk. if (resultSetSerializable.queryResultFormat == QueryResultFormat.ARROW) { resultSetSerializable.firstChunkStringData = rootNode.path("data").path("rowsetBase64").asText(); resultSetSerializable.rootAllocator = new RootAllocator(Integer.MAX_VALUE); } else { resultSetSerializable.firstChunkRowset = rootNode.path("data").path("rowset"); if (resultSetSerializable.firstChunkRowset == null || resultSetSerializable.firstChunkRowset.isMissingNode()) { resultSetSerializable.firstChunkRowCount = 0; resultSetSerializable.firstChunkStringData = null; } else { resultSetSerializable.firstChunkRowCount = resultSetSerializable.firstChunkRowset.size(); resultSetSerializable.firstChunkStringData = resultSetSerializable.firstChunkRowset.toString(); } logger.debug("First chunk row count: {}", resultSetSerializable.firstChunkRowCount); } // parse file chunks resultSetSerializable.parseChunkFiles(rootNode, sfStatement); // result version JsonNode versionNode = rootNode.path("data").path("version"); if (!versionNode.isMissingNode()) { resultSetSerializable.resultVersion = versionNode.longValue(); } // number of binds JsonNode numberOfBindsNode = rootNode.path("data").path("numberOfBinds"); if (!numberOfBindsNode.isMissingNode()) { resultSetSerializable.numberOfBinds = numberOfBindsNode.intValue(); } JsonNode arrayBindSupported = rootNode.path("data") .path("arrayBindSupported"); resultSetSerializable.arrayBindSupported = !arrayBindSupported.isMissingNode() && arrayBindSupported.asBoolean(); // time result sent by GS (epoch time in millis) JsonNode sendResultTimeNode = rootNode.path("data").path("sendResultTime"); if (!sendResultTimeNode.isMissingNode()) { resultSetSerializable.sendResultTime = sendResultTimeNode.longValue(); } logger.debug("result version={}", resultSetSerializable.resultVersion); // Bind parameter metadata JsonNode bindData = rootNode.path("data").path("metaDataOfBinds"); if (!bindData.isMissingNode()) { List returnVal = new ArrayList<>(); for (JsonNode child : bindData) { int precision = child.path("precision").asInt(); boolean nullable = child.path("nullable").asBoolean(); int scale = child.path("scale").asInt(); int byteLength = child.path("byteLength").asInt(); int length = child.path("length").asInt(); String name = child.path("name").asText(); String type = child.path("type").asText(); MetaDataOfBinds param = new MetaDataOfBinds(precision, nullable, scale, byteLength, length, name, type); returnVal.add(param); } resultSetSerializable.metaDataOfBinds = returnVal; } // setup fields from sessions. resultSetSerializable.ocspMode = sfSession.getOCSPMode(); resultSetSerializable.snowflakeConnectionString = sfSession.getSnowflakeConnectionString(); resultSetSerializable.networkTimeoutInMilli = sfSession.getNetworkTimeoutInMilli(); resultSetSerializable.isResultColumnCaseInsensitive = sfSession.isResultColumnCaseInsensitive(); // setup transient fields from parameter resultSetSerializable.setupFieldsFromParameters(); // The chunk downloader will start prefetching // first few chunk files in background thread(s) resultSetSerializable.chunkDownloader = (resultSetSerializable.chunkFileCount > 0) ? new SnowflakeChunkDownloader(resultSetSerializable) : new SnowflakeChunkDownloader.NoOpChunkDownloader(); // Setup ResultSet metadata resultSetSerializable.resultSetMetaData = new SFResultSetMetaData(resultSetSerializable.getResultColumnMetadata(), resultSetSerializable.queryId, sfSession, resultSetSerializable.isResultColumnCaseInsensitive, resultSetSerializable.timestampNTZFormatter, resultSetSerializable.timestampLTZFormatter, resultSetSerializable.timestampTZFormatter, resultSetSerializable.dateFormatter, resultSetSerializable.timeFormatter); return resultSetSerializable; } /** * Some fields are generated from this.parameters, so generate them from * this.parameters instead of serializing them. */ private void setupFieldsFromParameters() { String sqlTimestampFormat = (String) ResultUtil.effectiveParamValue( this.parameters, "TIMESTAMP_OUTPUT_FORMAT"); // Special handling of specialized formatters, use a helper function this.timestampNTZFormatter = ResultUtil.specializedFormatter( this.parameters, "timestamp_ntz", "TIMESTAMP_NTZ_OUTPUT_FORMAT", sqlTimestampFormat); this.timestampLTZFormatter = ResultUtil.specializedFormatter( this.parameters, "timestamp_ltz", "TIMESTAMP_LTZ_OUTPUT_FORMAT", sqlTimestampFormat); this.timestampTZFormatter = ResultUtil.specializedFormatter( this.parameters, "timestamp_tz", "TIMESTAMP_TZ_OUTPUT_FORMAT", sqlTimestampFormat); String sqlDateFormat = (String) ResultUtil.effectiveParamValue( this.parameters, "DATE_OUTPUT_FORMAT"); this.dateFormatter = new SnowflakeDateTimeFormat(sqlDateFormat); logger.debug("sql date format: {}, java date format: {}", sqlDateFormat, (ArgSupplier) () -> this.dateFormatter.toSimpleDateTimePattern()); String sqlTimeFormat = (String) ResultUtil.effectiveParamValue( this.parameters, "TIME_OUTPUT_FORMAT"); this.timeFormatter = new SnowflakeDateTimeFormat(sqlTimeFormat); logger.debug("sql time format: {}, java time format: {}", sqlTimeFormat, (ArgSupplier) () -> this.timeFormatter.toSimpleDateTimePattern()); String timeZoneName = (String) ResultUtil.effectiveParamValue( this.parameters, "TIMEZONE"); this.timeZone = TimeZone.getTimeZone(timeZoneName); this.honorClientTZForTimestampNTZ = (boolean) ResultUtil.effectiveParamValue( this.parameters, "CLIENT_HONOR_CLIENT_TZ_FOR_TIMESTAMP_NTZ"); logger.debug("Honoring client TZ for timestamp_ntz? {}", this.honorClientTZForTimestampNTZ); String binaryFmt = (String) ResultUtil.effectiveParamValue( this.parameters, "BINARY_OUTPUT_FORMAT"); this.binaryFormatter = SFBinaryFormat.getSafeOutputFormat(binaryFmt); } /** * Parse the chunk file nodes from result JSON node * * @param rootNode result JSON node received from GS * @param sfStatement the snowflake statement */ private void parseChunkFiles(JsonNode rootNode, SFStatement sfStatement) { JsonNode chunksNode = rootNode.path("data").path("chunks"); if (!chunksNode.isMissingNode()) { this.chunkFileCount = chunksNode.size(); // Try to get the Query Result Master Key JsonNode qrmkNode = rootNode.path("data").path("qrmk"); this.qrmk = qrmkNode.isMissingNode() ? null : qrmkNode.textValue(); // Determine the prefetch thread count and memoryLimit if (this.chunkFileCount > 0) { logger.debug("#chunks={}, initialize chunk downloader", this.chunkFileCount); this.resultPrefetchThreads = DEFAULT_CLIENT_PREFETCH_THREADS; if (this.statementType.isSelect() && this.parameters .containsKey(CLIENT_ENABLE_CONSERVATIVE_MEMORY_USAGE) && (boolean) this.parameters .get(CLIENT_ENABLE_CONSERVATIVE_MEMORY_USAGE)) { // use conservative memory settings this.resultPrefetchThreads = sfStatement.getConservativePrefetchThreads(); this.memoryLimit = sfStatement.getConservativeMemoryLimit(); int chunkSize = (int) this.parameters.get(CLIENT_RESULT_CHUNK_SIZE); logger.debug( "enable conservative memory usage with prefetchThreads = {} and memoryLimit = {} and " + "resultChunkSize = {}", this.resultPrefetchThreads, this.memoryLimit, chunkSize); } else { // prefetch threads if (this.parameters.get(CLIENT_PREFETCH_THREADS) != null) { this.resultPrefetchThreads = (int) this.parameters.get(CLIENT_PREFETCH_THREADS); } this.memoryLimit = initMemoryLimit(this.parameters); } // Parse chunk header JsonNode chunkHeaders = rootNode.path("data").path("chunkHeaders"); if (chunkHeaders != null && !chunkHeaders.isMissingNode()) { Iterator> chunkHeadersIter = chunkHeaders.fields(); while (chunkHeadersIter.hasNext()) { Map.Entry chunkHeader = chunkHeadersIter.next(); logger.debug("add header key={}, value={}", chunkHeader.getKey(), chunkHeader.getValue().asText()); this.chunkHeadersMap.put(chunkHeader.getKey(), chunkHeader.getValue().asText()); } } // parse chunk files metadata e.g. url and row count for (int idx = 0; idx < this.chunkFileCount; idx++) { JsonNode chunkNode = chunksNode.get(idx); String url = chunkNode.path("url").asText(); int rowCount = chunkNode.path("rowCount").asInt(); int compressedSize = chunkNode.path("compressedSize").asInt(); int uncompressedSize = chunkNode.path("uncompressedSize").asInt(); this.chunkFileMetadatas.add( new ChunkFileMetadata(url, rowCount, compressedSize, uncompressedSize)); logger.debug("add chunk, url={} rowCount={} " + "compressedSize={} uncompressedSize={}", url, rowCount, compressedSize, uncompressedSize); } /* * Should JsonParser be used instead of the original Json deserializer. */ this.useJsonParserV2 = this.parameters.get("JDBC_USE_JSON_PARSER") != null && (boolean) this.parameters.get("JDBC_USE_JSON_PARSER"); } } } /** * Calculate memory limit in bytes * * @param parameters The parameters for result JSON node * @return memory limit in bytes */ private static long initMemoryLimit(Map parameters) { // default setting long memoryLimit = DEFAULT_CLIENT_MEMORY_LIMIT * 1024 * 1024; if (parameters.get(CLIENT_MEMORY_LIMIT) != null) { // use the settings from the customer memoryLimit = (int) parameters.get(CLIENT_MEMORY_LIMIT) * 1024L * 1024L; } long maxMemoryToUse = Runtime.getRuntime().maxMemory() * 8 / 10; if ((int) parameters.get(CLIENT_MEMORY_LIMIT) == DEFAULT_CLIENT_MEMORY_LIMIT) { // if the memory limit is the default value and best effort memory is enabled // set the memory limit to 80% of the maximum as the best effort memoryLimit = Math.max(memoryLimit, maxMemoryToUse); } // always make sure memoryLimit <= 80% of the maximum memoryLimit = Math.min(memoryLimit, maxMemoryToUse); logger.debug("Set allowed memory usage to {} bytes", memoryLimit); return memoryLimit; } /** * Setup all transient fields based on serialized fields and System Runtime. * * @throws SQLException if fails to setup any transient fields */ private void setupTransientFields() throws SQLException { // Setup transient fields from serialized fields setupFieldsFromParameters(); // Setup memory limitation from parameters and System Runtime. this.memoryLimit = initMemoryLimit(this.parameters); // Create below transient fields on the fly. if (QueryResultFormat.ARROW.equals(this.queryResultFormat)) { this.rootAllocator = new RootAllocator(Integer.MAX_VALUE); this.firstChunkRowset = null; } else { this.rootAllocator = null; try { this.firstChunkRowset = (this.firstChunkStringData != null) ? mapper.readTree(this.firstChunkStringData) : null; } catch (IOException ex) { throw new SQLException("The JSON data is invalid. The error is: " + ex.getMessage()); } } // Setup ResultSet metadata this.resultSetMetaData = new SFResultSetMetaData(this.getResultColumnMetadata(), this.queryId, null, // This is session less this.isResultColumnCaseInsensitive, this.timestampNTZFormatter, this.timestampLTZFormatter, this.timestampTZFormatter, this.dateFormatter, this.timeFormatter); // Allocate chunk downloader if necessary chunkDownloader = (this.chunkFileCount > 0) ? new SnowflakeChunkDownloader(this) : new SnowflakeChunkDownloader.NoOpChunkDownloader(); } /** * Split this object into small pieces based on the user specified data size. * * @param maxSizeInBytes the expected max data size wrapped in the result * ResultSetSerializables object. * NOTE: if a result chunk size is greater than this * value, the ResultSetSerializable object will * include one result chunk. * @return a list of SnowflakeResultSetSerializable * @throws SQLException if fails to split objects. */ public List splitBySize(long maxSizeInBytes) throws SQLException { List resultSetSerializables = new ArrayList<>(); if (this.chunkFileMetadatas.isEmpty() && this.firstChunkStringData == null) { throw new SQLException("The Result Set serializable is invalid."); } // In the beginning, only the first data chunk is included in the result // serializable, so the chunk files are removed from the copy. // NOTE: make sure to handle the case that the first data chunk doesn't // exist. SnowflakeResultSetSerializableV1 curResultSetSerializable = new SnowflakeResultSetSerializableV1(this); curResultSetSerializable.chunkFileMetadatas = new ArrayList<>(); curResultSetSerializable.chunkFileCount = 0; for (int idx = 0; idx < this.chunkFileCount; idx++) { ChunkFileMetadata curChunkFileMetadata = this.getChunkFileMetadatas().get(idx); // If the serializable object has reach the max size, // save current one and create new one. if ((curResultSetSerializable.getUncompressedDataSize() > 0) && (maxSizeInBytes < (curResultSetSerializable.getUncompressedDataSize() + curChunkFileMetadata.getUncompressedByteSize()))) { resultSetSerializables.add(curResultSetSerializable); // Create new result serializable and reset it as empty curResultSetSerializable = new SnowflakeResultSetSerializableV1(this); curResultSetSerializable.chunkFileMetadatas = new ArrayList<>(); curResultSetSerializable.chunkFileCount = 0; curResultSetSerializable.firstChunkStringData = null; curResultSetSerializable.firstChunkRowCount = 0; curResultSetSerializable.firstChunkRowset = null; } // Append this chunk file to result serializable object curResultSetSerializable.getChunkFileMetadatas().add(curChunkFileMetadata); curResultSetSerializable.chunkFileCount++; } // Add the last result serializable object into result. resultSetSerializables.add(curResultSetSerializable); return resultSetSerializables; } /** * Setup JDBC proxy properties if necessary. * * @param info proxy server properties. */ private void setupProxyPropertiesIfNecessary(Properties info) { // Setup proxy properties. if (info != null && info.size() > 0 && info.getProperty(SFSessionProperty.USE_PROXY.getPropertyKey()) != null) { Map connectionPropertiesMap = new HashMap<>(info.size()); Boolean useProxy = Boolean.valueOf(info.getProperty(SFSessionProperty.USE_PROXY.getPropertyKey())); if (useProxy) { connectionPropertiesMap.put(SFSessionProperty.USE_PROXY, true); // set up other proxy related values. String propValue = null; if ((propValue = info.getProperty( SFSessionProperty.PROXY_HOST.getPropertyKey())) != null) { connectionPropertiesMap.put(SFSessionProperty.PROXY_HOST, propValue); } if ((propValue = info.getProperty( SFSessionProperty.PROXY_PORT.getPropertyKey())) != null) { connectionPropertiesMap.put(SFSessionProperty.PROXY_PORT, propValue); } if ((propValue = info.getProperty( SFSessionProperty.PROXY_USER.getPropertyKey())) != null) { connectionPropertiesMap.put(SFSessionProperty.PROXY_USER, propValue); } if ((propValue = info.getProperty( SFSessionProperty.PROXY_PASSWORD.getPropertyKey())) != null) { connectionPropertiesMap.put(SFSessionProperty.PROXY_PASSWORD, propValue); } if ((propValue = info.getProperty( SFSessionProperty.NON_PROXY_HOSTS.getPropertyKey())) != null) { connectionPropertiesMap.put(SFSessionProperty.NON_PROXY_HOSTS, propValue); } // Setup proxy properties into HttpUtil static cache HttpUtil.configureCustomProxyProperties(connectionPropertiesMap); } } } /** * Get ResultSet from the ResultSet Serializable object so that the user can * access the data. The ResultSet is sessionless. * * @return a ResultSet which represents for the data wrapped in the object */ public ResultSet getResultSet() throws SQLException { return getResultSet(null); } /** * Get ResultSet from the ResultSet Serializable object so that the user can * access the data. * * @param info The proxy sever information if proxy is necessary. * @return a ResultSet which represents for the data wrapped in the object */ public ResultSet getResultSet(Properties info) throws SQLException { // Setup proxy info if necessary setupProxyPropertiesIfNecessary(info); // Setup transient fields setupTransientFields(); // This result set is sessionless, so it doesn't support telemetry. Telemetry telemetryClient = new NoOpTelemetryClient(); // The use case is distributed processing, so sortResult is not necessary. boolean sortResult = false; // Setup base result set. SFBaseResultSet sfBaseResultSet = null; switch (getQueryResultFormat()) { case ARROW: { sfBaseResultSet = new SFArrowResultSet(this, telemetryClient, sortResult); break; } case JSON: { sfBaseResultSet = new SFResultSet(this, telemetryClient, sortResult); break; } default: throw new SnowflakeSQLException(ErrorCode.INTERNAL_ERROR, "Unsupported query result format: " + getQueryResultFormat().name()); } // Create result set SnowflakeResultSetV1 resultSetV1 = new SnowflakeResultSetV1(sfBaseResultSet, this); return resultSetV1; } public String toString() { StringBuilder builder = new StringBuilder(16 * 1024); builder.append("hasFirstChunk: ") .append(this.firstChunkStringData != null ? true : false) .append("\n"); builder.append("RowCountInFirstChunk: ") .append(this.firstChunkRowCount) .append("\n"); builder.append("queryResultFormat: ") .append(this.queryResultFormat) .append("\n"); builder.append("chunkFileCount: ") .append(this.chunkFileCount) .append("\n"); for (ChunkFileMetadata chunkFileMetadata : chunkFileMetadatas) { builder.append("\t").append(chunkFileMetadata.toString()).append("\n"); } return builder.toString(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy