pgx في Go: دليل شامل مع أمثلة عملية

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة عن pgx: مشغل PostgreSQL القوي للغة Go

مكتبة pgx (PostgreSQL eXtended) هي مشغل وأدوات حديثة وعالية الأداء للتعامل مع PostgreSQL من لغة البرمجة Go. على عكس الحزمة القياسية database/sql، توفر pgx وصولاً مباشراً إلى بروتوكول PostgreSQL، مما يمنحك زيادة كبيرة في الأداء وإمكانية الوصول إلى ميزات فريدة مثل LISTEN/NOTIFY، بروتوكول COPY، التعامل مع المصفوفات والأنواع المركبة، وغيرها الكثير.

لماذا تستخدم pgx بدلاً من database/sql القياسي؟ أولاً، pgx أسرع بنسبة 30-50% بسبب عدم وجود طبقات تجريد زائدة. ثانياً، تدعم جميع ميزات PostgreSQL 12+ (والإصدارات الأحدث). ثالثاً، توفر pgx واجهة برمجة تطبيقات غنية للتعامل مع الاتصالات، مجمع الاتصالات، الاستعلامات المُعدة، والمعاملات. أخيراً، pgx متوافقة تماماً مع database/sql عبر محول خاص، مما يسمح باستخدامها في المشاريع التي تستخدم الواجهة القياسية بالفعل.

تثبيت pgx

يتم تثبيت pgx في مشروع Go باستخدام الأمر القياسي go get. للعمل مع PostgreSQL، ستحتاج إلى تثبيت الحزمة الأساسية pgx وربما وحدات إضافية:

# تثبيت الحزمة الأساسية pgx v5 (الإصدار الموصى به)
go get github.com/jackc/pgx/v5

# تثبيت حزمة مجمع الاتصالات
go get github.com/jackc/pgx/v5/pgxpool

# تثبيت المحول للتوافق مع database/sql
go get github.com/jackc/pgx/v5/stdlib

بعد تنفيذ هذه الأوامر، يمكنك استيراد pgx في ملفات Go الخاصة بك. تأكد من تثبيت Go إصدار 1.18 أو أعلى، لأن pgx v5 تستخدم الأدوية (Generics).

بداية سريعة: أول اتصال بـ PostgreSQL

لنكتب برنامجاً بسيطاً يتصل بقاعدة بيانات PostgreSQL وينفذ استعلاماً. افترض أن PostgreSQL يعمل على localhost:5432، وقاعدة البيانات اسمها testdb، والمستخدم testuser بكلمة مرور testpass.

package main

import (
    "context"
    "fmt"
    "log"
    "os"

    "github.com/jackc/pgx/v5"
)

func main() {
    // سلسلة الاتصال
    connStr := "postgres://testuser:testpass@localhost:5432/testdb"

    // إنشاء الاتصال
    conn, err := pgx.Connect(context.Background(), connStr)
    if err != nil {
        log.Fatalf("فشل الاتصال بقاعدة البيانات: %v", err)
    }
    defer conn.Close(context.Background())

    // تنفيذ استعلام بسيط
    var greeting string
    err = conn.QueryRow(context.Background(), "SELECT 'مرحباً، PostgreSQL!'").Scan(&greeting)
    if err != nil {
        log.Fatalf("خطأ في تنفيذ الاستعلام: %v", err)
    }

    fmt.Println(greeting)
}

يقوم هذا الكود بإنشاء اتصال، تنفيذ استعلام، وعرض النتيجة. لاحظ استخدام context.Background() - وهي الطريقة القياسية لتمرير السياق في Go، والتي تسمح بالتحكم في المهلات الزمنية وإلغاء العمليات.

الدوال والوظائف الأساسية في pgx

دعنا نستعرض أهم دوال ووظائف مكتبة pgx. لكل دالة سنقدم وصفاً، المعاملات، ومثالاً عملياً.

1. pgx.Connect - إنشاء اتصال

الوظيفة: إنشاء اتصال جديد بـ PostgreSQL. هذه هي الطريقة الأساسية لبدء العمل مع قاعدة البيانات.

المعاملات:

  • ctx context.Context - سياق للتحكم في عمر الاتصال
  • connString string - سلسلة الاتصال بصيغة URI أو DSN

الإرجاع: (*pgx.Conn, error)

مثال:

conn, err := pgx.Connect(context.Background(), "postgres://user:pass@localhost:5432/mydb")
if err != nil {
    log.Fatal(err)
}
defer conn.Close(context.Background())

2. conn.QueryRow - استعلام صف واحد

الوظيفة: تنفيذ استعلام يُتوقع أن يعيد صفاً واحداً من النتائج. مثالية لاستعلامات SELECT مع شرط WHERE فريد.

المعاملات:

  • ctx context.Context - سياق التنفيذ
  • sql string - جملة SQL مع عناصر نائبة (مثل $1, $2)
  • args ...any - قيم العناصر النائبة

الإرجاع: pgx.Row (واجهة تحتوي على دالة Scan)

مثال:

var name string
var age int
err := conn.QueryRow(context.Background(),
    "SELECT name, age FROM users WHERE id = $1", 1).Scan(&name, &age)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("الاسم: %s، العمر: %d\n", name, age)

3. conn.Query - استعلام صفوف متعددة

الوظيفة: تنفيذ استعلام يُعيد صفوفاً متعددة. تستخدم مع الحلقات التكرارية لقراءة جميع النتائج.

المعاملات:

  • ctx context.Context - سياق التنفيذ
  • sql string - جملة SQL
  • args ...any - قيم العناصر النائبة

الإرجاع: (pgx.Rows, error)

مثال:

rows, err := conn.Query(context.Background(), "SELECT id, name FROM users WHERE age > $1", 18)
if err != nil {
    log.Fatal(err)
}
defer rows.Close()

for rows.Next() {
    var id int
    var name string
    err := rows.Scan(&id, &name)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("المعرف: %d، الاسم: %s\n", id, name)
}

4. conn.Exec - تنفيذ أوامر SQL

الوظيفة: تنفيذ أوامر SQL التي لا تُعيد صفوفاً (مثل INSERT, UPDATE, DELETE, CREATE TABLE).

المعاملات:

  • ctx context.Context - سياق التنفيذ
  • sql string - جملة SQL
  • args ...any - قيم العناصر النائبة

الإرجاع: (pgx.CommandTag, error)

مثال:

result, err := conn.Exec(context.Background(),
    "INSERT INTO users (name, age) VALUES ($1, $2)", "أحمد", 25)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("تم إدراج %d صف\n", result.RowsAffected())

5. conn.Begin - بدء معاملة

الوظيفة: بدء معاملة قاعدة بيانات جديدة. تسمح بتنفيذ عدة أوامر SQL كوحدة واحدة قابلة للتراجع.

المعاملات:

  • ctx context.Context - سياق التنفيذ

الإرجاع: (pgx.Tx, error)

مثال:

tx, err := conn.Begin(context.Background())
if err != nil {
    log.Fatal(err)
}
defer tx.Rollback(context.Background()) // التراجع في حالة الخطأ

_, err = tx.Exec(context.Background(), "UPDATE accounts SET balance = balance - 100 WHERE id = $1", 1)
if err != nil {
    log.Fatal(err)
}

_, err = tx.Exec(context.Background(), "UPDATE accounts SET balance = balance + 100 WHERE id = $1", 2)
if err != nil {
    log.Fatal(err)
}

err = tx.Commit(context.Background()) // تأكيد المعاملة
if err != nil {
    log.Fatal(err)
}

6. pgxpool.New - إنشاء مجمع اتصالات

الوظيفة: إنشاء مجمع اتصالات (Connection Pool) لإدارة اتصالات متعددة بكفاءة. مثالي للتطبيقات عالية التحميل.

المعاملات:

  • ctx context.Context - سياق التهيئة
  • connString string - سلسلة الاتصال

الإرجاع: (*pgxpool.Pool, error)

مثال:

pool, err := pgxpool.New(context.Background(), "postgres://user:pass@localhost:5432/mydb")
if err != nil {
    log.Fatal(err)
}
defer pool.Close()

// استخدام المجمع لتنفيذ استعلام
var count int
err = pool.QueryRow(context.Background(), "SELECT COUNT(*) FROM users").Scan(&count)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("عدد المستخدمين: %d\n", count)

7. conn.Prepare - إعداد استعلام مُعد

الوظيفة: إعداد استعلام مُعد (Prepared Statement) لتحسين الأداء عند تنفيذ نفس الاستعلام عدة مرات.

المعاملات:

  • ctx context.Context - سياق التنفيذ
  • name string - اسم الاستعلام المُعد
  • sql string - جملة SQL

الإرجاع: (*pgx.PreparedStatement, error)

مثال:

// إعداد الاستعلام
_, err := conn.Prepare(context.Background(), "getUser", "SELECT name, age FROM users WHERE id = $1")
if err != nil {
    log.Fatal(err)
}

// تنفيذ الاستعلام المُعد
var name string
var age int
err = conn.QueryRow(context.Background(), "getUser", 1).Scan(&name, &age)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("الاسم: %s، العمر: %d\n", name, age)

8. conn.CopyFrom - نسخ البيانات بكميات كبيرة

الوظيفة: إدراج كميات كبيرة من البيانات بكفاءة عالية باستخدام بروتوكول COPY من PostgreSQL. أسرع بكثير من INSERT العادي.

المعاملات:

  • ctx context.Context - سياق التنفيذ
  • tableName pgx.Identifier - اسم الجدول
  • columnNames []string - أسماء الأعمدة
  • rowSrc pgx.CopyFromSource - مصدر الصفوف

الإرجاع: (int64, error) - عدد الصفوف المنسوخة

مثال:

// إنشاء بيانات للنسخ
rows := [][]any{
    {"أحمد", 25},
    {"سارة", 30},
    {"خالد", 22},
}

// نسخ البيانات
copyCount, err := conn.CopyFrom(
    context.Background(),
    pgx.Identifier{"users"},
    []string{"name", "age"},
    pgx.CopyFromRows(rows),
)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("تم نسخ %d صف\n", copyCount)

9. conn.Listen / conn.Unlisten - الاستماع للإشعارات

الوظيفة: الاشتراك في إشعارات PostgreSQL (LISTEN/NOTIFY) لاستقبال إشعارات فورية من قاعدة البيانات.

المعاملات:

  • ctx context.Context - سياق التنفيذ
  • channel string - اسم القناة

الإرجاع: error

مثال:

// الاشتراك في قناة الإشعارات
err := conn.Listen(context.Background(), "user_changes")
if err != nil {
    log.Fatal(err)
}

// انتظار الإشعارات
notification, err := conn.WaitForNotification(context.Background())
if err != nil {
    log.Fatal(err)
}
fmt.Printf("إشعار من القناة %s: %s\n", notification.Channel, notification.Payload)

أفضل الممارسات والنصائح

  • استخدم مجمع الاتصالات (pgxpool) في التطبيقات الإنتاجية بدلاً من الاتصالات الفردية
  • حدد المهلات الزمنية باستخدام context.WithTimeout لمنع تعليق الاتصالات
  • استخدم الاستعلامات المُعدة للاستعلامات المتكررة لتحسين الأداء ومنع هجمات SQL Injection
  • أغلق الاتصالات والمجمعات باستخدام defer لضمان تحرير الموارد
  • استخدم COPYFrom لإدراج كميات كبيرة من البيانات بدلاً من INSERT المتكرر
  • تعامل مع الأخطاء بشكل صحيح وتحقق من err بعد كل عملية

الخلاصة

مكتبة pgx هي الخيار الأمثل للتعامل مع PostgreSQL في Go. توفر أداءً عالياً، ميزات متقدمة، ومرونة كبيرة. سواء كنت تبني تطبيق ويب صغير أو نظاماً ضخماً، فإن pgx ستلبي احتياجاتك بكفاءة. ابدأ باستخدام pgx اليوم واستفد من قوة PostgreSQL الكاملة في تطبيقات Go الخاصة بك!

توصيات