PostgreSQL eklenti geliştirme rehberi

Selamlar, bu yazımda PostgreSQL'in en sevdiğim taraflarından birine, eklenti (extension) sistemine bakacağız. Çoğumuz CREATE EXTENSION pg_trgm ya da CREATE EXTENSION postgis yazarken arka planda neler olduğunu çok düşünmüyoruz - oysa orası, PostgreSQL'i 'sadece bir veritabanı' olmaktan çıkarıp adeta bir uygulama platformuna çeviren yer. Bence her backend geliştiricinin hayatında en az bir kez kendi eklentisini yazıp \dx listesinde görmesi lazım. O zaman dalalım.

Eklenti tam olarak nedir?

Eklenti, bir grup SQL nesnesini (fonksiyon, tip, operatör, cast, index sınıfı) tek bir paket gibi kurup, sürümleyip, kaldırabilmenizi sağlayan resmi PostgreSQL mekanizması. Yani projenize 12 tane fonksiyon CREATE FUNCTION'la atmak yerine, hepsini myext adı altında toplar; tek CREATE EXTENSION myext ile kurulur, pg_dump bunu otomatik yakalar, ALTER EXTENSION ... UPDATE ile sürüm atlatırsınız.

Düz SQL script'leriyle farkı şurada belli oluyor:

  • Migration sırasında bağımlılığı PostgreSQL biliyor; siz takip etmiyorsunuz.
  • pg_dump eklenti içindeki nesneleri ayrı ayrı dump etmez, sadece CREATE EXTENSION satırını koyar. Geri yüklemede çok temiz.
  • Sürüm dosyaları (myext--1.0.sql, myext--1.0--1.1.sql) sayesinde upgrade rotanız git'te dursun.

Dosya iskeleti

Bir eklenti aslında dört-beş dosyadan ibaret. Tipik yerleşim şöyle:

myext/
  myext.control          # metaveri
  myext--1.0.sql         # ilk surum SQL
  myext--1.0--1.1.sql    # upgrade scripti
  Makefile               # PGXS yapilandirmasi
  src/myext.c            # opsiyonel C kaynak

control dosyası ve en az bir SQL dosyası zorunlu. Geri kalanı, ihtiyaca göre. C tarafına girmediğiniz sürece dünyanın en mütevazı projesi - ben de buradan başlamanızı öneririm.

Control dosyasi

Bu dosya PostgreSQL'e 'sen bu eklenti hakkında neyi nereden okuyacaksın' diyor. En sade haliyle şuna benziyor:

comment = 'JSON yardimci fonksiyonlari'
default_version = '1.0'
relocatable = true
trusted = true
superuser = false

trusted = true PostgreSQL 13 ile gelen güzel bir özellik: superuser olmayan rol bile (yetkisi varsa) eklentiyi kurabiliyor. Ama dikkat - sadece SQL ve güvenli PL/pgSQL ile yazılmış eklentilerde bunu işaretleyin. C kodu çağıran bir şey için trusted yazmayın, üretimde başınız yanar.

SQL-only bir ornek

Hadi minimum çalışan bir örnek görelim. jsontools--1.0.sql içeriği:

\echo Use 'CREATE EXTENSION jsontools' to load this file. \quit

CREATE OR REPLACE FUNCTION json_get_path(
    data jsonb,
    path text[],
    fallback jsonb DEFAULT NULL
)
RETURNS jsonb
LANGUAGE plpgsql IMMUTABLE
AS $$
BEGIN
    RETURN COALESCE(data #> path, fallback);
END;
$$;

COMMENT ON FUNCTION json_get_path(jsonb, text[], jsonb)
    IS 'Verilen yolda deger yoksa fallback dondurur.';

İlk satırdaki \echo ... \quit kalıbı çok önemli. Birisi yanlışlıkla psql -f jsontools--1.0.sql çalıştırırsa - ki bu sıkça yapılan bir hata - script orada durur ve uyarır. Eklenti dosyalarını CREATE EXTENSION çağırmadan elle yüklememek lazım, yoksa nesneler eklentiye bağlı olmadan oluşur ve pg_dump bunları kaybeder.

Karşılığında bir de şu Makefile yeter:

EXTENSION = jsontools
DATA = jsontools--1.0.sql
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)

make install deyip psql'de CREATE EXTENSION jsontools çağırdığınızda fonksiyonunuz hazır. Açıkçası ben PGXS'i ilk gördüğümde 'bu kadar mı?' diye şaşırmıştım - PostgreSQL ekibi bu kısmı gerçekten zarif bırakmış.

C tarafina ne zaman gecmeli?

PL/pgSQL'le yapılamayacak veya yapılırsa çok yavaş olacak şeyler için. Tipik örnekler: kendi veri tipiniz, özel bir index operator class'ı, harici bir C kütüphanesine sarmalayıcı, veya çok hot path'te çalışacak bir karşılaştırma fonksiyonu. Aksi halde, bence SQL'de kalın - C tarafı palloc / pfree / PG_MODULE_MAGIC / PG_FUNCTION_INFO_V1 gibi bir sürü ritüel istiyor ve bir backend crash sizin sürecinizi değil, tüm DB sunucunuzu götürür.

İşte örnek bir Levenshtein imzası:

PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(levenshtein_distance);

Datum
levenshtein_distance(PG_FUNCTION_ARGS)
{
    text *t1 = PG_GETARG_TEXT_PP(0);
    text *t2 = PG_GETARG_TEXT_PP(1);
    /* hesaplama burada... */
    PG_RETURN_INT32(result);
}

Bellek için malloc değil palloc kullanın - PostgreSQL kendi memory context'lerini yönetiyor; siz malloc çekerseniz transaction sonunda sızıntı kalır.

Sik karsilasilan tuzaklar

  • PG_MODULE_MAGIC unutmak: Modülünüz yüklenirken sessizce reddedilir. Bütün C eklentilerinin tam olarak bir tane bu makroyu içermesi şart.
  • Eski surum SQL dosyasini duzenlemek: myext--1.0.sql'i sonradan değiştirmeyin. Yeni özellikler için myext--1.0--1.1.sql ve myext--1.1.sql yazın. Yoksa pg_upgrade zincirleri tutmaz.
  • SET search_path koymadan SECURITY DEFINER yazmak: Eklenti içinde tanımlı bir SECURITY DEFINER fonksiyon, çağıranın search_path'ini kullanıyorsa ortada tatlı bir güvenlik açığı durur. Fonksiyon başında SET search_path FROM CURRENT veya benzerini koyun.
  • module_pathname yanlis: $libdir/myext yazmazsanız ya da .so dosyası başka isme sahipse PostgreSQL kütüphaneyi bulamaz. PGXS bunu hizalar; manuel kurarken dikkat.
  • Trusted bayragini hafife almak: trusted = true derseniz superuser olmayan rol kurabilir. C tarafı çalıştıran bir eklenti için bu işaret koymayın.

Dogrulama

Eklenti gerçekten kurulmuş mu, hızlı bir kontrol:

SELECT extname, extversion FROM pg_extension WHERE extname = 'jsontools';
\dx+ jsontools

İkinci komut size eklentinin sahiplendiği nesnelerin tam listesini verir. Burada beklediğiniz fonksiyonlar görünmüyorsa muhtemelen CREATE EXTENSION yerine SQL dosyasını elle yüklediniz - drop edip baştan kurun.

Kapanis

Bu yazıda PostgreSQL eklentilerinin neden güçlü olduğuna, control dosyasına, sade bir SQL örneğe ve C tarafına ne zaman geçileceğine baktık. Bana sorarsanız, %90 ihtiyaç PL/pgSQL ile çözülüyor ve C'ye girmek lüzumsuz bir vergi haline geliyor; gerçekten yüksek performans veya yeni bir tip lazımsa konu farklı, ama oraya kadar SQL-only paketinizi versiyonlayın yeter. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.