קישוריות לבסיסי נתונים מ־C++‎

ב־14.12.2010, מאת ארתיום; פורסם תחת: תכנה חופשית, פיתוח, תכנה ומחשבים, C++‎‏; ‏24 תגובות

לאחרונה אני הייתי מאוד לא מרוצה מעבודה עם libdbi‏ בתור ספריית הקישוריות לבסיסי נתונים וגם לא הצלחתי להתחבר ל־SOCI‏ ממספר סיבות נוספות.

הספריה הראשונה libdbi לא תומכת בכלל ב־Prepared Statements, יש לה לא מעט בעיות בגישה לטעינה של מודולים, הספריה השניה soci לא מי יודע מה מוצלחת מבחינת הגישה שלה לפיתוח, בפרט, סירוב עקבי שלא לתמוך ב־Last-Insert-Id או עבודה קצת עקומה עם prepared-statements ומחזור שחרור גרסאות מאוד ארוך (שבפועל דורש ממך לעבוד עם גרסת git).

אז מתוך ניסיון עבודה עם שתי הספריות האלה ניסיון מסוים בעבודה עם JDBC החלטתי לבנות משהו חילופי כאשר הדרישה העיקרית היא:

  1. לתמוך בצורה שקופה ב־prepared-statements וגם ליצור cache שלהם כך שתהליך ההכנה שלהם יתבצע בפועל פעם אחת בלבד בצורה שקופה.
  2. לתמוך ב־connection pooling בצורה שקופה ונוחה.
  3. אפשרות לטעון מודולים של בסיסי הנתונים בצורה דינאמית וגם לקשר אותם בצורה סטטית לצורך הפצה נוחה.
  4. גמישות מבחינת בחירה של statement רגיל ו־prepared.‏
  5. מהירות
  6. תמיכה מלאה ב־postgresql,‏ mysql וב־sqlite3.‏
  7. תמיכה במספר גבוהה ככל האפשר של בסיסי הנתונים דרך מערכת קישוריות חילופית (odbc).

כך נוצרה ספריית cppdb‏ הזמינה תחת LGPLv3 (קישור חילופי).

כמובן רבים יאשימו אותי בהמצאת גלגל מחדש, אבל למעשה יצרתי משהו מאוד ייחודי שלא קיים במרבית ספריות הקישוריות הקיימות (ולא רק ב־C++‎) - הסיבה העיקרית זאת הנוחות העבודה עם prepared-statements והשמירה האוטומטית שלהם לצורך השימוש החוזר שנותנת תוספת ביצועים של עשרות אחוזים.

בנוסף שחררתי גם עדכון ל־dbixx‏ (המעטפת של libdbi) המביאה מספר תיקונים ושיפורים יחד עם ההחלטה להזניח את הפיתוח שלה תוך כשנה לטובת ה־cppdb.

תגובות

אורגד, ב־14.12.2010, 10:19

מה רע ב־libQtSql?

ארתיום, ב־14.12.2010, 10:35

מה רע ב־libQtSql?

שום דבר רע מלבד ש־Qt מתעלם מכל דבר קיים ב־C++‎ למשל, הם משתמשים ב־QSting במקום std::string,‏ הם משתמשים ב־QDate במקום std::tm ועוד. שלא לדבר על העובדה שהספריה לא זורקת exceptions בכלל.

מאיר, ב־14.12.2010, 11:43

אתה אומר את זה כאילו שזה משהו רע :-P

ארתיום, ב־14.12.2010, 11:54

אתה אומר את זה כאילו שזה משהו רע :-P

חחחחח, תלוי מה הציפיות שלך...

אגב שלא יהיו אי הבנות, לדעתי Qt היא אחת הספריות הטובות ביותר שיש ל־C++‎.

ik_5, ב־14.12.2010, 11:57

אחד הדברים שיש בדלפי ו FPC שלדעתי אין להם תחליף ראוי בשום שפה זה ספריית DataSet.

היא מספקת לך API דמוי ORM למסדי נתונים מצד אחד, ומימוש ה API לחיבור בצד השני, והוא אגנוסטי לגמרי, כך שהוא נותן לך כלים לכתוב למשל ממשקים גרפיים שתומכים לך במסדי נתונים בלי קשר למה שהמסד נתונים שלך תומך, או החיבור שלך. כלומר להחליף עכשיו חיבור לוקח ממש מהר, פחות מ10 דקות בד"כ וזה כל מה שאתה צריך.

איזה סוגי חיבורים הוא תומך ? הוא לא. המימוש שלך של TDatabase למשל תומך בהם, כך שאתה יכול לתמוך ב Firebird בצורה ישירה, דרך ODBC או על ידי מימוש עצמי שלך של הפרוטוקול תקשורת שלו, זה לא משנה כל עוד אתה עובד לפי מימוש ה API שלו.

ד"א הוא תומך בכל מה שאתה כתבת לו תמיכה במנוע שלך, ועוד הרבה יותר. כלומר הוא תומך ב prepare statements, יש לו תמיכה ב cache, הוא תומך ב binding אנונימי ושמי, הוא תומך ביכולת ליצור lookup tables בלי קשר למסדי הנתונים שלך ועוד המון דברים, כולל תמיכה ב format למילוי התוכן, הגבלה האם השדה יכול להיות לקריאה בלבד, כתיבה בלבד, וכו', אם הוא נדרש למילוי או לא, מה לעשות כאשר המידע עליו נאסף, והרשימה עוד ארוכה מאוד. כמות הכוח שיש לך שם מאוד ייחודית להרבה ספריות מסדי נתונים אחרים שעבדתי איתם, כולל DBI וכמה סוגי ORM, וכמובן JDBC.

ארתיום, ב־14.12.2010, 12:15

עידו, שים לב, אני לא מנסה לבנות ORM (שאני לא כל־כך מאמין בו) אלא משהו הרבה יותר פשוט וישיר - API אחיד לעבודה עם SQL.

ד"א אם אתה מוכן להשקיע קצת מאמץ אתה מוזמן לבנות מודול עבור Firebird או לפחות לבדוק עבודה תקינה שלו דרך ODBC. ;-)

יורם, ב־14.12.2010, 12:41

נכון שQT לא משתמשת בטיפוסים מstdc++ אבל היא מספקת התממשקות שפוטה למדי לטיפוסים האלו, כך שזה לא נורא. באשר לאקספשנים זה נכון, שיטת העבודה המקובלת של QT איננה כוללת אקספשנים.

ik_5, ב־14.12.2010, 12:54

ארתיום, אם היית משתמש בשפה נורמאלית והגיונית לשימוש ולא ב ++C אולי הייתי עוזר לך עם Firebird (שגם כתוב בשפה ההזויה שאתה כותב בה).

וב Qt יש לך תמיכה גם בFirebird :)

ik_5, ב־14.12.2010, 13:27

בנוסף, ה TDataSet של FPC ודלפי הוא לא ORM נקי, אלא שכבה של חיבור בין מסדי נתונים לבין האפליקציה שלך. רק זו תמיכה מקיפה.

ד"א בקשר ל last inserted id, לא כל מסדי הנתונים בעולם תומכים בו, ועצם זה ש mysql צריך שאילתא לזה בניגוד לעבוד עם sequence רק מדגיש את הבעיתיות בנושא.

ארתיום, ב־14.12.2010, 14:42

ד"א בקשר ל last inserted id, לא כל מסדי הנתונים בעולם תומכים בו, ועצם זה ש mysql צריך שאילתא לזה בניגוד לעבוד עם sequence רק מדגיש את הבעיתיות בנושא.

למעשה אני התכוונתי גם ל־sequences באשר הם, כל למשל:

- sqlite3 - "select last_insert_rowid()"
- mysql - "select last_insert_id()"
- postgresql - "select currval('seq')"
- mssql - "select @@identity"
- oracle - "select seq.currval from dual"

אגב, איך עושים את זה ב־firebird?

ik_5, ב־14.12.2010, 15:02
SHOW GENERATOR <GeneratorName>;
SHOW SEQUENCE <SequenceName>;
ארתיום, ב־14.12.2010, 15:40

קודם כל לפי תיעוד ‏ show generator אמור לעבוד רק מ-isql ולא ב-API.

שנית אני ראיתי דברים כאלה באותו התיעוד:‏

SELECT GEN_ID(seq,0) FROM RDB$DATABASE

אבל לפי: http://www.firebirdfaq.org/faq243/ נראה כי זה לא נכון לעשות דבר כזה.

בגלל זה אני לא מבין מה אפשר לעשות בנידון?

ik_5, ב־14.12.2010, 16:22

זה תלוי אם אתה רוצה להסתמך עליו כה id בשביל insert או אם כאשר אתה עושה את השאילתא, אתה רוצה לדעת את הערך האחרון של ה sequence.

אתה לא יכול לסמוך עליו בשביל להזין את הערך הבא, בשביל זה צריך GEN_ID(seq,1) מתוך ה insert.

ארתיום, ב־14.12.2010, 16:29

זה תלוי אם אתה רוצה להסתמך עליו כה id בשביל insert או אם כאשר אתה עושה את השאילתא, אתה רוצה לדעת את הערך האחרון של ה sequence.

אתה לא יכול לסמוך עליו בשביל להזין את הערך הבא, בשביל זה צריך GEN_ID(seq,1) מתוך ה insert.

סליחה, לא הבנתי את התשובה שלך? אז האם זה בסדר:

נניח יש לי סכמה:

CREATE GENERATOR foo_id_seq;
create table foo ( id intger not null, name varchar(10));
create trigger ...
-- Some sequence trigger that
-- uses foo_id_seq;

עכשיו אני מכניס ערך לטבלה ורוצה לדעת את ה-id שלה

insert into foo(name) values('ido');
select GEN_ID(foo_id_seq,0) from  RDB$DATABASE;

זה נכון או לא?

ik_5, ב־14.12.2010, 18:50

כן זה נכון אם בטריגר אתה אמרת לו GEN_ID(foo_id_seq, 1)‎

ארתיום, ב־14.12.2010, 21:24

תודה!

ליבוביץ, ב־15.12.2010, 23:23

Qt מתעלם מכל מה שקיים בC++ וגרוע. תנסה את QString, ותהנה.

אתה באמת רוצה לעודד שימוש בstd::string?

למשל:

string s; string t = s; //in critical section t[0] = 1; //now go figure why is my code slow

ארתיום, ב־16.12.2010, 11:52

אתה באמת רוצה לעודד שימוש ב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 במרבית הספריות וזה דבר ענק שמאפשר לך לחסוך בהעתקה של מחרוזות אז אם אתה מבצע פעולה כזו בקטע קריטי מה שקורה שהמחרוזת מועתקת כדי ליצור עותק נפרד לשינויים.

ליבוביץ, ב־17.12.2010, 9:10

אבל מה שאמרת הוא בהחלט בעיה. 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

ליבוביץ, ב־17.12.2010, 9:59

אה, ושכחתי, מה עם שרשור של מחרוזות, append?

ארתיום, ב־17.12.2010, 10:39

אבל מה שאמרת הוא בהחלט בעיה. std::string הוא COW ברב הספריות,

ליתר דיוק ב־MSVC החל מגרסה 7 וב־STL-Port (אם כי אני חושב שזה ניתן לקינפוג) ברוב הספריות הסטנדרטיות המודרניות הן דווקא כן COW.

בכל אופן, לגמרי מה שאמרת אתה צריך להניח שהמחרוזת יכולה להיות COW ויכול לא להיות, לכן, למשל כשאתה מעביר אותה כפרמטר תעביר אותה כ־std::string const &val ואל תעתיק.

אתה בטוח שאתה רוצה להשתמש בstd::string, ומה עם תמיכה ביוניקוד, או בכלל שינויי קידוד?

מה הבעיה? מה רע ב־UTF-8? להזכירך Boost.Locale נותן תמיכה מצויינת ב־Unicode ומשתמש ב־std::string בכל מקום. "הבעיות" שיש ל־std::string מבחינת תמיכה ב־Unicode ישנם גם ל־string של Java וגם ל־QString (להזכירך UTF-16 הוא קידוד באורך משתנה)

אני לא צריך שהאיטרטור יעבוד גם עם פוינטרים, מעדיף שהוא יהיה נוח לשימוש.

זה עניין של טעם, למשל איטרטורים של C++‎ (בניגוד ל־Java) הם יכולים להיות forward, backward וגם random-access

מה עם format("%.3f"), את זה כבר הרבה יותר מסורבל לעשות עם std::string.

מה מסורבל:

std::string str=(boost::format("%.3f") % value).str();

או אם לא רוצים boost אז

std::stringstream ss;
ss << std::setpresision(3) << value;
return ss.str();

(כשקריאה לפונקציה וירטואלית נחשבה ליקרה, לכן אין שימוש בה בSTL)

קודם כל זה לא נכון, כל מערכת iostreams מבוססת על זה, והעובדה שלא הכל יורש מ־Object זה לא בדיוק דבר רע, הגישה פשוט שונה. וצריך להבין את זה.

רק שאתה שוכח יתרון קטן, למשל עבודה עם std::vector מהירה באותה מידה כמו עם מערך רגיל או מצביעים - זה אפילו מתורגם אותו קוד assembly.

אבל אני לא רואה שום אידיאל להשתמש בstd::string למשל בקוד קיים.

סליחה? אז למה שלא תמציא הכל מחדש? std::string הוא חלק אינוגרלי ומהשפה אז למה לא להשתמש בו?!

אה, ושכחתי, מה עם שרשור של מחרוזות, append?

???

מה הבעיה? אתה יכול להסביר? ל־std::string אין append או אין לה אופרטור + או ‎+=‎?

ארתיום, ב־17.12.2010, 10:40

http://www.boostcon.com/site-media/var/sphene/sphwiki/attachment/2009/05/08/iterators-must-go.pdf

הכתבה הזו היא מאוד שנויה במחלוקת גם בקרב מפתחי Boost.

ליבוביץ, ב־19.12.2010, 14:35

לכן אם אתה רוצה לקבל מחרוזת קבל אותה by reference

ומה אם אני עובד בגוגל, שם הסטנדרט קידוד לא מאפשר קבלת אובייקטים by reference? אז אני אקבל פוינטר למצביע? אפשרי אבל מגעיל.

מה הבעיה? מה רע ב־UTF-8?

מה יחזיר לי size() של מחרוזת בעברית? אז זהו, שלא ברור לי שיש שם מחרוזת בקידוד מסויים. בQt אני יודע במה הקידוד.

זה עניין של טעם, למשל איטרטורים של C++‎ (בניגוד ל־Java) הם יכולים להיות forward, backward וגם random-access

זה לא עניין של טעם. אם בשביל לעבור על וקטור אני צריך לכתוב משהו כזה

for (std::vector<int>::iterator it(v.begin());i!=u.end();++i)

ויש לי הרבה נקודות כשל (++i או i++, זה יכול לנשוך אותך, שמת לב לטעות העדינה שגוררת התנהגות בלתי מוגדרת שיש לי בתוכנית) אז משהו כאן לא טוב נקודה. אולי אם הייתי רוצה לסחוט את האמא של הביצועים ולעשות משהו שהוא גנרי גם לפוינטרים "ערומים" אז זה היה הגיוני, אבל היום אין שום הגיון לעשות את זה (אז אל תעבוד עם מצביעים ערומים, תעטוף אותם באובייקט, אתה תשרוד).

מה מסורבל: std::string str=(boost::format("%.3f") % value).str(); או אם לא רוצים boost אז

בלי בוסט, שים לב כמה קוד כתבת בשביל משהו פשוט. זה נקרא מסורבל, לפחות אצלי. העניין הוא שהפעולה הזו היא כמעט יום יומית, ואם היא מסובכת זה מעצבן.

סליחה? אז למה שלא תמציא הכל מחדש? std::string הוא חלק אינוגרלי ומהשפה אז למה לא להשתמש בו?!

כי צריך לעדכן את הסטנדרט, כך שלמשל הקידוד של המחרוזת יהיה מוטמע בה ומוגדר היטב. עד אז לגיטימי להשתמש בסטנדרד אחר, יותר מודרני, כמו Qt.

מה הבעיה? אתה יכול להסביר? ל־std::string אין append או אין לה אופרטור + או ‎+=‎?

הניסוח שלי באמת לא טוב. כוונתי היתה ששרשור של מחרוזות באופן יעיל יכול להיות עכש"י רק עם sstream, ובQt אפשר לעשות שגם שרשור של מחרוזות רגילות יעבוד מהר.

קודם כל זה לא נכון, כל מערכת iostreams מבוססת על זה, והעובדה שלא הכל יורש מ־Object זה לא בדיוק דבר רע, הגישה פשוט שונה. וצריך להבין את זה. רק שאתה שוכח יתרון קטן, למשל עבודה עם std::vector מהירה באותה מידה כמו עם מערך רגיל או מצביעים - זה אפילו מתורגם אותו קוד assembly.

תחכים אותי, מה בSTL משתמש בפונ' וירטואליות? יכול להיות שאני לא מכיר. שמעתי את ההסבר הזה (אין פונ' וירטואליות בגלל שיקולי יעילות) משועל C++ והוא נשמע לי הגיוני.

אבל תסכים איתי ששיקול של לחסוך שני פקודות אסמבלי על כתיבת wrapper למצביעים לא רלוונטי היום, לא?

ארתיום, ב־19.12.2010, 15:09

מה יחזיר לי size() של מחרוזת בעברית?

מה היית רוצה שיהיה האורך של "שָׁלוֹם"? 4 או 7? גם ב-Qt וגם ב-Java תקבל 7. מה היית רוצה שיהיה האורך של "𝄞"? בטח 1, נכון, אבל גם ב-QString וגם ב-Java תקבל 2 כי UTF-16 הוא גם בעל אורך משתנה.

אז size()‎ הוא חסר משמעות לכל צורך לינגוויסטי קיומי. אם אתה רוצה לקבל אורך אתה צריך להגדיר משהו יותר מדויק.

אז זהו, שלא ברור לי שיש שם מחרוזת בקידוד מסויים.

פשוט תשתמש תמיד ב־UTF-8 וזהו. אין לך מה לחשוב יותר מידי.

זה לא עניין של טעם. אם בשביל לעבור על וקטור אני צריך לכתוב משהו כזה

 for (std::vector<int>::iterator it(v.begin());i!=u.end();++i)

אפשר לכתוב גם אחרת (וגם לתקן טעות שהכנסת).

for(some_type::iterator i=v.begin();i!=v.end();i++)

שד"א i++ ו־++i עובד בצורה זהה מבחינת ביצועים בווקטור רק שאתה גם תוכל לשנות some_type בלי לשנות קידוד.

ואגב, עם יש לך קומפיילר מספיק חדש אפשר לכתוב גם

for(auto i=v.begin();i!=v.end();i++)

או אפילו

for(auto i: v)

הניסוח שלי באמת לא טוב. כוונתי היתה ששרשור של מחרוזות באופן יעיל יכול להיות עכש"י רק עם sstream, ובQt אפשר לעשות שגם שרשור של מחרוזות רגילות יעבוד מהר

ממש לא: שתי דוגמאות פשוטות איך עושים:

std::string res;
for(auto it : others)
   res+=*it

עובד בזמן לימארי (נחש מדוע). כמובן אפשר לייעל יותר

std::string res;
res.reserve(approx_size)
for(auto it : others)
   res+=*it

זה עניין של שימוש נכון וגישה נכונה, הגישה שלך היא גישה של C#/Java האומרת תעבוד עם string-builder - הסיבה - כי שם string הוא immutable שזה שונה לחלוטין, בניגוד ל־C++‎ שזה מאוד יעיל.

תשמע, STL הוא לא אוייב שלך, הוא דווקא ידיד טוב, אבל צריך לדעת להשתמש בו ולעבוד בגישה שלו.

אם אתה בא עם הגישה של Java או C#‎ זה לא יעבוד, צריך לחשוב אחרת.

אבל תסכים איתי ששיקול של לחסוך שני פקודות אסמבלי על כתיבת wrapper למצביעים לא רלוונטי היום, לא?

תראה יש יתרון גדול ל"מבציעים" רק צריך להשתמש בהם בצורה נכונה. זה עניין של גישה.

ואם אתה מדבר על Qt אז בואו נדבר?

  • מה עם exception? לא צריך אותם?
  • מה עם exception-safety (גם איננה לרוב)

יש עוד דברים אבל עזוב. הכל עניין של גישה.

אני בוחר בגישת של השפה, בגישה של Boost אתה בוחר להתעלם ממה שיש בשפה ולהתרכז במה שיש ב־Toolkit מסוים. זה גם בסדר.

לכל אחת יש יתרונות וחסרונות

הוסף תגובה:

 
 כתובת דוא"ל לא תוצג
 

ניתן לכתוב תגובות עם שימוש בתחביר Markdown.

חובה לאפשר JavaScript כדי להגיב.

דפים

נושאים