الوحدة 10 — قواعد الرسوم البيانيّة، وNeo4j، والخطوات الأولى في Cypher
ترسم إيناس على السبّورة سؤالًا تودّ Veille أن تُجيب عنه لعملائها: «من يكتب في الفئات نفسها التي يكتب فيها Lee Moran، وعلى أيّ مسافة كتابيّة؟». في SQL يتحوّل الاستعلام إلى تكديس JOIN متعاقبة. المسألة نفسها، في رسم بيانيّ، تنحصر في ثلاثة أسطر. يُثبِّت سامي Neo4j محلّيًّا عبر الحقيبة؛ تُعلّمه هذه الوحدة المفردات، ولغة Cypher، وتجعله يبني رسمًا بيانيًّا مصغَّرًا للفريق سيكون ميدان تمرينه.
تبقى تصنيفات Neo4j (Labels) وأنواع العلاقات كما هي بالفرنس يّة (:Article، :Categorie، :Auteur، :Personne، :Ville، :Competence، :PUBLIE_DANS، :ECRIT_PAR، :HABITE، :MAITRISE، :ENCADRE …) لأنّها مُعرَّفة داخل سكربتات الحقيبة المشتركة (neo4j/cypher/*.cypher). لا تُترجَم هذه الأسماء حتّى تعمل الاستعلامات كما هي.
متى يفوز الرسم البيانيّ على العلائقيّ
القاعدة العلائقيّة ممتازة لتجميع الأعمدة، أقلّ كفاءةً لتتبّع علاقات بعمق متغيّر. ثلاث علامات تدلّ على أنّ المسألة مسألة رسم بيانيّ:
- يستحضر السؤال مسارًا: «هل هناك رابط بين A وB؟»، «على أيّ مسافة؟».
- ليس العمق ثابتًا: «من يؤطّر من، على مستوى أو مستويَين؟»، «توصية على قفزتَين».
- تلعب الكيانات عدّة أدوار: قد يكون المؤلّف مصدرًا أيضًا، وقد تكون الفئة مركز اهتمام أيضًا.
في SQL يترجَم مسار بطول 3 إلى ثلاث عبارات JOIN صريحة. في Cypher نكتب (a)-[*1..3]->(b) ويعثر المحرّك على المسار بنفسه. المكسب ليس مكسب تعبير فقط: يُخزِّن محرّك الرسم البيانيّ العلاقات مؤشِّرات ويعبرها بزمن ثابت، خلافًا للـjointure التي تعتمد على فهرس على المفتاح الأجنبيّ. بعبارة أخرى: كلّما زادت أعماق العبور، اتّسعت الفجوة بين SQL وCypher لصالح هذا الأخير.
نموذج الخصائص
يتبع Neo4j property graph model. أربع لبنات تكفي.
- العقدة — الكيان. تُمثَّل بين قوسَين:
(p). - التصنيف (Label) — نوع العقدة، مسبوقًا بـ
::(:Personne)،(:Ville). قد تحمل العقدة عدّة تصنيفات. - العلاقة الموسومة والموجَّهة — الرابط، بين معقوفتَين وأسهم:
-[:ENCADRE]->. النوع إلزاميّ، والاتّجاه أيضًا (حتّى وإن أمكن قراءته في الاتّجاهَين). - الخاصّيّة — زوج مفتاح/قيمة تحمله العقدة أو العلاقة:
{nom: 'Inès', anciennete: 6}.
نُجمِّع هذه اللبنات في أنماط برسم ASCII:
(p:Personne)-[:HABITE {depuis: 2018}]->(v:Ville {nom: 'Montréal'})
يُقرأ هذا السطر «توجد عقدة p بتصنيف Personne في علاقة HABITE مع العقدة v بتصنيف Ville واسمها Montréal، وتحمل العلاقة الخاصّيّة depuis بقيمة 2018». السطر نفسه يخدم القراءة (في MATCH) والكتابة (في CREATE أو MERGE). هذا هو الرهان الكبير الأوّل للّغة: مفردات واحدة للاستفسار والبناء.
فتح Neo4j Browser
تعرِض الحقيبة Neo4j Community 5.26 على http://localhost:7474 (HTTP) وعلى bolt://localhost:7687 (البروتوكول الثنائيّ للعملاء). افتح المتصفّح.
- عنوان الاتّصال:
bolt://localhost:7687 - المستخدم:
neo4j - كلمة المرور:
veille2026
بعد الاتّصال، يقبل شريط الأعلى ثلاث عائلات من المُدخلات. استعلامات Cypher (كلّ ما يبدأ بـMATCH أو CREATE أو MERGE أو RETURN …). أوامر Browser المسبوقة بـ: : :play (دروس مصغّرة مدمجة)، و:schema (قائمة التصنيفات والقيود والفهارس)، و:sysinfo (حالة الخادم، وحجم الـheap، وذاكرة الصفحات)، و:history (سجلّ الأوامر). أخيرًا EXPLAIN وأمّا PROFILE (الوحدة 12) فتُسبقان استعلامًا لعرض خطّة تنفيذه.
بعد RETURN، يقترح لوح النتيجة ثلاث أيقونات: الرسم البيانيّ (تصوّر بصريّ)، والجدول (أعمدة)، والنصّ (JSON خام). الجدول مثاليّ للتحقّق من القيم، والرسم البيانيّ أساسيّ لفهم الطوبولوجيا.
سكربت الحقيبة: 10-premiers-pas.cypher
تُسلّم الحقيبة رسمًا بيانيًّا مصغَّرًا من إحدى عشرة عقدة وأربع عشرة علاقة يصف فريق Veille: أربعة أعضاء يقيمون في ثلاث مدن ويتقنون بضع كفاءات، مع ثلاث علاقات تأطير. ينفَّذ بأمر واحد من shell المضيف.
./lab.sh cypher 10-premiers-pas.cypher
يُضاف الملفّ للقراءة فقط داخل الحاوية veille-neo4j تح ت /cypher. لنستعرضه كتلةً كتلةً.
الكتلة 1 — قيود الفرادة
CREATE CONSTRAINT personne_nom IF NOT EXISTS FOR (p:Personne) REQUIRE p.nom IS UNIQUE;
CREATE CONSTRAINT ville_nom IF NOT EXISTS FOR (v:Ville) REQUIRE v.nom IS UNIQUE;
CREATE CONSTRAINT competence_nom IF NOT EXISTS FOR (c:Competence) REQUIRE c.nom IS UNIQUE;
ثلاث قيود فرادة على الخاصّيّة nom، و احدة لكلّ تصنيف. أثران. أوّلًا، تضمن الفرادة: سيرفض Neo4j إدراج شخصَين :Personne باسم nom نفسه. ثانيًا، تُنشئ ضمنيًّا فهرسًا على nom: تصبح استعلامات MATCH (p:Personne {nom: 'Inès'}) وصولًا مباشرًا، لا مسحًا. تجعل IF NOT EXISTS الأمر عديم التأثير إن أُعيد — يمكن إعادة تشغيل السكربت بلا خطأ.
منذ Neo4j 5، نكتب CREATE CONSTRAINT nom IF NOT EXISTS FOR (n:Label) REQUIRE n.prop IS UNIQUE. الصيغة القديمة CREATE CONSTRAINT ON ... ASSERT صارت مهجورة ولم تعد تعمل في مشروع جديد.
الكتلة 2 — العقد
MERGE (:Personne {nom: 'Inès', role: 'lead data', anciennete: 6});
MERGE (:Personne {nom: 'Sami', role: 'data engineer', anciennete: 1});
MERGE (:Personne {nom: 'Léa', role: 'analyste', anciennete: 3});
MERGE (:Personne {nom: 'Karim', role: 'développeur', anciennete: 4});
يُحاول كلّ MERGE أوّلًا MATCH على النمط، وإن لم يوجد نفّذ CREATE. هذا هو نمط الـupsert في Cypher. بفضل قيود الكتلة 1، البحث فوريّ. تتبع المدن الثلاث والكفاءات الأربع النمط نفسه:
MERGE (:Ville {nom: 'Montréal', pays: 'Canada'});
MERGE (:Competence {nom: 'Elasticsearch'});
// وهكذا دواليك
الكتلة 3 — العلاقات
تُوضع العلاقات بعد العقد، بالنمط MATCH ... MERGE (a)-[:REL]->(b):
MATCH (p:Personne {nom: 'Inès'}), (v:Ville {nom: 'Montréal'})
MERGE (p)-[:HABITE {depuis: 2018}]->(v);
يستعيد MATCH العقدتَين (وصول بالفهرس بفضل القيد)، ويقوم MERGE بإنشاء العلاقة إن لم توجد. تحمل الخاصّيّة depuis سنة الإقامة. تحمل علاقات MAITRISE مستوى niveau من 1 إلى 5:
MATCH (p:Personne {nom: 'Inès'}), (c:Competence {nom: 'Elasticsearch'})
MERGE (p)-[:MAITRISE {niveau: 5}]->(c);
أخيرًا، ENCADRE تُجسِّد التأطير:
MATCH (a:Personne {nom: 'Inès'}), (b:Personne {nom: 'Sami'})
MERGE (a)-[:ENCADRE]->(b);