tSQLt will not apply misnamed constraints (PK_dbo ....) to fake table - tsql

Adding tSQLt tests to an existing production product, so we're not able to alter tables, constraints, etc. Currently all the constraints are labeled like 'PK_dbo.ViolationCategory' when they should be like 'PK_ViolationCategory'
When I run:
EXEC tSQLt.NewTestClass 'AdHocReportFiltersTestConstraint';
GO
CREATE PROCEDURE [AdHocReportFiltersTestConstraint].[Setup]
AS
BEGIN
EXEC tSQLt.FakeTable 'dbo.AdHocReports'
END
GO
CREATE PROCEDURE [AdHocReportFiltersTestConstraint].[test_AdHocReportFilters_Constraint]
AS
BEGIN
DECLARE #Id uniqueidentifier = NEWID()
DECLARE #Name NVARCHAR(Max) = 'Test_Name'
DECLARE #Value NVARCHAR(Max) = 'Test_Value'
DECLARE #AdHocReport_ID uniqueidentifier = NEWID()
INSERT INTO dbo.AdHocReportFilters ([Id], [Name], [Value], [AdHocReport_Id])
VALUES (#Id, #Name, #Value, #AdHocReport_ID)
exec tSQLt.ApplyConstraint 'dbo.AdHocReports', 'PK_dbo.AdHocReportFilters';
END
EXEC tSQLt.RunTestClass 'AdHocReportFiltersTestConstraint';
GO
I receive the error,
(1 row affected)
[AdHocReportFiltersTestConstraint].[test_AdHocReportFilters_Constraint] failed:
(Error) The INSERT statement conflicted with the FOREIGN KEY constraint
"FK_dbo.AdHocReportFilters_dbo.AdHocReports_AdHocReport_Id". The conflict occurred in
database "CR", table "dbo.tSQLt_tempobject_fbc9c8bf09e742929eccae914d5e440d",
column 'Id'.[16,0]{test_AdHocReportFilters_Constraint,11}
Any ideas of how to work around this?
Once I get this working, I will add a second record to violate the PK constraint and catch the error.

Just looking at your code, I see you are doing the following:
Fake the table dbo.AdHocReports
Insert a row into the table dbo.AdHocReportFilters
Apply a PK constraint called "PK_dbo.AdHocReportFilters" to the table dbo.AdHocReports
You will then try and add another row to validate the PK_dbo.AdHocReportFilters constraint
The error you are getting appears to to suggest that you are violating a foreign key on the AdHocReportFilters table - which is expected since that table hasn't been faked.
It is not clear from the test name whether you are trying to validate the behaviour of the primary key or foreign key.
Looking at the steps, I think you may be mixing up the two tables but without more detailed code (i.e. CREATE TABLE) statements, it is difficult for me to help you further.

There are two tables:
dbo.AdHocReports (Id,... (more columns))
dbo.AdHocReportFilters ([Id], [Name], [Value], [AdHocReport_Id] )
AdHocReportFilters.AdHocReport_Id is foriegn key for table dbo.AdHocReportFilters referencing dbo.AdHocReports.Id
When you are inserting insert a row in table AdHocReportFilters, you should make sure AdHocReportFilters.AdHocReport_Id is an id column in table dbo.AdHocReports
But in your SP, #AdHocReport_ID is NEWID(), which is of course a unique value and hence not in table dbo.AdHocReports.Id
Work around,
Disable foreign key contraint
ALTER TABLE dbo.AdHocReportFilters NOCHECK CONSTRAINT FK_dbo.AdHocReportFilters_dbo.AdHocReports_AdHocReport_Id

Related

Insert Procedure check if foreign key exists in another table

I am running into an issue where I am supposed to create a stored procedure. However my stored procedure needs to check if columns exists in a different table, this is my code at the moment:
CREATE PROCEDURE InsertTrail (
#Mount int,
#Skill varchar(20),
#Snow int,
#Lift int,
#Open char(3),
#AmountReal int,
#AmountFake int,
#Name varchar(50)
)
AS
BEGIN
If not exists (select mountainid from MOUNTAIN where #mount = mountainid)
and
If not exists (select skilllevel from SKILLLEVEL where #skill = SkillLevel)
and
If not exists (select snowmakerID from SNOWMAKER where #snow = SnowMakerID)
and
If not exists (select liftID from LIFT where #lift = LiftID)
BEGIN
INSERT INTO TRAIL (MountainID, SkillLevel, SnowMakerID, LiftID, TOpen, AmountRealSnow, AmountFakeSnow, TrailName)
values
(#Mount, #Skill, #Snow, #Lift, #Open, #AmountReal, #AmountFake, #Name)
Return ##Identity
END
END
The columns I need to make sure exist are the mountainid in the mountain table, the skilllevel in the SKILLLEVEL table, the snowmakerid in the snowmakertable and the liftID in the lift table.
If you need any more information please let me know! Please help if you can!
Currently you're inserting your value only if none of these values exist. If you want to check if all values exists before inserting you should use IF EXISTS instead of IF NOT EXISTS.
Still, I think the easiest way to achieve this is to make foreign keys and let the database administrator handle this validations.
I'm assuming SQL SERVER per your sample code's syntaxis. Correct me if I'm wrong to change the query:
ALTER TABLE TRAIL
ADD CONSTRAINT [FK_TRAIL_MOUNTAIN] FOREING KEY [MountainID]
REFERENCES [MOUNTAIN] ([MountainID])
ALTER TABLE TRAIL
ADD CONSTRAINT [FK_TRAIL_SKILLLEVEL] FOREING KEY [SkillLevel]
REFERENCES [SKILLLEVEL] ([SkillLevel])
ALTER TABLE TRAIL
ADD CONSTRAINT [FK_TRAIL_SNOWMAKER] FOREING KEY [SnowMakerID]
REFERENCES [SNOWMAKER] ([SnowMakerID])
ALTER TABLE TRAIL
ADD CONSTRAINT [FK_TRAIL_LIFT] FOREING KEY [LiftID]
REFERENCES [LIFT] ([LiftID])
Also IMO you should also check these values on client-side, and just let your SP "crash" when there's a non-referenced value.

How can two rows be written atomically when one row has a foreign key into the other?

I have a transaction where we insert a row of table foo and then a row of table bar. This ensures we either write both rows or neither. The trouble with this is bar has a foreign key into foo. Because we don't know the id of foo at the time of the bar insert, this fails the foreign key constraint.
Previously I've used tools like SQLAlchemy, when writing Python backends, that include the capability of flushing a session before the transaction is committed--this allows the user to derive the id of foo and pass it along to the INSERT into bar before actually writing anything.
My question is, in the context of JDBC and its Clojure wrapper, how can this be done?
Previously I was attempting to use (jdbc/query db-spec ["select id from foo where name='my_foo'"]) within the transaction to derive the dependent foo row ID. This was returning nil and so it seemed like the obvious method didn't work. However it turned out I was using db-spec and not the transaction connection, which if you use jdbc/with-db-transaction is bound in the vector.
For example:
(jdbc/with-db-transaction [t-conn db-spec]
(jdbc/insert! t-conn :foo {:name "my_foo"})
(jdbc/query t-conn ["select id from foo where name='my_foo'"]))
The query in the above form will yield the correct row ID.
You can insert values into both tables in one query, e.g.:
create table foo (
foo_id serial primary key,
name text);
create table bar (
bar_id serial primary key,
foo_id int references foo,
name text);
with insert_into_foo as (
insert into foo (name)
values ('some foo')
returning foo_id
)
insert into bar (foo_id, name)
select foo_id, 'some bar'
from insert_into_foo;
This is part of what DEFERRABLE foreign key constraints are for.
ALTER TABLE mytable
DROP CONSTRAINT the_fk_name;
ALTER TABLE
ADD CONSTRAINT the_fk_name
FOREIGN KEY (thecol) REFERENCES othertable(othercol)
DEFERRABLE INITIALLY IMMEDIATE;
then
BEGIN;
SET CONSTRAINTS DEFERRED;
INSERT thetable ...;
INSERT INTO othertable ...;
-- optional, but if you do this you get any errors BEFORE commit
SET CONSTRAINTS IMMEDIATE;
COMMIT;
I suggest using initially immediate and set constraints so that the rest of the time you don't create queued triggers. It's better for performance and memory use, plus it won't confuse apps that don't understand and expect deferred cosntraints.
If your framework can't cope with this you can use DEFERRABLE INITIALLY DEFERRED instead.

Set column as primary key if the table doesn't have a primary key

I have a column in db which has 5 columns but no primary key.
One of the columns is named myTable_id and is integer.
I want to check if the table has a primary key column. If it doesn't, then make myTable_id a primary key column and make it identity column. Is there a way to do this?
I tried with this:
ALTER TABLE Persons
DROP CONSTRAINT pk_PersonID
ALTER TABLE Persons
ADD PRIMARY KEY (P_Id)
and I get syntax error in Management studio.
This checks if primary key exists, if not it is created
IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
WHERE CONSTRAINT_TYPE = 'PRIMARY KEY' AND TABLE_NAME = 'Persons'
AND TABLE_SCHEMA ='dbo')
BEGIN
ALTER TABLE Persons ADD CONSTRAINT pk_PersonID PRIMARY KEY (P_Id)
END
ELSE
BEGIN
-- Key exists
END
fiddle: http://sqlfiddle.com/#!6/e165d/2
ALTER TABLE Persons
ADD CONSTRAINT pk_PersonID PRIMARY KEY (P_Id)
An IDENTITY constraint can't be added to an existing column, so how you add this needs to be your initial thought. There are two options:
Create a new table including a primary key with identity and drop the existing table
Create a new primary key column with identity and drop the existing 'P_ID' column
There is a third way, which is a better approach for very large tables via the ALTER TABLE...SWITCH statement. See Adding an IDENTITY to an existing column for an example of each. In answer to this question, if the table isn't too large, I recommend running the following:
-- Check that the table/column exist and no primary key is already on the table.
IF COL_LENGTH('PERSONS','P_ID') IS NOT NULL
AND NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
WHERE CONSTRAINT_TYPE = 'PRIMARY KEY' AND TABLE_NAME = 'PERSONS')
-- Add table schema to the WHERE clause above e.g. AND TABLE_SCHEMA ='dbo'
BEGIN
ALTER TABLE PERSONS
ADD P_ID_new int IDENTITY(1, 1)
GO
ALTER TABLE PERSONS
DROP COLUMN P_ID
GO
EXEC sp_rename 'PERSONS.P_ID_new', 'P_ID', 'Column'
GO
ALTER TABLE PERSONS
ADD CONSTRAINT PK_P_ID PRIMARY KEY CLUSTERED (P_ID)
GO
END
Notes:
By explicitly using the CONSTRAINT keyword the primary key constraint is given a particular name rather than depending on SQL Server to auto-assign a name.
Only include CLUSTERED on the PRIMARY KEY if the balance of searches for a particular P_ID and the amount of writing outweighs the benefits of clustering the table by some other index. See Create SQL IDENTITY as PRIMARY KEY.
You can check if primary key exists or not using OBJECTPROPERTY Transact SQL, use 'TableHasPrimaryKey' for the second arguments.
DECLARE #ISHASPRIMARYKEY INT;
SELECT #ISHASPRIMARYKEY = OBJECTPROPERTY(OBJECT_ID('PERSONS'), 'TABLEHASPRIMARYKEY');
IF #ISHASPRIMARYKEY IS NULL
BEGIN
-- generate identity column
ALTER TABLE PERSONS
DROP COLUMN P_ID;
ALTER TABLE PERSONS
ADD P_ID INT IDENTITY(1,1);
-- add primary key
ALTER TABLE PERSONS
ADD CONSTRAINT PK_PERSONID PRIMARY KEY (P_ID);
END;
I don't think you can do that. For making a column into an identity column I think you have to drop the table entirely.

postgres - trigger before transaction commit

I was wondering if it is indirectly possible to have a trigger executed just before the transaction is about to commit? In this trigger, I will do consistency checks and rollback the transaction if required.
For example, I have three tables:
users (id, name)
groups (id, name)
user_in_group (user_id, group_id)
I would like to create a trigger which verifies that a user is always part of a group. No orphan users are allowed. Each time an insert into users occurs, this trigger will verify that a correspondering insert into user_in_group also occured. If not, the transaction will not commit.
This cannot be done using a simple row- or statement- based trigger, since the above scenario requires two separate statements.
The other way around, when a delete from user_in_group happens, can be easily done by a row-based trigger.
Did you look into the CREATE CONSTRAINT TRIGGER, with the DEFERRABLE (INITIALLY DEFERRED) option?
Wouldn't the correct method really be to establish constraints into the database? That seems exactly what constraints are for. Add a foreign key constraint and a not null and it would seem you should be in business.
Now revised for symmetrical constraints:
drop table foousers cascade;
drop table foogroups cascade;
drop table foousergrps cascade;
create table foousers (id int primary key, name text);
create table foogroups (id int primary key, name text);
create table foousergrps (user_id int unique references foousers not null, group_id int unique references foogroups not null);
alter table foogroups add foreign key (id) references foousergrps (group_id) deferrable initially deferred;
alter table foousers add foreign key (id) references foousergrps (user_id) deferrable initially deferred;
begin;
insert into foousers values (0, 'root');
insert into foousers values (1, 'daemon');
insert into foogroups values (0, 'wheel');
insert into foogroups values (1, 'daemon');
insert into foousergrps values (0,0);
insert into foousergrps values (1,1);
commit;
Forbidden:
insert into foousers values (2, 'bad');
insert into foousergrps values (2,2);
Example of (non-deferrable, boo) check function:
create table foousergrps (user_id int unique references foousers not null, group_id int not null);
create function fooorphangroupcheck(int) returns boolean as $$
declare
gid alias for $1;
begin
perform 1 from foousergrps where group_id = gid limit 1;
if NOT FOUND then return false;
end if;
return true;
end;
$$
LANGUAGE 'plpgsql';
alter table foogroups add check (fooorphangroupcheck(id));
Lookind at the doc, there seems to be no such trigger option... so one way to achieve "no orphan users" rule would be to not allow direct insert into users and user_in_group tables. Instead create a view (which combines these tables, ie user_id, user_name, group_id) with a update rule which inserts data into right tables.
Or only allow inserting new users via stored procedure which takes all required data as inpud and thus doesn't allow users withoud group.
BTW, why do you have separate table for user and group relationship? Why not add group_id field into users table with FK / NOT NULL constraint?
From the docs . . .
Triggers can be defined to execute either before or after any INSERT,
UPDATE, or DELETE operation, either once per modified row, or once per
SQL statement.
You can use the sql WITH operator, like this:
WITH insert_user AS (
INSERT INTO users(name) VALUES ('bla-bla-user') RETURNING id
)
INSERT INTO user_in_group(user_id, group_id)
SELECT id, 999 FROM insert_user UNION
SELECT id, 888 FROM insert_user;
SELECT groups (id, name) user_in_group (user_id, group_id)

Foreign keys in postgresql can be violated by trigger

I've created some tables in postgres, added a foreign key from one table to another and set ON DELETE to CASCADE. Strangely enough, I have some fields that appear to be violating this constraint.
Is this normal behaviour? And if so, is there a way to get the behaviour I want (no violations possible)?
Edit:
I orginaly created the foreign key as part of CREATE TABLE, just using
... REFERENCES product (id) ON UPDATE CASCADE ON DELETE CASCADE
The current code pgAdmin3 gives is
ALTER TABLE cultivar
ADD CONSTRAINT cultivar_id_fkey FOREIGN KEY (id)
REFERENCES product (id) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE CASCADE;
Edit 2:
To Clarify, I have a sneaking suspicion that the constraints are only checked when updates/inserts happen but are then never looked at again. Unfortunately I don't know enough about postgres to find out if this is true or how fields could end up in the database without those checks being run.
If this is the case, is there some way to check all the foreign keys and fix those problems?
Edit 3:
A constraint violation can be caused by a faulty trigger, see below
I tried to create a simple example that shows foreign key constraint being enforced. With this example I prove I'm not allowed to enter data that violates the fk and I prove that if the fk is not in place during insert, and I enable the fk, the fk constraint throws an error telling me data violates the fk. So I'm not seeing how you have data in the table that violates a fk that is in place. I'm on 9.0, but this should not be different on 8.3. If you can show a working example that proves your issue that might help.
--CREATE TABLES--
CREATE TABLE parent
(
parent_id integer NOT NULL,
first_name character varying(50) NOT NULL,
CONSTRAINT pk_parent PRIMARY KEY (parent_id)
)
WITH (
OIDS=FALSE
);
ALTER TABLE parent OWNER TO postgres;
CREATE TABLE child
(
child_id integer NOT NULL,
parent_id integer NOT NULL,
first_name character varying(50) NOT NULL,
CONSTRAINT pk_child PRIMARY KEY (child_id),
CONSTRAINT fk1_child FOREIGN KEY (parent_id)
REFERENCES parent (parent_id) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE CASCADE
)
WITH (
OIDS=FALSE
);
ALTER TABLE child OWNER TO postgres;
--CREATE TABLES--
--INSERT TEST DATA--
INSERT INTO parent(parent_id,first_name)
SELECT 1,'Daddy'
UNION
SELECT 2,'Mommy';
INSERT INTO child(child_id,parent_id,first_name)
SELECT 1,1,'Billy'
UNION
SELECT 2,1,'Jenny'
UNION
SELECT 3,1,'Kimmy'
UNION
SELECT 4,2,'Billy'
UNION
SELECT 5,2,'Jenny'
UNION
SELECT 6,2,'Kimmy';
--INSERT TEST DATA--
--SHOW THE DATA WE HAVE--
select parent.first_name,
child.first_name
from parent
inner join child
on child.parent_id = parent.parent_id
order by parent.first_name, child.first_name asc;
--SHOW THE DATA WE HAVE--
--DELETE PARENT WHO HAS CHILDREN--
BEGIN TRANSACTION;
delete from parent
where parent_id = 1;
--Check to see if any children that were linked to Daddy are still there?
--None there so the cascade delete worked.
select parent.first_name,
child.first_name
from parent
right outer join child
on child.parent_id = parent.parent_id
order by parent.first_name, child.first_name asc;
ROLLBACK TRANSACTION;
--TRY ALLOW NO REFERENTIAL DATA IN--
BEGIN TRANSACTION;
--Get rid of fk constraint so we can insert red headed step child
ALTER TABLE child DROP CONSTRAINT fk1_child;
INSERT INTO child(child_id,parent_id,first_name)
SELECT 7,99999,'Red Headed Step Child';
select parent.first_name,
child.first_name
from parent
right outer join child
on child.parent_id = parent.parent_id
order by parent.first_name, child.first_name asc;
--Will throw FK check violation because parent 99999 doesn't exist in parent table
ALTER TABLE child
ADD CONSTRAINT fk1_child FOREIGN KEY (parent_id)
REFERENCES parent (parent_id) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE CASCADE;
ROLLBACK TRANSACTION;
--TRY ALLOW NO REFERENTIAL DATA IN--
--DROP TABLE parent;
--DROP TABLE child;
Everything I've read so far seems to suggest that constraints are only checked when the data is inserted. (Or when the constraint is created) For example the manual on set constraints.
This makes sense and - if the database works properly - should be good enough. I'm still curious how I managed to circumvent this or if I just read the situation wrong and there was never a real constraint violation to begin with.
Either way, case closed :-/
------- UPDATE --------
There was definitely a constraint violation, caused by a faulty trigger. Here's a script to replicate:
-- Create master table
CREATE TABLE product
(
id INT NOT NULL PRIMARY KEY
);
-- Create second table, referencing the first
CREATE TABLE example
(
id int PRIMARY KEY REFERENCES product (id) ON DELETE CASCADE
);
-- Create a (broken) trigger function
--CREATE LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION delete_product()
RETURNS trigger AS
$BODY$
BEGIN
DELETE FROM product WHERE product.id = OLD.id;
-- This is an error!
RETURN null;
END;
$BODY$
LANGUAGE plpgsql;
-- Add it to the second table
CREATE TRIGGER example_delete
BEFORE DELETE
ON example
FOR EACH ROW
EXECUTE PROCEDURE delete_product();
-- Now lets add a row
INSERT INTO product (id) VALUES (1);
INSERT INTO example (id) VALUES (1);
-- And now lets delete the row
DELETE FROM example WHERE id = 1;
/*
Now if everything is working, this should return two columns:
(pid,eid)=(1,1). However, it returns only the example id, so
(pid,eid)=(0,1). This means the foreign key constraint on the
example table is violated.
*/
SELECT product.id AS pid, example.id AS eid FROM product FULL JOIN example ON product.id = example.id;