Psycopg: Cursor & Row Factory

Postingan terakhir mengenai cursor factory di sqlite bikin saya penasaran dengan fitur serupa tapi untuk database postgre kebetulan kerjaan saya menggunakan postgre, jadi, munculah tulisan ini.

Saat mencari referensi saya menemukan di tutorial di internet banyak yang masih mengunakan psycopg2 padahal psycopg3 sudah rilis, jadi daripada pusing coba saja dulu dua-duanya.

Kode Awal

Psycopg2

kode awal saya buat seperti ini

import psycopg2

# Connect to PostgreSQL database
conn = psycopg2.connect(
    dbname="dbnames",
    user="users",
    password="passwords",
    host="localhost",
    port="5432",
)
cursor = conn.cursor()
query = "SELECT id, isbn, title FROM books"

cursor.execute(query)
rows = cursor.fetchall()
for r in rows:
    print(f"{r[0]} - {r[1]} - {r[2]}")
    
# Result
1 - 4449623391 - Book 5210 by Author 5
2 - 1717473490 - Book 1800 by Author 5
3 - 1117078207 - Book 4719 by Author 2
4 - 4022514182 - Book 4644 by Author 2
5 - 1063894849 - Book 517 by Author 4

Dictionary

Seperti argumen di tulisan sebelumnya, menggunakan nomor untuk menampilkan data bukanlah hal yang menyenangkan jadi mari ubah agar setidaknya bisa mengakses nilai menggunakan nama field.

Ubah kode menjadi seperti berikut

import psycopg2
from psycopg2 import extras # Tambahkan ini

# Connect to PostgreSQL database
conn = psycopg2.connect(
    dbname="dbnames",
    user="users",
    password="passwords",
    host="localhost",
    port="5432",
)
cursor = conn.cursor(cursor_factory=extras.DictCursor) #ubah jadi seperti init
query = "SELECT id, isbn, title FROM books"

cursor.execute(query)
rows = cursor.fetchall()
for r in rows:
    print(f"{r['id']} - {r['isbn']} - {r['title']}")

Terdapat dua perbedaan:

  1. Saya melakukan import extras
  2. berbeda dengan sqlite di psycopg2 diberi nama cursor_factory

Jika di sqlite saya bisa menemukan untuk mengubah hasil query menjadi objek dataclass di sini saya tidak menemukannya tapi ada hal yang bisa menghasilkan bagaimana hasil dari query bisa diakses seperti book.title yaitu dengan cara NamedTuplleCursor.

NamedTuppleCursor

Untuk mengubah cara di atas menjadi namedTuplle cukup bagian ini yang diubah

cursor = conn.cursor(cursor_factory=extras.NamedTupleCursor)
.
.
.
for r in rows:
    print(f"{r.id} - {r.isbn} - {r.title}")

Kembali dengan 3 cara di atas saya bandingkan mendapatkan hasil

namedtuple 
64437 function calls (63610 primitive calls) in 0.097 seconds

dictionary 
194371 function calls (193544 primitive calls) in 0.218 seconds

default
34365 function calls (33538 primitive calls) in 0.085 seconds

Psycopg3

Ubah kode awal hanya saja saya menggunakan Psycopg3 dibanding 2.

import psycopg

# Connect to PostgreSQL database
conn = psycopg.connect(
    dbname="dbanames",
    user="users",
    password="passwords",
    host="localhost",
    port="5432",
)
cursor = conn.cursor()
query = "SELECT id, isbn, title FROM books"

cursor.execute(query)
rows = cursor.fetchall()
for r in rows:
    print(f"{r[0]} - {r[1]} - {r[2]}")

Bisa dilihat penggunaan default hampir tidak ada bedanya kecuali yang sebelumnya bernama psycopg2 yang ini pscopg

Serupa dengan versi sqlite, di sini namanya adalah row_factory untuk memanipulasi hasil dari sql.

dict_row

from psycopg.rows import dict_row # tambahkan ini
.
.
.
.
cursor = conn.cursor(row_factory=dict_row) # ubah jadi seperti ini
.
.
.
.
#panggil seperti ini
print(f"{r['id']} - {r['isbn']} - {r['title']}")

Dataclass

Penggunaan dataclass di psycopg lebih simple dibanding sqlite, jika di sqlite perlu membuat converter-nya terlebih dahulu di psycopg tidak perlu, cukup kirimkan objek dataclass ke dalam classrow

from psycopg.rows import class_row
from dataclasses import dataclass


@dataclass
class Books:
    id: int
    isbn: str
    title: str


cursor = conn.cursor(row_factory=class_row(Books))


print(f"{r.id} - {r.isbn} - {r.title}")

Perbandingan



Default
78624 function calls (275568 primitive calls) in 0.521 seconds

Dict_row
288642 function calls (285586 primitive calls) in 0.578 seconds

class_row/Dataclass 
288983 function calls (285923 primitive calls) in 0.569 seconds

Referensi

Row factories - psycopg 3.2.0.dev1 documentation

psycopg2.extras – Miscellaneous goodies for Psycopg 2 — Psycopg 2.9.9 documentation