Enforce foreign key constraints in GORM SQLite - sqlite

Answer: Use db.Exec("PRAGMA foreign_keys = ON") to enforce foreign key constraint checks. Thanks #outdead
When I update my SQLite database using GORM, foreign key constraints aren't enforced.
I have these 2 models:
type Cat struct {
ID int
Name string
Breed string
OwnerID int
Owner Owner
}
type Owner struct {
ID int
Name string
Phone string
}
Which correctly creates a foreign key constraint where owner_id references id in owners. This can be verified by running: .schema cats in the SQLite shell:
CREATE TABLE `cats` (`id` integer,`name` text,`breed` text,`owner_id` integer,PRIMARY KEY (`id`),CONSTRAINT `fk_cats_owner` FOREIGN KEY (`owner_id`) REFERENCES `owners`(`id`));
I have tried PRAGMA foreign_keys = ON; which enforces foreign keys when I run commands in the SQLite shell. If I try to update an owner_id to an id that doesn't exist in owners, I get: Error: FOREIGN KEY constraint failed, which is the behaviour that I want, however, GORM is still able to execute these updates without receiving this error.

You need to exec query to turn on PRAGMA foreign_keys before updating
if res := db.Exec("PRAGMA foreign_keys = ON", nil); res.Error != nil {
return res.Error
}

An alternative to the other answer is to append ?_foreign_keys=on to the connection string:
db, err := gorm.Open(sqlite.Open("my.db?_foreign_keys=on"), &gorm.Config{})
See the go-sqlite3 driver and this question.
Verified working with gorm v1.23.1, gorm's sqlite driver v1.3.1, and go-sqlite3 v2.0.3.

Related

SQLite - FOREIGN KEY doesn't work (yes, it is enabled)

For some reason, I cannot get FOREIGN KEY to work.
Any INSERT into primary_specs will go through, despite the 'ad_ids' table being empty.
EDIT: for some reason the INSERT will NOT go through when using DB Browser, but my python script waltzes right pass that constraint and is able to save the data...
Main table:
CREATE TABLE "ad_ids" (
"ad_id" INTEGER,
"ad_url" TEXT,
PRIMARY KEY("ad_id")
)
Secondary table:
CREATE TABLE "primary_specs" (
"ad_id" INTEGER,
"version" TEXT,
"year" INTEGER,
PRIMARY KEY("ad_id"),
FOREIGN KEY("ad_id")
REFERENCES ad_ids("ad_id")
)
PRAGMA foreign_key_list(primary_specs);
returns:
id seq table from to on_update on_delete match
0 0 ad_ids ad_id ad_id NO ACTION NO ACTION NONE
PRAGMA foreign_keys
returns 1
Apparently PRAGMA foreign_keys = ON; is applied on connection and not a database.

JDBC ignores SQLite foreign key constraint ON DELETE action

I've a Rate table with the following structre:
CREATE TABLE Rate (
id INTEGER PRIMARY KEY AUTOINCREMENT,
book_id INTEGER,
value INTEGER,
user_id INTEGER,
FOREIGN KEY(user_id) REFERENCES User(ID),
FOREIGN KEY(book_id) REFERENCES book(ID) ON DELETE RESTRICT
)
And a book table with the following structure:
DROP TABLE IF EXISTS Book;
CREATE TABLE Book (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL,
ISBN VARCHAR,
author_id INTEGER,
editor_id INTEGER,
translator_id INTEGER,
publisher_id INTEGER,
"type" VARCHAR,
language VARCHAR,
"date" VARCHAR,
format VARCHAR,
summary TEXT,
FOREIGN KEY(author_id) REFERENCES Author(ID) ON DELETE SET NULL ON UPDATE CASCADE,
FOREIGN KEY(editor_id) REFERENCES Editor(ID) ON DELETE SET NULL ON UPDATE CASCADE,
FOREIGN KEY(translator_id) REFERENCES Translator(ID) ON DELETE SET NULL ON UPDATE CASCADE,
FOREIGN KEY(publisher_id) REFERENCES Publisher(ID) ON DELETE SET NULL ON UPDATE CASCADE
);
So, if I have a Rate entry like this:
1 1 4 3(ID, BOOK_ID, VALUE, USER_ID)
I shouldn't be able to delete the Book with the ID 1, right?
This is exactly what happens when I try to delete the Book with ID 1 on SQLITE Manager. It gives me
FOREIGN KEY constraint failed
However, when I call my delete from the code, it totally ignores the restriction and deletes the book and there's no change in the Rate entry it is still:
1 1 4 3
And my delete method is like the following:
public void delete() {
try {
String query = "DELETE FROM book WHERE id = ? ";
PreparedStatement statement = db.prepareStatement(query);
statement.setInt(1, (Integer) this.id);
statement.executeUpdate();
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
When i run PRAGMA foreign_keys on SQLITE Manager it returns 1. So, I assume that the database is created correctly.
The documentation says:
... foreign key constraints ... must still be enabled by the application at runtime, using the PRAGMA foreign_keys command. For example:
PRAGMA foreign_keys = ON;
Foreign key constraints are disabled by default (for backwards compatibility), so must be enabled separately for each database connection.

SQLite [Err] 21 - not an error

I have the following code in SQL
-- SCHEMA VERSION: 2
-- Pre-update actions
PRAGMA foreign_keys = OFF;
-- end
-- Create HARVEST_PERIOD table
CREATE TABLE "main"."HARVEST_PERIOD" (
"ID" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
"CODE" TEXT(64) NOT NULL,
"PERIOD" TEXT(64) NOT NULL,
"CURRENT_STATE" TEXT(128)
)
;
-- Post-update actions
INSERT OR REPLACE INTO "main"."SETTINGS" ("NAME", "VALUE") values ("SchemaVersion", "2");
PRAGMA foreign_keys = ON;
-- end
The new table is created as expected and the settings table updated as expected, too. What could be the reason for getting this: [Err] 21 - not an error
Is there any better suggested way to create the new schema?
I encounter this error as well. Later I figured it out. It's because another application was connected to the same db. So, my application can't modify the db -- create a table.
I created it successfully just by closing the another db connection.

LINQPad create sqlite database in C# query

The IQ Connection plugin for LINQPad that allows one to use SQLite has a checkbox for "Create database if missing" but that will only create an empty file. Is there anyway to build the tables automatically when the file doesn't exist?
Shouldn't there be a way to get the DataContext and create tables using that interface? Hopefully causing LINQPad to update its DataContext at the same time.
The best I've been able to do so far is below, creating DbCommands and executing them on the first run after deleting the sqlite file, then I have to refresh the database, and run it again.
void Main()
{
if (!File.Exists(this.Connection.ConnectionString.Split('=')[1]))
{
"CREATING DATABASE TABLES".Dump();
CREATE_TABLES();
}
else
{
"RUNNING CODE".Dump();
//Code goes here...
}
}
public void CREATE_TABLES()
{
this.Connection.Open();
System.Data.Common.DbCommand sup = this.Connection.CreateCommand();
sup.CommandText = #"create table MainTable
(
MainTableID INTEGER not null PRIMARY KEY AUTOINCREMENT,
FileName nvarchar(500) not null
)";
sup.ExecuteNonQuery();
sup.CommandText = #"create table SubTable
(
SubTableID int not null,
MainTableID int not null,
Count int not null,
primary key (SubTableID, MainTableID),
FOREIGN KEY(MainTableID) REFERENCES MainTable(MainTableID)
)";
//Apparently this version of sqlite doesn't support foreign keys really
sup.ExecuteNonQuery();
this.Connection.Close();
}
Just set the query language dropdown to 'SQL', type in the DDL and hit F5. For instance:
PRAGMA foreign_keys = ON
GO
create table Customer
(
ID int not null primary key,
Name nvarchar(30) not null
)
GO
create table Purchase
(
ID int not null primary key,
CustomerID int null references Customer (ID),
Date datetime not null,
Description varchar(30) not null,
Price decimal not null
)
(Note the syntax for creating foreign key constraints.)
Once you're done, right-click the connection in the Schema Explorer and choose 'Refresh'. You can then switch the query language back to C# Expression or C# Statements and start querying in a proper query language :)

Foreign key definition in sqlite

Cant add foreign key constraint in sqlite ...........
As of SQLite 3.6.19, SQLite supports foreign keys. You need to enable them via:
sqlite> PRAGMA foreign_keys = ON;
They are turned off by default for backwards compatibility.
See the documentation for more details.
sqlite does not enforce foreign key constraints.
in sqlite 3 :
examples :
create table student (_id integer autoincrement primary key ,master_id integer);
create table master (_id integer autoincrement primary key , name varchar(30) );
select * from student where master_id in (select _id from master where name like '...')
Don not need foreign key (master_id) references master(_id) ;
:)

Resources