DML statements in reader account is not supported, but object creation is supported - snowflake-cloud-data-platform

The documentation of Snowflake says as below, which is correct. I could not do an insert, but it allowed me to create a table within a database.
Users in a reader account can query data that has been shared with the
reader account, but cannot perform any of the DML tasks that are
allowed in a full account, such as data loading, insert, update, and
similar data manipulation operations.
use database db1;
use schema db1_schema;
create or replace table t1(col varchar);
But when I tried to insert into the table, it said insert cannot be done in reader accounts. Should not creation of objects be then disallowed in reader accocunts.

No - because a reader account is empty until you configure it by creating objects: https://docs.snowflake.com/en/user-guide/data-sharing-reader-config

As per documentation 'https://docs.snowflake.com/en/user-guide/data-sharing-reader-create#what-is-restricted-allowed-in-a-reader-account'
, create table command is allowed in reader account.
"
The following commands cannot be executed in a reader account:
INSERT
UPDATE
DELETE
MERGE
COPY INTO
CREATE MASKING POLICY
CREATE PIPE
CREATE ROW ACCESS POLICY
CREATE SHARE
CREATE STAGE
SHOW PROCEDURES
All other operations are allowed.
"

Related

How to acquire lock on a table and not let other transaction get read access?

I am having a nodejs program which uses sequelize to create tables and insert data based on it.
Now, in future we are going to have multiple instances of the program and so we don't want multiple instances to read from the table during program startup so that only one instance can do the setup thing if required and other instance shouldn't get 'any access' to the table until the first instance has completed it's work.
I have checked 'transaction locking' - shared and exclusive but both of them seems to be giving reading access to the tables which I don't want.
My requirement is specifically that once a transaction acquires lock on a table, other transaction shouldn't be able to read from that table unless first one has completed it's work. How can I do this?
In MySQL use LOCK TABLES to lock an entire table.
In postgresql LOCK TABLE whatever IN EXCLUSIVE MODE; does the trick.
For best results have your app, when starting, look for a particular table. Do something simple and fast such as SELECT id FROM whatever LIMIT 1; to probe whether the table exists. If your app gets an exception because the table isn't there, then do
CREATE TABLE whatever ....;
LOCK TABLES whatever WRITE;
from the app creating the table. It blocks access to the table from all instances of your app except the one that gets the LOCK.
Once your table is locked, the initial SELECT I suggested will block from other clients. There's a possible race condition if two clients try to create the table more-or-less concurrently. But the extra CREATE TABLE will throw an exception.
Note: if you LOCK more than one table, and it's possible to run the code from more than one instance of the app, always always lock the tables in the same order, or you have the potential for a deadlock.
As documented in the manual the statement to lock a table is, LOCK TABLE ...
If you lock a table in exclusive mode, then no other access is allowed - not even a SELECT. Exclusive mode is the default:
If no lock mode is specified, then ACCESS EXCLUSIVE, the most restrictive mode, is used.
The manual explains the different lock modes:
ACCESS EXCLUSIVE
This mode guarantees that the holder is the only transaction accessing the table in any way.

Snowflake: Is there a way to create a warehouse without using it?

We're using DBT to run automated CI/CD to provision all our resources in Snowflake, including databases, schemas, users, roles, warehouses, etc.
The issue comes up when we're creating warehouses -- the active warehouse automatically switches over to the newly created one. And this happens whether or not the warehouse already exists (we're using CREATE WAREHOUSE IF NOT EXISTS commands).
This basically resumes/turns on all our warehouses for no reason (even though we're using INITIALLY_SUSPENDED = TRUE), because snowflake is then using that warehouse to execute the subsequent queries. And then our CI/CD continues on the wrong warehouse (whichever one was the last one to execute). We have a dedicated warehouse for CI/CD, and we'd like the execution to remain on that one (so we can monitor the costs).
We're aware that this is the default behavior specified in the documentation, but is there any way to create a warehouse without using it?
I wish the CREATE WAREHOUSE command had a parameter like USE_WAREHOUSE = TRUE|FALSE.
As a workaround, we're exploring ways to skip the CREATE WAREHOUSE commands entirely if the warehouse already exists, but that doesn't solve the issue for warehouses that do need to be created.
Otherwise, we might just add a USE WAREHOUSE command after every CREATE WAREHOUSE, in order to return to the original CI/CD warehouse.
The idea is to store current warehouse in a variable and restore it:
SET warehouse_name = (SELECT CURRENT_WAREHOUSE());
CREATE WAREHOUSE TEST WAREHOUSE_SIZE=XSMALL, INITIALLY_SUSPENDED=TRUE;
USE WAREHOUSE IDENTIFIER($warehouse_name);
Alternatively wrapping it with stored procedure(simplified version - no error handling and only warehouse name provided as parameter):
CREATE OR REPLACE PROCEDURE create_warehouse(CURRENT_WAREHOUSE_NAME STRING
,WAREHOUSE_NAME STRING)
RETURNS VARCHAR
LANGUAGE javascript
AS
$$
var rs = snowflake.execute({sqlText: `CREATE WAREHOUSE IF NOT EXISTS IDENTIFIER(?) WAREHOUSE_SIZE=MEDIUM, INITIALLY_SUSPENDED=TRUE`, binds:[WAREHOUSE_NAME]});
// restore original warehouse, USE WAREHOUSE cannot be used inside SP
var rs2 = snowflake.execute({sqlText:`CREATE WAREHOUSE IF NOT EXISTS IDENTIFIER(?)`, binds:[CURRENT_WAREHOUSE_NAME]});
return 'Done.';
$$;
CALL create_warehouse(CURRENT_WAREHOUSE(), 'TEST');
The Snowflake docs describe an INITIALLY_SUSPENDED property, defaulting FALSE, which specifies whether the warehouse should be created initially in Suspended state.
I think you should set that property TRUE in your script.

SQL Server trigger in database a should input row in database b with different user

We have a system that stores tens of thousands of databases, each holding the data of a customer subscribing to our services. Each of those databases has their own user that is known and used by the custom thorough the distributed, thick, application front-ends we provide.
Now I would like to add a trigger to one of the tables in each one of those dbs, that should update one common db with some of the data from the inserted rows. Sort of a many-to-one db scenario if you will...
This new common db can be set up pretty much how we like - as far as users/permissions and so on, and the trigger we insert in the old dbs can be written pretty freely. But we can not change those customer dbs as far as users/permissions.
Currently I'm experimenting with using the guest user, it has been given writing access, on the common db (named "foo" in example below) like this, but it is not working (i think due to the guest user of the common db not being allowed to access the customer db table - named Bf in example below - it is triggered in?). It may also be that I'm using "execute as" where I should be using execute as login="? I'm having a hard time finding a comprehensible place that describes the difference.
This is the trigger we would like to get working, and inserted in every customer db:
ALTER TRIGGER [dbo].[trgAfterInsert] ON [dbo].[Bf]
WITH EXECUTE AS 'guest'
FOR INSERT
AS
begin
insert into [foo].[dbo].[baar]
(publicKey, fileId)
SELECT b.publicKey, a.autoId
FROM client_db_1.[dbo].[Integrationer] as b, inserted as a where
a.autoId in (select top 1 autoId from inserted order by autoId)
end
As you may guess, I'm not an experienced user of triggers or sql permissions/access work. But the info we want to collect is harmless, and it should take close to 0 time to execute, and in a secured, non-exposed, environment, so I'm very willing to read/learn if anyone has advice?
/Henry

Specify trigger's parent schema in trigger body

In DB2 for IBM System i I create this trigger for recording on MYLOGTABLE every insert operation made on MYCHECKEDTABLE:
SET SCHEMA MYSCHEMA;
CREATE TRIGGER MYTRIGGER AFTER INSERT ON MYCHECKEDTABLE
REFERENCING NEW AS ROWREF
FOR EACH ROW BEGIN ATOMIC
INSERT INTO MYLOGTABLE -- after creation becomes MYSCHEMA.MYLOGTABLE
(MMACOD, OPTYPE, OPDATE)
VALUES (ROWREF.ID, 'I', CURRENT TIMESTAMP);
END;
The DBMS stores the trigger body with MYSCHEMA.MYLOGTABLE hardcoded.
Now imagine that we copy the entire schema as a new schema NEWSCHEMA. When I insert a record in NEWSCHEMA.MYCHECKEDTABLE a log record will be added to MYSCHEMA.MYLOGTABLE instead of NEWSCHEMA.MYLOGTABLE, i.e. in the schema where trigger and its table live. This is cause of big issues!! Also because many users can copy the schema without my control...
So, is there a way to specify, in the trigger body, the schema where the trigger lives? In this way we'll write the log record in the correct MYLOGTABLE. Something like PARENT SCHEMA... Or is there a workaround?
Many thanks!
External triggers defined in an HLL have access to a trigger buffer that includes the library name of the table that fired the trigger. This could be used to qualify the reference to the MYLOGTABLE.
See chapter 11.2 "Trigger program structure" of the IBM Redbook Stored Procedures, Triggers, and User-Defined Functions on DB2 Universal Database for iSeries for more information.
Alternatively you may be able to use the CURRENT SCHEMA special register or the GET DESCRIPTOR statement to find out where the trigger and/or table are currently located.
Unfortunately I realized that the schema where a trigger lives can't be detected from inside trigger's body.
But there are some workarounds (thanks to #krmilligan too):
Take away the user's authority to execute CPYLIB and make them use a utility.
Create a background agent on the system that peridiocally runs looking for triggers that are out of synch.
For command CPYLIB set the default for TRG option to *NO. In this way triggers will never be copied, except if the user explicitly specifies it.
I choose the last one because it's the simplest one, even if there can be contexts where trigger copy is required. In such cases I'd take the first workaround.

How can I set a username for a Postgresql audit trigger?

I am using a trigger in PostgreSQL 8.2 to audit changes to a table:
CREATE OR REPLACE FUNCTION update_issue_history() RETURNS trigger as $trig$
BEGIN
INSERT INTO issue_history (username, issueid)
VALUES ('fixed-username', OLD.issueid);
RETURN NULL;
END;
$trig$ LANGUAGE plpgsql;
CREATE TRIGGER update_issue_history_trigger
AFTER UPDATE ON issue
FOR EACH ROW EXECUTE PROCEDURE update_issue_history();
What I want to do is have some way to provide the value of fixed-username at the time that I execute the update. Is this possible? If so, how do I accomplish it?
Try something like this:
CREATE OR REPLACE FUNCTION update_issue_history()
RETURNS trigger as $trig$
DECLARE
arg_username varchar;
BEGIN
arg_username := TG_ARGV[0];
INSERT INTO issue_history (username, issueid)
VALUES (arg_username, OLD.issueid);
RETURN NULL;
END;
$trig$ LANGUAGE plpgsql;
CREATE TRIGGER update_issue_history_trigger
AFTER UPDATE ON issue
FOR EACH ROW EXECUTE PROCEDURE update_issue_history('my username value');
I don't see a way to do this other than to create temp tables and use EXECUTE in your trigger. This will have performance consequences though. A better option might be to tie into another table somewhere and log in who logs in/out by session id and back-end PID, and reference that?
Note you don't have any other way of getting the information into the update statement. Keep in mind that the trigger can only see what is available in the API or in the database. If you want a trigger to work transparently, you can't expect to pass information to it at runtime that it would not have access to otherwise.
The basic question you have to ask is "How does the db know what to put there?" Once you decide on a method there the answer should be straight-forward but there are no free lunches.
Update
In the past when I have had to do something like this when the login to the db is with an application role, the way I have done it is to create a temporary table and then access that table from the stored procedures. Currently stored procedures can handle temporary tables in this way but in the past we had to use EXECUTE.
There are two huge limitations with this approach. The first is that it creates a lot of tables and this leads eventually to the possibility of oid wraparound.
These days I much prefer to have the logins to the db being user logins. This makes this far easier to manage and you can just access via the value SESSION_USER (a newbie mistake is to use CURRENT_USER which shows you the current security context rather than the login of the user.
Neither of these approaches work well with connection pooling. In the first case you can't do connection pooling because your temporary tables will get misinterpreted or clobbered. In the second, you can't do it because the login roles are different.

Resources