snowflake-connector-nodejs icon indicating copy to clipboard operation
snowflake-connector-nodejs copied to clipboard

Transaction Support

Open katm201 opened this issue 5 years ago • 15 comments

When trying to create a transaction in Node, I am consistently running into this error:

OperationFailedError: Multiple SQL statements in a single API call are not supported; use one API call per statement instead.

When I looked up the issue, I found this thread from 2 years ago in the forums: https://support.snowflake.net/s/question/0D50Z00007UwSeWSAV/transaction-mode-in-node-js-driver

A Python user mentioned that they got around this problem for transactions by using the cursor object from their connector's API (https://docs.snowflake.net/manuals/user-guide/python-connector-api.html#cursor). When I looked for similar functionality from the Node sdk, it wasn't there.

Can we please get a reliable way to handle transactions? This is obviously a huge problem for complex inserts/updates.

katm201 avatar Nov 26 '19 23:11 katm201

By default, autotcommit is enabled, so when you start a transaction, you need to call begin first followed by DMLs and finish it by commit or rollback.

The above error occurs because multiple statements are submitted in a single execution context. As of today, only JDBC supports multiple statements. Other supports are in the pipeline but no ETA can be given. But even JDBC doesn't provide transaction specific API, so the application will still have to call begin and commit/rollback.

Can you elaborate how you run your transaction? A sample code would be useful for us to understand what you need.

sfc-gh-stakeda avatar Nov 27 '19 23:11 sfc-gh-stakeda

Thanks for getting back to me.

Here's the example code I was trying to run (I've stripped out the error handling, setting up the Snowflake connection, caching, etc.):

const sql = `
  BEGIN TRANSACTION;

  UPDATE props_payloads
  SET status_id = (
    SELECT status_id FROM status WHERE LOWER(status) = 'retired'
  ), retired_at = TO_TIMESTAMP_NTZ(CURRENT_TIMESTAMP())
  WHERE status_id = (SELECT status_id FROM status WHERE LOWER(status) = 'active')
  AND prop_id = (SELECT prop_id FROM props WHERE prop_name = '${propsId}');

  INSERT INTO props_payloads (prop_id, payload)
  SELECT column1 AS prop_id, PARSE_JSON(column2) AS payload
  FROM VALUES (${PROP_ID}, '${JSON.stringify(payload)}');

  COMMIT;
`;

await snowflake.execute(sql);

Since it uses multiple statements, I'm not surprised that it failed with that error. What I can't figure out is how to run an equivalent to that transaction using the Node SDK.

I broke it up into 2 queries for now, but that obviously runs the risk of not being able to roll back the first one if the second one fails:

const retireSQL = `
  UPDATE props_payloads
  SET 
    status_id = (SELECT status_id FROM status WHERE LOWER(status) = 'retired'),
    retired_at = TO_TIMESTAMP_NTZ(CURRENT_TIMESTAMP())
  WHERE status_id = (SELECT status_id FROM status WHERE LOWER(status) = 'active')
    AND prop_id = (SELECT prop_id FROM props WHERE prop_name = '${propsId}');
`;

const insertSQL = `
  INSERT INTO props_payloads (prop_id, payload)
  SELECT column1 AS prop_id, PARSE_JSON(column2) AS payload
  FROM VALUES (${PROP_ID}, '${JSON.stringify(payload)}');
`;

await snowflake.execute(retireSQL);
await snowflake.execute(insertSQL);

I'll be the first to admit I'm not super knowledgeable about raw SQL or Snowflake. Up until recently, I was working primarily with MySQL, PostgreSQL, and MariaDB using the Knex.js ORM. Here's how Knex deals with transactions (and what I'd love to see from the Snowflake SDK): http://knexjs.org/#Transactions

If you can give me an idea of how to implement the above using transactions from what's already available in the SDK, I would be forever grateful.

katm201 avatar Nov 28 '19 00:11 katm201

I can see threads on google requesting this feature back to 2017 !!! I can't understand how Snowflake can state long in the market without a reasonable amount of features for developers can build their applications on top of it. And to be fair, this a quite BASIC feature as far as I'm concerned !

brugos avatar May 29 '20 19:05 brugos

@sfc-gh-stakeda any update on where this sits on your roadmap?

smbkr avatar Jun 09 '20 16:06 smbkr

All issues have been in the queue of product roadmaps. No ETA is given at the moment.

sfc-gh-stakeda avatar Jun 09 '20 16:06 sfc-gh-stakeda

This is a joke!

safareli avatar Sep 01 '21 10:09 safareli

bumping this thread any updates? we really need this feature as well, very similar use case as @katm201.

kning avatar Mar 16 '22 23:03 kning

Hey folks -- I've bumped this up in priority internally here at Snowflake. Stay tuned on the timeline.

jdanielmyers avatar Mar 18 '22 16:03 jdanielmyers

somebody on my team actually pointed out this thread to me as a workaround: https://community.snowflake.com/s/question/0D50Z00007UwSeWSAV/transaction-mode-in-node-js-driver

kning avatar Mar 18 '22 22:03 kning

somebody on my team actually pointed out this thread to me as a workaround: https://community.snowflake.com/s/question/0D50Z00007UwSeWSAV/transaction-mode-in-node-js-driver

@kning that workaround does not appear to work with the node js driver.

  await conn.execute(`
    BEGIN TRANSACTION;
    USE WAREHOUSE ...;
    USE DATABASE ...;
    COMMIT;
  `);

will still throw OperationFailedError: Multiple SQL statements in a single API call are not supported; use one API call per statement instead.

dylangrandmont avatar Mar 31 '22 14:03 dylangrandmont

We just did:

await conn.execute('BEGIN TRANSACTION;');
await conn.execute('USE WAREHOUSE ...;');
await conn.execute('USE DATABASE ...;');
await conn.execute('COMMIT;');

And seemed to work. If you check the Snowflake logs after you'll see they should all have the same session id.

kning avatar Mar 31 '22 23:03 kning

await conn.execute('BEGIN TRANSACTION;'); await conn.execute('USE WAREHOUSE ...;'); await conn.execute('USE DATABASE ...;'); await conn.execute('COMMIT;');

That will certainly execute but those are four separate API calls (compared with the goal of executing multiple statements with a single API call). Each API call incurs some round-trip performance costs.

dylangrandmont avatar Mar 31 '22 23:03 dylangrandmont

Yeah that's true, but in our case this was part of a regularly scheduled batch script so we don't care too much about performance. Definitely a workaround and agree the multi-statement with single API call would be preferred!

kning avatar Apr 01 '22 01:04 kning

you can use this package to manage multiple requests if you want: https://www.npmjs.com/package/snowflake-multisql

brugos avatar Jun 30 '22 13:06 brugos

@jdanielmyers any update on this?

vaibhavphutane avatar Nov 11 '22 17:11 vaibhavphutane

after all this time, finally a version (1.6.18) of the connector was just released which now supports multistatement. thank you everyone for the patience here !

sfc-gh-dszmolka avatar Jan 31 '23 20:01 sfc-gh-dszmolka