문 실행하기

문은 connection.execute() 메서드를 호출하여 실행할 수 있습니다. execute() 메서드에서는 SQL 텍스트 및 complete 콜백을 지정하기 위해 사용할 수 있는 options 오브젝트를 허용합니다. complete 콜백은 문의 실행이 종료되고 결과를 사용할 수 있을 때 호출됩니다.

var statement = connection.execute({
  sqlText: 'CREATE DATABASE testdb',
  complete: function(err, stmt, rows) {
    if (err) {
      console.error('Failed to execute statement due to the following error: ' + err.message);
    } else {
      console.log('Successfully executed statement: ' + stmt.getSqlText());
    }
  }
});
Copy

참고

단일 요청의 최대 페이로드 크기는 16MB입니다.

비동기적으로 쿼리 실행

Snowflake Node.js 드라이버는 비동기 쿼리(즉, 쿼리가 완료되기 이전에 사용자에게 제어를 반환하는 쿼리)를 지원합니다. 사용자는 쿼리를 시작한 후 폴링을 사용하여 쿼리가 완료된 시점을 확인할 수 있습니다. 쿼리가 완료된 후 사용자는 결과 세트를 읽을 수 있습니다.

connection.execute 메서드에 asyncExec: true 를 포함하면 비동기 쿼리를 활성화할 수 있습니다.

다음 예제에서는 Promise 를 사용하여 쿼리를 비동기적으로 실행하는 방법을 보여줍니다.

let queryId;
let statement;

// 1. Execute query with asyncExec set to true
await new Promise((resolve) =>
{
  connection.execute({
    sqlText: 'CALL SYSTEM$WAIT(3, \'SECONDS\')',
    asyncExec: true,
    complete: async function (err, stmt, rows)
    {
      queryId = stmt.getQueryId(); // Get the query ID
      resolve();
    }
  });
});

// 2. Get results using the query ID
const statement = await connection.getResultsFromQueryId({ queryId: queryId });
await new Promise((resolve, reject) =>
{
  var stream = statement.streamRows();
  stream.on('error', function (err)
  {
    reject(err);
  });
  stream.on('data', function (row)
  {
    console.log(row);
  });
  stream.on('end', function ()
  {
    resolve();
  });
});
Copy

다음 예와 같이 콜백을 사용하여 비동기 쿼리를 모니터링할 수도 있습니다.

  1. connection.execute 메서드에 asyncExec: true 를 포함하면 비동기 쿼리를 활성화할 수 있습니다.

    // 1. Execute query with asyncExec set to true
    connection.execute({
      sqlText: 'CALL SYSTEM$WAIT(3, \'SECONDS\')',
      asyncExec: true,
      complete: async function (err, stmt, rows)
      {
        let queryId = stmt.getQueryId();
    
        // 2. Get results using the query ID
        connection.getResultsFromQueryId({
          queryId: queryId,
          complete: async function (err, _stmt, rows)
          {
            console.log(rows);
          }
        });
      }
    });
    
    Copy
  2. 비동기적으로 실행되도록 제출된 쿼리의 상태를 확인합니다.

    let queryId;
    
    // 1. Execute query with asyncExec set to true
    await new Promise((resolve, reject) =>
    {
      statement = connection.execute({
        sqlText: 'CALL SYSTEM$WAIT(3, \'SECONDS\')',
        asyncExec: true,
        complete: async function (err, stmt, rows)
        {
          queryId = statement.getQueryId();
          resolve();
        }
      });
    });
    
    // 2. Check query status until it's finished executing
    const seconds = 2;
    let status = await connection.getQueryStatus(queryId);
    while (connection.isStillRunning(status))
    {
      console.log(`Query status is ${status}, timeout for ${seconds} seconds`);
    
      await new Promise((resolve) =>
      {
        setTimeout(() => resolve(), 1000 * seconds);
      });
    
      status = await connection.getQueryStatus(queryId);
    }
    
    console.log(`Query has finished executing, status is ${status}`);
    
    Copy

SQL 문 일괄 실행(다중 문 지원)

Node.js의 버전 1.6.18 이상에서는 세미콜론으로 구분된 SQL 문 배치를 전송하여 단일 요청에서 실행되도록 할 수 있습니다.

참고

  • 단일 쿼리에서 여러 개의 문을 실행하려면 세션에서 유효한 웨어하우스를 사용할 수 있어야 합니다.

  • 기본적으로 Snowflake는 다중 문으로 실행된 쿼리에 대한 오류를 반환하여 SQL 삽입 공격으로부터 보호합니다. 단일 쿼리에서 여러 문을 실행하면 SQL 삽입의 위험이 높아집니다. Snowflake는 가급적 적게 사용할 것을 권장합니다. MULTI_STATEMENT_COUNT 매개 변수를 사용해 실행할 문의 개수를 지정하여 위험을 줄일 수 있으며, 문을 매개 변수에 추가하여 삽입하는 것이 더욱 어려워집니다.

이러한 유형의 공격에 대한 자세한 내용은 SQL 주입 을 참조하십시오.

쿼리 문자열에 세미콜론으로 구분된 여러 문이 포함되어 있다는 점을 제외하면 단일 문으로 쿼리를 실행하는 것과 동일한 방식으로 여러 문을 일괄 처리로 실행할 수 있습니다. 여러 문은 병렬이 아니라 순차적으로 실행됩니다. MULTI_STATEMENT_COUNT 매개 변수는 배치에 포함된 문의 정확한 개수를 지정합니다.

예를 들어 MULTI_STATEMENT_COUNT=3 을 설정하면 배치 문에 정확히 세 개의 문이 포함되어야 합니다. 다른 개수의 문과 함께 배치 문을 제출하면 Node.js 드라이버가 요청을 거부합니다. MULTI_STATEMENT_COUNT=0 을 설정하여 배치 쿼리에 개수에 관계없이 문을 포함할 수 있습니다. 하지만 이 값을 사용하면 SQL 삽입 공격에 대한 보호가 감소한다는 점에 유의하십시오.

다음 명령을 사용하여 세션 수준에서 이 매개 변수를 설정하거나 쿼리를 제출할 때마다 별도로 값을 설정할 수 있습니다.

ALTER SESSION SET multi_statement_count = <n>
Copy

세션 수준 값을 설정하면 배치 문을 실행할 때마다 실행할 때 값을 설정할 필요가 없습니다. 다음 예에서는 세션 수준에서 문 개수를 3개로 설정한 다음 SQL 문 세 개를 실행합니다.

var statement = connection.execute({
    sqlText: "ALTER SESSION SET multi_statement_count=0",
    complete: function (err, stmt, rows) {
      if (err) {
        console.error('1 Failed to execute statement due to the following error: ' + err.message);
      } else {
        testMulti();
      }
    }
  });
function testMulti() {
  console.log('select bind execute.');
  var selectStatement = connection.execute({
    sqlText: "create or replace table test(n int); insert into test values(1), (2); select * from test order by n",
    complete: function (err, stmt, rows) {
      if (err) {
        console.error('1 Failed to execute statement due to the following error: ' + err.message);
      }
      else {
        console.log('==== complete');
        console.log('==== sqlText=' + stmt.getSqlText());
        if(stmt.hasNext())
        {
          stmt.NextResult();
        }
        else {
          // do something else, e.g. close the connection
        }
      }
    }
  });
}
Copy

MULTI_STATEMENT_COUNTconnection.execute 함수의 매개 변수로 설정하여 다중 문 쿼리를 실행할 때마다 배치의 문 개수를 설정할 수도 있습니다. 다음 예에서는 배치에 대해 문 개수를 3개로 설정하고 배치 쿼리에 3개의 SQL 문을 포함합니다.

var selectStatement = connection.execute({
    sqlText: "CREATE OR REPLACE TABLE test(n int); INSERT INTO test values(1), (2); SELECT * FROM test ORDER BY n",
    parameters: { MULTI_STATEMENT_COUNT: 3 },
    complete: function (err, stmt, rows) {
      if (err) {
        console.error('1 Failed to execute statement due to the following error: ' + err.message);
      }
      else {
        console.log('==== complete');
        console.log('==== sqlText=' + stmt.getSqlText());
        if(stmt.hasNext())
        {
          stmt.NextResult();
        }
        else {
          // do something else, e.g. close the connection
        }
      }
    }
  });
Copy

문 매개 변수 바인딩하기

종종 문의 데이터와 자리 표시자를 바인딩 해야 하는 경우가 있습니다. 이러한 방식으로 문을 실행하는 것은 SQL 삽입 공격을 방지할 수 있으므로 유용합니다. 다음 문을 살펴보겠습니다.

connection.execute({
  sqlText: 'SELECT c1 FROM (SELECT 1 AS c1 UNION ALL SELECT 2 AS c1) WHERE c1 = 1;'
});
Copy

다음 바인딩을 사용하여 동일한 결과를 얻을 수 있습니다.

connection.execute({
  sqlText: 'SELECT c1 FROM (SELECT :1 AS c1 UNION ALL SELECT :2 AS c1) WHERE c1 = :1;',
  binds: [1, 2]
});
Copy

바인딩을 위한 ? 구문도 지원됩니다.

connection.execute({
p
  sqlText: 'SELECT c1 FROM (SELECT ? AS c1 UNION ALL SELECT ? AS c1) WHERE c1 = ?;',
  binds: [1, 2, 1]
});
Copy

참고

바인딩할 수 있거나 일괄 결합할 수 있는 데이터 크기의 상한에는 제한이 있습니다. 자세한 내용은 쿼리 텍스트 크기 제한 섹션을 참조하십시오.

대량 삽입을 위한 배열 바인딩하기

일괄 INSERT 연산을 위한 데이터 배열 바인딩이 지원됩니다. 배열의 배열을 다음과 같이 전달합니다.

connection.execute({
  sqlText: 'INSERT INTO t(c1, c2, c3) values(?, ?, ?)',
  binds: [[1, 'string1', 2.0], [2, 'string2', 4.0], [3, 'string3', 6.0]]
});
Copy

참고

대규모 배열을 바인딩하면 성능에 영향을 주며 데이터의 크기가 너무 커서 서버에서 처리할 수 없는 경우에는 거부될 수 있습니다.

문 취소하기

문은 statement.cancel() 메서드를 호출하여 취소할 수 있습니다.

statement.cancel(function(err, stmt) {
  if (err) {
    console.error('Unable to abort statement due to the following error: ' + err.message);
  } else {
    console.log('Successfully aborted statement');
  }
});
Copy

요청 다시 제출하기

어쩌면 네트워크 오류 또는 시간 초과로 인해 Snowflake가 SQL 문을 성공적으로 실행했는지 확실하지 않은 경우 요청 ID를 사용하여 같은 문을 다시 제출할 수 있습니다. 예를 들어, 데이터를 추가하려고 INSERT 명령을 제출했지만 적시에 확인을 받지 못했는데, 명령에 어떤 문제가 있는지는 모른다고 가정해 보십시오. 이런 상황에서는 똑같은 명령을 새 명령으로 실행하면 명령을 두 번 실행하여 데이터 중복을 일으킬 수 있으므로 그렇게 하고 싶지는 않을 것입니다.

SQL 문에 요청 ID를 포함하면 데이터 중복 가능성을 피할 수 있습니다. 초기 요청에서 요청 ID로 요청을 다시 제출하면 초기 요청이 실패하는 경우에만 다시 제출된 명령이 실행됩니다. 자세한 내용은 SQL 문 실행 요청 다시 제출하기 섹션을 참조하십시오.

참고

요청 ID를 사용하여 쿼리를 다시 제출하려면 요청 ID를 생성한 것과 동일한 연결을 사용해야 합니다. 다른 연결에서 쿼리 결과를 검색하려면 RESULT_SCAN 섹션을 참조하십시오.

다음 코드 샘플에서는 요청 ID를 저장하고 사용하여 문을 다시 제출하는 방법을 보여줍니다. 문을 실행할 때 getRequestId() 함수를 사용하여 제출된 요청의 ID를 검색할 수 있습니다. 그런 다음 해당 ID를 사용하여 나중에 같은 문을 실행할 수 있습니다. 다음 예제에서는 INSERT 문을 실행하고 requestId 변수에 요청 ID를 저장합니다.

var requestId;
connection.execute({
  sqlText: 'INSERT INTO testTable VALUES (1);',
    complete: function (err, stmt, rows)
    {
      var stream = stmt.streamRows();
      requestId = stmt.getRequestId();   // Retrieves the request ID
      stream.on('data', function (row)
      {
        console.log(row);
      });
      stream.on('end', function (row)
      {
        console.log('done');
      });
    }
});
Copy

명령이 성공적으로 실행되었다는 확인을 받지 못한 경우 아래 그림과 같이 저장된 요청 ID를 사용하여 요청을 다시 제출할 수 있습니다.

connection.execute({
  sqlText: 'INSERT INTO testTable VALUES (1);',  // optional
    requestId: requestId,  // Uses the request ID from before
    complete: function (err, stmt, rows)
    {
      var stream = stmt.streamRows();
      stream.on('data', function (row)
      {
        console.log(row);
      });
      stream.on('end', function (row)
      {
        console.log('done');
      });
  }
});
Copy

requestIdsqlText 를 사용하여 요청을 다시 제출하기로 할 때 다음 상호 작용에 유의하십시오.

  • requestId 가 이미 존재하는 경우, 즉 다시 제출하는 요청이 이전 요청과 일치하는 경우, 이 명령은 sqlText 쿼리를 무시하고 원래 명령에서 쿼리를 다시 제출합니다.

  • requestId 가 존재하지 않는 경우, 즉 이전 요청과 일치하지 않는 경우, 이 명령은 sqlText 쿼리를 실행합니다.