הבלוג של ארתיום
בלוג על לינוקס, תוכנה חופשית, מוזיקה, סלסה, ומה לא!
קישוריות לבסיסי נתונים מ־C++
לאחרונה אני הייתי מאוד לא מרוצה מעבודה עם libdbi בתור ספריית הקישוריות לבסיסי נתונים וגם לא הצלחתי להתחבר ל־SOCI ממספר סיבות נוספות.
הספריה הראשונה libdbi לא תומכת בכלל ב־Prepared Statements, יש לה לא מעט בעיות בגישה לטעינה של מודולים, הספריה השניה soci לא מי יודע מה מוצלחת מבחינת הגישה שלה לפיתוח, בפרט, סירוב עקבי שלא לתמוך ב־Last-Insert-Id או עבודה קצת עקומה עם prepared-statements ומחזור שחרור גרסאות מאוד ארוך (שבפועל דורש ממך לעבוד עם גרסת git).
אז מתוך ניסיון עבודה עם שתי הספריות האלה ניסיון מסוים בעבודה עם JDBC החלטתי לבנות משהו חילופי כאשר הדרישה העיקרית היא:
- לתמוך בצורה שקופה ב־prepared-statements וגם ליצור cache שלהם כך שתהליך ההכנה שלהם יתבצע בפועל פעם אחת בלבד בצורה שקופה.
- לתמוך ב־connection pooling בצורה שקופה ונוחה.
- אפשרות לטעון מודולים של בסיסי הנתונים בצורה דינאמית וגם לקשר אותם בצורה סטטית לצורך הפצה נוחה.
- גמישות מבחינת בחירה של statement רגיל ו־prepared.
- מהירות
- תמיכה מלאה ב־postgresql, mysql וב־sqlite3.
- תמיכה במספר גבוהה ככל האפשר של בסיסי הנתונים דרך מערכת קישוריות חילופית (odbc).
כך נוצרה ספריית cppdb הזמינה תחת LGPLv3 (קישור חילופי).
כמובן רבים יאשימו אותי בהמצאת גלגל מחדש, אבל למעשה יצרתי משהו מאוד ייחודי שלא קיים במרבית ספריות הקישוריות הקיימות (ולא רק ב־C++) - הסיבה העיקרית זאת הנוחות העבודה עם prepared-statements והשמירה האוטומטית שלהם לצורך השימוש החוזר שנותנת תוספת ביצועים של עשרות אחוזים.
בנוסף שחררתי גם עדכון ל־dbixx (המעטפת של libdbi) המביאה מספר תיקונים ושיפורים יחד עם ההחלטה להזניח את הפיתוח שלה תוך כשנה לטובת ה־cppdb.
תגובות
מה רע ב־libQtSql?
שום דבר רע מלבד ש־Qt מתעלם מכל דבר קיים ב־C++ למשל, הם משתמשים ב־QSting במקום std::string, הם משתמשים ב־QDate במקום std::tm ועוד. שלא לדבר על העובדה שהספריה לא זורקת exceptions בכלל.
אתה אומר את זה כאילו שזה משהו רע :-P
חחחחח, תלוי מה הציפיות שלך...
אגב שלא יהיו אי הבנות, לדעתי Qt היא אחת הספריות הטובות ביותר שיש ל־C++.
אחד הדברים שיש בדלפי ו FPC שלדעתי אין להם תחליף ראוי בשום שפה זה ספריית DataSet.
היא מספקת לך API דמוי ORM למסדי נתונים מצד אחד, ומימוש ה API לחיבור בצד השני, והוא אגנוסטי לגמרי, כך שהוא נותן לך כלים לכתוב למשל ממשקים גרפיים שתומכים לך במסדי נתונים בלי קשר למה שהמסד נתונים שלך תומך, או החיבור שלך. כלומר להחליף עכשיו חיבור לוקח ממש מהר, פחות מ10 דקות בד"כ וזה כל מה שאתה צריך.
איזה סוגי חיבורים הוא תומך ? הוא לא. המימוש שלך של TDatabase למשל תומך בהם, כך שאתה יכול לתמוך ב Firebird בצורה ישירה, דרך ODBC או על ידי מימוש עצמי שלך של הפרוטוקול תקשורת שלו, זה לא משנה כל עוד אתה עובד לפי מימוש ה API שלו.
ד"א הוא תומך בכל מה שאתה כתבת לו תמיכה במנוע שלך, ועוד הרבה יותר. כלומר הוא תומך ב prepare statements, יש לו תמיכה ב cache, הוא תומך ב binding אנונימי ושמי, הוא תומך ביכולת ליצור lookup tables בלי קשר למסדי הנתונים שלך ועוד המון דברים, כולל תמיכה ב format למילוי התוכן, הגבלה האם השדה יכול להיות לקריאה בלבד, כתיבה בלבד, וכו', אם הוא נדרש למילוי או לא, מה לעשות כאשר המידע עליו נאסף, והרשימה עוד ארוכה מאוד. כמות הכוח שיש לך שם מאוד ייחודית להרבה ספריות מסדי נתונים אחרים שעבדתי איתם, כולל DBI וכמה סוגי ORM, וכמובן JDBC.
עידו, שים לב, אני לא מנסה לבנות ORM (שאני לא כל־כך מאמין בו) אלא משהו הרבה יותר פשוט וישיר - API אחיד לעבודה עם SQL.
ד"א אם אתה מוכן להשקיע קצת מאמץ אתה מוזמן לבנות מודול עבור Firebird או לפחות לבדוק עבודה תקינה שלו דרך ODBC.
;-)
נכון שQT לא משתמשת בטיפוסים מstdc++ אבל היא מספקת התממשקות שפוטה למדי לטיפוסים האלו, כך שזה לא נורא. באשר לאקספשנים זה נכון, שיטת העבודה המקובלת של QT איננה כוללת אקספשנים.
ארתיום, אם היית משתמש בשפה נורמאלית והגיונית לשימוש ולא ב ++C אולי הייתי עוזר לך עם Firebird (שגם כתוב בשפה ההזויה שאתה כותב בה).
וב Qt יש לך תמיכה גם בFirebird :)
בנוסף, ה TDataSet של FPC ודלפי הוא לא ORM נקי, אלא שכבה של חיבור בין מסדי נתונים לבין האפליקציה שלך. רק זו תמיכה מקיפה.
ד"א בקשר ל last inserted id, לא כל מסדי הנתונים בעולם תומכים בו, ועצם זה ש mysql צריך שאילתא לזה בניגוד לעבוד עם sequence רק מדגיש את הבעיתיות בנושא.
למעשה אני התכוונתי גם ל־sequences באשר הם, כל למשל:
אגב, איך עושים את זה ב־firebird?
קודם כל לפי תיעוד show generator אמור לעבוד רק מ-isql ולא ב-API.
שנית אני ראיתי דברים כאלה באותו התיעוד:
אבל לפי: http://www.firebirdfaq.org/faq243/ נראה כי זה לא נכון לעשות דבר כזה.
בגלל זה אני לא מבין מה אפשר לעשות בנידון?
זה תלוי אם אתה רוצה להסתמך עליו כה id בשביל insert או אם כאשר אתה עושה את השאילתא, אתה רוצה לדעת את הערך האחרון של ה sequence.
אתה לא יכול לסמוך עליו בשביל להזין את הערך הבא, בשביל זה צריך GEN_ID(seq,1) מתוך ה insert.
סליחה, לא הבנתי את התשובה שלך? אז האם זה בסדר:
נניח יש לי סכמה:
עכשיו אני מכניס ערך לטבלה ורוצה לדעת את ה-id שלה
זה נכון או לא?
כן זה נכון אם בטריגר אתה אמרת לו
GEN_ID(foo_id_seq, 1)
תודה!
Qt מתעלם מכל מה שקיים בC++ וגרוע. תנסה את QString, ותהנה.
אתה באמת רוצה לעודד שימוש בstd::string?
למשל:
string s; string t = s; //in critical section t[0] = 1; //now go figure why is my code slow
בהחלט
הסיבה ש־std::string הוא Copy-On-Write במרבית הספריות וזה דבר ענק שמאפשר לך לחסוך בהעתקה של מחרוזות אז אם אתה מבצע פעולה כזו בקטע קריטי מה שקורה שהמחרוזת מועתקת כדי ליצור עותק נפרד לשינויים.
אבל מה שאמרת הוא בהחלט בעיה. std::string הוא COW ברב הספריות, אבל לא בכולם, ואז, קשה לי לדעת מהם הביצועים באופן Cross platform. בQt זה מוגדר היטב.
אגב, טעיתי, בQt המחרוזות הם גם COW, ויכולה להיות אותה בעיה. אז הנקודה הקטנה הזו בכלל לא נכונה. לדעתי יותר הגיוני שהמחרוזות תהינה Immutable.
אבל,
אתה בטוח שאתה רוצה להשתמש בstd::string, ומה עם תמיכה ביוניקוד, או בכלל שינויי קידוד? ומה עם תמיכה באיטרטורים שפויים נוסח ג'אווה? אני לא צריך שהאיטרטור יעבוד גם עם פוינטרים, מעדיף שהוא יהיה נוח לשימוש. מה עם format("%.3f"), את זה כבר הרבה יותר מסורבל לעשות עם std::string.
אין מה לעשות, הספריות האלו נכתבו פעם, לצרכים של פעם (כשקריאה לפונקציה וירטואלית נחשבה ליקרה, לכן אין שימוש בה בSTL), והיא לא הכי מתאימה היום. כתוצאה מזה, כל פרוייקט מממש מחדש חלקים של Qt (ובצדק, כי Qt מאד שמן, ולא תמיד כדאי להשתמש בו), אבל אני לא רואה שום אידיאל להשתמש בstd::string למשל בקוד קיים.
http://www.boostcon.com/site-media/var/sphene/sphwiki/attachment/2009/05/08/iterators-must-go.pdf
אה, ושכחתי, מה עם שרשור של מחרוזות, append?
ליתר דיוק ב־MSVC החל מגרסה 7 וב־STL-Port (אם כי אני חושב שזה ניתן לקינפוג) ברוב הספריות הסטנדרטיות המודרניות הן דווקא כן COW.
בכל אופן, לגמרי מה שאמרת אתה צריך להניח שהמחרוזת יכולה להיות COW ויכול לא להיות, לכן, למשל כשאתה מעביר אותה כפרמטר תעביר אותה כ־
std::string const &val
ואל תעתיק.מה הבעיה? מה רע ב־UTF-8? להזכירך Boost.Locale נותן תמיכה מצויינת ב־Unicode ומשתמש ב־std::string בכל מקום. "הבעיות" שיש ל־std::string מבחינת תמיכה ב־Unicode ישנם גם ל־string של Java וגם ל־QString (להזכירך UTF-16 הוא קידוד באורך משתנה)
זה עניין של טעם, למשל איטרטורים של C++ (בניגוד ל־Java) הם יכולים להיות forward, backward וגם random-access
מה מסורבל:
או אם לא רוצים boost אז
קודם כל זה לא נכון, כל מערכת iostreams מבוססת על זה, והעובדה שלא הכל יורש מ־Object זה לא בדיוק דבר רע, הגישה פשוט שונה. וצריך להבין את זה.
רק שאתה שוכח יתרון קטן, למשל עבודה עם std::vector מהירה באותה מידה כמו עם מערך רגיל או מצביעים - זה אפילו מתורגם אותו קוד assembly.
סליחה? אז למה שלא תמציא הכל מחדש? std::string הוא חלק אינוגרלי ומהשפה אז למה לא להשתמש בו?!
???
מה הבעיה? אתה יכול להסביר? ל־std::string אין append או אין לה אופרטור + או +=?
הכתבה הזו היא מאוד שנויה במחלוקת גם בקרב מפתחי Boost.
ומה אם אני עובד בגוגל, שם הסטנדרט קידוד לא מאפשר קבלת אובייקטים by reference? אז אני אקבל פוינטר למצביע? אפשרי אבל מגעיל.
מה יחזיר לי size() של מחרוזת בעברית? אז זהו, שלא ברור לי שיש שם מחרוזת בקידוד מסויים. בQt אני יודע במה הקידוד.
זה לא עניין של טעם. אם בשביל לעבור על וקטור אני צריך לכתוב משהו כזה
ויש לי הרבה נקודות כשל (++i או i++, זה יכול לנשוך אותך, שמת לב לטעות העדינה שגוררת התנהגות בלתי מוגדרת שיש לי בתוכנית) אז משהו כאן לא טוב נקודה. אולי אם הייתי רוצה לסחוט את האמא של הביצועים ולעשות משהו שהוא גנרי גם לפוינטרים "ערומים" אז זה היה הגיוני, אבל היום אין שום הגיון לעשות את זה (אז אל תעבוד עם מצביעים ערומים, תעטוף אותם באובייקט, אתה תשרוד).
בלי בוסט, שים לב כמה קוד כתבת בשביל משהו פשוט. זה נקרא מסורבל, לפחות אצלי. העניין הוא שהפעולה הזו היא כמעט יום יומית, ואם היא מסובכת זה מעצבן.
כי צריך לעדכן את הסטנדרט, כך שלמשל הקידוד של המחרוזת יהיה מוטמע בה ומוגדר היטב. עד אז לגיטימי להשתמש בסטנדרד אחר, יותר מודרני, כמו Qt.
הניסוח שלי באמת לא טוב. כוונתי היתה ששרשור של מחרוזות באופן יעיל יכול להיות עכש"י רק עם sstream, ובQt אפשר לעשות שגם שרשור של מחרוזות רגילות יעבוד מהר.
תחכים אותי, מה בSTL משתמש בפונ' וירטואליות? יכול להיות שאני לא מכיר. שמעתי את ההסבר הזה (אין פונ' וירטואליות בגלל שיקולי יעילות) משועל C++ והוא נשמע לי הגיוני.
אבל תסכים איתי ששיקול של לחסוך שני פקודות אסמבלי על כתיבת wrapper למצביעים לא רלוונטי היום, לא?
מה היית רוצה שיהיה האורך של "שָׁלוֹם"? 4 או 7? גם ב-Qt וגם ב-Java תקבל 7. מה היית רוצה שיהיה האורך של "𝄞"? בטח 1, נכון, אבל גם ב-QString וגם ב-Java תקבל 2 כי UTF-16 הוא גם בעל אורך משתנה.
אז size() הוא חסר משמעות לכל צורך לינגוויסטי קיומי. אם אתה רוצה לקבל אורך אתה צריך להגדיר משהו יותר מדויק.
פשוט תשתמש תמיד ב־UTF-8 וזהו. אין לך מה לחשוב יותר מידי.
אפשר לכתוב גם אחרת (וגם לתקן טעות שהכנסת).
שד"א i++ ו־++i עובד בצורה זהה מבחינת ביצועים בווקטור רק שאתה גם תוכל לשנות some_type בלי לשנות קידוד.
ואגב, עם יש לך קומפיילר מספיק חדש אפשר לכתוב גם
או אפילו
ממש לא: שתי דוגמאות פשוטות איך עושים:
עובד בזמן לימארי (נחש מדוע). כמובן אפשר לייעל יותר
זה עניין של שימוש נכון וגישה נכונה, הגישה שלך היא גישה של C#/Java האומרת תעבוד עם string-builder - הסיבה - כי שם string הוא immutable שזה שונה לחלוטין, בניגוד ל־C++ שזה מאוד יעיל.
תשמע, STL הוא לא אוייב שלך, הוא דווקא ידיד טוב, אבל צריך לדעת להשתמש בו ולעבוד בגישה שלו.
אם אתה בא עם הגישה של Java או C# זה לא יעבוד, צריך לחשוב אחרת.
תראה יש יתרון גדול ל"מבציעים" רק צריך להשתמש בהם בצורה נכונה. זה עניין של גישה.
ואם אתה מדבר על Qt אז בואו נדבר?
יש עוד דברים אבל עזוב. הכל עניין של גישה.
אני בוחר בגישת של השפה, בגישה של Boost אתה בוחר להתעלם ממה שיש בשפה ולהתרכז במה שיש ב־Toolkit מסוים. זה גם בסדר.
לכל אחת יש יתרונות וחסרונות
הוסף תגובה:
חובה לאפשר JavaScript כדי להגיב.