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:

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(...);
Copy

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();
        }
    
    Copy

    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) {
        ...
        }
    
    Copy
  • 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;
Copy

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);
      }
    }
Copy

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);
      }
    }
Copy

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
Copy

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);
Copy

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));
Copy

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;
Copy

Uso de amostras:

Connection connection = DriverManager.getConnection(url, prop);
InputStream out = connection.unwrap(SnowflakeConnection.class).downloadStream(
    "~",
    DEST_PREFIX + "/" + TEST_DATA_FILE + ".gz",
    true);
Copy

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(...));
Copy

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;
    
    Copy

    Ou:

    alter account set MULTI_STATEMENT_COUNT = 0;
    
    Copy

    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);
Copy

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)
Copy

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);
Copy

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");
Copy

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();
Copy

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")));
Copy

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âmetro targetSqlType 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);
    
    Copy

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();
Copy

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.
Copy

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
Copy

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")
    
    Copy
  • 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>
    
    Copy

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.