12-step migration fails for views referencing the altered table
(1) By Simon Binder (sbinder) on 2022-12-16 14:20:54 [source]
I'm carefully following the 12-step general table migration procedure. Unfortunately, I cannot seem to get the rename step to work if an existing view is referencing the old table. As an example:
-- before: Create original schema
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);
CREATE VIEW user_names AS SELECT NAME FROM users;
-- begin 12-step migration procedure. We can skip step 1 assuming that FKs are disabled
BEGIN; -- step 2
-- step 3: We remember to re-create user_names later
CREATE TABLE new_users (id INTEGER PRIMARY KEY, name TEXT DEFAULT 'no name set'); --step 4
INSERT INTO new_users SELECT * FROM users; -- step 5
DROP TABLE users; -- step 6
ALTER TABLE new_users RENAME to users; -- step 7
! Runtime error: error in view user_names: no such table: main.users
From my reading, I think I've followed the procedure as described in the documentation. And yet, it fails and I can't complete the migration. The step where I re-create views is supposed to happen afterwards, but I can't even get there. It seems like I have to drop all views referencing the affected table after step 3 before re-creating them. Should this be mentioned in the documentation or is there something else that I'm doing wrong?
(2) By ddevienne on 2022-12-16 14:48:02 in reply to 1 [link] [source]
Try it again with pragma legacy_alter_table
, and see what happens.
That doc was likely written before better tracking of dependencies between objects,
and restoring the old behavior for your upgrade might help. --DD
PS: I'll defer to the experts for what to do w/o that pragma.
(3) By anonymous on 2022-12-17 13:07:54 in reply to 1 [link] [source]
You might also be interested on this post https://sqlite.org/forum/forumpost/1d904235f5
The change you mention would be easily done via that writable schema way.
(4) By Simon Binder (sbinder) on 2022-12-18 21:17:11 in reply to 2 [link] [source]
Thanks for the suggestion! Using the legacy_alter_table
pragma fixes the issue, but ideally I'd want a solution that doesn't require me to enable pragmas the documentation discourages me from enabling. So I'll wait for what the experts have to say then ;)
It's also interesting (at least to me) that this only appears to be broken with views - creating an index or a trigger on the affected table does not prevent the migration procedure from succeeding.
(5.2) By Keith Medcalf (kmedcalf) on 2022-12-18 21:45:05 edited from 5.1 in reply to 4 [link] [source]
Once you drop the table users you have an "inconsistent schema". This prevents the ALTER TABLE command from functioning because it is designed to transmorgify the schema from one valid state to another valid state by applying the changes specified in the command. If either the before or after states are invalid, then the command cannot be executed (will fail with an error).
If you are knowingly and with aforethought transitioning from an invalid state to another state which may or may not be invalid, then you need to turn off the consistency checking by using the legacy_alter_table pragma which exists, solely for the purpose, of allowing you to knowingly use ALTER TABLE with an invalid starting or ending state: this also happens to be how the ALTER TABLE command worked before it was changed to work properly -- thus the name "legacy".
After you drop the table users you must also drop any views which refer to that table. Indexes and triggers that refer to the table are dropped automatically. views cannot be dropped automatically because they are not compiled statements stored in the internal dictionary. They are merely textual substitutions.
If you fail to make the schema consistent, then you will only be able to modify it with legacy_alter_table enabled.
(6) By Chris Locke (chrisjlocke1) on 2022-12-18 21:53:15 in reply to 4 [link] [source]
ideally I'd want a solution that doesn't require me to enable pragmas
Delete views. Follow steps. Recreate views.
(7) By Keith Medcalf (kmedcalf) on 2022-12-18 22:01:46 in reply to 5.2 [link] [source]
I will note, however, that the very same procedure that is used to detect that the "starting state" is invalid could be used to get rid of the view when the table is dropped.
I am quite sure, however, that this would result in quite the volume of complaints that "I used to have this 47 page long view that used another 47 page long view that used another 47 page long view, each of which used another smattering of 47 page views. I dropped a table and the whole kaboodle of views was deleted!"
It could also be used to prevent the dropping of the table which I am sure would generate just as many complaints.
So the solution there would be to add yet another pragma to determine whether to "cascade table drops though views" or not. Which would yet surely generate quite the volume of complaints.
In each and all of the above cases the answer is simply that the "changer of the schema" should know what it is that it is doing, why, and the implications of their actions.
Having the software ask "Are you Sure?" over and over again when presented with a command to carry out is folly. If you are not sure then you would not have given the command you did, would you not have?
(8) By ddevienne on 2022-12-19 06:44:24 in reply to 7 [link] [source]
this would result in quite the volume of complaints that
Why? Dropping dependent views is typically opt-in and explicit, with the CASCADE
keyword required.
Case in point in PostgreSQL. No need for a pragma
or an are-you-sure question.
(9) By ddevienne on 2022-12-19 07:29:41 in reply to 6 [link] [source]
Personally I'd vote for better ALTER TABLE
support instead :)
Because the OP's issue is entirely due to it, since the documented work-around no longer works.
Only proper ALTER TABLE
can atomically edit a table schema w/o affecting dependent objects,
or affecting only the subset that depend on what is being edited.
This is one of the last remaining weak point of SQLite, I think.
Along the years, I've seen SQLite gain so many features, some very large, so there's still hope :).
There's a long tradition on the ML/forum of Xmas wishes, this is mine :).
PS: My other wish is basically built-in TOAST in SQLite, with efficient subsetting like PostgreSQL does,
PS: i.e. substr(blob, off, len)
can read only the necessary shard(s) in O(1)
instead of O(N)
PS: once the row has been located (in O(log(N))
, w/o having to follow a long linked-list of overflow pages.
PS: Which would also avoid rewriting the whole row and its huge blob(s) when tiny values of that row change.
(10) By Keith Medcalf (kmedcalf) on 2022-12-19 10:04:31 in reply to 8 [link] [source]
That is a proprietary extension. I assume (although it is not stated) that the default in PostgreSQL is RESTRICT. SQLite3 currently defaults to the IGNORE option.
(11) By ddevienne on 2022-12-19 11:43:21 in reply to 10 [link] [source]
SQLite3 currently defaults to the IGNORE option.
There's no such option that I can see in the doc for DROP TABLE
.
IF EXISTS
is something else, and explicitly opt-in.
Furthermore, that doc mentions the implicit cascade to indexes and triggers,
but does not say anything about dependent objects, i.e. views or triggers (on other tables) using the dropped table.
That doc perhaps needs a refresh?
(12) By Keith Medcalf (kmedcalf) on 2022-12-19 16:19:44 in reply to 11 [link] [source]
Of course not. That is why it is invisible and the current default.
Dependent objects are not modified:
sqlite> create table x(x);
sqlite> create table y(x);
sqlite> create trigger x_bi before insert on x begin insert into y(x) values (new.x); end;
sqlite> .schema
CREATE TABLE x(x);
CREATE TABLE y(x);
CREATE TRIGGER x_bi before insert on x begin insert into y(x) values (new.x); end;
sqlite> drop table y;
sqlite> .schema
CREATE TABLE x(x);
CREATE TRIGGER x_bi before insert on x begin insert into y(x) values (new.x); end;
sqlite> create table y(x);
sqlite> drop table x;
sqlite> .schema
CREATE TABLE y(x);
sqlite>
nor are dependent referrents:
sqlite> create table x(x integer primary key);
sqlite> create table y(x integer references x);
sqlite> .schema
CREATE TABLE x(x integer primary key);
CREATE TABLE y(x integer references x);
sqlite> drop table x;
sqlite> .schema
CREATE TABLE y(x integer references x);
sqlite> create table x(x integer primary key);
sqlite> .schema
CREATE TABLE y(x integer references x);
CREATE TABLE x(x integer primary key);
sqlite> drop table y;
sqlite> .schema
CREATE TABLE x(x integer primary key);
sqlite>
nor views either
sqlite> create table x(x);
sqlite> create view y as select x from x;
sqlite> .schema
CREATE TABLE x(x);
CREATE VIEW y as select x from x
/* y(x) */;
sqlite> drop table x;
sqlite> .schema
CREATE VIEW y as select x from x;
sqlite>
(13) By PaulK (paulclinger) on 2023-01-18 23:02:23 in reply to 5.2 [link] [source]
I'm considering using "legacy_alter_table", but would still try to avoid it if possible and have been wondering how step 3 in the 12-step migration can be done.
The documentation states "Remember the format of all indexes, triggers, and views associated with table X.", but I don't see how to find all the views associated with table X, as the suggested query ("SELECT type, sql FROM sqlite_schema WHERE tbl_name='X'") doesn't seem to return views created as "CREATE VIEW Y AS SELECT * FROM X". What am I missing?
(14.1) By Larry Brasfield (larrybr) on 2023-01-19 09:39:45 edited from 14.0 in reply to 13 [link] [source]
(Edited to change a clearly false assertion to what is true.)
... What am I missing?
A couple of facts: (1) The sqlite_schema table has no columns with which to discover tables involved in a view1; and (2) Its "name" column for a view named "Y" will be "Y", not "X".
You would have to parse the "sql" column content for views which involve "X".
- ^ The "tbl_name" column only names a table covered by an index when the type is 'index' and is otherwise adds no information.
(15) By PaulK (paulclinger) on 2023-01-18 23:58:04 in reply to 14.0 [link] [source]
A couple of facts: (1) The sqlite_schema table has no column named "tbl_name"; and (2) Its "name" column for a view named "Y" will be "Y", not "X".
I copied the example straight from the documentation and it does work for me. I was using X to get all the objects related to table X:
SQLite version 3.40.0 2022-11-16 12:10:08
Enter ".help" for usage hints.
Connected to a ←[1mtransient in-memory database←[0m.
Use ".open FILENAME" to reopen on a persistent database.
sqlite>: create table x(x);
sqlite>: create index foo on x(x);
sqlite>: SELECT name,tbl_name,type,sql FROM sqlite_schema WHERE tbl_name='x';
x|x|table|CREATE TABLE x(x)
foo|x|index|CREATE INDEX foo on x(x)
You would have to parse the "sql" column content for views which involve "X".
That's what I suspected and just wanted to confirm, as the documentation gave me the impression that there is a way to track the dependencies without parsing. Thank you.
(16.1) By ddevienne on 2023-01-19 09:09:54 edited from 16.0 in reply to 14.0 [link] [source]
(1) The sqlite_schema table has no column named
"tbl_name"
;
Uh?
sqlite> create table t(v);
sqlite> select * from sqlite_schema;
┌───────┬──────┬──────────┬──────────┬───────────────────┐
│ type │ name │ tbl_name │ rootpage │ sql │
├───────┼──────┼──────────┼──────────┼───────────────────┤
│ table │ t │ t │ 2 │ CREATE TABLE t(v) │
└───────┴──────┴──────────┴──────────┴───────────────────┘
(17) By Larry Brasfield (larrybr) on 2023-01-19 09:33:57 in reply to 16.1 [link] [source]
"Uh?" indeed. Even "Duh". That started as a longer sentence regarding absence of a means to enumerate participating tables in sqlite_schema. But it's certainly nonsense now, soon to be edited.