ما هو الـ Embedded SQL ؟


 

تخيل معي أن لديك برنامجًا عاديًا، مثل برنامج لإدارة مكتبة، مكتوبًا بلغة برمجة مثل C و JAVA . هذا البرنامج يحتاج إلى التفاعل مع قاعدة بيانات ليخزن معلومات الكتب والعملاء. لكن، لغات البرمجة هذه لا "تفهم" أوامر قواعد البيانات (مثل SELECT و INSERT ) بشكل مباشر.

هنا يأتي دور Embedded SQL هو ببساطة عبارة عن دمج أو تضمين أوامر لغة SQL مباشرة داخل كود البرنامج المكتوب بلغة برمجة أخرى (C و C++). هذا الدمج يتم بطريقة خاصة، حيث يتم وضع أوامر SQL بين رموز معينة ليتمكن المعالج الأولي (Preprocessor) من التعرف عليها.

فكر في الأمر كأنك تضع "رسالة سرية" مكتوبة بلغة مختلفة داخل رسالتك العادية. برنامجك العادي يقرأ كل شيء، لكن "الرسالة السرية" (وهي أوامر SQL) لا تُنفذ إلا عندما يمر عليها "جاسوس" متخصص (المعالج الأولي) الذي يترجمها إلى شيء يفهمه البرنامج .

 

لماذا نستخدمه؟

  • الكفاءة: يمنحك تحكمًا عاليًا في كيفية تفاعل برنامجك مع قاعدة البيانات، مما يمكن أن يحسن الأداء.
  • التحكم: يتيح لك بناء تطبيقات قوية ومعقدة تحتاج إلى الوصول إلى البيانات وتعديلها بشكل ديناميكي.
  • المرونة: يمكنك استخدام قوة لغة البرمجة (مثل الحلقات والشروط) مع قوة لغة SQL في نفس الوقت.

 

كيف يعمل Embedded SQL ؟

لنتخيل أن لديك كود برنامج بلغة C، وهذا الكود يحتوي على أوامر SQL مضمنة. ما الذي يحدث بالضبط حتى تتمكن قاعدة البيانات من فهمه؟


1 - المعالج الأولي (The Preprocessor):هذه هي الخطوة السحرية! قبل أن يتم تجميع الكود (Compilation)، يمر الكود على أداة خاصة تسمى المعالج الأولي. وظيفته هي البحث عن أي أوامر SQL مضمنة (التي عادة ما تبدأ بـ EXEC SQL)
 
2 - التحويل : يقوم المعالج الأولي بتحويل كل أمر SQL مضمن إلى استدعاءات لوظائف (Function Calls) خاصة بلغة البرمجة الأساسية (في مثالنا، لغة C). هذه الوظائف هي التي ستتعامل لاحقاً مع قاعدة البيانات.
 
3 - التجميع والربط : بعد أن يقوم المعالج الأولي بتحويل أوامر SQL، يصبح الكود برنامجًا عاديًا بلغة C. يتم تجميع هذا الكود، ثم يتم ربطه (Linking) مع مكتبة وقت التشغيل (Runtime Library) التي تحتوي على تعريفات الوظائف التي تم استدعاؤها في الخطوة السابقة. هذه المكتبة هي التي تقوم بالاتصال الفعلي بقاعدة البيانات وتنفيذ الأوامر.

4 - التنفيذ : الآن، عندما يتم تشغيل البرنامج، ستقوم استدعاءات الوظائف بالاتصال بقاعدة البيانات وتنفيذ أوامرSQL بشكل مباشر، وتسترجع النتائج إلى برنامجك.

 

باختصار، يمكننا تلخيص العملية بهذه الخطوات الثلاث:

كود SQL مضمن ß معالج أولي ß كود C ß تجميع ß برنامج تنفيذي

 

مزايا Embedded SQL

  • أداء أفضل: بما أن أوامر SQL تُجهز مسبقًا ويتم تحويلها إلى استدعاءات لوظائف مباشرة، فإن هذا يقلل من الوقت اللازم لمعالجة الأوامر أثناء تشغيل البرنامج، مما يعطي أداءً أسرع مقارنة بطرق أخرى.
  • تحكم كامل : يمنحك Embedded SQL تحكمًا دقيقًا في كل خطوة من خطوات التعامل مع قاعدة البيانات. يمكنك التحكم في نوع البيانات، ومعالجة الأخطاء، وكيفية جلب البيانات من دون أي قيود.
  • أمان أعلى : نظرًا لأن استعلامات SQL تكون مضمنة وثابتة في الكود، فإنها تقلل بشكل كبير من مخاطر حقن SQL (SQL Injection) التي تحدث عندما يتم تمرير أوامر ضارة من خلال إدخال المستخدم.

عيوب Embedded SQL

  • صعوبة الصيانة: إذا كنت بحاجة إلى تعديل استعلام SQL بسيط، يجب عليك إعادة تجميع (Recompile) الكود بالكامل، وهذا قد يكون مرهقًا في المشاريع الكبيرة.
  • اعتمادية كبيرة: برنامجك يصبح معتمدًا بشكل كبير على قاعدة بيانات معينة ومعالجها الأولي. إذا قررت تغيير نوع قاعدة البيانات (مثلًا Oracle إلى PostgreSQL)، فستحتاج على الأرجح إلى إعادة كتابة أجزاء كبيرة من الكود.
  • كود أقل وضوحًا :  وجود أوامر SQL بلغة مختلفة داخل كود لغة البرمجة الأساسية قد يجعل الكود أصعب في القراءة والفهم للمبرمجين الآخرين.

 

 مثال بلغة C  

بدون التضمين (Embedded) 

 ```

 #include <stdio.h>
#include <libpq-fe.h> // مكتبة للاتصال بقاعدة بيانات PostgreSQL

int main() {
    PGconn *conn;
    PGresult *res;
    char emp_id[10];

    // اتصال بقاعدة البيانات
    conn = PQconnectdb("dbname=company user=postgres");
    if (PQstatus(conn) != CONNECTION_OK) {
        // معالجة خطأ الاتصال
        return 1;
    }

    printf("أدخل رقم الموظف: ");
    scanf("%s", emp_id);

    // بناء استعلام SQL كسلسلة نصية
    char query[256];
    sprintf(query, "SELECT first_name, last_name FROM employees WHERE employee_id = '%s'", emp_id);

    // إرسال الاستعلام إلى قاعدة البيانات
    res = PQexec(conn, query);
    if (PQresultStatus(res) != PGRES_TUPLES_OK) {
        // معالجة خطأ الاستعلام
        return 1;
    }

    // جلب النتائج وطباعتها
    printf("اسم الموظف: %s %s\n", PQgetvalue(res, 0, 0), PQgetvalue(res, 0, 1));

    // إغلاق الاتصال
    PQclear(res);
    PQfinish(conn);
    return 0;
}

```  

هنا، نحن نستخدم مكتبة خاصة (مثل libpd لـ PostgreSQL) لنباء الاستعلام كسلسلة نصية (char query[]) ثم نرسله كوظيفة (PQexec). هذا يتطلب كتابة الكثير من الكود للتعامل مع الاتصال بالخادم، ومعالجة الأخطاء، وجلب النتائج.

 

مع التضمين (Embedded) 

 

```

 #include <stdio.h>

// الإعلان عن المضيف (Host)
EXEC SQL INCLUDE sqlca;

int main() {
    char emp_id[10];
    char first_name[20];
    char last_name[20];

    // الاتصال بقاعدة البيانات
    EXEC SQL CONNECT TO 'company' USER 'postgres';

    printf("أدخل رقم الموظف: ");
    scanf("%s", emp_id);

    // أمر SQL المضمن مباشرة في الكود
    EXEC SQL SELECT first_name, last_name
        INTO :first_name, :last_name
        FROM employees
        WHERE employee_id = :emp_id;

    // التحقق من حالة التنفيذ
    if (sqlca.sqlcode == 0) {
        printf("اسم الموظف: %s %s\n", first_name, last_name);
    } else {
        printf("لم يتم العثور على موظف بهذا الرقم.\n");
    }

    // قطع الاتصال
    EXEC SQL COMMIT WORK;
    EXEC SQL DISCONNECT;

    return 0;
}

``` 

في هذا المثال، نرى كيف تم تضمين أوامر SQL مباشرةً باستخدام العبارة EXEC SQL. المعالج الأولي هو من سيتعامل مع هذه الأوامر ويحولها إلى استدعاءات وظائف C نيابةً عنا.

لاحظ النقاط الرئيسية التالية:

  • عبارة EXEC SQL تستخدم لبدء كل أمر SQL مضمن.
  • تُستخدم المتغيرات المستضافة (Host variables) مثل "emp_id:" لربط المتغيرات الموجودة في كود C مع استعلام SQL.
  • تُستخدم العبارة "INTO" لتخزين نتيجة الاستعلام في متغيرات كود C.

 

 مثال بلغة Java

في لغة الـ Java، طريقة التضمين (Embedding) تختلف قليلاً عن  لغة الـC، حيث لا يوجد معالج أولي بنفس الطريقة، بل غالبًا ما يتم استخدام JPA(Java Persistence API) أو Hibernate أو ما شابه ذلك لإدارة الاتصال بقاعدة البيانات. ولكن، يمكننا أن نوضح الفكرة بأسلوب أقرب إلى Embedded SQL من خلال مكتبة  JDBC (Java Database Connectivity) التي تعد الأساس للتواصل مع قواعد البيانات في لغة الـJava.

 

```

 import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Scanner;

public class EmployeeLookup {

    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        try {
            // الخطوة 1: الاتصال بقاعدة البيانات
            // لا يوجد EXEC SQL هنا، بل نستخدم DriverManager لإنشاء الاتصال
            conn = DriverManager.getConnection("jdbc:postgresql://localhost:5432/company", "postgres", "password");
            
            Scanner scanner = new Scanner(System.in);
            System.out.print("أدخل رقم الموظف: ");
            String empId = scanner.nextLine();
            
            // الخطوة 2: إنشاء استعلام SQL مضمن (Prepared Statement)
            // هذه الطريقة تشبه Embedded SQL في كون الاستعلام ثابتًا ومجهزًا مسبقًا
            String sql = "SELECT first_name, last_name FROM employees WHERE employee_id = ?";
            pstmt = conn.prepareStatement(sql);
            
            // الخطوة 3: ربط متغير Java مع الاستعلام
            // هنا يتم ربط المتغير empId مع علامة الاستفهام (?) في الاستعلام
            pstmt.setString(1, empId);
            
            // الخطوة 4: تنفيذ الاستعلام
            rs = pstmt.executeQuery();
            
            // الخطوة 5: معالجة النتائج
            if (rs.next()) {
                String firstName = rs.getString("first_name");
                String lastName = rs.getString("last_name");
                System.out.printf("اسم الموظف: %s %s%n", firstName, lastName);
            } else {
                System.out.println("لم يتم العثور على موظف بهذا الرقم.");
            }

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // إغلاق الموارد دائمًا
            try {
                if (rs != null) rs.close();
                if (pstmt != null) pstmt.close();
                if (conn != null) conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

``` 

ما الذي يجب أن تلاحظه في هذا المثال؟

  • لا يوجد "EXEC SQL": في لغة الـJava، لا نستخدم هذه العبارة. بدلاً من ذلك، نستخدم الكائنات الخاصة بمكتبة JDBC مثل "Connection" و "PreparedStatement" و "ResultSet"
  • Prepared Statement : هذه الطريقة الأقرب لمفهوم Embedded SQL. بدلاً من بناء استعلام كـ "نص عادي" قد يكون عرضة للاختراق، نحن نعد الاستعلام مسبقًا باستخدام علامة "؟" كـ "مُتغير".
  • ربط المتغيرات : يتم ربط متغيرات جافا (empId) بالاستعلام بشكل آمن باستخدام "pstmt.setString(1, empId)".

 

الهدف من هذا المثال هو توضيح أن مفهوم "تضمين" أوامر SQL موجود في معظم لغات البرمجة الحديثة، ولكنه يتم بآليات مختلفة أكثر أمانًا وتطورًا من الأسلوب الثديم في Embedded SQL.

 

 

 

 

 

 

 

 

 

 

تعليقات

المشاركات الشائعة من هذه المدونة

منحة كورسيرا عبر تجمع الشباب السوري

مصادر لتقوية لغتك الإنجليزية

ما هي قواعد البيانات ؟

توثيق حسابات السوريين والسودانيين على منصة كورسيرا: دليل شامل ومفصل

تعريف بمنصة كورسيرا