I have the following code to create a database table if it does not already exist then check if the current user is in the database or not. I do this by selecting the user with id equal to their device id. The code right now should print hello world and then go to forms. However when I run the code nothing happens, no errors and no print statement. Any ideas why this might be happening?
local tablesetup = [[CREATE TABLE IF NOT EXISTS User (id VARCHAR(255) PRIMARY KEY, name);]]
db:exec(tablesetup)
--Check if the user is in the database already
for row in db:nrows("SELECT * FROM User WHERE id = " .. "'" .. system.getInfo("deviceID") .. "'") do
print("hello")
if(row.id == nil) then
print("world")
storyboard.gotoScene("forms")
end
end
nrows returns all of the rows produced by a query. If no rows were produced in a query (because, for example, the item(s) you were looking for was not found), then the loop will not start. Because there was no row that matched the query.
If you're looking to detect if a query succeeded or not, then you'll need to actually check that.
Also, please stop building queries with string concatenation. Learn how to use prepared statements with proper value binding. It's much cleaner and has the benefit of not leaving you open to SQL injection attacks.
Related
I am creating a book tracking database for myself that holds information about my books and allows me to keep track of who is borrowing them. I am trying to create a trigger on my Checkouts table that runs if a record is added or updated that will determine if a checkout data has been entered or if a checkin date has been entered and change the "available" field in my Books table to "Y" or "N".
I have created a trigger called "update_book_availablility" on my Checkouts table but I keep getting this error:
"PLS-00103: Encountered the symbol 'end-of-file' when expecting one of the following: ( begin case declare and exception exit for goto if loop mod null pragma raise return select update while with <<continue close current delete fetch lock insert open rollback savepoint set sql execute commit forall merge standard pipe purge json_object
Errors: check compiler log"
Here is my trigger code:
CREATE OR REPLACE NONEDITIONABLE TRIGGER "UPDATE_BOOK_AVAILABILITY"
AFTER INSERT OR UPDATE OF ISBN, PersonID, checkout_date, checkin_date
ON Checkouts
FOR EACH ROW
BEGIN
IF :NEW.checkout_date = NULL
THEN
UPDATE Book
SET available = 'N'
WHERE ISBN IN (SELECT :NEW.ISBN FROM Checkouts);
END IF;
END;
Here is an image of my ERD:
ERD
I have been looking into and double checking my trigger syntax, If condition syntax, subquery syntax, and googling this error but have found nothing that has helped. I am new to PL/SQL and would appreciate any help in understanding what I have done wrong or missed.
PLS-00103: Encountered the symbol end-of-file error is SYNTAX ERROR
Copied your trigger and adjusted it to one of my test tables - it works. I removed NONEDITIONABLE and changed trigger table name as well as column names and table/column beeing updated by trigger.
To Do:
Check your syntax again or write the trigger from scratch once more
"...WHERE ISBN IN (SELECT :NEW.ISBN FROM Checkouts)..." selects one fixed value (FOR EACH ROW) :NEW.ISBN of triggering table, better ->> "... WHERE ISBN = :NEW.ISBN ..."
Prety sure that you don't need NONEDITIONABLE trigger for your books tracking app...
Regards...
I am new to robotframework and I am trying to get the hang of the keywords of DatabaseLibrary. I am getting error at 3 of such keywords.
1) I am using rowcount keywords as below-
${rowCount} Row Count <sql query>
And I always get ${rowCount}=0 irrespective of the number of rows in my table.
2) I am using Delete All Rows From Table as below-
Delete All Rows From Table <Table_Name>
And I get ORA-00911: invalid character but if use the same table with other keywords like Query ,it works fine.
3) I am using Table Must Exist as below-
Table Must Exist <Table_Name>
And I get ORA-00942: table or view does not exist but this table is very much there.
Please help me find what am I doing wrong.
Thanks in Advance!!!
I could be wrong but I believe a colleague told me there were issues, at the very least with the Row Count keyword.
However, for all three options there are easy solutions, which you've even hinted at in your question by using Query or Execute SQL Script
1)
${result}= Query Select count(id) from table
${rc} = ${result[0][0]} #Play with this as I forget exact syntax
2) Put your delete script in a test scripts folder with your tests and call it using Execute SQL script. You could also use Query to perform a select query before and after to confirm expected states.
3) Again perform a query against the table you're expecting to be there, a simple row count on id would do for this purpose. You could set a variable based on the result and use this again later if required.
I had similar issues.
I use cx_Oracle.
With the Table Must Exist keyword my problem was the same.
I dont really understand why, but first I have to use Encode String to Bytes keyword.
And I need to use a DatabaseLibrary 0.8 at least, because earlier versions didnt have solution for cx_Oracle. These solved this issue for me.
But with Delete all rows from table I still have problems.
Because this keyword puts a ; at the end of the line and it passes on that line to execute query if I understand weel, so it still causes an ORA-00911 error for me.
With Execute Sql String and the command DELETE FROM tablename you can have the same results, but it will work this way.
I hope it helps a little
I would like to ask you how would you increase the performance on Insert cursor in this code?
I need to use dynamic plsql to fetch data but dont know how to improve the INSERT in best way. like Bulk Insert maybe?
Please let me know with code example if possible.
// This is how i use cur_handle:
cur_HANDLE integer;
cur_HANDLE := dbms_sql.open_cursor;
DBMS_SQL.PARSE(cur_HANDLE, W_STMT, DBMS_SQL.NATIVE);
DBMS_SQL.DESCRIBE_COLUMNS2(cur_HANDLE, W_NO_OF_COLS, W_DESC_TAB);
LOOP
-- Fetch a row
IF DBMS_SQL.FETCH_ROWS(cur_HANDLE) > 0 THEN
DBMS_SQL.column_value(cur_HANDLE, 9, cont_ID);
DBMS_SQL.COLUMN_VALUE(cur_HANDLE, 3, proj_NR);
ELSE
EXIT;
END IF;
Insert into w_Contracts values(counter, cont_ID, proj_NR);
counter := counter + 1;
END LOOP;
You should do database actions in sets whenever possible, rather than row-by-row inserts. You don't tell us what CUR_HANDLE is, so I can't really rewrite this, but you should probably do something like:
INSERT INTO w_contracts
SELECT ROWNUM, cont_id, proj_nr
FROM ( ... some table or joined tables or whatever... )
Though if your first value there is a primary key, it would probably be better to assign it from a sequence.
Solution 1) You can populate inside the loop a PL/SQL array and then just after the loop insert the whole array in one step using:
FORALL i in contracts_tab.first .. contracts_tab.last
INSERT INTO w_contracts VALUES contracts_tab(i);
Solution 2) if the v_stmt contains a valid SQL statement you can directly insert data into the table using
EXECUTE IMMEDIATE 'INSERT INTO w_contracts (counter, cont_id, proj_nr)
SELECT rownum, 9, 3 FROM ('||v_stmt||')';
"select statement is assembled from a website, ex if user choose to
include more detailed search then the select statement is changed and
the result looks different in the end. The whole application is a web
site build on dinamic plsql code."
This is a dangerous proposition, because it opens your database to SQL injection. This is the scenario in which Bad People subvert your parameters to expand the data they can retrieve or to escalate privileges. At the very least you need to be using DBMS_ASSERT to validate user input. Find out more.
Of course, if you are allowing users to pass whole SQL strings (you haven't provided any information regarding the construction of W_STMT) then all bets are off. DBMS_ASSERT won't help you there.
Anyway, as you have failed to give the additional information we actually need, please let me spell it out for you:
will the SELECT statement always have the same column names from the same table name, or can the user change those two?
will you always be interested in the third and ninth columns?
how is the W_STMT string assembled? How much control do you have over its projection?
Suppose I have a user table that creates strong relationships (Enforce Foreign Key Constraint) with many additional tables. Such orders table ..
If we try to delete a user with some orders then SqlException will arise.. How can I catch this exception and treat it properly?
Is this strategy at all?
1) first try the delete action if an exception Occur handel it?
2) Or maybe before the delete action using code adapted to ensure that offspring records throughout the database and alert according to .. This piece of work ...
So how to do it?
--Edit:
The goal is not to delete the records from the db! the goal is to inform the user that this record has referencing records. do i need to let sql to execute the delete command and try to catch SqlException? And if so, how to detect that is REFERENCE constraint SqlException?
Or - should I need to write some code that will detect if there are referencing records before the delete command. The last approach give me more but its a lot of pain to implement this kind of verification to each entity..
Thanks
Do you even really want to actually delete User records? Instead I'd suggest having a "deleted" flag in your database, so when you "delete" a user through the UI, all it does is update that record to set the flag to 1. After all, you wouldn't want to delete users that had orders etc.
Then, you just need to support this flag in the appropriate areas (i.e. don't show "deleted" users in the UI).
Edit:
"...but just for the concept, assume that i do want delete the user how do i do that?"
You'd need to delete the records from the other tables that reference that user first, before deleting the user record (i.e. delete the referencing records first then delete the referenced records). But to me that doesn't make sense as you would be deleting e.g. order data.
Edit 2:
"And if so, how to detect that is REFERENCE constraint SqlException?"
To detect this specific error, you can just check the SqlException.Number - I think for this error, you need to check for 547 (this is the error number on SQL 2005). Alternatively, if using SQL 2005 and above, you could handle this error entirely within SQL using the TRY...CATCH support:
BEGIN TRY
DELETE FROM User WHERE UserId = #MyUserId
END TRY
BEGIN CATCH
IF (ERROR_NUMBER() = 547)
BEGIN
-- Foreign key constraint violation. Handle as you wish
END
END CATCH
However, I'd personally perform a pre-check like you suggested though, to save the exception. It's easily done using an EXISTS check like this:
IF NOT EXISTS(SELECT * FROM [Orders] WHERE UserId=#YourUserId)
BEGIN
-- User is not referenced
END
If there are more tables that reference a User, then you'd need to also include those in the check.
I would like to find out if it is possible to find out which package or procedure in a package is updating a table?
Due to a certain project being handed over (the person who handed over the project has since left) without proper documentation, data that we know we have updated always go back to some strange source point.
We are guessing that this could be a database job or scheduler that is running the update command without our knowledge. I am hoping that there is a way to find out where the source code is calling from that is updating the table and inserting the source as a trigger on that table that we are monitoring.
Any ideas?
Thanks.
UPDATE: I poked around and found out
how to trace a statement back to its
owning PL/SQL object.
In combination with what Tony mentioned, you can create a logging table and a trigger that looks like this:
CREATE TABLE statement_tracker
( SID NUMBER
, serial# NUMBER
, date_run DATE
, program VARCHAR2(48) null
, module VARCHAR2(48) null
, machine VARCHAR2(64) null
, osuser VARCHAR2(30) null
, sql_text CLOB null
, program_id number
);
CREATE OR REPLACE TRIGGER smb_t_t
AFTER UPDATE
ON smb_test
BEGIN
INSERT
INTO statement_tracker
SELECT ss.SID
, ss.serial#
, sysdate
, ss.program
, ss.module
, ss.machine
, ss.osuser
, sq.sql_fulltext
, sq.program_id
FROM v$session ss
, v$sql sq
WHERE ss.sql_address = sq.address
AND ss.SID = USERENV('sid');
END;
/
In order for the trigger above to compile, you'll need to grant the owner of the trigger these permissions, when logged in as the SYS user:
grant select on V_$SESSION to <user>;
grant select on V_$SQL to <user>;
You will likely want to protect the insert statement in the trigger with some condition that only makes it log when the the change you're interested in is occurring - on my test server this statement runs rather slowly (1 second), so I wouldn't want to be logging all these updates. Of course, in that case, you'd need to change the trigger to be a row-level one so that you could inspect the :new or :old values. If you are really concerned about the overhead of the select, you can change it to not join against v$sql, and instead just save the SQL_ADDRESS column, then schedule a job with DBMS_JOB to go off and update the sql_text column with a second update statement, thereby offloading the update into another session and not blocking your original update.
Unfortunately, this will only tell you half the story. The statement you're going to see logged is going to be the most proximal statement - in this case, an update - even if the original statement executed by the process that initiated it is a stored procedure. This is where the program_id column comes in. If the update statement is part of a procedure or trigger, program_id will point to the object_id of the code in question - you can resolve it thusly:
SELECT * FROM all_objects where object_id = <program_id>;
In the case when the update statement was executed directly from the client, I don't know what program_id represents, but you wouldn't need it - you'd have the name of the executable in the "program" column of statement_tracker. If the update was executed from an anonymous PL/SQL block, I'm not how to track it back - you'll need to experiment further.
It may be, though, that the osuser/machine/program/module information may be enough to get you pointed in the right direction.
If it is a scheduled database job then you can find out what scheduled database jobs exist and look into what they do. Other things you can do are:
look at the dependencies views e.g. ALL_DEPENDENCIES to see what packages/triggers etc. use that table. Depending on the size of your system that may return a lot of objects to trawl through.
Search all the database source code for references to the table like this:
select distinct type, name
from all_source
where lower(text) like lower('%mytable%');
Again that may return a lot of objects, and of course there will be some "false positives" where the search string appears but isn't actually a reference to that table. You could even try something more specific like:
select distinct type, name
from all_source
where lower(text) like lower('%insert into mytable%');
but of course that would miss cases where the command was formatted differently.
Additionally, could there be SQL scripts being run through "cron" jobs on the server?
Just write an "after update" trigger and, in this trigger, log the results of "DBMS_UTILITY.FORMAT_CALL_STACK" in a dedicated table.
The purpose of this function is exactly to give you the complete call stack of al the stored procedures and triggers that have been fired to reach your code.
I am writing from the mobile app, so i can't give you more detailed examples, but if you google for it you'll find many of them.
A quick and dirty option if you're working locally, and are only interested in the first thing that's altering the data, is to throw an error in the trigger instead of logging. That way, you get the usual stack trace and it's a lot less typing and you don't need to create a new table:
AFTER UPDATE ON table_of_interest
BEGIN
RAISE_APPLICATION_ERROR(-20001, 'something changed it');
END;
/