Uso do driver JDBC¶
Este tópico fornece informações sobre como usar o driver JDBC.
Neste tópico:
Extensões de API JDBC do Snowflake¶
O driver JDBC do Snowflake suporta métodos adicionais além da especificação JDBC padrão. Esta seção documenta como usar o desencapsulamento para acessar os métodos específicos do Snowflake e, em seguida, descreve três das situações nas quais você pode precisar desencapsular:
Carregamento de arquivos de dados diretamente de um fluxo para um estágio interno.
Download de arquivos de dados diretamente de um estágio interno para um fluxo.
Desencapsulamento de classes específicas do Snowflake¶
O driver JDBC do Snowflake suporta métodos específicos do Snowflake. Estes métodos são definidos em interfaces Java específicas do Snowflake, tais como SnowflakeConnection, SnowflakeStatement e SnowflakeResultSet. Por exemplo, a interface SnowflakeStatement contém um método getQueryID()
que não está na interface Statement do JDBC.
Quando o driver JDBC do Snowflake é solicitado a criar um objeto JDBC (por exemplo, criar um objeto JDBC Statement
chamando um método createStatement()
de um objeto Connection
), o driver JDBC do Snowflake realmente cria objetos específicos do Snowflake que implementam não apenas os métodos do padrão JDBC, mas também os métodos adicionais das interfaces do Snowflake.
Para acessar estes métodos do Snowflake, você “desencapsula” um objeto (como um objeto Statement
) para expor o objeto Snowflake e seus métodos. Você pode então chamar os métodos adicionais.
O código a seguir mostra como desencapsular um objeto JDBC Statement
para expor os métodos da interface SnowflakeStatement
e depois chamar um desses métodos, neste caso setParameter
:
Statement statement1; ... // Unwrap the statement1 object to expose the SnowflakeStatement object, and call the // SnowflakeStatement object's setParameter() method. statement1.unwrap(SnowflakeStatement.class).setParameter(...);
Realização de uma consulta assíncrona¶
O driver JDBC do Snowflake suporta consultas assíncronas (ou seja, consultas que devolvem o controle ao usuário antes que seja concluída). Os usuários podem iniciar uma consulta e, em seguida, usar a sondagem para determinar quando a consulta foi concluída. Após a conclusão da consulta, o usuário pode ler o conjunto de resultados.
Este recurso permite que um programa cliente execute várias consultas em paralelo sem o programa cliente em si, usando vários threads.
Consultas assíncronas utilizam métodos adicionados às classes SnowflakeConnection
, SnowflakeStatement
, SnowflakePreparedStatement
e SnowflakeResultSet
.
Nota
Para realizar consultas assíncronas, é necessário garantir que o parâmetro de configuração ABORT_DETACHED_QUERY
seja FALSE
(valor padrão).
O Snowflake fecha automaticamente as conexões após um período de tempo (padrão: 5 minutos), o que ///orphans//isola/// qualquer consulta ativa. Se o valor for TRUE
, o Snowflake encerra essas consultas órfãs, o que pode impactar as consultas assíncronas.
Você pode executar uma mistura de consultas síncronas e assíncronas na mesma sessão.
Práticas recomendadas para consultas assíncronas¶
Certifique-se de que você saiba quais consultas dependem de outras consultas antes de executar qualquer consulta em paralelo. Algumas consultas são interdependentes e sensíveis à ordem e, portanto, não são adequadas para paralelização. Por exemplo, obviamente uma instrução INSERT não deve começar até que a instrução CREATE TABLE correspondente tenha terminado.
Assegure-se de não fazer muitas consultas para a memória que você tem disponível. Executar várias consultas em paralelo normalmente consome mais memória, especialmente se mais de um ResultSet estiver armazenado na memória ao mesmo tempo.
Ao realizar uma sondagem, trate dos raros casos em que uma consulta não tenha êxito. Por exemplo, evite o seguinte potencial loop infinito:
QueryStatus queryStatus = QueryStatus.RUNNING; while (queryStatus != QueryStatus.SUCCESS) { // NOT RECOMMENDED Thread.sleep(2000); // 2000 milliseconds. queryStatus = resultSet.unwrap(SnowflakeResultSet.class).getStatus(); }
Em vez disso, use um código semelhante ao seguinte:
// Assume that the query is not done yet. QueryStatus queryStatus = QueryStatus.RUNNING; while (queryStatus == QueryStatus.RUNNING || queryStatus == QueryStatus.RESUMING_WAREHOUSE) { Thread.sleep(2000); // 2000 milliseconds. queryStatus = resultSet.unwrap(SnowflakeResultSet.class).getStatus(); } if (queryStatus == QueryStatus.SUCCESS) { ... }
Certifique-se de que as instruções de controle de transações (BEGIN, COMMIT e ROLLBACK) não sejam executadas em paralelo com outras instruções.
Exemplos de consultas assíncronas¶
A maioria desses exemplos exige que o programa importe classes, como mostrado abaixo:
import java.sql.Connection; import java.sql.ResultSet; import java.sql.Statement; import net.snowflake.client.core.QueryStatus; import net.snowflake.client.jdbc.SnowflakeConnection; import net.snowflake.client.jdbc.SnowflakeResultSet; import net.snowflake.client.jdbc.SnowflakeStatement;
Este é um exemplo muito simples:
String sql_command = ""; ResultSet resultSet; System.out.println("Create JDBC statement."); Statement statement = connection.createStatement(); sql_command = "SELECT PI()"; System.out.println("Simple SELECT query: " + sql_command); resultSet = statement.unwrap(SnowflakeStatement.class).executeAsyncQuery(sql_command); // Assume that the query isn't done yet. QueryStatus queryStatus = QueryStatus.RUNNING; while (queryStatus == QueryStatus.RUNNING || queryStatus == QueryStatus.RESUMING_WAREHOUSE) { Thread.sleep(2000); // 2000 milliseconds. queryStatus = resultSet.unwrap(SnowflakeResultSet.class).getStatus(); } if (queryStatus == QueryStatus.FAILED_WITH_ERROR) { // Print the error code to stdout System.out.format("Error code: %d%n", queryStatus.getErrorCode()); System.out.format("Error message: %s%n", queryStatus.getErrorMessage()); } else if (queryStatus != QueryStatus.SUCCESS) { System.out.println("ERROR: unexpected QueryStatus: " + queryStatus); } else { boolean result_exists = resultSet.next(); if (!result_exists) { System.out.println("ERROR: No rows returned."); } else { float pi_result = resultSet.getFloat(1); System.out.println("pi = " + pi_result); } }
Este exemplo armazena a ID da consulta, fecha a conexão, reabre a conexão e usa a ID da consulta para recuperar os dados:
String sql_command = ""; ResultSet resultSet; String queryID = ""; System.out.println("Create JDBC statement."); Statement statement = connection.createStatement(); sql_command = "SELECT PI() * 2"; System.out.println("Simple SELECT query: " + sql_command); resultSet = statement.unwrap(SnowflakeStatement.class).executeAsyncQuery(sql_command); queryID = resultSet.unwrap(SnowflakeResultSet.class).getQueryID(); System.out.println("INFO: Closing statement."); statement.close(); System.out.println("INFO: Closing connection."); connection.close(); System.out.println("INFO: Re-opening connection."); connection = create_connection(args); use_warehouse_db_and_schema(connection); resultSet = connection.unwrap(SnowflakeConnection.class).createResultSet(queryID); // Assume that the query isn't done yet. QueryStatus queryStatus = QueryStatus.RUNNING; while (queryStatus == QueryStatus.RUNNING) { Thread.sleep(2000); // 2000 milliseconds. queryStatus = resultSet.unwrap(SnowflakeResultSet.class).getStatus(); } if (queryStatus == QueryStatus.FAILED_WITH_ERROR) { System.out.format( "ERROR %d: %s%n", queryStatus.getErrorMessage(), queryStatus.getErrorCode()); } else if (queryStatus != QueryStatus.SUCCESS) { System.out.println("ERROR: unexpected QueryStatus: " + queryStatus); } else { boolean result_exists = resultSet.next(); if (!result_exists) { System.out.println("ERROR: No rows returned."); } else { float pi_result = resultSet.getFloat(1); System.out.println("pi = " + pi_result); } }
Upload de arquivos de dados diretamente de um fluxo para um estágio interno¶
Você pode fazer o upload de arquivos de dados usando o comando PUT. Entretanto, às vezes faz sentido transferir dados diretamente de um fluxo para um estágio interno (ou seja, o Snowflake) como um arquivo. (O estágio pode ser qualquer tipo de estágio interno: estágio de tabela, estágio de usuário ou estágio nomeado. O driver JDBC não suporta o upload para um estágio externo). Aqui está o método exposto na classe SnowflakeConnection
:
/** * Method to compress data from a stream and upload it at a stage location. * The data will be uploaded as one file. No splitting is done in this method. * * Caller is responsible for releasing the inputStream after the method is * called. * * @param stageName stage name: e.g. ~ or table name or stage name * @param destPrefix path / prefix under which the data should be uploaded on the stage * @param inputStream input stream from which the data will be uploaded * @param destFileName destination file name to use * @param compressData compress data or not before uploading stream * @throws java.sql.SQLException failed to compress and put data from a stream at stage */ public void uploadStream(String stageName, String destPrefix, InputStream inputStream, String destFileName, boolean compressData) throws SQLException
Uso de amostras:
Connection connection = DriverManager.getConnection(url, prop); File file = new File("/tmp/test.csv"); FileInputStream fileInputStream = new FileInputStream(file); // upload file stream to user stage connection.unwrap(SnowflakeConnection.class).uploadStream("MYSTAGE", "testUploadStream", fileInputStream, "destFile.csv", true);
Código escrito para versões do driver JDBC anteriores a 3.9.2 podem converter SnowflakeConnectionV1
em vez de desencapsular SnowflakeConnection.class
. Por exemplo:
... // For versions prior to 3.9.2: // upload file stream to user stage ((SnowflakeConnectionV1) connection.uploadStream("MYSTAGE", "testUploadStream", fileInputStream, "destFile.csv", true));
Nota
Os clientes que utilizam versões mais recentes do driver devem atualizar seu código para usar unwrap
.
Download de arquivos de dados diretamente de um estágio interno para um fluxo¶
Você pode baixar arquivos de dados usando o comando GET. Entretanto, às vezes faz sentido transferir dados diretamente de um arquivo em um estágio interno (ou seja, o Snowflake) para um fluxo. (O estágio pode ser qualquer tipo de estágio interno: estágio de tabela, estágio de usuário ou estágio nomeado. O driver JDBC não suporta o download para um estágio externo). Aqui está o método exposto na classe SnowflakeConnection
:
/** * Download file from the given stage and return an input stream * * @param stageName stage name * @param sourceFileName file path in stage * @param decompress true if file compressed * @return an input stream * @throws SnowflakeSQLException if any SQL error occurs. */ InputStream downloadStream(String stageName, String sourceFileName, boolean decompress) throws SQLException;
Uso de amostras:
Connection connection = DriverManager.getConnection(url, prop); InputStream out = connection.unwrap(SnowflakeConnection.class).downloadStream( "~", DEST_PREFIX + "/" + TEST_DATA_FILE + ".gz", true);
Código escrito para versões do driver JDBC anteriores a 3.9.2 podem converter SnowflakeConnectionV1
em vez de desencapsular SnowflakeConnection.class
. Por exemplo:
... // For versions prior to 3.9.2: // download file stream to user stage ((SnowflakeConnectionV1) connection.downloadStream(...));
Suporte para múltiplas instruções¶
Esta seção descreve como executar várias instruções em uma única solicitação usando o Driver JDBC.
Nota
Por padrão, o Snowflake retorna um erro para consultas emitidas com múltiplas instruções. Este comportamento é feito em parte para proteger contra injeção SQL . O uso de múltiplas instruções abre a possibilidade de injeção SQL, e por isso deve ser usado com cuidado. O risco pode ser reduzido usando o método setParameter() da classe SnowflakeStatement para especificar o número de instruções a serem executadas, o que torna mais difícil injetar uma instrução anexando-a. Para obter mais detalhes, consulte Interface: SnowflakeStatement.
Envio de múltiplas instruções e tratamento de resultados¶
Consultas contendo múltiplas instruções podem ser executadas da mesma forma que consultas com uma única instrução, exceto que a cadeia de caracteres de consulta contém múltiplas instruções separadas por ponto e vírgula.
Há duas maneiras de permitir múltiplas instruções:
Call Statement.setParameter(“MULTI_STATEMENT_COUNT“, n) para especificar quantas instruções de cada vez este Statement deve poder executar. Consulte abaixo para mais detalhes.
Defina o parâmetro MULTI_STATEMENT_COUNT no nível da sessão ou no nível da conta executando um dos seguintes comandos:
alter session set MULTI_STATEMENT_COUNT = 0;
Ou:
alter account set MULTI_STATEMENT_COUNT = 0;
Definir o parâmetro como 0 permite um número ilimitado de instruções. A definição do parâmetro como 1 permite apenas uma instrução de cada vez.
A fim de tornar ataques de injeção SQL mais difíceis, os usuários podem chamar o método setParameter
para especificar o número de instruções a serem executadas em uma única chamada, como mostrado abaixo. Neste exemplo, o número de instruções a serem executadas em uma única chamada é 3:
// Specify the number of statements that we expect to execute. statement.unwrap(SnowflakeStatement.class).setParameter( "MULTI_STATEMENT_COUNT", 3);
O número padrão de instruções é 1; em outras palavras, o modo de múltiplas instruções está desativado.
Para executar instruções múltiplas sem especificar o número exato, passe um valor de 0.
O parâmetro MULTI_STATEMENT_COUNT não faz parte do padrão JDBC; ele é uma extensão do Snowflake. Este parâmetro afeta mais de um driver/conector do Snowflake.
Quando múltiplas instruções são executadas em uma única chamada execute()
, o resultado da primeira instrução está disponível através dos métodos padrão getResultSet()
e getUpdateCount()
. Para acessar os resultados das instruções que se seguem, use o método getMoreResults()
. Este método retorna true
quando mais instruções estão disponíveis para iteração e false
, caso contrário.
O exemplo abaixo define o parâmetro MULTI_STATEMENT_COUNT, executa 3 instruções e recupera contagens de atualização e conjuntos de resultados:
// Create a string that contains multiple SQL statements. String command_string = "create table test(n int); " + "insert into test values (1), (2); " + "select * from test order by n"; Statement stmt = connection.createStatement(); // Specify the number of statements (3) that we expect to execute. stmt.unwrap(SnowflakeStatement.class).setParameter( "MULTI_STATEMENT_COUNT", 3); // Execute all of the statements. stmt.execute(command_string); // false // --- Get results. --- // First statement (create table) stmt.getUpdateCount(); // 0 (DDL) // Second statement (insert) stmt.getMoreResults(); // true stmt.getUpdateCount(); // 2 // Third statement (select) stmt.getMoreResults(); // true ResultSet rs = stmt.getResultSet(); rs.next(); // true rs.getInt(1); // 1 rs.next(); // true rs.getInt(1); // 2 rs.next(); // false // Past the last statement executed. stmt.getMoreResults(); // false stmt.getUpdateCount(); // 0 (no more results)
O Snowflake recomenda o uso de execute()
para consultas com múltiplas instruções. Os métodos executeQuery()
e executeUpdate()
também suportam instruções múltiplas, mas geram uma exceção se o primeiro resultado não for o tipo de resultado esperado (conjunto de resultados e contagem de atualizações, respectivamente).
Falha nas instruções¶
Se qualquer uma das instruções SQL não for compilada ou executada, a execução é abortada. Todas as instruções anteriores não são afetadas.
Por exemplo, se as instruções abaixo forem executadas como uma única consulta com múltiplas instruções, a consulta falhará na terceira instrução, e uma exceção será gerada.
CREATE OR REPLACE TABLE test(n int); INSERT INTO TEST VALUES (1), (2); INSERT INTO TEST VALUES ('not_an_int'); -- execution fails here INSERT INTO TEST VALUES (3);
Se você consultasse o conteúdo da tabela test
, os valores 1
e 2
estariam presentes.
Recursos sem suporte¶
Instruções PUT e GET não têm suporte para consultas com múltiplas instruções.
A preparação de instruções e o uso de variáveis de vinculação também não têm suporte para consultas com múltiplas instruções.
Vinculação de variáveis a instruções¶
A vinculação permite que uma instrução SQL use um valor que está armazenado em uma variável Java.
Vinculação simples¶
Sem a vinculação, uma instrução SQL especifica valores especificando literais dentro da instrução. Por exemplo, a seguinte instrução usa o valor literal 42
em uma instrução UPDATE:
stmt.execute("UPDATE table1 SET integer_column = 42 WHERE ID = 1000");
Com a vinculação, você pode executar uma instrução SQL que usa um valor que está dentro de uma variável. Por exemplo:
int my_integer_variable = 42; PreparedStatement pstmt = connection.prepareStatement("UPDATE table1 SET integer_colum = ? WHERE ID = 1000"); pstmt.setInt(1, my_integer_variable); pstmt.executeUpdate();
O ?
dentro da cláusula VALUES
especifica que a instrução SQL usa o valor de uma variável. O método setInt()
especifica que o primeiro ponto de interrogação na instrução SQL deve ser substituído pelo valor na variável chamada my_integer_variable
. Observe que setInt()
usa valores baseados em 1, ao invés de baseados em 0 (ou seja, o primeiro ponto de interrogação é referenciado por 1, e não por 0).
Vinculação de variáveis a colunas de carimbo de data/hora¶
O Snowflake suporta três variações diferentes para os carimbos de data/hora: TIMESTAMP_LTZ , TIMESTAMP_NTZ , TIMESTAMP_TZ. Quando você chama PreparedStatement.setTimestamp
para vincular uma variável a uma coluna de data/hora, o driver JDBC interpreta o valor do carimbo de data/hora em termos do fuso horário local (TIMESTAMP_LTZ
) ou o fuso horário do objeto Calendar
passado como um argumento:
// The following call interprets the timestamp in terms of the local time zone.
insertStmt.setTimestamp(1, myTimestamp);
// The following call interprets the timestamp in terms of the time zone of the Calendar object.
insertStmt.setTimestamp(1, myTimestamp, Calendar.getInstance(TimeZone.getTimeZone("America/New_York")));
Se você quiser que o driver interprete o carimbo de data/hora usando uma variação diferente (por exemplo, TIMESTAMP_NTZ
), use uma das seguintes abordagens:
Defina o parâmetro de sessão CLIENT_TIMESTAMP_TYPE_MAPPING para a variação.
Observe que o parâmetro afeta todas as operações de vinculação para a sessão atual. Se você precisar alterar a variação (por exemplo, voltar para
TIMESTAMP_LTZ
), deve definir novamente este parâmetro de sessão.(Nas versões do driver JDBC 3.13.3 e posteriores) Chame o método
PreparedStatement.setObject
e use o parâmetrotargetSqlType
para especificar uma das seguintes variações de carimbo de data/hora do Snowflake:SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_LTZ
SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_TZ
SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_NTZ
Por exemplo:
import net.snowflake.client.jdbc.SnowflakeUtil; ... insertStmt.setObject(1, myTimestamp, SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_NTZ);
Inserções em lote¶
Em seu código de aplicação Java, você pode inserir várias linhas em um único lote vinculando parâmetros em uma instrução INSERT e chamando addBatch()
e executeBatch()
.
Como exemplo, o código a seguir insere duas linhas em uma tabela que contém uma coluna INTEGER e uma coluna VARCHAR. O exemplo vincula os valores aos parâmetros da instrução INSERT e chama addBatch()
e executeBatch()
para executar uma inserção em lote.
Connection connection = DriverManager.getConnection(url, prop); connection.setAutoCommit(false); PreparedStatement pstmt = connection.prepareStatement("INSERT INTO t(c1, c2) VALUES(?, ?)"); pstmt.setInt(1, 101); pstmt.setString(2, "test1"); pstmt.addBatch(); pstmt.setInt(1, 102); pstmt.setString(2, "test2"); pstmt.addBatch(); int[] count = pstmt.executeBatch(); // After execution, count[0]=1, count[1]=1 connection.commit();
Quando você usa esta técnica para inserir um grande número de valores, o driver pode melhorar o desempenho ao transmitir os dados (sem criar arquivos na máquina local) para um estágio temporário de ingestão. O driver faz isso automaticamente quando o número de valores excede um limite.
Além disso, o banco de dados e o esquema atual da sessão devem ser definidos. Se não forem definidos, o comando CREATE TEMPORARY STAGE executado pelo driver pode falhar com o seguinte erro:
CREATE TEMPORARY STAGE SYSTEM$BIND file_format=(type=csv field_optionally_enclosed_by='"')
Cannot perform CREATE STAGE. This session does not have a current schema. Call 'USE SCHEMA', or use a qualified name.
Nota
Para formas alternativas de carregar dados no banco de dados Snowflake (incluindo carregamento em massa usando o comando COPY), consulte Carregamento de dados para o Snowflake.
Exemplo de programa Java¶
Para uma amostra de trabalho escrita em Java, clique com o botão direito do mouse no nome do arquivo, SnowflakeJDBCExample.java
e salve o link/arquivo em seu sistema de arquivos local.
Solução de problemas¶
I/O Error: Connection Reset¶
Em alguns casos, o driver JDBC pode falhar com a seguinte mensagem de erro após um período de inatividade:
I/O error: Connection reset
Você pode contornar o problema definindo um “tempo de vida” específico para as conexões. Se uma conexão estiver ociosa por mais tempo que o “tempo de vida”, o driver JDBC retira a conexão do pool de conexões e cria uma nova conexão.
Para definir o tempo de vida, defina a propriedade do sistema Java chamada net.snowflake.jdbc.ttl
com o número de segundos que a conexão deve viver:
Para programar esta propriedade, chame
System.setProperty
:// Set the "time to live" to 60 seconds. System.setProperty("net.snowflake.jdbc.ttl", "60")
Para definir esta propriedade ao executar o comando
java
, use o sinalizador-D
:# Set the "time to live" to 60 seconds. java -cp .:snowflake-jdbc-<version>.jar -Dnet.snowflake.jdbc.ttl=60 <ClassName>
O valor padrão da propriedade net.snowflake.jdbc.ttl
é -1
, o que significa que as conexões ociosas não são removidas do pool de conexões.
Tratamento de erros¶
Ao tratar erros e exceções para um aplicativo JDBC, você pode usar o arquivo ErrorCode.java que o Snowflake fornece para determinar a causa dos problemas. Os códigos de erro específicos do driver JDBC começam com 2, no formato: 2NNNNN.
Nota
O link para o arquivo ErrorCode.java no repositório git público snowflake-jdbc aponta para a última versão do arquivo, que pode ser diferente da versão do driver JDBC que você usa atualmente.