From http://www.sqlite.org/lang_conflict.html
ABORT
When an applicable constraint violation occurs, the ABORT resolution algorithm aborts the current SQL statement with an SQLITE_CONSTRAIT error and backs out any changes made by the current SQL statement; but changes caused by prior SQL statements within the same transaction are preserved and the transaction remains active. This is the default behavior and the behavior proscribed the SQL standard.
FAIL
When an applicable constraint violation occurs, the FAIL resolution algorithm aborts the current SQL statement with an SQLITE_CONSTRAINT error. But the FAIL resolution does not back out prior changes of the SQL statement that failed nor does it end the transaction. For example, if an UPDATE statement encountered a constraint violation on the 100th row that it attempts to update, then the first 99 row changes are preserved but changes to rows 100 and beyond never occur.
Both preserve changes made before the statement that caused constraint violation and do not end transaction. So, I suppose the only difference is that FAIL resolution does not let further changes to be made, while ABORT does only back up only conflicting statement. Did I get right?
The answer is simple: FAIL does not rollback changes done by the current statement.
Consider those 2 tables:
CREATE TABLE IF NOT EXISTS constFAIL (num UNIQUE ON CONFLICT FAIL);
CREATE TABLE IF NOT EXISTS constABORT (num UNIQUE ON CONFLICT ABORT);
INSERT INTO constFAIL VALUES (1),(3),(4),(5);
INSERT INTO constABORT VALUES (1),(3),(4),(5);
The statement
UPDATE constABORT SET num=num+1 WHERE num<5
will fail and change nothing.
But this satement
UPDATE constFAIL SET num=num+1 WHERE num<5
will update the first row, then fail and leave the 1 row updated, so the new values are 2, 3, 4, 5
Related
I'm building this DB about the University for one of my course classes and I'm trying to create a trigger that doesn't allow for a professor to be under 21yo.
I have a Person class and then a Professor subclass.
What I want to happen is, you create a Person object, then a Professor object using that Person object's id, but, if the Person is under 21yo, delete this Professor object, then delete the Person object.
Everything works fine up until the "delete the Person object" part where this doesn't happen and I'm not sure why. Any help?
This is the sqlite code I have:
AFTER INSERT ON Professor
FOR EACH ROW
WHEN strftime('%J', 'now') - strftime('%J', (SELECT dateOfBirth from Person WHERE personId = NEW.personId)) < 7665 -- 21 years in days
BEGIN
SELECT RAISE(ROLLBACK, 'Professor cant be under 21');
DELETE FROM Person WHERE (personId= new.personId);
END;```
One common issue is that there many not be a current transaction scope to rollback to, which would result in this error:
Error: cannot rollback - no transaction is active
If that occurs, then the trigger execution will be aborted and the delete never executed.
If ROLLBACK does succeed, then this creates a paradox, by rolling back to before the trigger was executed in a strictly ACID environment it would not be valid to continue executing the rest of this trigger, because the INSERT never actually occurred. To avoid this state of ambiguity, any call to RAISE() that is NOT IGNORE will abort the processing of the trigger.
CREATE TRIGGER - The RAISE()
When one of RAISE(ROLLBACK,...), RAISE(ABORT,...) or RAISE(FAIL,...) is called during trigger-program execution, the specified ON CONFLICT processing is performed and the current query terminates. An error code of SQLITE_CONSTRAINT is returned to the application, along with the specified error message.
NOTE: This behaviour is different to some other RDBMS, for instance see this explanation on MS SQL Server where execution will specifically continue in the trigger.
As OP does not provide calling code that demonstrates the scenario it is worth mentioning that in SQLite on RAISE(ROLLBACK,)
If no transaction is active (other than the implied transaction that is created on every command) then the ROLLBACK resolution algorithm works the same as the ABORT algorithm.
Generally, if you wanted to Create a Person and then a Professor as a single operation, you would Create a Stored Procedure that would validate the inputs first, preventing the original insert at the start.
To maintain referential integrity, even if an SP is used, you could still add a check constraint on the Professor record or raise an ABORT from a BEFORE trigger to prevent the INSERT from occurring in the first place:
BEFORE INSERT ON Professor
FOR EACH ROW
WHEN strftime('%J', 'now') - strftime('%J', (SELECT dateOfBirth from Person WHERE personId = NEW.personId)) < 7665 -- 21 years in days
BEGIN
SELECT RAISE(ABORT, 'Professor can''t be under 21');
END
This way it is up to the calling process to manage how to handle the error. The ABORT can be caught in the calling logic and would effectively result in rolling back the outer transaction, but the point is that the calling logic should handle negative side effects. As a general rule triggers that cascade logic should only perform positive side effects, that is to say they should only affect data if the inserted row succeeds. In this case we are rolling back the insert, so it becomes hard to identify why the Person would be deleted.
before inserting in the table i am validating if the ID already exists in table.
It does not exists. Even though it through this error. Is there any possibility that the table does not allow to insert may be permission issue.
I have that insert query in a procedure, is there a possibility that due to procedure permission it doesn't allow.
If you check in the first statement/transaction for existences and you run this SP in parallel it is possible that the second (INSERT) statement/transaction fails.
In this case you can use a MERGE statement which either inserts the data or updates on existences in one transaction.
There is this documentation for sqlite: https://www.sqlite.org/lang_transaction.html which doesn't say anything about what happens in case I have case like:
BEGIN;
INSERT INTO a (x, y) VALUES (0, 0);
INSERT INTO b (x, y) VALUES (1, 2); -- line 3 error here, b doesn't have column x
COMMIT;
What happens in this case? Does it commit or rollback if there is error in line 3? I would expect automatic rollback, but I would like to be sure about it.
SQL statements are executed individually.
When a statement fails, any effects of this single statement are rolled back, but the transaction stays open and active.
When the application receives the error code, it must decide whether it wants to roll back the transaction, or retry, or do something else.
If you are using a function that executes multiple SQL statements, nothing changes; the effect is the same as if you had executed all statements up to the failing one individually.
I need to modify all the content in a table. So I wrap the modifications inside a transaction to ensure either all the operations succeed, or none do. I start the modifications with a DELETE statement, followed by INSERTs. What I’ve discovered is even if an INSERT fails, the DELETE has still takes place, and the database is not rolled back to the pre-transaction state.
I’ve created an example to demonstrate this issue. Put the following commands into a script called EXAMPLE.SQL
CREATE TABLE A(id INT PRIMARY KEY, val TEXT);
INSERT INTO A VALUES(1, “hello”);
BEGIN;
DELETE FROM A;
INSERT INTO A VALUES(1, “goodbye”);
INSERT INTO A VALUES(1, “world”);
COMMIT;
SELECT * FROM A;
If you run the script: “sqlite3 a.db < EXAMPLE.SQL”, you will see:
SQL error near line 10: column id is not unique
1|goodbye
What’s surprising is that the SELECT statement results did not show ‘1|hello’.
It would appear the DELETE was successful, and the first INSERT was successful. But when the second INSERT failed (as it was intended to)….it did not ROLLBACK the database.
Is this a sqlite error? Or an error in my understanding of what is supposed to happen?
Thanks
It works as it should.
COMMIT commits all operations in the transaction. The one involving world had problems so it was not included in the transaction.
To cancel the transaction, use ROLLBACK, not COMMIT. There is no automatic ROLLBACK unless you specify it as conflict resolution with e.g. INSERT OR ROLLBACK INTO ....
And use ' single quotes instead of “ for string literals.
This documentation shows the error types that lead to an automatic rollback:
SQLITE_FULL: database or disk full
SQLITE_IOERR: disk I/O error
SQLITE_BUSY: database in use by another process
SQLITE_NOMEM: out or memory
SQLITE_INTERRUPT: processing interrupted by application request
For other error types you will need to catch the error and rollback, more on this is covered in this SO question.
I'm running an asp.net 2.0 web application. Suddenly this morning, ExecuteNonQuery started returning -1, even though the commands in the SP that ExecuteNonQuery is running, are executing (I see items inserted and updated in the DB), and there is no exception thrown.
This happens only when I am connected to our production DB. When I'm connected to our development DB they return the correct number of rows affected.
Also, interestingly enough, ExecuteDataSet doesn't have problems - it returns DataSets beautifully.
So it doesn't seem like a connection problem. But then, what else could it be?
You can also check the procedure to see if there's this line of code in there -
SET NOCOUNT ON
This will cause the ExecuteNonQuery to always return -1.
-1 means "No rows effected".
SELECT statement will also return -1.
Check the effect of the SP on dev and live environments.
Taken from SqlCommand::ExecuteNonQuery Method
For UPDATE, INSERT, and DELETE statements, the return value is the number of rows affected by the command. When a trigger exists on a table being inserted or updated, the return value includes the number of rows affected by both the insert or update operation and the number of rows affected by the trigger or triggers. For all other types of statements, the return value is -1. If a rollback occurs, the return value is also -1.
I encounter almost same problem (in Postgresql though) with NpgsqlDataAdapter, the Update method always returns 1.
Perhaps your connection string to your development db is different from your production db. Or if there's really no difference between their connection strings, perhaps your production db's service pack is different from your development db.
ExceuteNonQuery returns -1 for all types stored procedure as per the msdn
it will returns updated records values only in case of sattment