Rilascio della colonna PostgreSQL 9.6 ed effetti collaterali sulle funzioni SQL con CTE


15

Se avessi una tabella con 3 colonne - diciamo A, B e D - e dovessi introdurne una nuova - dì C per sostituire l'attuale posizione di D. Userei il seguente metodo:

  1. Introduci 2 nuove colonne come C e D2.
  2. Copia il contenuto di D in D2.
  3. Elimina D.
  4. Rinomina D2 in D.

Il nuovo ordine sarebbe A, B, C e D.

Ho pensato che fosse una pratica legittima in quanto (finora) non ha prodotto problemi.

Tuttavia, oggi ho riscontrato un problema quando una funzione che eseguiva un'istruzione sulla stessa tabella restituiva il seguente errore:

table row type and query-specified row type do not match

E il seguente dettaglio:

Query provides a value for a dropped column at ordinal position 13

Ho provato a riavviare PostgreSQL, facendo una VACUUM FULLe infine cancellando e ricreando la funzione come suggerito qui e qui, ma queste soluzioni non hanno funzionato (a parte il fatto che provano ad affrontare una situazione in cui una tabella di sistema è stata modificata).

Avendo il lusso di lavorare con un database molto piccolo, l'ho esportato, cancellato e poi reimportato e ciò ha risolto il problema con la mia funzione.


Ero consapevole del fatto che non si dovrebbe scherzare con l'ordine naturale delle colonne modificando le tabelle di sistema (sporcandosi le mani pg_attribute, ecc.) Come visto qui:

È possibile cambiare l'ordine naturale delle colonne in Postgres?

A giudicare dall'errore generato dalla mia funzione, ora mi rendo conto che spostare l'ordine delle colonne con il mio metodo è anche un no-no. Qualcuno può far luce sul perché anche quello che sto facendo è sbagliato?


La versione di Postgres è 9.6.0.

Ecco la funzione:

CREATE OR REPLACE FUNCTION "public"."__post_users" ("facebookid" text, "useremail" text, "username" text) RETURNS TABLE (authentication_code text, id integer, key text, stripe_id text) AS '

-- First, select the user:
WITH select_user AS
(SELECT
users.id
FROM
users
WHERE
useremail = users.email),

-- Second, update the user (if user exists):
update_user AS
(UPDATE
users
SET
authentication_code = GEN_RANDOM_UUID(),
authentication_date = current_timestamp,
facebook_id = facebookid
WHERE EXISTS (SELECT * FROM select_user)
AND
useremail = users.email
RETURNING
users.authentication_code,
users.id,
users.key,
users.stripe_id),

-- Third, insert the user (if user does not exist):
insert_user AS
(INSERT INTO
users (authentication_code, authentication_date, email, key, name, facebook_id)
SELECT
GEN_RANDOM_UUID(),
current_timestamp,
useremail,
GEN_RANDOM_UUID(),
COALESCE(username, SUBSTRING(useremail FROM ''([^@]+)'')),
facebookid
WHERE NOT EXISTS (SELECT * FROM select_user)
RETURNING
users.authentication_code,
users.id,
users.key,
users.stripe_id)

-- Finally, select the authentication code, ID, key and Stripe ID:
SELECT
*
FROM
update_user
UNION ALL
SELECT
*
FROM
insert_user' LANGUAGE "sql" COST 100 ROWS 1
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER

Ho eseguito la ridenominazione / riordino su entrambe le colonne facebook_idestripe_id (prima di queste è stata aggiunta una nuova colonna, che è la ragione della ridenominazione, ma non viene toccata da questa query).

Avere le colonne in un certo ordine non ha alcun interesse per l'ordine. Tuttavia, il motivo per cui si pone questa domanda non è preoccupato che una semplice ridenominazione ed eliminazione di una colonna possa innescare problemi reali per qualcuno che utilizza le funzioni in modalità di produzione (come è successo a me stesso).


Dai un'occhiata a Come posso modificare la posizione di una colonna in una tabella del database PostgreSQL? su Stack Overflow che potrebbe essere di aiuto, anche se suggeriscono principalmente di NON riordinare le colonne.
joanolo,

Risposte:


16

Probabile bug su 9.6 e 9.6.1

Questo mi sembra completamente un bug ...

Non so perché accada, ma posso confermare che succede. Questa è la configurazione più semplice trovata che riproduce il problema (nelle versioni 9.6.0 e 9.6.1).

CREATE TABLE users
(
    id SERIAL PRIMARY KEY,
    email TEXT NOT NULL,
    column_that_we_will_drop TEXT
) ;

-- Function that uses the previous table, and that has a CTE
CREATE OR REPLACE FUNCTION __post_users
    (_useremail text) 
RETURNS integer AS
$$
-- Need a CTE to produce the error. A 'constant' one suffices.
WITH something_even_if_useless(a) AS
(
    VALUES (1)
)
UPDATE
    users
SET
    id = id
WHERE 
    -- The CTE needs to be referenced, if the next
    -- condition were not in place, the problem is not reproduced
    EXISTS (SELECT * FROM something_even_if_useless)
    AND email = _useremail
RETURNING
    id
$$
LANGUAGE "sql" ;

Dopo questa configurazione, la dichiarazione successiva funziona

SELECT * FROM __post_users('a@b.com');

A questo punto, DROP una colonna:

ALTER TABLE users 
    DROP COLUMN column_that_we_will_drop ;

Questa modifica rende l'istruzione successiva per generare un errore

SELECT * FROM __post_users('a@b.com');

che è lo stesso menzionato da @Andy:

ERROR: table row type and query-specified row type do not match
SQL state: 42804
Detail: Query provides a value for a dropped column at ordinal position 3.
Context: SQL function "__post_users" statement 1
    SELECT * FROM __post_users('a@b.com');

Eliminare e ricreare la funzione NON risolve il problema.
VACUUM FULL (la tabella o l'intero database) non risolve il problema.


La segnalazione di bug è stata passata alla mailing list PostgreSQL appropriata e abbiamo avuto una risposta molto veloce :

Non riesco a riprodurre questo in HEAD o 9.6 branch tip. Credo che sia già stato risolto da questa patch, che è andata un po 'dopo 9.6.1:

https://git.postgresql.org/gitweb/?p=postgresql.git&a=commitdiff&h=f4d865f22

Ma grazie per il rapporto!

saluti, Tom Lane


Versione 9.6.2

Il 06-03-2017, posso confermare che non riesco a riprodurre questo comportamento nella versione 9.6.2. Cioè, il bug sembra essere stato corretto in questa versione.

AGGIORNARE

Per commento di @Jana: "Posso confermare che il bug è presente in 9.6.1 ed è stato corretto in 9.6.2. La correzione è anche elencata sul sito Web di rilascio di postgres : Correzione di errori" query fornisce un valore per una colonna rilasciata "durante INSERISCI o AGGIORNA su una tabella con una colonna rilasciata "



4
Sto usando 9.6.0 e @joanolo è corretto, posso riprodurre il bug con il suo metodo. Se Tom non è in grado di riprodurlo, probabilmente era un bug isolato con questa versione specifica (e 9.6.1 suppongo?). Come indicato nella risposta, il problema appare quando rilascia una colonna in una tabella e si utilizza una funzione con CTE che influisce su quella tabella. Pertanto, il problema non riguarda solo il riordino ed è quello che stavo cercando di ottenere con la mia domanda. Modificherò il titolo per riflettere questo.
Andy,

Posso confermare che il bug è presente in 9.6.1 ed è stato corretto in 9.6.2. La correzione è anche elencata sul sito Web di rilascio di postgres : Correzione di errori "query fornisce un valore per una colonna eliminata" spuri durante INSERT o AGGIORNAMENTO su una tabella con una colonna eliminata
Jana,

0

So che probabilmente l'hai già sentito prima, ma questa è un'idea orribile.

  • L'ordinamento logico influenza solo cose come SELECT *
  • L'effetto dell'ordinamento logico è limitato all'apparenza.

Quindi se non importa affatto non ti dissuade e riconosciamo che stiamo solo giocando a Photoshop con la struttura delle righe e ossessionando la visualizzazione, spieghiamo alcune altre cose.

  • L'ordinamento logico non esiste in PostgreSQL
  • L'ordinamento fisico ha benefici reali (imballaggio da tavola)
  • PostgreSQL non ti dà alcun controllo sull'ordine fisico dopo CREATE TABLEentrambi (anche se sarebbe una priorità molto più alta)

Quindi PostgreSQL è un cattivo livello di visualizzazione. Su tutto ciò, mentre ALTERfunziona benissimo non dovresti tentare il drago. Entrambi ALTER TABLEe la sezione CAVEAT ne fanno menzione,

Alcuni comandi DDL, attualmente solo TRUNCATEe le forme di riscrittura delle tabelle ALTER TABLE, non sono sicuri per MVCC. Ciò significa che dopo il troncamento o la riscrittura dei commit, la tabella apparirà vuota per le transazioni simultanee, se utilizzano uno snapshot prima del commit del comando DDL. Questo sarà solo un problema per una transazione che non ha avuto accesso alla tabella in questione prima dell'inizio del comando DDL - qualsiasi transazione che lo ha fatto avrebbe almeno un blocco della tabella ACCESS SHARE, che bloccherebbe il comando DDL fino al completamento della transazione. Pertanto, questi comandi non causeranno alcuna incoerenza apparente nel contenuto della tabella per successive query sulla tabella di destinazione, ma potrebbero causare incoerenza visibile tra il contenuto della tabella di destinazione e altre tabelle nel database.

E, se tutto ciò non è abbastanza, e vuoi ancora far finta che questa sia una buona idea piuttosto un'idea orribile. Quindi prova questo,

  1. Scarica il tavolo con pg_dump -t
  2. Riordina manualmente le colonne nella discarica.
  3. Carica il materiale in una tabella TEMP.
  4. BEGIN una transazione
  5. DROP il vecchio tavolo interamente,
  6. RENAME la tabella temporanea alla tabella prod.
  7. COMMIT

Se tutto ciò sembra eccessivo, tieni presente che l'aggiornamento delle righe nel database richiede la riscrittura delle righe (supponendo che non siano TOAST . Devi analizzare i dati e ricostruire lo schema della tabella, ma in entrambi i casi devi riscrivere la fila. Se mi avesse fare questo compito, è così che lo farei.

Ma tutto questo in generale. Nessuno ha riprodotto i tuoi risultati.

Hai fornito un caso di prova che non possiamo eseguire

ERROR:  column users.authentication_code does not exist
LINE 24: users.authentication_code,

E non ci hai detto la versione esatta in cui ti trovi.


Grazie per la spiegazione approfondita, ho modificato la domanda per includere la versione specifica.
Andy,

4
Il bug è riproducibile nella versione 9.6.0
ypercubeᵀᴹ

0

Ho aggirato questo bug eseguendo il backup e il ripristino del mio database.

Passaggi per Heroku

  • Attiva modalità di manutenzione: heroku maintenance:on
  • Eseguire il backup del database: heroku pg:backups:capture
  • Ripristina database: heroku pg:backups:restore
  • Riavvia app: heroku restart
  • Disattiva la modalità di manutenzione: heroku maintenance:off

0

Mi sono imbattuto anche in questo bug. Per coloro che non desiderano eseguire il backup / ripristino completo del proprio DB. Sappi che semplicemente copiare la tabella funziona. Non esiste un modo "magico" per copiare un tavolo. L'ho fatto usando:

SELECT * INTO mytable_copy FROM mytable;
ALTER TABLE mytable RENAME TO mytable_backup; -- just in case. you never know
ALTER TABLE mytable_copy RENAME TO mytable;

Successivamente, dovrai comunque ricreare manualmente gli indici, le chiavi esterne e i valori predefiniti. Ricreare la tabella in questo modo ha fatto scomparire il bug.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.