Creating SQL triggers for full text search index in SQLite - sqlite

I'm trying to create triggers for a regular table to then update a full text index in SQLite, but I'm getting some errors and I'm not sure where I've gone wrong.
The app I'm making is a bookmarking app and the database I save the bookmark data to is created using the following SQL statement:
create table "pages" (
"pageUrl" text not null unique on conflict replace,
"dateCreated" integer not null,
"pageDomain" text not null,
"pageTitle" text null,
"pageText" text null,
"pageDescription" text null,
"archiveLink" text null,
"safeBrowsing" text null,
primary key ("pageUrl")
);
Then the full text search index is created with:
create virtual table fts using fts5(
content='pages',
content_rowid='pageUrl',
pageDomain,
pageTitle,
pageText,
pageDescription
);
So then I'd like to update the fts index when the "pages" table is updated via an insert or a delete.
The trigger I have for insert:
create trigger afterPagesInsert after insert on pages begin
insert into fts(
rowid,
pageDomain,
pageTitle,
pageText,
pageDescription
)
values(
new.pageUrl,
new.pageDomain,
new.pageTitle,
new.pageText,
new.pageDescription
);
end;
The trigger I have for the delete:
create trigger afterPagesDelete after delete on pages begin
insert into fts(
fts,
rowid,
pageDomain,
pageTitle,
pageText,
pageDescription
)
values(
'delete',
old.pageUrl,
old.pageDomain,
old.pageTitle,
old.pageText,
old.pageDescription
);
end;
Here's an example of an sql insert statement I'm using:
insert into "pages" (
"pageUrl",
"dateCreated",
"pageDomain",
"pageTitle",
"pageText",
"pageDescription",
"archiveLink",
"safeBrowsing"
)
values(
'https://www.reddit.com/',
1456465040177,
'reddit.com',
'reddit: the front page of the internet',
'reddit: the front page of the internet',
'reddit: the front page of the internet',
NULL,
NULL
)
And the delete statement:
delete from "pages" where "pageUrl" = 'https://www.reddit.com/'
But, I'm getting an error of SQLITE_MISMATCH: datatype mismatch] errno: 20, code: 'SQLITE_MISMATCH' for both the insert and the delete trigger, which I guess seems to indicate that the wrong data is going in to the wrong column, but I'm not sure why. I've gone through the triggers section in the External Content Tables section in the docs here and I've followed what was listed, so I'm not sure where I'm going wrong.
Any help would be appreciated.
note: I'm using the fts5 version of the SQLite full text search: https://www.sqlite.org/fts5.html

The content_rowid must refer to the rowid of the actual table, i.e., the INTEGER PRIMARY KEY column.

Related

Why view with column alias is not insertable?

CREATE TABLE `projects` (
`stars` integer,
`title` varchar(255)
);
CREATE VIEW projects_view AS SELECT *, title AS name FROM projects;
When I try to insert something:
INSERT INTO `projects_view` (`name`) VALUES ('Name');
I get: ERROR 1471 (HY000): The target table projects_view of the INSERT is not insertable-into
But this works in PostgreSQL.
You cannot insert because by using select * you have two references in the view to a single column.
From the documentation a view is not insertable where
Multiple references to any column of a base table (fails for INSERT, okay for UPDATE, DELETE)
It should work if you name each column:
CREATE VIEW projects_view AS
SELECT stars, title as name FROM projects;
Fiddle example

trigger with insert inside a select

I'm trying to create a trigger in which, only under certain circumstances, an insert is performed on another table. Consider the following code:
create table journal (
pk integer primary key autoincrement,
dsc varchar(10) not null
);
create table users (
name varchar(30) primary key not null
);
create trigger users_ai
after insert on users
begin
select
case
when 1 then
insert into journal(dsc) values('foo')
end;
end;
I get the following error when I run this code:
Error: near line 10: near "insert": syntax error
In production, the "1" in the when clause would be replaced by a more complex expression. I've also tried "true" and get the same results. I've also tried surrounding the insert statement in parens and get the same results. Any ideas how to accomplish what I want?
If you look at the syntax diagram for "CREATE TRIGGER", you'll see your attempt just doesn't match. You can, however, simply use the WHEN branch (without needing FOR EACH ROW):
create trigger users_ai
after insert on users
when 1 begin
insert into journal(dsc) values('foo');
end;
OK, figured it out. Instead of putting a conditional expression in the block of the trigger, I used a when clause. Here's the code that works:
create trigger users_ai
after insert on users when 1
begin
insert into journal(dsc) values('foo');
end;
If that when expression is changed to something that returns false (say 0) then the insert isn't done. In production, the expression will sometimes return true, sometimes false, which, of course, is the point of this code. Thanks everybody!
I think that you want a CASE statement, not a CASE expression.
create trigger users_ai after insert on users
begin
case
when ... then insert into journal(dsc) values('foo');
when ... then ...;
else ...;
end case;
end;
Note: if your trigger needs access to the data that was just inserted, its definition should the for each row option.
You can try to use an INSERT ... SELECT and your expression in the WHERE clause.
...
INSERT INTO journal
(dsc)
SELECT 'foo'
WHERE 1 = 1;
...
1 = 1 needs to be replaced by your Boolean expression.

Do SQLite FTS tables need to be manually populated?

The documentation for SQLite FTS implies that FTS tables should be populated and updated using INSERT, UPDATE, DELETE, etc.
That's what I was doing - adding rows, deleting them, etc., but recently I've noticed that as soon as I create the FTS table, it is automatically populated using the data from the source. I create it this way:
CREATE VIRTUAL TABLE notes_fts USING fts4(content="notes", notindexed="id", id, title, body)
If I add a row to the "notes" table, it is also automatically added to notes_fts. I guess that's what virtual tables are.
But then, why is there a chapter about populating FTS tables? What would even be the point since for example if I delete a row, it will come back if it's still in the source table.
Any idea about this? Do FTS actually need to be populated?
After further reading I found that the FTS table indeed need to be manually kept in sync with the content table. When running the CREATE VIRTUAL TABLE call, the FTS table is automatically populated but after that deletions, insertions and updates have to be done manually.
In my case I've done it using the following triggers:
CREATE VIRTUAL TABLE notes_fts USING fts4(content="notes", notindexed="id", id, title, body
CREATE TRIGGER notes_fts_before_update BEFORE UPDATE ON notes BEGIN
DELETE FROM notes_fts WHERE docid=old.rowid;
END
CREATE TRIGGER notes_fts_before_delete BEFORE DELETE ON notes BEGIN
DELETE FROM notes_fts WHERE docid=old.rowid;
END
CREATE TRIGGER notes_after_update AFTER UPDATE ON notes BEGIN
INSERT INTO notes_fts(docid, id, title, body) SELECT rowid, id, title, body FROM notes WHERE is_conflict = 0 AND encryption_applied = 0 AND new.rowid = notes.rowid;
END
CREATE TRIGGER notes_after_insert AFTER INSERT ON notes BEGIN
INSERT INTO notes_fts(docid, id, title, body) SELECT rowid, id, title, body FROM notes WHERE is_conflict = 0 AND encryption_applied = 0 AND new.rowid = notes.rowid;
END;
According to sqlite document
To delete entry, either
-- Insert a row with rowid=14 into the fts5 table.
INSERT INTO ft(rowid, a, b, c) VALUES(14, $a, $b, $c);
-- Remove the same row from the fts5 table.
INSERT INTO ft(ft, rowid, a, b, c) VALUES('delete', 14, $a, $b, $c);
or
CREATE TRIGGER tbl_ad AFTER DELETE ON tbl BEGIN
INSERT INTO fts_idx(fts_idx, rowid, b, c) VALUES('delete', old.a, old.b, old.c);
END;
To rebuild based on the modified virtual table
INSERT INTO ft(ft) VALUES('rebuild');

SQLite syntax error on insert default value during trigger after

The problem: SQLite yields "near DEFAULT: syntax error" when running this through sqlite3_exec. The insertion works fine outside the trigger, and other statements works inside the trigger, but somehow the DEFAULT VALUES won´t work inside the trigger. Why is this happening?
SQLite code:
CREATE TABLE Symbol (
Label VARCHAR(127) PRIMARY KEY
);
CREATE TABLE Process (
Name INTEGER PRIMARY KEY
);
CREATE TABLE Named_Process_Definition (
Label VARCHAR(127),
Name INTEGER,
FOREIGN KEY (Label) REFERENCES Symbol (Label),
FOREIGN KEY (Name) REFERENCES Process_Definition (Name)
);
CREATE TRIGGER pre_new_named_process BEFORE INSERT ON Named_Process_Definition
BEGIN
INSERT INTO Symbol (Label) VALUES (NEW.Label);
END;
CREATE TRIGGER post_new_named_process AFTER INSERT ON Named_Process_Definition
BEGIN
INSERT INTO Process DEFAULT VALUES;
UPDATE Named_Process_Definition SET Name=last_insert_rowid() WHERE rowid=NEW.rowid;
END;
The triggers are meant to simplify inserting Named_Process_Definitions by automatically generating internal "unnamed" resources such as Process.
sqlite docs state:
The "INSERT INTO table DEFAULT VALUES" form of the INSERT statement is not supported.
You can work around this by inserting a null, e.g.:
CREATE TRIGGER post_new_named_process AFTER INSERT ON Named_Process_Definition
BEGIN
INSERT INTO Process(rowid) VALUES(NULL);
END;

How to add columns between fields or change existing ones in SQL?

Does any body know how to add a column to a table in SQL 2005/2008 between specific fields or after a specific field? And, how about changing the position on existing columns?
Example:
AHADRS, AHCITY, AHZIPC
I would like to add the state between AHCITY and AHZIPC
This is what I've tried so far but they are not working
ALTER TABLE AHASNF00 ADD AHZIPC varchar(max) AFTER AHCITY
ALTER TABLE AHASNF00 ADD AHZIPC varchar(max) FIRST AHZIPC
Any help will be really appreciate it, Thanks.
SqlServer has no concept of "position" when it comes to storing columns.
If you need a different column order, change the position in your select statement.
Usually this is solved by creating a temporary table with a temporary name that has the columns in the order you wish you have them. Then the data is copied from one the current table to another, after which the old table is destroyed and the temporary one renamed back to the original.
This, of course, all has to be coordinated with dropping and readding primary and foreign key references.
For example, this is the change script that SQL Server Management Studio generates when I try adding a column in the middle of other columns on a real life example table called "Term."
/* To prevent any potential data loss issues, you should review this script in detail before running it outside the context of the database designer.*/
BEGIN TRANSACTION
SET QUOTED_IDENTIFIER ON
SET ARITHABORT ON
SET NUMERIC_ROUNDABORT OFF
SET CONCAT_NULL_YIELDS_NULL ON
SET ANSI_NULLS ON
SET ANSI_PADDING ON
SET ANSI_WARNINGS ON
COMMIT
BEGIN TRANSACTION
GO
ALTER TABLE dbo.Term
DROP CONSTRAINT FK_Term_TermUnit
GO
ALTER TABLE dbo.TermUnit SET (LOCK_ESCALATION = TABLE)
GO
COMMIT
BEGIN TRANSACTION
GO
CREATE TABLE dbo.Tmp_Term
(
Id int NOT NULL IDENTITY (1, 1),
Label varchar(50) NOT NULL,
TermUnitId int NOT NULL,
NewColumnInTheMiddle bit NULL,
UnitCount int NOT NULL
) ON [PRIMARY]
GO
ALTER TABLE dbo.Tmp_Term SET (LOCK_ESCALATION = TABLE)
GO
SET IDENTITY_INSERT dbo.Tmp_Term ON
GO
IF EXISTS(SELECT * FROM dbo.Term)
EXEC('INSERT INTO dbo.Tmp_Term (Id, Label, TermUnitId, UnitCount)
SELECT Id, Label, TermUnitId, UnitCount FROM dbo.Term WITH (HOLDLOCK TABLOCKX)')
GO
SET IDENTITY_INSERT dbo.Tmp_Term OFF
GO
ALTER TABLE dbo.TreasuryIndexRate
DROP CONSTRAINT FK_TreasuryIndexRate_Term
GO
ALTER TABLE dbo.TreasuryIndexField
DROP CONSTRAINT FK_TreasuryIndexField_Term
GO
DROP TABLE dbo.Term
GO
EXECUTE sp_rename N'dbo.Tmp_Term', N'Term', 'OBJECT'
GO
ALTER TABLE dbo.Term ADD CONSTRAINT
PK_Term PRIMARY KEY CLUSTERED
(
Id
) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
ALTER TABLE dbo.Term ADD CONSTRAINT
FK_Term_TermUnit FOREIGN KEY
(
TermUnitId
) REFERENCES dbo.TermUnit
(
Id
) ON UPDATE NO ACTION
ON DELETE NO ACTION
GO
COMMIT
BEGIN TRANSACTION
GO
ALTER TABLE dbo.TreasuryIndexField ADD CONSTRAINT
FK_TreasuryIndexField_Term FOREIGN KEY
(
TermId
) REFERENCES dbo.Term
(
Id
) ON UPDATE NO ACTION
ON DELETE NO ACTION
GO
ALTER TABLE dbo.TreasuryIndexField SET (LOCK_ESCALATION = TABLE)
GO
COMMIT
BEGIN TRANSACTION
GO
ALTER TABLE dbo.TreasuryIndexRate ADD CONSTRAINT
FK_TreasuryIndexRate_Term FOREIGN KEY
(
TermId
) REFERENCES dbo.Term
(
Id
) ON UPDATE NO ACTION
ON DELETE NO ACTION
GO
ALTER TABLE dbo.TreasuryIndexRate SET (LOCK_ESCALATION = TABLE)
GO
COMMIT
Unfortunately, I don't believe there is an easy way to accomplish this with T-SQL. You'll have to do something like create a new table with the order you want the columns in, insert the data from the old table into the new table, drop the old table, rename the new table.

Resources