Từ 1 tới 8 trên tổng số 8 kết quả

Đề tài: SQLite-ORM: một binding cho SQLite

  1. #1
    Ngày gia nhập
    01 2008
    Nơi ở
    Rất đông người
    Bài viết
    719

    Mặc định SQLite-ORM: một thư viện giao tiếp C++ với SQLite

    Còn nhớ thời sinh viên. Năm thứ nhất học ngôn ngữ C, có bài tập về hệ thống thông tin. Một record chứa thông tin về bác sỹ viết như thế này.
    C Code:
    1. typedef struct _doctor_ {
    2.     int        doctor_id;
    3.     char[30]   doctor_name;
    4.     char[5]    degree;
    5. } Doctor;

    Lên năm thứ hai học ngôn ngữ C++, viết như thế này.
    C++ Code:
    1. struct Doctor {
    2.     int         doctor_id;
    3.     std::string doctor_name;
    4.     std::string degree;
    5. };
    (Để đơn giản hoá vấn đề, mình hơi "bịp bợm" một chút. Thời mình học, ngôn ngữ C++ chưa có std::string.)

    Lên năm thứ ba thì không thể viết được như thế nữa, vì chương trình có một phần C++ và một phần SQL. Càng lên năm trên, lập trình càng dài dòng rắc rối, càng xa rời lý tưởng ban đầu. Từ ấy, mình cứ nhủ thầm, nếu mọi thứ đều viết cả bằng C++ thì có phải dễ biết bao nhiêu không. Chẳng những dễ hơn, mà còn an toàn hơn nữa. Nhưng đó là điều mà nhiều năm tháng sau khi ra trường, mình mới thấm thía. Ước gì được mãi làm sinh viên năm thứ nhất, thứ hai.

    Chắc hẳn cứ 100 người thì có 99 người nghĩ giống mình. Cho nên thư viện giao tiếp (binding) C++ sang SQLite nhiều vô số kể. Chỉ riêng mã nguồn mở, thuộc hàng danh tiếng, cũng có hàng chục cái. Nhưng mình chẳng ưng cái nào mãi đến tận ngày hôm qua.

    SQLite-ORM của anh Ép-ghê-nhi Za-kha-rốp, lập trình viên đến từ Belarus, là một thư viện giao tiếp C++ sang SQLite nổi tiếng, dù mới xuất hiện khoảng 5 năm nay. Nói ra thì ngượng, mình mới biết đến nó ngày hôm nay.

    Nó dễ dùng, trước hết, vì dễ học. Trang chủ có bài giới thiệu dễ đọc, trang wiki có hướng dẫn khá đầy đủ. Một video (YouTube) giảng giải chi tiết thực thi các kiểu mẫu và hàm mẫu một cách dễ hiểu. Một thư mục đầy thí dụ. Hãy xem qua một thí dụ dùng SQLite-ORM.

    Tệp nguồn natural_join.cpp trong thư mục con examples:
    C++ Code:
    1. /**
    2.  *  Implemented this example https://www.w3resource.com/sqlite/sqlite-natural-join.php
    3.  */
    4.  
    5. #include <sqlite_orm/sqlite_orm.h>
    6. #include <string>
    7. #include <iostream>
    8.  
    9. using std::cout;
    10. using std::endl;
    11.  
    12. struct Doctor {
    13.     int doctor_id;
    14.     std::string doctor_name;
    15.     std::string degree;
    16. };
    17.  
    18. struct Speciality {
    19.     int spl_id;
    20.     std::string spl_descrip;
    21.     int doctor_id;
    22. };
    23.  
    24. struct Visit {
    25.     int doctor_id;
    26.     std::string patient_name;
    27.     std::string vdate;
    28. };
    29.  
    30. int main() {
    31.     using namespace sqlite_orm;
    32.  
    33.     auto storage = make_storage("natural_join.sqlite",
    34.                                 make_table("doctors",
    35.                                            make_column("doctor_id", &Doctor::doctor_id, primary_key()),
    36.                                            make_column("doctor_name", &Doctor::doctor_name),
    37.                                            make_column("degree", &Doctor::degree)),
    38.                                 make_table("speciality",
    39.                                            make_column("spl_id", &Speciality::spl_id, primary_key()),
    40.                                            make_column("spl_descrip", &Speciality::spl_descrip),
    41.                                            make_column("doctor_id", &Speciality::doctor_id)),
    42.                                 make_table("visits",
    43.                                            make_column("doctor_id", &Visit::doctor_id),
    44.                                            make_column("patient_name", &Visit::patient_name),
    45.                                            make_column("vdate", &Visit::vdate)));
    46.     storage.sync_schema();
    47.     storage.remove_all<Doctor>();
    48.     storage.remove_all<Speciality>();
    49.     storage.remove_all<Visit>();
    50.  
    51.     storage.replace(Doctor{210, "Dr. John Linga", "MD"});
    52.     storage.replace(Doctor{211, "Dr. Peter Hall", "MBBS"});
    53.     storage.replace(Doctor{212, "Dr. Ke Gee", "MD"});
    54.     storage.replace(Doctor{213, "Dr. Pat Fay", "MD"});
    55.  
    56.     storage.replace(Speciality{1, "CARDIO", 211});
    57.     storage.replace(Speciality{2, "NEURO", 213});
    58.     storage.replace(Speciality{3, "ARTHO", 212});
    59.     storage.replace(Speciality{4, "GYNO", 210});
    60.  
    61.     storage.replace(Visit{210, "Julia Nayer", "2013-10-15"});
    62.     storage.replace(Visit{214, "TJ Olson", "2013-10-14"});
    63.     storage.replace(Visit{215, "John Seo", "2013-10-15"});
    64.     storage.replace(Visit{212, "James Marlow", "2013-10-16"});
    65.     storage.replace(Visit{212, "Jason Mallin", "2013-10-12"});
    66.  
    67.     {
    68.         //  SELECT doctor_id,doctor_name,degree,patient_name,vdate
    69.         //  FROM doctors
    70.         //  NATURAL JOIN visits
    71.         //  WHERE doctors.degree="MD";
    72.         auto rows = storage.select(
    73.             columns(&Doctor::doctor_id, &Doctor::doctor_name, &Doctor::degree, &Visit::patient_name, &Visit::vdate),
    74.             natural_join<Visit>(),
    75.             where(c(&Doctor::degree) == "MD"));
    76.         cout << "rows count = " << rows.size() << endl;
    77.         for(auto &row: rows) {
    78.             cout << std::get<0>(row) << '\t' << std::get<1>(row) << '\t' << std::get<2>(row) << '\t' << std::get<3>(row)
    79.                  << '\t' << std::get<4>(row) << endl;
    80.         }
    81.     }
    82.     cout << endl;
    83.     {
    84.         //  SELECT doctor_id,doctor_name,degree,spl_descrip,patient_name,vdate
    85.         //  FROM doctors
    86.         //  NATURAL JOIN speciality
    87.         //  NATURAL JOIN visits
    88.         //  WHERE doctors.degree='MD';
    89.         auto rows = storage.select(columns(&Doctor::doctor_id,
    90.                                            &Doctor::doctor_name,
    91.                                            &Doctor::degree,
    92.                                            &Speciality::spl_descrip,
    93.                                            &Visit::patient_name,
    94.                                            &Visit::vdate),
    95.                                    natural_join<Speciality>(),
    96.                                    natural_join<Visit>(),
    97.                                    where(c(&Doctor::degree) == "MD"));
    98.         cout << "rows count = " << rows.size() << endl;
    99.         for(auto &row: rows) {
    100.             cout << std::get<0>(row) << '\t' << std::get<1>(row) << '\t' << std::get<2>(row) << '\t' << std::get<3>(row)
    101.                  << '\t' << std::get<4>(row) << '\t' << std::get<5>(row) << endl;
    102.         }
    103.     }
    104.  
    105.     return 0;
    106. }

    Kết quả, thư mục làm việc xuất hiện tệp CSDL mới natural_join.sqlite, và màn hình hiển thị
    Code:
    rows count = 3
    210     Dr. John Linga  MD      Julia Nayer     2013-10-15
    212     Dr. Ke Gee      MD      James Marlow    2013-10-16
    212     Dr. Ke Gee      MD      Jason Mallin    2013-10-12
    
    rows count = 3
    212     Dr. Ke Gee      MD      ARTHO   James Marlow    2013-10-16
    212     Dr. Ke Gee      MD      ARTHO   Jason Mallin    2013-10-12
    210     Dr. John Linga  MD      GYNO    Julia Nayer     2013-10-15
    Thêm nữa, nó dễ dùng, vì có 2 nét xem qua đã thấy bắt mắt.

    Nét bắt mắt mình đầu tiên là cách viết định nghĩa dữ liệu. Khỏi phải nói, đó là thứ mà mình ao ước từ thời sinh viên:
    C++ Code:
    1. struct Doctor {
    2.     int doctor_id;
    3.     std::string doctor_name;
    4.     std::string degree;
    5. };

    Để so sánh, những thư viện khác thường định nghĩa dữ liệu bằng một struct và một class với "kính thưa" các kiểu publicprivate, các constructor, destructor, operator, gettersetter vớ vẩn. Những thứ đó trông rối mắt đến nỗi người ta thường phải giấu đi bằng một mớ macro, hay thậm chí bằng một ngôn ngữ riêng, có trình biên dịch riêng (!) sinh mã C++.

    Nét bắt mắt thứ hai là khả năng quản trị. Nghĩa là khả năng cập nhật lược đồ CSDL trên ổ cứng, duy trì tính đồng bộ (nhất quán) với cấu trúc dữ liệu tương ứng trong bộ nhớ. Người dùng chỉ cần viết 1 dòng code; mọi việc còn lại, thư viện tự lo liệu:
    C++ Code:
    1.     storage.sync_schema();

    Để so sánh, cần nhớ rằng SQLite là một hệ thống quản trị cơ sở dữ liệu. Nhiều thư viện giao tiếp khác chỉ chú tâm vào 4 chữ "cơ sở dữ liệu", quên mất 4 chữ "hệ thống quản trị" và, hậu quả, là lập trình viên chỉ có thể ghi, sửa, xoá dữ liệu chứ không thể làm thế với lược đồ cơ sở dữ liệu.

    Nói đi thì cũng phải nói lại. Đọc mã nguồn, có thể thấy SQLite-ORM được viết một cách chân phương, không dùng những thủ thuật né tránh khai triển đệ quy. Nên trình biên dịch đòi hỏi nhiều bộ nhớ, sinh mã đích rất lớn và mã đích có thể dùng nhiều bộ nhớ lúc chạy nếu nó cập nhật lược đồ CSDL. Nhược điểm này, chính tác giả cũng thừa nhận thẳng thắn [video, link đã dẫn]. Theo tác giả, vấn đề về tiêu hao bộ nhớ sẽ phát sinh cho các CSDL nhiều hơn 40 bảng. Nhưng đó không phải là vấn đề nghiêm trọng, có thể xử lý dễ dàng bằng một "chiêu" đơn giản, là cập nhật từng bước [video, link đã dẫn]. Nói chung, video đó là một bài giảng giá trị, vì tác giả đã đánh giá sản phẩm của mình trong sự phân tích, so sánh với các sản phẩm khác một cách khách quan và toàn diện.


    Thêm 2 tư liệu cho ai muốn tìm hiểu vấn đề từ góc nhìn hàn lâm. Phương pháp thực thi, ở đây cũng như hàng chục thư viện giao tiếp C++ với SQL danh tiếng khác, cụ thể là phương pháp lập trình siêu ngữ (metaprogramming) có thể được lý giải qua báo cáo khoa học của 2 tác giả Do Thái [1]. Thách đố và giải pháp của việc thực thi, cụ thể là viết bộ biên dịch bằng siêu ngữ (C++ template) để dịch các ngôn ngữ khác sang C++ (không còn template) được trình bày trong báo cáo khoa học của 4 tác giả Nhật Bản [2].





    Tư liệu (tham khảo)

    [1] Gil J, Lenz K: Simple and Safe SQL Queries with C++ Templates. 2007.

    [2] Yamazaki T et al.: Generating a Fluent API with Syntax Checking from an LR Grammar. 2016.

    - - - Nội dung đã được cập nhật ngày 19-07-2021 lúc 06:33 PM - - -




    Sau đây là một số hướng dẫn.


    Tải mã nguồn xuống

    -- Truy nhập trang bản release v1.6 (mới nhất ở thời điểm này). Ở cuối trang, phần Assets, sẽ thấy 3 link, là (1) sqlite_orm.h, tệp mã nguồn duy nhất cần dùng để viết ứng dụng, (2) Source code (zip), và (3) Source code (tar.gz).

    -- Chọn (2) hoặc (3) tải gói mã nguồn xuống và giải nén trong một thư mục nào đó, chẳng hạn C:\C-lib\sqlite_orm-1.6.



    Cài đặt (cho Code::Blocks với bộ biên dịch GCC mingw-w64)

    -- Tạo một project đặt tên là (chẳng hạn) test-sqlite-orm và mở hộp thoại Build Options của nó.

    -- Ở tab Compiler Settings, tab con Compiler Flags, chọn cho g++ ngôn ngữ nguồn là ISO C++14 hoặc cao hơn.

    -- Ở tab Linker Settings, trong khung Other linker options, viết thêm -lsqlite3.

    -- Ở tab Search Directories, tab con Compiler, chọn thêm C:\C-lib\sqlite_orm-1.6\include.



    Dùng thử (biên dịch trong Code::Blocks, chạy trong Win32 console)

    -- Để biên dịch chương trình, thí dụ natural_join.cpp trong thư mục con examples, thêm tệp này vào project, kích hoạt project và build.

    -- Để chạy chương trình trong console thông thường (cmd.exe), ở dấu nhắc >, gõ dòng lệnh
    Code:
    test-sqlite-orm
    NOTES. Mình đã cấu hình Code::Blocks dùng chung GCC cài đặt trong môi trường phát triển MSYS2-MINGW64 và đã đặt PATH trỏ đến thư mục C:\msys64\mingw64\bin, nơi chứa các phần mềm và thư viện (kể cả sqlite3) đã cài đặt trong môi trường này.

    Việc đặt PATH tiềm ẩn rủi ro xung đột giữa các bản khác nhau của thư viện, vốn đến từ các môi trường phát triển khác nhau, chung sống với nhau, nhưng biệt lập. PATH phá vỡ tính biệt lập đó, khiến cho chương trình .EXE biên dịch trong môi trường này có thể bị liên kết nhầm với một thư viện .DLL trong môi trường kia, dẫn đến các lỗi run-time hoặc hành vi bất thường của ứng dụng.

    Khi không đặt PATH, nghĩa là chương trình không thể chạy trong môi trường sản xuất (cmd.exe) mà chỉ có thể chạy trong môi trường phát triển (mingw64.exe), ở dấu nhắc MINGW64 $, gõ dòng lệnh
    Code:
    ./test-sqlite-orm
    Đã được chỉnh sửa lần cuối bởi Ada : 30-07-2021 lúc 12:53 PM.
    -...- -.- .. .-.. .-.. - .... . -... . .- ... - .-.-.

  2. #2
    Ngày gia nhập
    08 2017
    Bài viết
    4,091

    Thiết lập PATH (System variable) có nhiều cách, đây là cách hiệu quả tránh được xung đột
    - tạo thư mục, ví dụ d:\Cmds và PATH chỉ tới thư mục đó. Nó chứa các script (thứ cấp để bổ xung, cập nhật lại biến hệ thống) hay settings khác.
    Ví dụ một aCmdCB.cmd cho biên dịch Gcc đặt ở C:\msys64\mingw64\bin, nó chỉ cần update path:
    Cmd Code:
    1.  
    2. ::comment ...
    3. path=C:\msys64\mingw64\bin;%path%
    4. echo g++ --h
    5. ::comment ...
    Khi cần biên dịch Gcc, thì chỉ cần phát lệnh aCmdCB.cmd hay call aCmdCB.cmd
    Nó có giá trị cho phiên làm việc, không ảnh hưởng tới phiên làm của tiến trình khác
    ...
    ..
    .

  3. #3
    Ngày gia nhập
    01 2008
    Nơi ở
    Rất đông người
    Bài viết
    719

    MSYS2, MINGW64 và MINGW32, mỗi cái đều có PATH riêng rồi, và nhiều thứ riêng biệt khác. Còn PATH mình nói đây là chung của hệ thống, được giữ ổn định để dùng, chứ không phải để thử chương trình.
    -...- -.- .. .-.. .-.. - .... . -... . .- ... - .-.-.

  4. #4
    Ngày gia nhập
    08 2017
    Bài viết
    4,091

    PATH (System variable) có phân biệt PATH chung với PATH riêng?
    Vậy mỗi cái thiết lập như thế nào, cụ thể tường bước, mỗi bước khác ra sao?
    ...
    ..
    .

  5. #5
    Ngày gia nhập
    01 2008
    Nơi ở
    Rất đông người
    Bài viết
    719

    Mặc định SQLite-ORM: một thư viện giao tiếp C++ với SQLite

    Mỗi shell có 1 bộ các biến môi trường, như cmd.exe thì có PATH, PATHEXT, OS, USERNAME, v.v.

    MSYS2 thừa kế các biến của cmd.exe và còn có thêm một số biến khác, như BASH, COMMANDER_DRIVE, MSYS2_PATH, MSYSTEM, ORIGINAL_PATH, v.v.

    MINGW32, MINGW64, CLANG64,... thừa kế các biến của MSYS2 và cũng thêm một số biến khác nữa, như MINGW_PREFIX, MINGW_PACKAGE_PREFIX, v.v.

    Sự thừa kế ở đây là thừa kế biến, chứ không thừa kế giá trị của biến. Giá trị của 1 biến trong các môi trường nói chung là khác nhau. Thí dụ, biến MSYSTEM cho MSYS2, MINGW32, MINGW64 và CLANG64 có giá trị lần lượt là MSYS, MINGW32, MINGW64 và CLANG64.

    Các biến được tạo và gán trị mặc định khi cài đặt MSYS2. Khác với cmd.exe, chúng không hiểu lệnh path. Nhưng các biến có thể truy vấn nhờ lệnh set, giống như ở cmd.exe.

    Quy trình cài đặt, xem trang chủ MSYS2.

    Mình dùng bộ này chủ yếu vì nó có pacman, trình quản lý gói. Khi cập nhật bộ biên dịch, có thể cập nhật tất cả các gói đã cài đặt, nhất là các gói thư viện ở dạng nhị phân (.DLL). Nhờ thế, có thể liên kết chúng với các chương trình mới dịch ra (.EXE).

    EDIT. Trong MSYS2 và các môi trường con, nếu cần thêm một thư mục mới vào PATH, dùng lệnh
    Code:
    export PATH=$PATH:directory-to-add
    Trong đó, directory-to-add là thư mục mới thêm vào cuối PATH.
    Đã được chỉnh sửa lần cuối bởi Ada : 30-07-2021 lúc 12:52 PM. Lý do: Viết thêm cách sửa PATH
    -...- -.- .. .-.. .-.. - .... . -... . .- ... - .-.-.

  6. #6
    Ngày gia nhập
    01 2008
    Nơi ở
    Rất đông người
    Bài viết
    719

    Mặc định SQLite-ORM: một binding cho SQLite

    MSYS2 hỗ trợ 3 bộ biên dịch, nhưng có 5 môi trường phát triển. Bộ biên dịch nào, môi trường nào, quan hệ thế nào, xem https://www.msys2.org/docs/environments/.
    -...- -.- .. .-.. .-.. - .... . -... . .- ... - .-.-.

  7. #7
    Ngày gia nhập
    01 2008
    Nơi ở
    Rất đông người
    Bài viết
    719

    Có một vấn đề cần biết cho người dùng Việt, là hỗ trợ tiếng Việt. Cụ thể là so sánh hai xâu Việt theo chính tả VN. Trong SQLite thuần tuý, vốn cung cấp một API "chính thức" bằng ngôn ngữ C, người dùng tự viết một hàm gọi ngược (callback), chẳng hạn, vietnamese(), thực thi so sánh 2 xâu, có tính chất tương tự như strcmp() và có kiểu int (void*,int,void const*,int,void const*); đặt tên, chẳng hạn, là "VIETNAMESE"; đăng ký nó dưới tên ấy với SQLite bằng một lời gọi API, chẳng hạn, sqlite3_create_collation(myDbConnection, "VIETNAMESE", SQLITE_UTF8, NULL, vietnamese); và cuối cùng, sử dụng mệnh đề COLLATE VIETNAMESE trong các câu truy vấn bằng ngôn ngữ SQL.

    Với SQLite-ORM, việc viết hàm so sánh vẫn y như thế, việc đăng ký thực hiện bằng create_collation("VIETNAMESE", vietnamese) và việc sử dụng thực hiện bằng collate("VIETNAMESE"). Thí dụ,
    C++ Code:
    1. auto rows = storage.select(&Vendor::name, where(is_equal(&Vendor::name, "Nhật Cường Mobile").collate("VIETNAMESE")));

    Tất nhiên, chỉ ra hàm bằng tên thay vì bằng địa chỉ là kém hiệu quả và kém an toàn, đi ngược lại phương châm thiết kế của SQLite-ORM. Cách này chỉ là một giải pháp nửa vời, có tính chất "chữa cháy".

    Giải pháp căn bản, tuyệt đối an toàn và cho hiệu quả tuyệt đối, vẫn phải là collate<vietnamese>(). Thí dụ,
    C++ Code:
    1. auto rows = storage.select(&Vendor::name, where(is_equal(&Vendor::name, "Nhật Cường Mobile").collate<vietnamese>()));

    Trong đó, vietnamese là một hàm tử, được định nghĩa đại khái như sau.
    C++ Code:
    1. struct vietnamese {
    2.     int operator()(void *options, int leftLength, const void *lhs, int rightLength, const void *rhs) const {
    3.         //  viết mã so sánh 2 xâu Việt vào đây.
    4.     }
    5.  
    6.     static std::string_view name() {
    7.         return "VIETNAMESE";
    8.     }
    9. };

    Lối viết "triệt để" này là một trong những tính năng sẽ được thực thi trong một phiên bản tương lai của SQLite-ORM.
    Đã được chỉnh sửa lần cuối bởi Ada : 02-08-2021 lúc 09:45 AM.
    -...- -.- .. .-.. .-.. - .... . -... . .- ... - .-.-.

  8. #8
    Ngày gia nhập
    01 2008
    Nơi ở
    Rất đông người
    Bài viết
    719

    OK, đã thực thi.

    Cấu trúc của hàm tử vietnamese(), chốt lại, là
    C++ Code:
    1. struct vietnamese {
    2.     int operator()(int leftLength, const void *lhs, int rightLength, const void *rhs) const {
    3.         //  viết mã so sánh 2 xâu Việt vào đây.
    4.     }
    5.  
    6.     static const char* name() {
    7.         return "VIETNAMESE";
    8.     }
    9. };

    Và hàm tử này được đăng ký bằng cấu trúc create_collation<vietnamese>(). Thí dụ:
    C++ Code:
    1. storage.create_collation<vietnamese>();

    Tính năng này được thưc thi bằng 2 miếng vá #771, #772. Ai kiên nhẫn có thể chờ 1-2 ngày để tải xuống phiên bản mới của sqlite_orm.h từ nhánh dev (mã nguồn đang phát triển) đã vá bằng 2 miếng vá này. Do tương đối đơn giản và độc lập nên hầu như chắc chắn 2 miếng vá này sẽ được tích hợp nguyên vẹn vào nhánh master (mã nguồn phát hành) trong phiên bản sắp tới.
    Đã được chỉnh sửa lần cuối bởi Ada : 09-08-2021 lúc 10:03 AM.
    -...- -.- .. .-.. .-.. - .... . -... . .- ... - .-.-.

Quyền hạn của bạn

  • Bạn không thể gửi đề tài mới
  • Bạn không thể gửi bài trả lời
  • Bạn không thể gửi các đính kèm
  • Bạn không thể chỉnh sửa bài viết của bạn