PostgreSQL 9.6 kolom menjatuhkan dan efek samping pada fungsi SQL dengan CTE

15

Jika saya memiliki tabel dengan 3 kolom - katakan A, B dan D - dan saya harus memperkenalkan yang baru - katakan C untuk menggantikan posisi D. Saat ini saya akan menggunakan metode berikut:

  1. Perkenalkan 2 kolom baru sebagai C dan D2.
  2. Salin konten D ke D2.
  3. Hapus D.
  4. Ganti nama D2 menjadi D.

Pesanan baru adalah A, B, C dan D.

Saya pikir ini adalah praktik yang sah karena (sejauh ini) tidak menghasilkan masalah.

Namun, hari ini saya menemukan masalah ketika fungsi yang menjalankan pernyataan pada tabel yang sama mengembalikan kesalahan berikut:

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

Dan detail berikut:

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

Saya mencoba me-restart PostgreSQL, melakukan VACUUM FULLdan akhirnya menghapus dan menciptakan kembali fungsi seperti yang disarankan di sini dan di sini tetapi solusi ini tidak bekerja (selain dari fakta bahwa mereka mencoba menangani situasi di mana tabel sistem telah diubah).

Memiliki kemewahan bekerja dengan basis data yang sangat kecil saya mengekspornya, menghapusnya dan kemudian mengimpornya kembali dan itu memperbaiki masalah dengan fungsi saya.


Saya menyadari fakta bahwa seseorang tidak boleh dipusingkan dengan urutan kolom alami dengan memodifikasi tabel sistem (menjadi kotor dengan pg_attribute, dll.) Seperti terlihat di sini:

Apakah mungkin mengubah urutan alami kolom di Postgres?

Menilai oleh kesalahan yang dilemparkan oleh fungsi saya, saya sekarang menyadari bahwa mengubah urutan kolom dengan metode saya juga tidak-tidak. Adakah yang bisa menjelaskan mengapa apa yang saya lakukan juga salah?


Versi Postgres adalah 9.6.0.

Inilah fungsinya:

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

Saya melakukan penggantian nama / pemesanan ulang pada kedua kolom facebook_iddan stripe_id(kolom baru ditambahkan sebelum ini, yang merupakan alasan untuk penggantian nama, tetapi tidak tersentuh oleh permintaan ini).

Memiliki kolom dalam urutan tertentu murni tidak menarik untuk pesanan. Namun, alasan untuk mengajukan pertanyaan ini adalah karena kekhawatiran bahwa penggantian nama sederhana dan penghapusan kolom dapat memicu masalah nyata bagi seseorang yang menggunakan fungsi dalam mode produksi (seperti yang terjadi pada diri saya).

Andy
sumber
Lihatlah Bagaimana cara mengubah posisi kolom dalam tabel database PostgreSQL? pada Stack Overflow yang mungkin bisa membantu, meskipun sebagian besar menyarankan TIDAK untuk menyusun ulang kolom Anda.
joanolo

Jawaban:

16

Kemungkinan bug pada 9.6 dan 9.6.1

Ini benar-benar terlihat seperti bug bagi saya ...

Saya tidak tahu mengapa itu terjadi, tetapi saya bisa memastikan itu terjadi. Ini adalah pengaturan yang ditemukan paling sederhana yang mereproduksi masalah (dalam versi 9.6.0 dan 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" ;

Setelah pengaturan ini, pernyataan berikutnya hanya berfungsi

SELECT * FROM __post_users('[email protected]');

Pada titik ini, kami DROP satu kolom:

ALTER TABLE users 
    DROP COLUMN column_that_we_will_drop ;

Perubahan ini membuat pernyataan berikutnya untuk menghasilkan kesalahan

SELECT * FROM __post_users('[email protected]');

yang sama dengan yang disebutkan oleh @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('[email protected]');

Menjatuhkan dan membuat ulang fungsi TIDAK menyelesaikan masalah.
VACUUM FULL (tabel atau seluruh database) tidak menyelesaikan masalah.


Laporan bug dikirimkan ke milis PostgreSQL yang sesuai dan kami mendapat respons yang sangat cepat :

Saya tidak dapat mereproduksi ini dalam HEAD atau 9,6 tip cabang. Saya percaya ini sudah diperbaiki oleh tambalan ini, yang masuk sedikit setelah 9.6.1:

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

Tapi terima kasih atas laporannya!

salam, tom lane


Versi 9.6.2

Pada 2017-03-06, saya dapat mengonfirmasi bahwa saya tidak dapat mereproduksi perilaku ini pada versi 9.6.2. Artinya, bug tersebut sepertinya telah diperbaiki pada rilis ini.

MEMPERBARUI

Per komentar dari @Jana: "Saya dapat mengonfirmasi bug ada di 9.6.1 dan diperbaiki di 9.6.2. Perbaikan juga tercantum di situs web rilis postgres : Perbaiki palsu" kueri memberikan nilai untuk kolom yang dijatuhkan "kesalahan selama INSERT atau PEMBARUAN di atas meja dengan kolom yang dijatuhkan "


joanolo
sumber
4
Saya menggunakan 9.6.0 dan @joanolo benar, saya dapat mereproduksi bug dengan metodenya. Jika Tom tidak dapat mereproduksinya, itu mungkin bug yang terisolasi dengan versi spesifik ini (dan 9.6.1 saya berasumsi?). Seperti disebutkan dalam jawabannya, masalah muncul ketika menjatuhkan kolom dalam sebuah tabel dan menggunakan fungsi dengan CTE mempengaruhi yang meja. Dengan demikian, masalahnya bukan hanya dengan pemesanan ulang dan itulah yang saya coba selesaikan dengan pertanyaan saya. Saya akan mengedit judul untuk mencerminkan ini.
Andy
Saya dapat mengonfirmasi bahwa bug ada di 9.6.1 dan diperbaiki di 9.6.2. Perbaikan ini juga tercantum di situs web rilis postgres : Memperbaiki kesalahan "kueri memberikan nilai untuk kolom yang dijatuhkan" selama INSERT atau UPDATE pada tabel dengan kolom yang dijatuhkan
Jana
0

Saya tahu Anda mungkin pernah mendengar ini sebelumnya, tetapi ini adalah ide yang mengerikan.

  • Pemesanan logis hanya mempengaruhi hal-hal seperti SELECT *
  • Efek dari pemesanan logis terbatas pada penampilan.

Jadi jika tidak penting sama sekali tidak menghalangi Anda dan kami mengakui bahwa kami hanya bermain Photoshop dengan struktur baris dan terobsesi pada tampilan, mari kita jelaskan beberapa hal lagi.

  • Pemesanan logis tidak ada di PostgreSQL
  • Pemesanan fisik memiliki manfaat nyata (pengepakan meja)
  • PostgreSQL tidak memberi Anda kontrol atas Pemesanan Fisik setelah CREATE TABLEkeduanya (meskipun itu akan menjadi prioritas yang jauh lebih tinggi)

Jadi PostgreSQL adalah lapisan tampilan yang buruk. Ke semua itu, saat ALTERbekerja dengan baik Anda tidak harus temp naga Keduanya ALTER TABLE, dan bagian CAVEAT menyebutkan hal ini,

Beberapa perintah DDL, saat ini hanya TRUNCATEdan bentuk penulisan ulang tabel ALTER TABLE, tidak aman untuk MVCC. Ini berarti bahwa setelah pemotongan atau penulisan ulang melakukan, tabel akan tampak kosong untuk transaksi bersamaan, jika mereka menggunakan snapshot yang diambil sebelum perintah DDL dilakukan. Ini hanya akan menjadi masalah untuk transaksi yang tidak mengakses tabel yang dipermasalahkan sebelum perintah DDL dimulai - setiap transaksi yang telah melakukannya akan menahan setidaknya kunci tabel ACCESS SHARE, yang akan memblokir perintah DDL sampai transaksi selesai. Jadi perintah-perintah ini tidak akan menyebabkan ketidakkonsistenan yang tampak dalam isi tabel untuk kueri berturut-turut pada tabel target, tetapi mereka dapat menyebabkan ketidakkonsistenan yang terlihat antara konten tabel target dan tabel lain dalam database.

Dan, jika semua itu tidak cukup, dan Anda masih ingin berpura-pura bahwa ini adalah ide yang baik, bukan ide yang mengerikan. Lalu coba ini,

  1. Buang meja dengan pg_dump -t
  2. Susun ulang kolom dengan tangan di tempat sampah.
  3. Muat barang-barang ke dalam tabel TEMP.
  4. BEGIN sebuah transaksi
  5. DROP meja lama seluruhnya,
  6. RENAME tabel temp ke tabel prod.
  7. COMMIT

Jika semua itu terdengar berlebihan, perlu diingat bahwa memperbarui baris dalam database memerlukan penulisan ulang baris (dengan asumsi mereka tidak TOAST . Anda harus mengurai data dan membangun kembali skema tabel, tetapi Anda harus menulis ulang Jika saya harus melakukan tugas ini, itulah yang akan saya lakukan.

Tetapi, semua ini berbicara secara umum. Belum ada yang mereproduksi hasil Anda.

Anda telah memberikan test case yang tidak dapat kami jalankan

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

Dan, Anda belum memberi tahu kami versi persisnya .

Evan Carroll
sumber
Terima kasih atas penjelasannya, saya telah mengedit pertanyaan untuk menyertakan versi spesifik.
Andy
4
Bug ini dapat direproduksi dalam versi 9.6.0
ypercubeᵀᴹ
0

Saya mengatasi bug ini dengan mencadangkan dan memulihkan basis data saya.

Langkah-langkah untuk Heroku

  • Aktifkan mode pemeliharaan: heroku maintenance:on
  • Membuat cadangan basis data: heroku pg:backups:capture
  • Pulihkan basis data: heroku pg:backups:restore
  • Mulai ulang aplikasi: heroku restart
  • Matikan mode perawatan: heroku maintenance:off
ma11hew28
sumber
0

Saya juga menemukan bug ini. Bagi mereka yang tidak ingin sepenuhnya mencadangkan / mengembalikan DB mereka. Ketahuilah bahwa hanya menyalin tabel berfungsi. Tidak ada cara "ajaib" untuk menyalin tabel. Saya melakukannya dengan menggunakan:

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;

Setelah itu, Anda masih perlu membuat ulang indeks, kunci asing, dan default secara manual. Membuat ulang meja seperti ini membuat bug menghilang.

Thibauld
sumber