Access VMS database - API comparison

Content:

  1. Introduction
  2. Setup
  3. Embedded SQL
  4. Database API
    1. Database specific API
    2. Standard API
    3. Portability
  5. ORM
  6. Comparison
    1. Code size
    2. Summary

Introduction:

This article is a followup on the articles:

These articles cover:

This article has a much narrower scope and does not cover:

But instead this article goes much deeper and use more complex examples.

The original 2 articles only used the query:

SELECT f1,f2 FROM t1

This article use the queries:

INSERT INTO customer VALUES(?, ?, ?);
INSERT INTO customer_actual VALUES(?, ?, ?, ?, ?);
INSERT INTO order_main VALUES(?, ?, ?);
INSERT INTO order_line VALUES(?, ?, ?, ?, ?);

and:

SELECT '**future customer**', customer.id AS cid, name, phone, source
FROM customer JOIN customer_potential ON customer.id = customer_potential.id
ORDER BY cid;
SELECT customer.id AS cid, name, address1, address2, phone, order_main.id AS oid, status, SUM(quantity * price) AS sumval
FROM customer JOIN customer_actual ON customer.id = customer_actual.id
              JOIN order_main ON customer.id = order_main.customer
              JOIN order_line ON order_main.id = order_line.order_main
GROUP BY customer.id, name, address1, address2, phone, order_main.id, status
ORDER BY cid, oid;

The SQL is still relative simple, but does show elements like parameters and JOIN.

The article will cover 3 common types of database access:

Two types of Database API's exist:

Setup:

We will setup tables in and test 7 databases on VMS:

The following table structure and initial data will be loaded:

CREATE TABLE customer (
    id INTEGER NOT NULL,
    name VARCHAR(32),
    phone VARCHAR(16),
    PRIMARY KEY(id)
);
CREATE TABLE customer_potential (
    id INTEGER NOT NULL,
    source VARCHAR(256),
    PRIMARY KEY(id)
);
CREATE TABLE customer_actual (
    id INTEGER NOT NULL,
    address1 VARCHAR(64),
    address2 VARCHAR(64),
    contact VARCHAR(32),
    discount INTEGER,
    PRIMARY KEY(id)
);
CREATE TABLE order_main (
    id INTEGER NOT NULL,
    customer INTEGER NOT NULL,
    status VARCHAR(16),
    PRIMARY KEY(id)
);
CREATE TABLE order_line (
    id INTEGER NOT NULL,
    order_main INTEGER NOT NULL,
    item VARCHAR(32),
    quantity INTEGER,
    price DECIMAL(12,2),
    PRIMARY KEY(id)
);
INSERT INTO customer VALUES(1, 'A company', '1111-1111');
INSERT INTO customer VALUES(2, 'B company', '2222-2222');
INSERT INTO customer VALUES(3, 'C company', '3333-3333');
INSERT INTO customer VALUES(4, 'D company', '4444-4444');
INSERT INTO customer VALUES(5, 'E company', '5555-5555');
INSERT INTO customer_potential VALUES(1, 'Sleazy information seller');
INSERT INTO customer_potential VALUES(2, 'Sleazy information seller');
INSERT INTO customer_actual VALUES(3, 'C road 3', 'C town', 'Mr. C', 10);
INSERT INTO customer_actual VALUES(4, 'D road 4', 'D town', 'Mr. D', 20);
INSERT INTO customer_actual VALUES(5, 'E road 5', 'E town', 'Mr. E', 10);
INSERT INTO order_main VALUES(1, 3, 'Delivered');
INSERT INTO order_main VALUES(2, 4, 'Delivered');
INSERT INTO order_main VALUES(3, 4, 'Delivered');
INSERT INTO order_main VALUES(4, 4, 'Delivered');
INSERT INTO order_main VALUES(5, 5, 'Delivered');
INSERT INTO order_line VALUES(1, 1, 'X stuff', 1, 75.00);
INSERT INTO order_line VALUES(2, 2, 'Y stuff', 10, 40.00);
INSERT INTO order_line VALUES(3, 3, 'Y stuff', 5, 40.00);
INSERT INTO order_line VALUES(4, 4, 'Y stuff', 20, 40.00);
INSERT INTO order_line VALUES(5, 5, 'X stuff', 1, 72.00);
INSERT INTO order_line VALUES(6, 5, 'Y stuff', 1, 45.00);
INSERT INTO order_line VALUES(7, 5, 'Z stuff', 1, 90.00);
CREATE TABLE abbr (
    id INTEGER NOT NULL,
    term VARCHAR(32),
    PRIMARY KEY(id)
);
INSERT INTO abbr VALUES(1, 'Internationalization');
INSERT INTO abbr VALUES(2, 'Localization');
CREATE TABLE ts (
    id INTEGER NOT NULL,
    dt DATETIME,
    PRIMARY KEY (id)
);
INSERT INTO ts VALUES(1, '2023-10-01 08:00:00');
INSERT INTO ts VALUES(2, '2023-11-01 08:00:00');
INSERT INTO ts VALUES(3, '2023-12-01 08:00:00');
CREATE TABLE aid (
    id INTEGER NOT NULL AUTO_INCREMENT,
    val VARCHAR(255),
    PRIMARY KEY(id)
);
CREATE TABLE counter (
    id INTEGER NOT NULL,
    val INTEGER,
    PRIMARY KEY(id)
);
INSERT INTO counter VALUES(1, 0);

Embedded SQL:

Embedded SQL works like:

The syntax for embedded SQL is:

EXEC-SQL BEGIN DECLARE SECTION <statement terminator>
<declaration variables>
EXEC-SQL END DECLARE SECTION <statement terminator>
...
EXEC-SQL <SQL using variables directly> <statement terminator>

Example snippet in Cobol:

EXEC SQL BEGIN DECLARE SECTION END-EXEC.
01 v PIC S9(8) COMP.
EXEC SQL END DECLARE SECTION END-EXEC.
...
EXEC SQL INSERT INTO TABLE something VALUES(:v) END-EXEC

Example snippet in C:

EXEC SQL BEGIN DECLARE SECTION;
int v;
EXEC SQL END DECLARE SECTION;
...
EXEC SQL INSERT INTO TABLE something VALUES(:v);

Cobol:

customer.cob:

01 customer.
   03 customer-id pic s9(8) comp-3.
   03 customer-name pic x(32).
   03 customer-phone pic x(16).
01 customer-potential.
   03 customer-potential-id pic s9(8) comp-3.
   03 customer-potential-source pic x(256).
01 customer-actual.
   03 customer-actual-id pic s9(8) comp-3.
   03 customer-actual-address1 pic x(64).
   03 customer-actual-address2 pic x(64).
   03 customer-actual-contact pic x(16).
   03 customer-actual-discount pic s9(8) comp-3.

order.cob:

01 order-main.
   03 order-id pic s9(8) comp-3.
   03 order-customer pic s9(8) comp-3.
   03 order-status pic x(16).
01 order-line.
   03 order-line-id pic s9(8) comp-3.
   03 order-line-order pic s9(8) comp-3.
   03 order-line-item pic x(32).
   03 order-line-quantity pic s9(8) comp-3.
   03 order-line-price pic s9(8)v9(2) comp-3.

add_embrdb.sco:

identification division.program-id. xadd.

data division.
working-storage section.
EXEC SQL INCLUDE sqlca END-EXEC.
EXEC SQL BEGIN DECLARE SECTION END-EXEC.
EXEC SQL INCLUDE 'customer.cob' END-EXEC.
EXEC SQL INCLUDE 'order.cob' END-EXEC.
01 constr pic x(255).
EXEC SQL END DECLARE SECTION END-EXEC.

procedure division.
main-paragraph.
    move "FILENAME disk4:[rdb]test" to constr
    EXEC SQL CONNECT TO :constr END-EXEC
    move 6 to customer-id
    move "F company" to customer-name
    move "6666-6666" to customer-phone
    EXEC SQL
        INSERT INTO customer
            VALUES(:customer-id,
                   :customer-name,
                   :customer-phone)
    END-EXEC
    move 6 to customer-actual-id
    move "F road 6" to customer-actual-address1
    move "F town" to customer-actual-address2
    move "Mr. F" to customer-actual-contact
    move 10 to customer-actual-discount
    EXEC SQL
        INSERT INTO customer_actual
            VALUES(:customer-actual-id,
                   :customer-actual-address1,
                   :customer-actual-address2,
                   :customer-actual-contact,
                   :customer-actual-discount)
    END-EXEC
    move 6 to order-id
    move 6 to order-customer
    move "In progress" to order-status
    EXEC SQL
        INSERT INTO order_main
            VALUES(:order-id,
                   :order-customer,
                   :order-status)
    END-EXEC
    move 8 to order-line-id
    move 6 to order-line-order
    move "X stuff" to order-line-item
    move 1 to order-line-quantity
    move 72.00 to order-line-price
    EXEC SQL
        INSERT INTO order_line
            VALUES(:order-line-id,
                   :order-line-order,
                   :order-line-item,
                   :order-line-quantity,
                   :order-line-price)
    END-EXEC
    EXEC SQL COMMIT END-EXEC
    stop run.

list_embrdb.sco:

identification division.program-id. list.

data division.
working-storage section.
EXEC SQL INCLUDE sqlca END-EXEC.
EXEC SQL BEGIN DECLARE SECTION END-EXEC.
EXEC SQL INCLUDE 'customer.cob' END-EXEC.
EXEC SQL INCLUDE 'order.cob' END-EXEC.
01 constr pic x(255).
01 sumval pic s9(8)v9(2) comp-3.
EXEC SQL END DECLARE SECTION END-EXEC.
01 last-customer-id pic s9(8) comp.
01 name-len pic s9(8) comp.
01 phone-len pic s9(8) comp.
01 source-len pic s9(8) comp.
01 address1-len pic s9(8) comp.
01 address2-len pic s9(8) comp.
01 status-len pic s9(8) comp.
01 len pic s9(8) comp.
01 order-id-2 pic 9(8) display.
01 sumval2 pic 9(8)v9(2) display.
EXEC SQL
   DECLARE custpot CURSOR FOR
   SELECT customer.id AS cid, name, phone, source
   FROM customer JOIN customer_potential
                 ON customer.id = customer_potential.id
   ORDER BY cid
END-EXEC.
EXEC SQL
   DECLARE custact CURSOR FOR
   SELECT customer.id AS cid,name,address1,address2,phone,
          order_main.id AS oid,status,
          SUM(quantity*price) AS sumval
   FROM customer JOIN customer_actual
                      ON customer.id = customer_actual.id
                 JOIN order_main
                      ON customer.id = order_main.customer
                 JOIN order_line
                      ON order_main.id = order_line.order_main
                 GROUP BY customer.id,
                          name,
                          address1,
                          address2,
                          phone,
                          order_main.id,
                          status
                 ORDER BY cid, oid
END-EXEC.

procedure division.
main-paragraph.
    move "FILENAME disk4:[rdb]test" to constr
    EXEC SQL CONNECT TO :constr END-EXEC
    EXEC SQL OPEN custpot END-EXEC
    move 0 to sqlcode
    perform until not sqlcode = 0
        EXEC SQL
            FETCH custpot
            INTO :customer-id,
                 :customer-name,
                 :customer-phone,
                 :customer-potential-source
        END-EXEC
        if sqlcode = 0 then
            perform display-potential-paragraph
        end-if
    end-perform
    EXEC SQL CLOSE custpot END-EXEC
    EXEC SQL OPEN custact END-EXEC
    move 0 to sqlcode
    move 0 to last-customer-id
    perform until not sqlcode = 0
        EXEC SQL
            FETCH custact
            INTO :customer-id,
                 :customer-name,
                 :customer-actual-address1,
                 :customer-actual-address2,
                 :customer-phone,
                 :order-id,
                 :order-status,
                 :sumval
        END-EXEC
        if sqlcode = 0 then
            if customer-id not = last-customer-id then
                perform display-actual-paragraph
                move customer-id to last-customer-id
            end-if
            perform display-order-paragraph
        end-if
    end-perform
    EXEC SQL CLOSE custact END-EXEC
    stop run.
display-potential-paragraph.
    move 32 to len
    call "trim"
        using customer-name, len
        giving name-len
    end-call
    move 16 to len
    call "trim"
        using customer-phone, len
        giving phone-len
    end-call
    move 256 to len
    call "trim"
        using customer-potential-source, len
        giving source-len
    end-call
    display "**future customer** " customer-name(1:name-len)
            ", " customer-phone(1:phone-len)
            ", " customer-potential-source(1:source-len).
display-actual-paragraph.
    move 32 to len
    call "trim"
        using customer-name, len
        giving name-len
    end-call
    move 64 to len
    call "trim"
        using customer-actual-address1, len
        giving address1-len
    end-call
    move 64 to len
    call "trim"
        using customer-actual-address2, len
        giving address2-len
    end-call
    move 16 to len
    call "trim"
        using customer-phone, len
        giving phone-len
    end-call
    display customer-name(1:name-len)
            ", " customer-actual-address1(1:address1-len)
            ", " customer-actual-address2(1:address2-len)
            ", " customer-phone(1:phone-len).
display-order-paragraph.
    move order-id to order-id-2
    move 16 to len
    call "trim"
        using order-status, len
        giving status-len
    end-call
    move sumval to sumval2
    display "  " order-id-2
            ", " order-status(1:status-len)
            ", ", sumval2.

Build and run:

$ def/nolog sql$database disk4:[rdb]test
$ sqlpre /cob /sqloptions=connect add_embrdb
$ link add_embrdb + sys$library:sql$user73/libr
$ sqlpre /cob /sqloptions=connect list_embrdb
$ cob trim
$ link list_embrdb + trim + sys$library:sql$user73/libr
$ run list_embrdb
$ run add_embrdb
$ run list_embrdb

customer2.cob:

       01 customer.
          03 customer-id pic s9(8) comp-3.
          03 customer-name pic x(32).
          03 customer-phone pic x(16).
       01 customer-potential.
          03 customer-potential-id pic s9(8) comp-3.
          03 customer-potential-source pic x(256).
       01 customer-actual.
          03 customer-actual-id pic s9(8) comp-3.
          03 customer-actual-address1 pic x(64).
          03 customer-actual-address2 pic x(64).
          03 customer-actual-contact pic x(16).
          03 customer-actual-discount pic s9(8) comp-3.

order2.cob:

       01 order-main.
          03 order-id pic s9(8) comp-3.
          03 order-customer pic s9(8) comp-3.
          03 order-status pic x(16).
       01 order-line.
          03 order-line-id pic s9(8) comp-3.
          03 order-line-order pic s9(8) comp-3.
          03 order-line-item pic x(32).
          03 order-line-quantity pic s9(8) comp-3.
          03 order-line-price pic s9(8)v9(2) comp-3.

add_embmim.eco:

       identification division.program-id. xadd.
       
       data division.
       working-storage section.
       EXEC SQL INCLUDE sqlca END-EXEC.
       EXEC SQL BEGIN DECLARE SECTION END-EXEC.
       EXEC SQL INCLUDE 'customer2.cob' END-EXEC.
       EXEC SQL INCLUDE 'order2.cob' END-EXEC.
       01 con pic x(255).
       01 usr pic x(255).
       01 pwd pic x(255).
       EXEC SQL END DECLARE SECTION END-EXEC.
       
       procedure division.
       main-paragraph.
           move "test" to con
           move "SYSADM" to usr
           move "hemmeligt" to pwd
           EXEC SQL CONNECT TO :con USER :usr USING :pwd END-EXEC
           move 6 to customer-id
           move "F company" to customer-name
           move "6666-6666" to customer-phone
           EXEC SQL
               INSERT INTO customer
                   VALUES(:customer-id,
                          :customer-name,
                          :customer-phone)
           END-EXEC
           move 6 to customer-actual-id
           move "F road 6" to customer-actual-address1
           move "F town" to customer-actual-address2
           move "Mr. F" to customer-actual-contact
           move 10 to customer-actual-discount
           EXEC SQL
               INSERT INTO customer_actual
                   VALUES(:customer-actual-id,
                          :customer-actual-address1,
                          :customer-actual-address2,
                          :customer-actual-contact,
                          :customer-actual-discount)
           END-EXEC
           move 6 to order-id
           move 6 to order-customer
           move "In progress" to order-status
           EXEC SQL
               INSERT INTO order_main
                   VALUES(:order-id,
                          :order-customer,
                          :order-status)
           END-EXEC
           move 8 to order-line-id
           move 6 to order-line-order
           move "X stuff" to order-line-item
           move 1 to order-line-quantity
           move 72.00 to order-line-price
           EXEC SQL
               INSERT INTO order_line
                   VALUES(:order-line-id,
                          :order-line-order,
                          :order-line-item,
                          :order-line-quantity,
                          :order-line-price)
           END-EXEC
           EXEC SQL COMMIT END-EXEC
           stop run.

list_embmim.eco:

       identification division.program-id. list.
       
       data division.
       working-storage section.
       EXEC SQL INCLUDE sqlca END-EXEC.
       EXEC SQL BEGIN DECLARE SECTION END-EXEC.
       EXEC SQL INCLUDE 'customer2.cob' END-EXEC.
       EXEC SQL INCLUDE 'order2.cob' END-EXEC.
       01 con pic x(255).
       01 usr pic x(255).
       01 pwd pic x(255).
       01 sumval pic s9(8)v9(2) comp-3.
       EXEC SQL END DECLARE SECTION END-EXEC.
       01 last-customer-id pic s9(8) comp.
       01 name-len pic s9(8) comp.
       01 phone-len pic s9(8) comp.
       01 source-len pic s9(8) comp.
       01 address1-len pic s9(8) comp.
       01 address2-len pic s9(8) comp.
       01 status-len pic s9(8) comp.
       01 len pic s9(8) comp.
       01 order-id-2 pic 9(8) display.
       01 sumval2 pic 9(8)v9(2) display.
       EXEC SQL
          DECLARE custpot CURSOR FOR
          SELECT customer.id AS cid, name, phone, source
          FROM customer JOIN customer_potential
                        ON customer.id = customer_potential.id
          ORDER BY cid
       END-EXEC.
       EXEC SQL
          DECLARE custact CURSOR FOR
          SELECT customer.id AS cid,name,address1,address2,phone,
                 order_main.id AS oid,status,
                 SUM(quantity*price) AS sumval
          FROM customer JOIN customer_actual
                             ON customer.id = customer_actual.id
                        JOIN order_main
                             ON customer.id = order_main.customer
                        JOIN order_line
                             ON order_main.id = order_line.order_main
                        GROUP BY customer.id,
                                 name,
                                 address1,
                                 address2,
                                 phone,
                                 order_main.id,
                                 status
                        ORDER BY cid, oid
       END-EXEC.
       
       procedure division.
       main-paragraph.
           move "test" to con
           move "SYSADM" to usr
           move "hemmeligt" to pwd
           EXEC SQL CONNECT TO :con USER :usr USING :pwd END-EXEC
           EXEC SQL OPEN custpot END-EXEC
           move 0 to sqlcode
           perform until not sqlcode = 0
               EXEC SQL
                   FETCH custpot
                   INTO :customer-id,
                        :customer-name,
                        :customer-phone,
                        :customer-potential-source
               END-EXEC
               if sqlcode = 0 then
                   perform display-potential-paragraph
               end-if
           end-perform
           EXEC SQL CLOSE custpot END-EXEC
           EXEC SQL OPEN custact END-EXEC
           move 0 to sqlcode
           move 0 to last-customer-id
           perform until not sqlcode = 0
               EXEC SQL
                   FETCH custact
                   INTO :customer-id,
                        :customer-name,
                        :customer-actual-address1,
                        :customer-actual-address2,
                        :customer-phone,
                        :order-id,
                        :order-status,
                        :sumval
               END-EXEC
               if sqlcode = 0 then
                   if customer-id not = last-customer-id then
                       perform display-actual-paragraph
                       move customer-id to last-customer-id
                   end-if
                   perform display-order-paragraph
               end-if
           end-perform
           EXEC SQL CLOSE custact END-EXEC
           stop run.
       display-potential-paragraph.
           move 32 to len
           call "trim"
               using customer-name, len
               giving name-len
           end-call
           move 16 to len
           call "trim"
               using customer-phone, len
               giving phone-len
           end-call
           move 256 to len
           call "trim"
               using customer-potential-source, len
               giving source-len
           end-call
           display "**future customer** " customer-name(1:name-len)
                   ", " customer-phone(1:phone-len)
                   ", " customer-potential-source(1:source-len).
       display-actual-paragraph.
           move 32 to len
           call "trim"
               using customer-name, len
               giving name-len
           end-call
           move 64 to len
           call "trim"
               using customer-actual-address1, len
               giving address1-len
           end-call
           move 64 to len
           call "trim"
               using customer-actual-address2, len
               giving address2-len
           end-call
           move 16 to len
           call "trim"
               using customer-phone, len
               giving phone-len
           end-call
           display customer-name(1:name-len)
                   ", " customer-actual-address1(1:address1-len)
                   ", " customer-actual-address2(1:address2-len)
                   ", " customer-phone(1:phone-len).
       display-order-paragraph.
           move order-id to order-id-2
           move 16 to len
           call "trim"
               using order-status, len
               giving status-len
           end-call
           move sumval to sumval2
           display "  " order-id-2
                   ", " order-status(1:status-len)
                   ", ", sumval2.

Build and run:

$ esql/cobol add_embmim
$ cobol/ansi add_embmim
$ link add_embmim + sys$input/opt
mimlib9:lr/lib
mimlib9:mdr/lib
mimlib9:mimdb9/share
$
$ cobol trim
$ esql/cobol list_embmim
$ cobol/ansi list_embmim
$ link list_embmim + trim + sys$input/opt
mimlib9:lr/lib
mimlib9:mdr/lib
mimlib9:mimdb9/share
$ run list_embmim
$ run add_embmim
$ run list_embmim

The Rdb and Mimer code may look very different, but there are really only 2 differences: the Mimer code use ANSI format indentation while the Rdb code does not, the connection establishing is different.

C:

customer.h:

int customer_id;
char customer_name[33];
char customer_phone[17];

int customer_potential_id;
char customer_potential_source[257];

int customer_actual_id;
char customer_actual_address1[65];
char customer_actual_address2[65];
char customer_actual_contact[17];
int customer_actual_discount;

order.h:

int order_id;
int order_customer;
char order_status[17];

int order_line_id;
int order_line_order;
char order_line_item[33];
int order_line_quantity;
double order_line_price;

add_embrdb.sc:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

EXEC SQL INCLUDE SQLCA;
/* these are uppercase in Rdb */
#define sqlca SQLCA
#define sqlcode SQLCODE

int main()
{
    EXEC SQL BEGIN DECLARE SECTION;
    char con[256];
    EXEC SQL INCLUDE 'customer.h';
    EXEC SQL INCLUDE 'order.h';
    double sumval;
    EXEC SQL END DECLARE SECTION;
    strcpy(con, "FILENAME disk4:[rdb]test");
    EXEC SQL CONNECT TO :con;
    customer_id = 6;
    strcpy(customer_name, "F company");
    strcpy(customer_phone, "6666-6666");
    EXEC SQL INSERT INTO customer VALUES(:customer_id,:customer_name,:customer_phone);
    customer_actual_id = 6;
    strcpy(customer_actual_address1, "F road 6");
    strcpy(customer_actual_address2, "F town");
    strcpy(customer_actual_contact, "Mr. F");
    customer_actual_discount = 10;
    EXEC SQL INSERT INTO customer_actual VALUES(:customer_actual_id,:customer_actual_address1,:customer_actual_address2,:customer_actual_contact,:customer_actual_discount);
    order_id = 6;
    order_customer = 6;
    strcpy(order_status, "In progress");
    EXEC SQL INSERT INTO order_main VALUES(:order_id,:order_customer,:order_status);
    order_line_id = 8;
    order_line_order = 6;
    strcpy(order_line_item, "X stuff");
    order_line_quantity = 1;
    order_line_price = 72.00;
    EXEC SQL INSERT INTO order_line VALUES(:order_line_id,:order_line_order,:order_line_item,:order_line_quantity,:order_line_price);
    EXEC SQL COMMIT;
    return 0;
}

list_embrdb.sc:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

EXEC SQL INCLUDE SQLCA;
/* these are uppercase in Rdb */
#define sqlca SQLCA
#define sqlcode SQLCODE

char *trim(char *s)
{
    int ix = strlen(s) - 1;
    while(ix >= 0 && s[ix] == ' ')
    {
        s[ix] = 0;
        ix--;
    }
    return s;
}

int main()
{
    int last_customer_id;
    EXEC SQL BEGIN DECLARE SECTION;
    char con[256];
    EXEC SQL INCLUDE 'customer.h';
    EXEC SQL INCLUDE 'order.h';
    double sumval;
    EXEC SQL END DECLARE SECTION;
    EXEC SQL DECLARE curspot CURSOR FOR
             SELECT customer.id AS cid, name, phone, source
             FROM customer JOIN customer_potential ON customer.id = customer_potential.id
             ORDER BY cid;
    EXEC SQL DECLARE cursact CURSOR FOR
            SELECT customer.id AS cid,name,address1,address2,phone,order_main.id AS oid,status,SUM(quantity*price) AS sumval
            FROM customer JOIN customer_actual ON customer.id = customer_actual.id
                          JOIN order_main ON customer.id = order_main.customer
                          JOIN order_line ON order_main.id = order_line.order_main
            GROUP BY customer.id,name,address1,address2,phone,order_main.id,status
            ORDER BY cid, oid;
    strcpy(con, "FILENAME disk4:[rdb]test");
    EXEC SQL CONNECT TO :con;
    EXEC SQL OPEN curspot;
    for(;;)
    {
        EXEC SQL FETCH curspot INTO :customer_id, :customer_name, :customer_phone, :customer_potential_source;
        if(sqlca.sqlcode != 0) break;
        printf("**future customer** %s, %s, %s\n", trim(customer_name), trim(customer_phone), trim(customer_potential_source));
    }
    EXEC SQL CLOSE curspot;
    EXEC SQL OPEN cursact;
    last_customer_id = 0;
    for(;;)
    {
        EXEC SQL FETCH cursact INTO :customer_id, :customer_name, :customer_actual_address1, :customer_actual_address2, :customer_phone, :order_id, :order_status, :sumval;
        if(sqlca.sqlcode != 0) break;
        if(customer_id != last_customer_id)
        {
            printf("%s, %s, %s, %s\n", trim(customer_name), trim(customer_actual_address1), trim(customer_actual_address2), trim(customer_phone));
            last_customer_id = customer_id;
        }
        printf("  %d, %s, %.2f\n", order_id, trim(order_status), sumval);
    }
    EXEC SQL CLOSE cursact;
    return 0;
}

Build and run:

$ def/nolog sql$database disk4:[rdb]test
$ sqlpre /cc /sqloptions=connect add_embrdb
$ link add_embrdb + sys$library:sql$user73/libr
$ sqlpre /cc /sqloptions=connect list_embrdb
$ link list_embrdb + sys$library:sql$user73/libr
$ run list_embrdb
$ run add_embrdb
$ run list_embrdb

customer.h:

int customer_id;
char customer_name[33];
char customer_phone[17];

int customer_potential_id;
char customer_potential_source[257];

int customer_actual_id;
char customer_actual_address1[65];
char customer_actual_address2[65];
char customer_actual_contact[17];
int customer_actual_discount;

order.h:

int order_id;
int order_customer;
char order_status[17];

int order_line_id;
int order_line_order;
char order_line_item[33];
int order_line_quantity;
double order_line_price;

add_embmim.ec:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

EXEC SQL INCLUDE SQLCA;

int main()
{
    EXEC SQL BEGIN DECLARE SECTION;
    char con[256];
    char usr[256];
    char pwd[256];
    EXEC SQL INCLUDE 'customer.h';
    EXEC SQL INCLUDE 'order.h';
    double sumval;
    EXEC SQL END DECLARE SECTION;
    strcpy(con, "test");
    strcpy(usr, "SYSADM");
    strcpy(pwd, "hemmeligt");
    EXEC SQL CONNECT TO :con USER :usr USING :pwd;
    customer_id = 6;
    strcpy(customer_name, "F company");
    strcpy(customer_phone, "6666-6666");
    EXEC SQL INSERT INTO customer VALUES(:customer_id,:customer_name,:customer_phone);
    customer_actual_id = 6;
    strcpy(customer_actual_address1, "F road 6");
    strcpy(customer_actual_address2, "F town");
    strcpy(customer_actual_contact, "Mr. F");
    customer_actual_discount = 10;
    EXEC SQL INSERT INTO customer_actual VALUES(:customer_actual_id,:customer_actual_address1,:customer_actual_address2,:customer_actual_contact,:customer_actual_discount);
    order_id = 6;
    order_customer = 6;
    strcpy(order_status, "In progress");
    EXEC SQL INSERT INTO order_main VALUES(:order_id,:order_customer,:order_status);
    order_line_id = 8;
    order_line_order = 6;
    strcpy(order_line_item, "X stuff");
    order_line_quantity = 1;
    order_line_price = 72.00;
    EXEC SQL INSERT INTO order_line VALUES(:order_line_id,:order_line_order,:order_line_item,:order_line_quantity,:order_line_price);
    EXEC SQL COMMIT;
    return 0;
}

list_embmim.ec:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

EXEC SQL INCLUDE SQLCA;

char *trim(char *s)
{
    int ix = strlen(s) - 1;
    while(ix >= 0 && s[ix] == ' ')
    {
        s[ix] = 0;
        ix--;
    }
    return s;
}

int main()
{
    int last_customer_id;
    EXEC SQL BEGIN DECLARE SECTION;
    char con[256];
    char usr[256];
    char pwd[256];
    EXEC SQL INCLUDE 'customer.h';
    EXEC SQL INCLUDE 'order.h';
    double sumval;
    EXEC SQL END DECLARE SECTION;
    EXEC SQL DECLARE curspot CURSOR FOR
             SELECT customer.id AS cid, name, phone, source
             FROM customer JOIN customer_potential ON customer.id = customer_potential.id
             ORDER BY cid;
    EXEC SQL DECLARE cursact CURSOR FOR
            SELECT customer.id AS cid,name,address1,address2,phone,order_main.id AS oid,status,SUM(quantity*price) AS sumval
            FROM customer JOIN customer_actual ON customer.id = customer_actual.id
                          JOIN order_main ON customer.id = order_main.customer
                          JOIN order_line ON order_main.id = order_line.order_main
            GROUP BY customer.id,name,address1,address2,phone,order_main.id,status
            ORDER BY cid, oid;
    strcpy(con, "test");
    strcpy(usr, "SYSADM");
    strcpy(pwd, "hemmeligt");
    EXEC SQL CONNECT TO :con USER :usr USING :pwd;
    EXEC SQL OPEN curspot;
    for(;;)
    {
        EXEC SQL FETCH curspot INTO :customer_id, :customer_name, :customer_phone, :customer_potential_source;
        if(sqlca.sqlcode != 0) break;
        printf("**future customer** %s, %s, %s\n", trim(customer_name), trim(customer_phone), trim(customer_potential_source));
    }
    EXEC SQL CLOSE curspot;
    EXEC SQL OPEN cursact;
    last_customer_id = 0;
    for(;;)
    {
        EXEC SQL FETCH cursact INTO :customer_id, :customer_name, :customer_actual_address1, :customer_actual_address2, :customer_phone, :order_id, :order_status, :sumval;
        if(sqlca.sqlcode != 0) break;
        if(customer_id != last_customer_id)
        {
            printf("%s, %s, %s, %s\n", trim(customer_name), trim(customer_actual_address1), trim(customer_actual_address2), trim(customer_phone));
            last_customer_id = customer_id;
        }
        printf("  %d, %s, %.2f\n", order_id, trim(order_status), sumval);
    }
    EXEC SQL CLOSE cursact;
    return 0;
}

Build and run:

$ esql/c add_embmim
$ cc add_embmim
$ link add_embmim + sys$input/opt
mimlib9:lr/lib
mimlib9:mdr/lib
mimlib9:mimdb9/share
$
$ esql/c list_embmim
$ cc list_embmim
$ link list_embmim + sys$input/opt
mimlib9:lr/lib
mimlib9:mdr/lib
mimlib9:mimdb9/share
$ run list_embmim
$ run add_embmim
$ run list_embmim

The Rdb and Mimer code only differs in the connection establishing.

Support:

Embedded SQL support for databases available on VMS:

Database Cobol Basic Pascal C/C++ Fortran Java Python PHP
Rdb supported supported supported supported supported unsupported Not available Not available
MySQL Not available Not available Not available Not available Not available unsupported Not available Not available
Mimer supported Not available Not available supported supported unsupported Not available Not available
SQLite Not available Not available Not available Not available Not available unsupported Not available Not available
Java databases (Derby/H2/HSQLDB) Not available Not available Not available Not available Not available unsupported Not available Not available

Conclusion:

Embedded SQL works great.

The problem is the support. Today very few combinations of databases and languages support embedded SQL.

Database API:

Database specific API:

MySQL:

MySQL comes with several connectors including a C API called libmysql.

I have created a Pascal wrapper around it called pmysql and a Basic wrapper called bmysql that is available here.

PHP mysqli extension also use the C API.

customer.h:

int customer_id;
char customer_name[33];
char customer_phone[17];

int customer_potential_id;
char customer_potential_source[257];

int customer_actual_id;
char customer_actual_address1[65];
char customer_actual_address2[65];
char customer_actual_contact[17];
int customer_actual_discount;

order.h:

int order_id;
int order_customer;
char order_status[17];

int order_line_id;
int order_line_order;
char order_line_item[33];
int order_line_quantity;
double order_line_price;

add_mysql.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SOCKET int
#include <mysql.h>

#include "customer.h"
#include "order.h"

int main()
{
    MYSQL *con;
    MYSQL_STMT *stmt;
    MYSQL_BIND in[5];
    char *sqlstr;
    char temp[17];
    int stat;
    con = mysql_init(NULL);
    con = mysql_real_connect(con, "localhost", "root", "hemmeligt", "Test", 3306, NULL, 0);
    stmt = mysql_stmt_init(con);
    sqlstr = "INSERT INTO customer VALUES(?,?,?)";
    stat = mysql_stmt_prepare(stmt, sqlstr, strlen(sqlstr));
    customer_id = 6;
    strcpy(customer_name, "F company");
    strcpy(customer_phone, "6666-6666");
    memset(in, 0, sizeof(in));
    in[0].buffer_type = MYSQL_TYPE_LONG;
    in[0].buffer = &customer_id;
    in[0].buffer_length = sizeof(customer_id);
    in[0].is_null = 0;
    in[1].buffer_type = MYSQL_TYPE_STRING;
    in[1].buffer = customer_name;
    in[1].buffer_length = strlen(customer_name);
    in[1].is_null = 0;
    in[2].buffer_type = MYSQL_TYPE_STRING;
    in[2].buffer = customer_phone;
    in[2].buffer_length = strlen(customer_phone);
    in[2].is_null = 0;
    stat = mysql_stmt_bind_param(stmt, in);
    stat = mysql_stmt_execute(stmt);
    stmt = mysql_stmt_init(con);
    sqlstr = "INSERT INTO customer_actual VALUES(?,?,?,?,?)";
    stat = mysql_stmt_prepare(stmt, sqlstr, strlen(sqlstr));
    customer_actual_id = 6;
    strcpy(customer_actual_address1, "F road 6");
    strcpy(customer_actual_address2, "F town");
    strcpy(customer_actual_contact, "Mr. F");
    customer_actual_discount = 10;
    memset(in, 0, sizeof(in));
    in[0].buffer_type = MYSQL_TYPE_LONG;
    in[0].buffer = &customer_actual_id;
    in[0].buffer_length = sizeof(customer_actual_id);
    in[0].is_null = 0;
    in[1].buffer_type = MYSQL_TYPE_STRING;
    in[1].buffer = customer_actual_address1;
    in[1].buffer_length = strlen(customer_actual_address1);
    in[1].is_null = 0;
    in[2].buffer_type = MYSQL_TYPE_STRING;
    in[2].buffer = customer_actual_address2;
    in[2].buffer_length = strlen(customer_actual_address2);
    in[2].is_null = 0;
    in[3].buffer_type = MYSQL_TYPE_STRING;
    in[3].buffer = customer_actual_contact;
    in[3].buffer_length = strlen(customer_actual_contact);
    in[3].is_null = 0;
    in[4].buffer_type = MYSQL_TYPE_LONG;
    in[4].buffer = &customer_actual_discount;
    in[4].buffer_length = sizeof(customer_actual_discount);
    in[4].is_null = 0;
    stat = mysql_stmt_bind_param(stmt, in);
    stat = mysql_stmt_execute(stmt);
    stmt = mysql_stmt_init(con);
    sqlstr = "INSERT INTO order_main VALUES(?,?,?)";
    stat = mysql_stmt_prepare(stmt, sqlstr, strlen(sqlstr));
    order_id = 6;
    order_customer = 6;
    strcpy(order_status, "In progress");
    memset(in, 0, sizeof(in));
    in[0].buffer_type = MYSQL_TYPE_LONG;
    in[0].buffer = &order_id;
    in[0].buffer_length = sizeof(order_id);
    in[0].is_null = 0;
    in[1].buffer_type = MYSQL_TYPE_LONG;
    in[1].buffer = &order_customer;
    in[1].buffer_length = sizeof(order_customer);
    in[1].is_null = 0;
    in[2].buffer_type = MYSQL_TYPE_STRING;
    in[2].buffer = order_status;
    in[2].buffer_length = strlen(order_status);
    in[2].is_null = 0;
    stat = mysql_stmt_bind_param(stmt, in);
    stat = mysql_stmt_execute(stmt);
    stmt = mysql_stmt_init(con);
    sqlstr = "INSERT INTO order_line VALUES(?,?,?,?,?)";
    stat = mysql_stmt_prepare(stmt, sqlstr, strlen(sqlstr));
    order_line_id = 8;
    order_line_order = 6;
    strcpy(order_line_item, "X stuff");
    order_line_quantity = 1;
    order_line_price = 72.00;
    memset(in, 0, sizeof(in));
    in[0].buffer_type = MYSQL_TYPE_LONG;
    in[0].buffer = &order_line_id;
    in[0].buffer_length = sizeof(order_line_id);
    in[0].is_null = 0;
    in[1].buffer_type = MYSQL_TYPE_LONG;
    in[1].buffer = &order_line_order;
    in[1].buffer_length = sizeof(order_line_order);
    in[1].is_null = 0;
    in[2].buffer_type = MYSQL_TYPE_STRING;
    in[2].buffer = order_line_item;
    in[2].buffer_length = strlen(order_line_item);
    in[2].is_null = 0;
    in[3].buffer_type = MYSQL_TYPE_LONG;
    in[3].buffer = &order_line_quantity;
    in[3].buffer_length = sizeof(order_line_quantity);
    in[3].is_null = 0;
    sprintf(temp, "%.2f", order_line_price);
    in[4].buffer_type = MYSQL_TYPE_STRING;
    in[4].buffer = temp;
    in[4].buffer_length = strlen(temp);
    in[4].is_null = 0;
    stat = mysql_stmt_bind_param(stmt, in);
    stat = mysql_stmt_execute(stmt);
    mysql_close(con);
    return 0;
}

list_mysql.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SOCKET int
#include <mysql.h>

#include "customer.h"
#include "order.h"

int main()
{
    MYSQL *con;
    MYSQL_STMT *stmt;
    MYSQL_BIND out[8];
    char *sqlstr;
    unsigned long int namelen, phonelen, sourcelen, address1len, address2len, contactlen, statuslen, sumvallen;
    int stat;
    int last_customer_id;
    char sumval[11];
    con = mysql_init(NULL);
    con = mysql_real_connect(con, "localhost", "root", "hemmeligt", "Test", 3306, NULL, 0);
    stmt = mysql_stmt_init(con);
    sqlstr = "SELECT customer.id AS cid, name, phone, source "
             "FROM customer JOIN customer_potential ON customer.id = customer_potential.id "
             "ORDER BY cid";
    stat = mysql_stmt_prepare(stmt, sqlstr, strlen(sqlstr));
    stat = mysql_stmt_execute(stmt);
    memset(out, 0, sizeof(out));
    out[0].buffer_type = MYSQL_TYPE_LONG;
    out[0].buffer = &customer_id;
    out[0].buffer_length = sizeof(customer_id);
    out[1].buffer_type = MYSQL_TYPE_STRING;
    out[1].buffer = &customer_name;
    out[1].buffer_length = sizeof(customer_name);
    out[1].length = &namelen;
    out[2].buffer_type = MYSQL_TYPE_STRING;
    out[2].buffer = customer_phone;
    out[2].buffer_length = sizeof(customer_phone);
    out[2].length = &phonelen;
    out[3].buffer_type = MYSQL_TYPE_STRING;
    out[3].buffer = customer_potential_source;
    out[3].buffer_length = sizeof(customer_potential_source);
    out[3].length = &sourcelen;
    stat = mysql_stmt_bind_result(stmt, out);
    stat = mysql_stmt_store_result(stmt);
    while(!mysql_stmt_fetch(stmt))
    {
        customer_name[namelen] = 0;
        customer_phone[phonelen] = 0;
        customer_potential_source[sourcelen] = 0;
        printf("**future customer** %s, %s, %s\n", customer_name, customer_phone, customer_potential_source);
    }
    mysql_stmt_free_result(stmt);
    stmt = mysql_stmt_init(con);
    sqlstr = "SELECT customer.id AS cid,name,address1,address2,phone,order_main.id AS oid,status,SUM(quantity*price) AS sumval "
             "FROM customer JOIN customer_actual ON customer.id = customer_actual.id "
             "              JOIN order_main ON customer.id = order_main.customer "
             "              JOIN order_line ON order_main.id = order_line.order_main "
             "GROUP BY customer.id,name,address1,address2,phone,order_main.id,status "
             "ORDER BY cid, oid";
    stat = mysql_stmt_prepare(stmt, sqlstr, strlen(sqlstr));
    stat = mysql_stmt_execute(stmt);
    memset(out, 0, sizeof(out));
    out[0].buffer_type = MYSQL_TYPE_LONG;
    out[0].buffer = &customer_id;
    out[0].buffer_length = sizeof(customer_id);
    out[1].buffer_type = MYSQL_TYPE_STRING;
    out[1].buffer = customer_name;
    out[1].buffer_length = sizeof(customer_name);
    out[1].length = &namelen;
    out[2].buffer_type = MYSQL_TYPE_STRING;
    out[2].buffer = customer_actual_address1;
    out[2].buffer_length = sizeof(customer_actual_address1);
    out[2].length = &address1len;
    out[3].buffer_type = MYSQL_TYPE_STRING;
    out[3].buffer = customer_actual_address2;
    out[3].buffer_length = sizeof(customer_actual_address2);
    out[3].length = &address2len;
    out[4].buffer_type = MYSQL_TYPE_STRING;
    out[4].buffer = customer_phone;
    out[4].buffer_length = sizeof(customer_phone);
    out[4].length = &phonelen;
    out[5].buffer_type = MYSQL_TYPE_LONG;
    out[5].buffer = &order_id;
    out[5].buffer_length = sizeof(order_id);
    out[6].buffer_type = MYSQL_TYPE_STRING;
    out[6].buffer = order_status;
    out[6].buffer_length = sizeof(order_status);
    out[6].length = &statuslen;
    out[7].buffer_type = MYSQL_TYPE_DECIMAL;
    out[7].buffer = &sumval;
    out[7].buffer_length = sizeof(sumval);
    out[7].length = &sumvallen;
    stat = mysql_stmt_bind_result(stmt, out);
    stat = mysql_stmt_store_result(stmt);
    last_customer_id = 0;
    while(!mysql_stmt_fetch(stmt))
    {
        customer_name[namelen] = 0;
        customer_actual_address1[address1len] = 0;
        customer_actual_address2[address2len] = 0;
        customer_phone[phonelen] = 0;
        order_status[statuslen] = 0;
        sumval[sumvallen] = 0;
        if(last_customer_id != customer_id)
        {
            printf("%s, %s, %s, %s\n", customer_name, customer_actual_address1, customer_actual_address2, customer_phone);
            last_customer_id = customer_id;
        }
        printf("  %d, %s, %s\n", order_id, order_status, sumval);
    }
    mysql_stmt_free_result(stmt);
    mysql_close(con);
    return 0;
}

Build and run:

$ cc /include=mysql055_root:[include.mysql] /names=as_is /float=iee add_mysql.c
$ link add_mysql + sys$input/opt
mysql055_root:[lib.alpha]libclientlib/lib
mysql055_root:[lib.alpha]libsql/lib
mysql055_root:[lib.alpha]libmysys/lib
mysql055_root:[lib.alpha]libdbug/lib
mysql055_root:[lib.alpha]libstrings/lib
mysql055_root:[lib.alpha]libvio/lib
mysql055_root:[lib.alpha]libz/lib
mysql055_root:[lib.alpha]ssl_libssl32/lib
mysql055_root:[lib.alpha]ssl_libcrypto32/lib
$
$ cc /include=mysql055_root:[include.mysql] /names=as_is /float=ieee list_mysql.c
$ link list_mysql + sys$input/opt
mysql055_root:[lib.alpha]libclientlib/lib
mysql055_root:[lib.alpha]libsql/lib
mysql055_root:[lib.alpha]libmysys/lib
mysql055_root:[lib.alpha]libdbug/lib
mysql055_root:[lib.alpha]libstrings/lib
mysql055_root:[lib.alpha]libvio/lib
mysql055_root:[lib.alpha]libz/lib
mysql055_root:[lib.alpha]ssl_libssl32/lib
mysql055_root:[lib.alpha]ssl_libcrypto32/lib
$
$ run list_mysql
$ run add_mysql
$ run list_mysql

customer.pas:

var
   customer_id : integer;
   customer_name : longpstr(32);
   customer_phone : longpstr(16);
   customer_potential_id : integer;
   customer_potential_source : longpstr(256);
   customer_actual_id : integer;
   customer_actual_address1 : longpstr(64);
   customer_actual_address2 : longpstr(64);
   customer_actual_contact : longpstr(16);
   customer_actual_discount : integer;

order.pas:

var
   order_id : integer;
   order_customer : integer;
   order_status : longpstr(16);
   order_line_id : integer;
   order_line_order : integer;
   order_line_item : longpstr(32);
   order_line_quantity : integer;
   order_line_price : double;

add_mysql.pas:

[inherit('pmysqldir:pmysql2', 'pmysqldir:pmysql', 'pmysqldir:mysql')]
program add(input, output);

%include 'customer.pas'
%include 'order.pas'

var
   con : mysql_ptr;
   stmt : mysql_stmt_ptr;
   stat : integer;
   in3 : array [1..3] of mysql_bind;
   in5 : array [1..5] of mysql_bind;
   temp : pstr;

begin
   con := pmysql_init;
   con := pmysql_real_connect(con, 'localhost', 'root', 'hemmeligt', 'test');
   stmt := pmysql_prepare(con, 'INSERT INTO customer VALUES(?,?,?)');
   customer_id := 6;
   longstr('F company', customer_name);
   longstr('6666-6666', customer_phone);
   pmysql_init_bind_long(in3[1], customer_id);
   pmysql_init_bind_string_inx(in3[2], customer_name);
   pmysql_init_bind_string_inx(in3[3], customer_phone);
   stat := pmysql_stmt_bind_param(stmt, in3);
   stat := pmysql_stmt_execute(stmt);
   stmt := pmysql_prepare(con, 'INSERT INTO customer_actual VALUES(?,?,?,?,?)');
   customer_actual_id := 6;
   longstr('F road 6', customer_actual_address1);
   longstr('F town', customer_actual_address2);
   longstr('Mr. F', customer_actual_contact);
   customer_actual_discount := 10;
   pmysql_init_bind_long(in5[1], customer_actual_id);
   pmysql_init_bind_string_inx(in5[2], customer_actual_address1);
   pmysql_init_bind_string_inx(in5[3], customer_actual_address2);
   pmysql_init_bind_string_inx(in5[4], customer_actual_contact);
   pmysql_init_bind_long(in5[5], customer_actual_discount);
   stat := pmysql_stmt_bind_param(stmt, in5);
   stat := pmysql_stmt_execute(stmt);
   stmt := pmysql_prepare(con, 'INSERT INTO order_main VALUES(?,?,?)');
   order_id := 6;
   order_customer := 6;
   longstr('In progress', order_status);
   pmysql_init_bind_long(in3[1], order_id);
   pmysql_init_bind_long(in3[2], order_customer);
   pmysql_init_bind_string_inx(in3[3], order_status);
   stat := pmysql_stmt_bind_param(stmt, in3);
   stat := pmysql_stmt_execute(stmt);
   stmt := pmysql_prepare(con, 'INSERT INTO order_line VALUES(?,?,?,?,?)');
   order_line_id := 8;
   order_line_order := 6;
   longstr('X stuff', order_line_item);
   order_line_quantity := 1;
   order_line_price := 72.00;
   pmysql_init_bind_long(in5[1], order_line_id);
   pmysql_init_bind_long(in5[2], order_line_order);
   pmysql_init_bind_string_inx(in5[3], order_line_item);
   pmysql_init_bind_long(in5[4], order_line_quantity);
   writev(temp, order_line_price:3:1);
   pmysql_init_bind_string_in(in5[5], temp);
   stat := pmysql_stmt_bind_param(stmt, in5);
   stat := pmysql_stmt_execute(stmt);
   pmysql_close(con);
end.

list_mysql.pas:

[inherit('pmysqldir:pmysql2', 'pmysqldir:pmysql', 'pmysqldir:mysql')]
program list(input, output);

%include 'customer.pas'
%include 'order.pas'

var
   con : mysql_ptr;
   stmt : mysql_stmt_ptr;
   stat : integer;
   sumval : longpstr(10);
   last_customer_id : integer;

begin
   con := pmysql_init;
   con := pmysql_real_connect(con, 'localhost', 'root', 'hemmeligt', 'test');
   stmt := pmysql_prepare(con, 'SELECT customer.id AS cid, name, phone, source ' +
                               'FROM customer JOIN customer_potential ON customer.id = customer_potential.id ' +
                               'ORDER BY cid');
   stat := pmysql_get_result_start(stmt, long_arg(customer_id),
                                         longstring_arg(customer_name),
                                         longstring_arg(customer_phone),
                                         longstring_arg(customer_potential_source));
   while pmysql_stmt_fetch(stmt) = 0 do begin
      writeln('**future customer** ', stdstr(customer_name), ', ', stdstr(customer_phone), ', ', stdstr(customer_potential_source));
   end;
   pmysql_stmt_free_result(stmt);
   stmt := pmysql_prepare(con, 'SELECT customer.id AS cid,name,address1,address2,phone,order_main.id AS oid,status,SUM(quantity*price) AS sumval ' +
                               'FROM customer JOIN customer_actual ON customer.id = customer_actual.id ' +
                               '              JOIN order_main ON customer.id = order_main.customer ' +
                               '              JOIN order_line ON order_main.id = order_line.order_main ' +
                               'GROUP BY customer.id,name,address1,address2,phone,order_main.id,status ' +
                               'ORDER BY customer.id');
   stat := pmysql_get_result_start(stmt, long_arg(customer_id),
                                         longstring_arg(customer_name),
                                         longstring_arg(customer_actual_address1),
                                         longstring_arg(customer_actual_address2),
                                         longstring_arg(customer_phone),
                                         long_arg(order_id),
                                         longstring_arg(order_status),
                                         longstring_arg(sumval));
   last_customer_id := 0;
   while pmysql_stmt_fetch(stmt) = 0 do begin
      if customer_id <> last_customer_id then begin
         writeln(stdstr(customer_name), ', ', stdstr(customer_actual_address1), ', ', stdstr(customer_actual_address2), ', ', stdstr(customer_phone));
         last_customer_id := customer_id;
      end;
      writeln('  ', order_id:1, ', ', stdstr(order_status), ', ', stdstr(sumval));
   end;
   pmysql_close(con);
end.

Build and run:

$ pascal add_mysql
$ link add_mysql + sys$input/opt + pmysqldir:mysql55axp/opt
pmysqldir:pmysql2
pmysqldir:pmysql
pmysqldir:mysql
$
$ pascal list_mysql
$ link list_mysql + sys$input/opt + pmysqldir:mysql55axp/opt
pmysqldir:pmysql2
pmysqldir:pmysql
pmysqldir:mysql
$
$ run list_mysql
$ run add_mysql
$ run list_mysql

customer.bas:

declare long customer_id
declare string customer_name
declare string customer_phone
declare long customer_potential_id
declare string customer_potential_source
declare long customer_actual_id
declare string customer_actual_address1
declare string customer_actual_address2
declare string customer_actual_contact
declare long customer_actual_discount

order.bas:

declare long order_id
declare long order_customer
declare string order_status
declare long order_line_id
declare long order_line_order
declare string order_line_item
declare long order_line_quantity
declare tfloat order_line_price

add_mysql.bas:

program add_mysql

option type = explicit

%include "customer.bas"
%include "order.bas"
%include "bmysqldir:b.bas"

declare integer dbcon, stmt, inparam
declare integer stat
declare string temp

dbcon = bmysql_open("localhost", 3306, "root", "", "test")
stmt = bmysql_stmt_prepare(dbcon, "INSERT INTO customer VALUES(?,?,?)")
inparam = bmysql_inparam_init(3)
customer_id = 6
customer_name = "F company"
customer_phone = "6666-6666"
call bmysql_inparam_long(inparam, 0, customer_id)
call bmysql_inparam_string(inparam, 1, customer_name)
call bmysql_inparam_string(inparam, 2, customer_phone)
stat = bmysql_stmt_execute(stmt, inparam, 0)
call bmysql_inparam_free(inparam)
call bmysql_stmt_free(stmt)
stmt = bmysql_stmt_prepare(dbcon, "INSERT INTO customer_actual VALUES(?,?,?,?,?)")
inparam = bmysql_inparam_init(5)
customer_actual_id = 6
customer_actual_address1 = "F road 6"
customer_actual_address2 = "F town"
customer_actual_contact = "Mr. F"
customer_actual_discount = 10
call bmysql_inparam_long(inparam, 0, customer_actual_id)
call bmysql_inparam_string(inparam, 1, customer_actual_address1)
call bmysql_inparam_string(inparam, 2, customer_actual_address2)
call bmysql_inparam_string(inparam, 3, customer_actual_contact)
call bmysql_inparam_long(inparam, 4, customer_actual_discount)
stat = bmysql_stmt_execute(stmt, inparam, 0)
call bmysql_inparam_free(inparam)
call bmysql_stmt_free(stmt)
stmt = bmysql_stmt_prepare(dbcon, "INSERT INTO order_main VALUES(?,?,?)")
inparam = bmysql_inparam_init(3)
order_id = 6
order_customer = 6
order_status = "In progress"
call bmysql_inparam_long(inparam, 0, order_id)
call bmysql_inparam_long(inparam, 1, order_customer)
call bmysql_inparam_string(inparam, 2, order_status)
stat = bmysql_stmt_execute(stmt, inparam, 0)
call bmysql_stmt_free(stmt)
stmt = bmysql_stmt_prepare(dbcon, "INSERT INTO order_line VALUES(?,?,?,?,?)")
inparam = bmysql_inparam_init(5)
order_line_id = 8
order_line_order = 6
order_line_item = "X stuff"
order_line_quantity = 1
order_line_price = 72.00
call bmysql_inparam_long(inparam, 0, order_line_id)
call bmysql_inparam_long(inparam, 1, order_line_order)
call bmysql_inparam_string(inparam, 2, order_line_item)
call bmysql_inparam_long(inparam, 3, order_line_quantity)
temp = str$(order_line_price)
call bmysql_inparam_string(inparam, 4, temp)
stat = bmysql_stmt_execute(stmt, inparam, 0)
call bmysql_stmt_free(stmt)
call bmysql_close(dbcon)

end program

list_mysql.bas:

program list_mysql

option type = explicit

%include "customer.bas"
%include "order.bas"
%include "bmysqldir:b.bas"

declare integer dbcon, stmt, outparam
declare integer stat
declare tfloat sumval
declare integer last_customer_id

dbcon = bmysql_open("localhost", 3306, "root", "", "test")
stmt = bmysql_stmt_prepare(dbcon, "SELECT customer.id AS cid, name, phone, source " + &
                                  "FROM customer JOIN customer_potential ON customer.id = customer_potential.id " + &
                                  "ORDER BY cid")
outparam = bmysql_outparam_init(4)
call bmysql_outparam_long(outparam, 0, customer_id)
call bmysql_outparam_string(outparam, 1, customer_name)
call bmysql_outparam_string(outparam, 2, customer_phone)
call bmysql_outparam_string(outparam, 3, customer_potential_source)
stat = bmysql_stmt_execute(stmt, 0, outparam)
while bmysql_stmt_fetch(stmt, outparam) = 0
   print using "_**future customer_** 'E_, 'E_, 'E", customer_name, customer_phone, customer_potential_source
next
call bmysql_outparam_free(outparam)
call bmysql_stmt_free(stmt)
stmt = bmysql_stmt_prepare(dbcon, "SELECT customer.id AS cid,name,address1,address2,phone,order_main.id AS oid,status,SUM(quantity*price) AS sumval " + &
                                  "FROM customer JOIN customer_actual ON customer.id = customer_actual.id " + &
                                  "              JOIN order_main ON customer.id = order_main.customer " + &
                                  "              JOIN order_line ON order_main.id = order_line.order_main " + &
                                  "GROUP BY customer.id,name,address1,address2,phone,order_main.id,status " + &
                                  "ORDER BY customer.id")
outparam = bmysql_outparam_init(8)
call bmysql_outparam_long(outparam, 0, customer_id)
call bmysql_outparam_string(outparam, 1, customer_name)
call bmysql_outparam_string(outparam, 2, customer_actual_address1)
call bmysql_outparam_string(outparam, 3, customer_actual_address2)
call bmysql_outparam_string(outparam, 4, customer_phone)
call bmysql_outparam_long(outparam, 5, order_id)
call bmysql_outparam_string(outparam, 6, order_status)
call bmysql_outparam_tfloat(outparam, 7, sumval)
stat = bmysql_stmt_execute(stmt, 0, outparam)
last_customer_id = 0
while bmysql_stmt_fetch(stmt, outparam) = 0
    if customer_id <> last_customer_id then
        print using "'E_, 'E_, 'E_, 'E", customer_name, customer_actual_address1, customer_actual_address2, customer_phone
        last_customer_id = customer_id
    end if
    print using "  #_, 'E_, ########.##", order_id, order_status, sumval
next
call bmysql_outparam_free(outparam)
call bmysql_stmt_free(stmt)
call bmysql_close(dbcon)

end program

Build and run:

$ bas add_mysql
$ link add_mysql + bmysqldir:b.opt/opt
$ bas list_mysql
$ link list_mysql + bmysqldir:b.opt/opt
$ run list_mysql
$ run add_mysql
$ run list_mysql

add_mysqli.php:

<?php
$con = mysqli_connect('localhost', 'root', 'hemmeligt', 'test');
$stmt = $con->prepare('INSERT INTO customer VALUES(?,?,?)');
$cid = 6;
$name = 'F company';
$phone = '6666-6666';
$stmt->bind_param('iss', $cid, $name, $phone);
$stmt->execute();
$stmt = $con->prepare('INSERT INTO customer_actual VALUES(?,?,?,?,?)');
$cid = 6;
$address1 = 'F road 6';
$address2 = 'F town';
$contact = 'Mr. F';
$discount = 10;
$stmt->bind_param('isssi', $cid, $address1, $address2, $contact, $discount);
$stmt->execute();
$stmt = $con->prepare('INSERT INTO order_main VALUES(?,?,?)');
$oid = 6;
$customer = 6;
$status = 'In progress';
$stmt->bind_param('iis', $oid, $customer, $status);
$stmt->execute();
$stmt = $con->prepare('INSERT INTO order_line VALUES(?,?,?,?,?)');
$olid = 8;
$order = 6;
$item = 'X stuff';
$quantity = 1;
$price = '72.00';
$stmt->bind_param('iisis', $olid, $order, $item, $quantity, $price);
$stmt->execute();
mysqli_close($con);
?>

list_mysqli.php:

<?php
$con = mysqli_connect('localhost', 'root', 'hemmeligt', 'test');
$stmt = $con->prepare('SELECT customer.id AS cid, name, phone, source ' .
                      'FROM customer JOIN customer_potential ON customer.id = customer_potential.id ' .
                      'ORDER BY cid');
$stmt->execute();
$rs = $stmt->get_result();
while($row = $rs->fetch_array(MYSQLI_ASSOC)) {
    $name = $row['name'];
    $phone = $row['phone'];
    $source = $row['source'];
    echo "**future customer** $name, $phone, $source\r\n";
}
$stmt->close();
$stmt = $con->prepare('SELECT customer.id AS cid,name,address1,address2,phone,order_main.id AS oid,status,SUM(quantity*price) AS sumval ' .
                      'FROM customer JOIN customer_actual ON customer.id = customer_actual.id ' .
                      '              JOIN order_main ON customer.id = order_main.customer ' .
                      '              JOIN order_line ON order_main.id = order_line.order_main ' .
                      'GROUP BY customer.id,name,address1,address2,phone,order_main.id,status ' .
                      'ORDER BY cid, oid');
$stmt->execute();
$rs = $stmt->get_result();
$last_cid = 0;
while($row = $rs->fetch_array(MYSQLI_ASSOC)) {
    $cid = $row['cid'];
    $name = $row['name'];
    $address1 = $row['address1'];
    $address2 = $row['address2'];
    $phone = $row['phone'];
    $oid = $row['oid'];
    $status = $row['status'];
    $sumval = $row['sumval'];
    if($cid != $last_cid) {
        echo "$name, $address1, $address2, $phone\r\n";
        $last_cid = $cid;
    }
    echo "  $oid, $status, $sumval\r\n";
}
$stmt->close();
$con->close();
?>

Run:

$ php list_mysqli.php
$ php add_mysqli.php
$ php list_mysqli.php

SQLite:

SQLite comes with a sqlite3 library.

I have created a Pascal wrapper around it called psqlite that is available here.

PHP sqlite3 extension also use the C library..

customer.h:

int customer_id;
char customer_name[33];
char customer_phone[17];

int customer_potential_id;
char customer_potential_source[257];

int customer_actual_id;
char customer_actual_address1[65];
char customer_actual_address2[65];
char customer_actual_contact[17];
int customer_actual_discount;

order.h:

int order_id;
int order_customer;
char order_status[17];

int order_line_id;
int order_line_order;
char order_line_item[33];
int order_line_quantity;
double order_line_price;

add_sqlite.c:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "sqlite3.h"

#include "customer.h"
#include "order.h"

int main()
{
    sqlite3 *con;
    sqlite3_stmt *stmt;
    char *sqlstr;
    int stat;
    stat = sqlite3_open("test.db", &con);
    sqlstr = "INSERT INTO customer VALUES(?,?,?)";
    stat = sqlite3_prepare(con, sqlstr, strlen(sqlstr), &stmt, NULL);
    customer_id = 6;
    strcpy(customer_name, "F company");
    strcpy(customer_phone, "6666-6666");
    sqlite3_bind_int(stmt, 1, customer_id);
    sqlite3_bind_text(stmt, 2, customer_name, strlen(customer_name), NULL);
    sqlite3_bind_text(stmt, 3, customer_phone, strlen(customer_phone), NULL);
    stat = sqlite3_step(stmt);
    sqlite3_finalize(stmt);
    sqlstr = "INSERT INTO customer_actual VALUES(?,?,?,?,?)";
    stat = sqlite3_prepare(con, sqlstr, strlen(sqlstr), &stmt, NULL);
    customer_actual_id = 6;
    strcpy(customer_actual_address1, "F road 6");
    strcpy(customer_actual_address2, "F town");
    strcpy(customer_actual_contact, "Mr. F");
    customer_actual_discount = 10;
    sqlite3_bind_int(stmt, 1, customer_actual_id);
    sqlite3_bind_text(stmt, 2, customer_actual_address1, strlen(customer_actual_address1), NULL);
    sqlite3_bind_text(stmt, 3, customer_actual_address2, strlen(customer_actual_address2), NULL);
    sqlite3_bind_text(stmt, 4, customer_actual_contact, strlen(customer_actual_contact), NULL);
    sqlite3_bind_int(stmt, 5, customer_actual_discount);
    stat = sqlite3_step(stmt);
    sqlite3_finalize(stmt);
    sqlstr = "INSERT INTO order_main VALUES(?,?,?)";
    stat = sqlite3_prepare(con, sqlstr, strlen(sqlstr), &stmt, NULL);
    order_id = 6;
    order_customer = 6;
    strcpy(order_status, "In progress");
    sqlite3_bind_int(stmt, 1, order_id);
    sqlite3_bind_int(stmt, 2, order_customer);
    sqlite3_bind_text(stmt, 3, order_status, strlen(order_status), NULL);
    stat = sqlite3_step(stmt);
    sqlite3_finalize(stmt);
    sqlstr = "INSERT INTO order_line VALUES(?,?,?,?,?)";
    stat = sqlite3_prepare(con, sqlstr, strlen(sqlstr), &stmt, NULL);
    order_line_id = 8;
    order_line_order = 6;
    strcpy(order_line_item, "X stuff");
    order_line_quantity = 1;
    order_line_price = 72.00;
    sqlite3_bind_int(stmt, 1, order_line_id);
    sqlite3_bind_int(stmt, 2, order_line_order);
    sqlite3_bind_text(stmt, 3, order_line_item, strlen(order_line_item), NULL);
    sqlite3_bind_int(stmt, 4, order_line_quantity);
    sqlite3_bind_double(stmt, 5, order_line_price);
    stat = sqlite3_step(stmt);
    sqlite3_finalize(stmt);
    sqlite3_close(con);
    return 0;
}

list_sqlite.c:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "sqlite3.h"

#include "customer.h"
#include "order.h"

int main()
{
    sqlite3 *con;
    sqlite3_stmt *stmt;
    char *sqlstr;
    int stat;
    int last_customer_id;
    double sumval;
    stat = sqlite3_open("test.db", &con);
    sqlstr = "SELECT customer.id AS cid, name, phone, source "
             "FROM customer JOIN customer_potential ON customer.id = customer_potential.id "
             "ORDER BY cid";
    stat = sqlite3_prepare(con, sqlstr, strlen(sqlstr), &stmt, NULL);
    while(sqlite3_step(stmt) == SQLITE_ROW)
    {
        strcpy(customer_name, (char *)sqlite3_column_text(stmt, 1));
        strcpy(customer_phone, (char *)sqlite3_column_text(stmt, 2));
        strcpy(customer_potential_source, (char *)sqlite3_column_text(stmt, 3));
        printf("**future customer** %s, %s, %s\n", customer_name,
                                                   customer_phone,
                                                   customer_potential_source);
    }
    sqlite3_finalize(stmt);
    sqlstr = "SELECT customer.id AS cid,name,address1,address2,phone,order_main.id AS oid,status,SUM(quantity*price) AS sumval "
             "FROM customer JOIN customer_actual ON customer.id = customer_actual.id "
             "              JOIN order_main ON customer.id = order_main.customer "
             "              JOIN order_line ON order_main.id = order_line.order_main "
             "GROUP BY customer.id,name,address1,address2,phone,order_main.id,status "
             "ORDER BY cid, oid";
    stat = sqlite3_prepare(con, sqlstr, strlen(sqlstr), &stmt, NULL);
    last_customer_id = 0;
    while(sqlite3_step(stmt) == SQLITE_ROW)
    {
        customer_id = sqlite3_column_int(stmt, 0);
        strcpy(customer_name, (char *)sqlite3_column_text(stmt, 1));
        strcpy(customer_actual_address1, (char *)sqlite3_column_text(stmt, 2));
        strcpy(customer_actual_address2, (char *)sqlite3_column_text(stmt, 3));
        strcpy(customer_phone, (char *)sqlite3_column_text(stmt, 4));
        order_id = sqlite3_column_int(stmt, 5);
        strcpy(order_status, (char *)sqlite3_column_text(stmt, 6));
        sumval = sqlite3_column_double(stmt, 7);
        if(customer_id != last_customer_id)
        {
            printf("%s, %s, %s, %s\n", customer_name,
                                       customer_actual_address1,
                                       customer_actual_address2,
                                       customer_phone);
           last_customer_id = customer_id;
        }
        printf("  %d, %s, %.2f\n", order_id,
                                   order_status,
                                   sumval);
    }
    sqlite3_finalize(stmt);
    sqlite3_close(con);
    return 0;
}

Build and run:

$ cc/include=sqlite3_include:/float=ieee add_sqlite
$ link add_sqlite + sys$input/opt
sqlite3shr/share
$
$ cc/include=sqlite3_include:/float=ieee list_sqlite
$ link list_sqlite + sys$input/opt
sqlite3shr/share
$
$ run list_sqlite
$ run add_sqlite
$ run list_sqlite

customer2.pas:

var
   customer_id : integer;
   customer_name : pstr;
   customer_phone : pstr;
   customer_potential_id : integer;
   customer_potential_source : pstr;
   customer_actual_id : integer;
   customer_actual_address1 : pstr;
   customer_actual_address2 : pstr;
   customer_actual_contact : pstr;
   customer_actual_discount : integer;

order2.pas:

var
   order_id : integer;
   order_customer : integer;
   order_status : pstr;
   order_line_id : integer;
   order_line_order : integer;
   order_line_item : pstr;
   order_line_quantity : integer;
   order_line_price : double;

add_sqlite.pas:

[inherit('psqlitedir:sqlite', 'psqlitedir:psqlite')]
program add(input, output);

%include 'customer2.pas'
%include 'order2.pas'

var
   con : sqlite_ptr;
   stmt : sqlite_stmt_ptr;
   stat : integer;

begin
   con := psqlite_open('test.db');
   stmt := psqlite_prepare(con, 'INSERT INTO customer VALUES(?,?,?)');
   customer_id := 6;
   customer_name := 'F company';
   customer_phone := '6666-6666';
   psqlite_bind_int(stmt, 1, customer_id);
   psqlite_bind_text(stmt, 2, customer_name);
   psqlite_bind_text(stmt, 3, customer_phone);
   psqlite_step_nonquery(stmt);
   psqlite_finalize(stmt);
   stmt := psqlite_prepare(con, 'INSERT INTO customer_actual VALUES(?,?,?,?,?)');
   customer_actual_id := 6;
   customer_actual_address1 := 'F road 6';
   customer_actual_address2 := 'F town';
   customer_actual_contact := 'Mr. F';
   customer_actual_discount := 10;
   psqlite_bind_int(stmt, 1, customer_actual_id);
   psqlite_bind_text(stmt, 2, customer_actual_address1);
   psqlite_bind_text(stmt, 3, customer_actual_address2);
   psqlite_bind_text(stmt, 4, customer_actual_contact);
   psqlite_bind_int(stmt, 5, customer_actual_discount);
   psqlite_step_nonquery(stmt);
   psqlite_finalize(stmt);
   stmt := psqlite_prepare(con, 'INSERT INTO order_main VALUES(?,?,?)');
   order_id := 6;
   order_customer := 6;
   order_status := 'In progress';
   psqlite_bind_int(stmt, 1, order_id);
   psqlite_bind_int(stmt, 2, order_customer);
   psqlite_bind_text(stmt, 3, order_status);
   psqlite_step_nonquery(stmt);
   psqlite_finalize(stmt);
   stmt := psqlite_prepare(con, 'INSERT INTO order_line VALUES(?,?,?,?,?)');
   order_line_id := 8;
   order_line_order := 6;
   order_line_item := 'X stuff';
   order_line_quantity := 1;
   order_line_price := 72.00;
   psqlite_bind_int(stmt, 1, order_line_id);
   psqlite_bind_int(stmt, 2, order_line_order);
   psqlite_bind_text(stmt, 3, order_line_item);
   psqlite_bind_int(stmt, 4, order_line_quantity);
   psqlite_bind_double(stmt, 5, order_line_price);
   psqlite_step_nonquery(stmt);
   psqlite_finalize(stmt);
   psqlite_close(con);
end.

list_sqlite.pas:

[inherit('psqlitedir:sqlite', 'psqlitedir:psqlite')]
program list(input, output);

%include 'customer2.pas'
%include 'order2.pas'

var
   con : sqlite_ptr;
   stmt : sqlite_stmt_ptr;
   sumval : double;
   last_customer_id : integer;

begin
   con := psqlite_open('test.db');
   stmt := psqlite_prepare(con, 'SELECT customer.id AS cid, name, phone, source ' +
                                'FROM customer JOIN customer_potential ON customer.id = customer_potential.id ' +
                                'ORDER BY cid');
   while psqlite_step_query(stmt) do begin
      customer_name := psqlite_column_text(stmt, 1);
      customer_phone := psqlite_column_text(stmt, 2);
      customer_potential_source := psqlite_column_text(stmt, 3);
      writeln('**future customer** ', customer_name, ', ', customer_phone, ', ', customer_potential_source);
   end;
   psqlite_finalize(stmt);
   stmt := psqlite_prepare(con, 'SELECT customer.id AS cid,name,address1,address2,phone,order_main.id AS oid,status,SUM(quantity*price) AS sumval ' +
                                'FROM customer JOIN customer_actual ON customer.id = customer_actual.id ' +
                                '              JOIN order_main ON customer.id = order_main.customer ' +
                                '              JOIN order_line ON order_main.id = order_line.order_main ' +
                                'GROUP BY customer.id,name,address1,address2,phone,order_main.id,status ' +
                                'ORDER BY cid, oid');
   last_customer_id := 0;
   while psqlite_step_query(stmt) do begin
      customer_id := psqlite_column_int(stmt, 0);
      customer_name := psqlite_column_text(stmt, 1);
      customer_actual_address1 := psqlite_column_text(stmt, 2);
      customer_actual_address2 := psqlite_column_text(stmt, 3);
      customer_phone := psqlite_column_text(stmt, 4);
      order_id := psqlite_column_int(stmt, 5);
      order_status := psqlite_column_text(stmt, 6);
      sumval := psqlite_column_double(stmt, 7);
      if customer_id <> last_customer_id then begin
         writeln(customer_name, ', ', customer_actual_address1, ', ', customer_actual_address2, ', ', customer_phone);
         last_customer_id := customer_id;
      end;
      writeln('  ', order_id:1, ', ', order_status, ', ', sumval:3:1);
   end;
   psqlite_finalize(stmt);
   psqlite_close(con);
end.

Build and run:

$ pascal/float=ieee add_sqlite
$ link add_sqlite + psqlitedir:psqlite + psqlitedir:sqlite + psqlitedir:sqlite/opt
$ pascal/float=ieee list_sqlite
$ link list_sqlite + psqlitedir:psqlite + psqlitedir:sqlite + psqlitedir:sqlite/opt
$ run list_sqlite
$ run add_sqlite
$ run list_sqlite

add_sqlite3.php:

<?php
$con = new SQLite3('/disk2/arne/art/dbx/test.db');
$stmt = $con->prepare('INSERT INTO customer VALUES(?,?,?)');
$cid = 6;
$name = 'F company';
$phone = '6666-6666';
$stmt->bindParam(1, $cid, SQLITE3_INTEGER);
$stmt->bindParam(2, $name, SQLITE3_TEXT);
$stmt->bindParam(3, $phone, SQLITE3_TEXT);
$stmt->execute();
$stmt = $con->prepare('INSERT INTO customer_actual VALUES(?,?,?,?,?)');
$cid = 6;
$address1 = 'F road 6';
$address2 = 'F town';
$contact = 'Mr. F';
$discount = 10;
$stmt->bindParam(1, $cid, SQLITE3_INTEGER);
$stmt->bindParam(2, $address1, SQLITE3_TEXT);
$stmt->bindParam(3, $address2, SQLITE3_TEXT);
$stmt->bindParam(4, $contact, SQLITE3_TEXT);
$stmt->bindParam(5, $discount, SQLITE3_INTEGER);
$stmt->execute();
$stmt = $con->prepare('INSERT INTO order_main VALUES(?,?,?)');
$oid = 6;
$customer = 6;
$status = 'In progress';
$stmt->bindParam(1, $oid, SQLITE3_INTEGER);
$stmt->bindParam(2, $customer, SQLITE3_INTEGER);
$stmt->bindParam(3, $status, SQLITE3_TEXT);
$stmt->execute();
$stmt = $con->prepare('INSERT INTO order_line VALUES(?,?,?,?,?)');
$olid = 8;
$order = 6;
$item = 'X stuff';
$quantity = 1;
$price = '72.00';
$stmt->bindParam(1, $olid, SQLITE3_INTEGER);
$stmt->bindParam(2, $customer, SQLITE3_INTEGER);
$stmt->bindParam(3, $item, SQLITE3_TEXT);
$stmt->bindParam(4, $quantity, SQLITE3_INTEGER);
$stmt->bindParam(5, $price, SQLITE3_TEXT);
$stmt->execute();
$con->close();
?>

list_sqlite3.php:

<?php
$con = new SQLite3('/disk2/arne/art/dbx/test.db');
$stmt = $con->prepare('SELECT customer.id AS cid, name, phone, source ' .
                      'FROM customer JOIN customer_potential ON customer.id = customer_potential.id ' .
                      'ORDER BY cid');
$rs = $stmt->execute();
while($row = $rs->fetchArray()) {
    $name = $row['name'];
    $phone = $row['phone'];
    $source = $row['source'];
    echo "**future customer** $name, $phone, $source\r\n";
}
$stmt = $con->prepare('SELECT customer.id AS cid,name,address1,address2,phone,order_main.id AS oid,status,SUM(quantity*price) AS sumval ' .
                      'FROM customer JOIN customer_actual ON customer.id = customer_actual.id ' .
                      '              JOIN order_main ON customer.id = order_main.customer ' .
                      '              JOIN order_line ON order_main.id = order_line.order_main ' .
                      'GROUP BY customer.id,name,address1,address2,phone,order_main.id,status ' .
                      'ORDER BY cid, oid');
$rs = $stmt->execute();
$last_cid = 0;
while($row = $rs->fetchArray()) {
    $cid = $row['cid'];
    $name = $row['name'];
    $address1 = $row['address1'];
    $address2 = $row['address2'];
    $phone = $row['phone'];
    $oid = $row['oid'];
    $status = $row['status'];
    $sumval = $row['sumval'];
    if($cid != $last_cid) {
        echo "$name, $address1, $address2, $phone\r\n";
        $last_cid = $cid;
    }
    echo "  $oid, $status, $sumval\r\n";
}
$con->close();
?>

Run:

$ php list_sqlite3.php
$ php add_sqlite3.php
$ php list_sqlite3.php

Conclusion:

Database specific API's are usually available for C/C++ for all non-JVM databases.

But the API's tend to be a bit cumbersome to use and if alternatives (embedded SQL or standard API) are available then those are usually preferred.

Standard API:

Today most tech stacks has a standard database API:

Java
JDBC
.NET
ADO.NET
PHP
PDO
C/C++
CLI/ODBC

JDBC:

JDBC is the standard Java API for access to relational databases and available for Java and other JVM languages.

AddJDBC.java:

import java.math.BigDecimal;
import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.PreparedStatement;

public class AddJDBC {
    private static void perform(String clznam, String conurl, String usr, String pwd) throws Exception {
        System.out.println(conurl);
        Class.forName(clznam);
        Connection con = DriverManager.getConnection(conurl, usr, pwd);
        PreparedStatement c = con.prepareStatement("INSERT INTO customer VALUES(?,?,?)");
        c.setInt(1, 6);
        c.setString(2, "F company");
        c.setString(3, "6666-6666");
        c.executeUpdate();
        PreparedStatement ca = con.prepareStatement("INSERT INTO customer_actual VALUES(?,?,?,?,?)");
        ca.setInt(1, 6);
        ca.setString(2, "F road 6");
        ca.setString(3, "F town");
        ca.setString(4, "Mr. F");
        ca.setInt(5, 10);
        ca.executeUpdate();
        PreparedStatement o = con.prepareStatement("INSERT INTO order_main VALUES(?,?,?)");
        o.setInt(1, 6);
        o.setInt(2, 6);
        o.setString(3, "In progress");
        o.executeUpdate();
        PreparedStatement ol = con.prepareStatement("INSERT INTO order_line VALUES(?,?,?,?,?)");
        ol.setInt(1, 8);
        ol.setInt(2, 6);
        ol.setString(3, "X stuff");
        ol.setInt(4, 1);
        ol.setBigDecimal(5, new BigDecimal("72.00"));
        ol.executeUpdate();
        con.close();
    }
    public static void main(String[] args) throws Exception {
        if(args.length == 4) {
            perform(args[0], args[1], args[2], args[3]);
        } else if(args.length == 2) {
            perform(args[0], args[1], null, null);
        }
    }
}

ListJDBC.java:

import java.math.BigDecimal;
import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.Statement;
import java.sql.ResultSet;

public class ListJDBC {
    private static void perform(String clznam, String conurl, String usr, String pwd) throws Exception {
        System.out.println(conurl);
        Class.forName(clznam);
        Connection con = DriverManager.getConnection(conurl, usr, pwd);
        Statement stmt = con.createStatement();
        ResultSet rspot = stmt.executeQuery("SELECT customer.id AS cid, name, phone, source " +
                                            "FROM customer JOIN customer_potential ON customer.id = customer_potential.id " +
                                            "ORDER BY cid");
        while(rspot.next()) {
            String customer_name = rspot.getString(2);
            String customer_phone = rspot.getString(3);
            String customer_potential_source = rspot.getString(4);
            System.out.printf("**future customer** %s, %s, %s\n", customer_name, customer_phone, customer_potential_source);
        }
        rspot.close();
        ResultSet rsact = stmt.executeQuery("SELECT customer.id AS cid,name,address1,address2,phone,order_main.id AS oid,status,SUM(quantity*price) AS sumval " +
                                            "FROM customer JOIN customer_actual ON customer.id = customer_actual.id " +
                                            "              JOIN order_main ON customer.id = order_main.customer " +
                                            "              JOIN order_line ON order_main.id = order_line.order_main " +
                                            "GROUP BY customer.id,name,address1,address2,phone,order_main.id,status " +
                                            "ORDER BY cid, oid");
        int last_customer_id = 0;
        while(rsact.next()) {
            int customer_id = rsact.getInt(1);
            String customer_name = rsact.getString(2);
            String customer_actual_address1 = rsact.getString(3);
            String customer_actual_address2 = rsact.getString(4);
            String customer_phone = rsact.getString(5);
            int order_id = rsact.getInt(6);
            String order_status = rsact.getString(7);
            BigDecimal sumval = rsact.getBigDecimal(8);
            if(customer_id != last_customer_id) {
                System.out.printf("%s, %s, %s, %s\n", customer_name.trim(), customer_actual_address1.trim(), customer_actual_address2.trim(), customer_phone.trim());
                last_customer_id = customer_id;
            }
            System.out.printf("  %d, %s, %.2f\n", order_id, order_status.trim(), sumval);
        }
        rsact.close();
        stmt.close();
        con.close();
    }
    public static void main(String[] args) throws Exception {
        if(args.length == 4) {
            perform(args[0], args[1], args[2], args[3]);
        } else if(args.length == 2) {
            perform(args[0], args[1], null, null);
        }
    }
}

Build and run:

$ define/nolog java$filename_controls 8
$ define/nolog decc$efs_charset true
$ javac AddJDBC.java
$ javac ListJDBC.java
$ java -cp .:/javalib/rdbthinv5.jar "ListJDBC" "oracle.rdb.jdbc.rdbThin.Driver" "jdbc:rdbThin://localhost:1701/dka4:[rdb]test" "arne" "hemmeligt"
$ java -cp .:/javalib/rdbthinv5.jar "AddJDBC" "oracle.rdb.jdbc.rdbThin.Driver" "jdbc:rdbThin://localhost:1701/dka4:[rdb]test" "arne" "hemmeligt"
$ java -cp .:/javalib/rdbthinv5.jar "ListJDBC" "oracle.rdb.jdbc.rdbThin.Driver" "jdbc:rdbThin://localhost:1701/dka4:[rdb]test" "arne" "hemmeligt"
$ java -cp .:/javalib/mimjdbc3.jar "ListJDBC" "com.mimer.jdbc.Driver" "jdbc:mimer:/test" "SYSADM" "hemmeligt"
$ java -cp .:/javalib/mimjdbc3.jar "AddJDBC" "com.mimer.jdbc.Driver" "jdbc:mimer:/test" "SYSADM" "hemmeligt"
$ java -cp .:/javalib/mimjdbc3.jar "ListJDBC" "com.mimer.jdbc.Driver" "jdbc:mimer:/test" "SYSADM" "hemmeligt"
$ java -cp .:/javalib/mysql-connector-java-5_1_36-bin.jar "ListJDBC" "com.mysql.jdbc.Driver" "jdbc:mysql://localhost:3306/test" "root" "hemmeligt"
$ java -cp .:/javalib/mysql-connector-java-5_1_36-bin.jar "AddJDBC" "com.mysql.jdbc.Driver" "jdbc:mysql://localhost:3306/test" "root" "hemmeligt"
$ java -cp .:/javalib/mysql-connector-java-5_1_36-bin.jar "ListJDBC" "com.mysql.jdbc.Driver" "jdbc:mysql://localhost:3306/test" "root" "hemmeligt"
$ java -cp .:/javalib/sqlite-jdbc-3_14_1-vms.jar "ListJDBC" "org.sqlite.JDBC" "jdbc:sqlite:test.db"
$ java -cp .:/javalib/sqlite-jdbc-3_14_1-vms.jar "AddJDBC" "org.sqlite.JDBC" "jdbc:sqlite:test.db"
$ java -cp .:/javalib/sqlite-jdbc-3_14_1-vms.jar "ListJDBC" "org.sqlite.JDBC" "jdbc:sqlite:test.db"
$ java -cp .:/javalib/derby.jar "ListJDBC" "org.apache.derby.jdbc.EmbeddedDriver" "jdbc:derby:test"
$ java -cp .:/javalib/derby.jar "AddJDBC" "org.apache.derby.jdbc.EmbeddedDriver" "jdbc:derby:test"
$ java -cp .:/javalib/derby.jar "ListJDBC" "org.apache.derby.jdbc.EmbeddedDriver" "jdbc:derby:test"
$ java -cp .:/javalib/h2-1_2_147.jar "ListJDBC" "org.h2.Driver" "jdbc:h2:test;FILE_LOCK=FS"
$ java -cp .:/javalib/h2-1_2_147.jar "AddJDBC" "org.h2.Driver" "jdbc:h2:test;FILE_LOCK=FS"
$ java -cp .:/javalib/h2-1_2_147.jar "ListJDBC" "org.h2.Driver" "jdbc:h2:test;FILE_LOCK=FS"
$ java -cp .:/javalib/hsqldb.jar "ListJDBC" "org.hsqldb.jdbc.JDBCDriver" "jdbc:hsqldb:file:test"
$ java -cp .:/javalib/hsqldb.jar "AddJDBC" "org.hsqldb.jdbc.JDBCDriver" "jdbc:hsqldb:file:test"
$ java -cp .:/javalib/hsqldb.jar "ListJDBC" "org.hsqldb.jdbc.JDBCDriver" "jdbc:hsqldb:file:test"
$ exit

DB API 2.0:

DB API 2.0 is the standard Python database API.

add_dbapi.py:

from sys import argv

from rdb import dbapi2
#import pymysql
import sqlite3

def perform(con):
    c = con.cursor()
    c.execute('INSERT INTO customer VALUES(?,?,?)', (6, 'F company', '6666-6666'))
    c.execute('INSERT INTO customer_actual VALUES(?,?,?,?,?)', (6, 'F road 6', 'F town', 'Mr. F', 10))
    c.execute('INSERT INTO order_main VALUES(?,?,?)', (6, 6, 'In progress'))
    c.execute('INSERT INTO order_line VALUES(?,?,?,?,?)', (8, 6, 'X stuff', 1, 72.00));
    c.close()
    con.commit()
    con.close()
    
if argv[1] == 'Rdb':
    perform(dbapi2.connect(database='disk4:[rdb]test'))
#if argv[1] == 'MySQL':
#    perform(pymysql.connect(host='localhost',user='root',password='',db='test'))
if argv[1] == 'SQLite':
    perform(sqlite3.connect('test.db'))

list_dbapi.py:

from sys import argv

from rdb import dbapi2
#import pymysql
import sqlite3

def perform(con):
    c = con.cursor()
    c.execute('SELECT customer.id AS cid, name, phone, source ' + \
              'FROM customer JOIN customer_potential ON customer.id = customer_potential.id ' + \
              'ORDER BY cid')
    data = c.fetchall()
    c.close()
    for row in data:
        print('**future customer** %s, %s, %s' % (row[1], row[2], row[3]))
    c = con.cursor()
    c.execute('SELECT customer.id AS cid,name,address1,address2,phone,order_main.id AS oid,status,SUM(quantity*price) AS sumval ' + \
              'FROM customer JOIN customer_actual ON customer.id = customer_actual.id ' + \
              '              JOIN order_main ON customer.id = order_main.customer ' + \
              '              JOIN order_line ON order_main.id = order_line.order_main ' + \
              'GROUP BY customer.id,name,address1,address2,phone,order_main.id,status ' + \
              'ORDER BY cid, oid')
    data = c.fetchall()
    c.close()
    last_customer_id = 0
    for row in data:
        if row[0] != last_customer_id:
            print('%s, %s, %s, %s' % (row[1], row[2], row[3], row[4]))
            last_customer_id = row[0]
        print('  %s, %s, %s' % (row[5], row[6], row[7]))
    con.close()
    
if argv[1] == 'Rdb':
    perform(dbapi2.connect(database='disk4:[rdb]test'))
#if argv[1] == 'MySQL':
#    perform(pymysql.connect(host='localhost',user='root',password='',db='test'))
if argv[1] == 'SQLite':
    perform(sqlite3.connect('test.db'))

Run:

$ python list_dbapi.py "Rdb"
$ python add_dbapi.py "Rdb"
$ python list_dbapi.py "Rdb"
$! python list_dbapi.py "MySQL"
$! python add_dbapi.py "MySQL"
$! python list_dbapi.py "MySQL"
$ python list_dbapi.py "SQLite"
$ python add_dbapi.py "SQLite"
$ python list_dbapi.py "SQLite"

add_jdbc.py:

from sys import argv

from com.ziclix.python.sql import zxJDBC

def perform(con):
    c = con.cursor()
    c.execute('INSERT INTO customer VALUES(?,?,?)', (6, 'F company', '6666-6666'))
    c.execute('INSERT INTO customer_actual VALUES(?,?,?,?,?)', (6, 'F road 6', 'F town', 'Mr. F', 10))
    c.execute('INSERT INTO order_main VALUES(?,?,?)', (6, 6, 'In progress'))
    c.execute('INSERT INTO order_line VALUES(?,?,?,?,?)', (8, 6, 'X stuff', 1, 72.00));
    c.close()
    con.commit()
    con.close()
    
def performj(clznam, conurl, usr, pwd):
    print(conurl)
    perform(zxJDBC.connect(conurl, usr, pwd, clznam))

if len(argv) == 5:
    performj(argv[1], argv[2], argv[3], argv[4])
if len(argv) == 3:
    performj(argv[1], argv[2], None, None)

list_jdbc.py:

from sys import argv

from com.ziclix.python.sql import zxJDBC

def perform(con):
    c = con.cursor()
    c.execute('SELECT customer.id AS cid, name, phone, source ' + \
              'FROM customer JOIN customer_potential ON customer.id = customer_potential.id ' + \
              'ORDER BY cid')
    data = c.fetchall()
    c.close()
    for row in data:
        print('**future customer** %s, %s, %s' % (row[1], row[2], row[3]))
    c = con.cursor()
    c.execute('SELECT customer.id AS cid,name,address1,address2,phone,order_main.id AS oid,status,SUM(quantity*price) AS sumval ' + \
              'FROM customer JOIN customer_actual ON customer.id = customer_actual.id ' + \
              '              JOIN order_main ON customer.id = order_main.customer ' + \
              '              JOIN order_line ON order_main.id = order_line.order_main ' + \
              'GROUP BY customer.id,name,address1,address2,phone,order_main.id,status ' + \
              'ORDER BY cid, oid')
    data = c.fetchall()
    c.close()
    last_customer_id = 0
    for row in data:
        if row[0] != last_customer_id:
            print('%s, %s, %s, %s' % (row[1], row[2], row[3], row[4]))
            last_customer_id = row[0]
        print('  %s, %s, %.2f' % (row[5], row[6], row[7]))
    con.commit()
    con.close()
    
def performj(clznam, conurl, usr, pwd):
    print(conurl)
    perform(zxJDBC.connect(conurl, usr, pwd, clznam))

if len(argv) == 5:
    performj(argv[1], argv[2], argv[3], argv[4])
if len(argv) == 3:
    performj(argv[1], argv[2], None, None)

Run:

$ define/nolog java$filename_controls 8
$ define/nolog decc$efs_charset true
$ define/nolog jython_libs "/javalib/zxjdbc.jar:/javalib/rdbthinv5.jar"
$ jython list_jdbc.py """oracle.rdb.jdbc.rdbThin.Driver""" """jdbc:rdbThin://localhost:1701/dka4:[rdb]test""" """arne""" """hemmeligt"""
$ jython add_jdbc.py """oracle.rdb.jdbc.rdbThin.Driver""" """jdbc:rdbThin://localhost:1701/dka4:[rdb]test""" """arne""" """hemmeligt"""
$ jython list_jdbc.py """oracle.rdb.jdbc.rdbThin.Driver""" """jdbc:rdbThin://localhost:1701/dka4:[rdb]test""" """arne""" """hemmeligt"""
$ define/nolog jython_libs "/javalib/zxjdbc.jar:/javalib/mimjdbc3.jar"
$ jython list_jdbc.py """com.mimer.jdbc.Driver""" """jdbc:mimer:/test""" """SYSADM""" """hemmeligt"""
$ jython add_jdbc.py """com.mimer.jdbc.Driver""" """jdbc:mimer:/test""" """SYSADM""" """hemmeligt"""
$ jython list_jdbc.py """com.mimer.jdbc.Driver""" """jdbc:mimer:/test""" """SYSADM""" """hemmeligt"""
$ define/nolog jython_libs "/javalib/zxjdbc.jar:/javalib/mysql-connector-java-5_1_36-bin.jar"
$ jython list_jdbc.py """com.mysql.jdbc.Driver""" """jdbc:mysql://localhost:3306/test""" """root""" """hemmeligt"""
$ jython add_jdbc.py """com.mysql.jdbc.Driver""" """jdbc:mysql://localhost:3306/test""" """root""" """hemmeligt"""
$ jython list_jdbc.py """com.mysql.jdbc.Driver""" """jdbc:mysql://localhost:3306/test""" """root""" """hemmeligt"""
$!!!! define/nolog jython_libs "/javalib/zxjdbc.jar:/javalib/sqlite-jdbc-3_14_1-vms.jar"
$!!!! jython list_jdbc.py """org.sqlite.JDBC""" """jdbc:sqlite:test.db"""
$!!!! jython add_jdbc.py """org.sqlite.JDBC""" """jdbc:sqlite:test.db"""
$!!!! jython list_jdbc.py """org.sqlite.JDBC""" """jdbc:sqlite:test.db"""
$ define/nolog jython_libs "/javalib/zxjdbc.jar:/javalib/derby.jar
$ jython list_jdbc.py """org.apache.derby.jdbc.EmbeddedDriver""" """jdbc:derby:test"""
$ jython add_jdbc.py """org.apache.derby.jdbc.EmbeddedDriver""" """jdbc:derby:test"""
$ jython list_jdbc.py """org.apache.derby.jdbc.EmbeddedDriver""" """jdbc:derby:test"""
$ define/nolog jython_libs "/javalib/zxjdbc.jar:/javalib/h2-1_2_147.jar
$ jython list_jdbc.py """org.h2.Driver""" """jdbc:h2:test;FILE_LOCK=FS"""
$ jython add_jdbc.py """org.h2.Driver""" """jdbc:h2:test;FILE_LOCK=FS"""
$ jython list_jdbc.py """org.h2.Driver""" """jdbc:h2:test;FILE_LOCK=FS"""
$ define/nolog jython_libs "/javalib/zxjdbc.jar:/javalib/hsqldb.jar
$ jython list_jdbc.py """org.hsqldb.jdbc.JDBCDriver""" """jdbc:hsqldb:file:test""" """sa""" """"""
$ jython add_jdbc.py """org.hsqldb.jdbc.JDBCDriver""" """jdbc:hsqldb:file:test""" """sa""" """"""
$ jython list_jdbc.py """org.hsqldb.jdbc.JDBCDriver""" """jdbc:hsqldb:file:test""" """sa""" """"""

SQLite is outcommented because the combination of zxJDBC (DB API 2.0 provider for JDBC), SQLIte JDBC driver and SQLite I used to test with does not like the second query in list_jdbc.py even though it works with the same driver and database from Java. It seems to relate to usage of the aggregating function. In general Jython and zxJDBC work fine with SQLite.

PDO:

PDO is the standard PHP database API.

add_pdo.php:

<?php
if($argc == 2) {
    $con = new PDO($argv[1]);
}
if($argc == 4) {
    $con = new PDO($argv[1], $argv[2], $argv[3]);
}
$stmt = $con->prepare('INSERT INTO customer VALUES(?,?,?)');
$cid = 6;
$name = 'F company';
$phone = '6666-6666';
$stmt->execute(array($cid, $name, $phone));
$stmt = $con->prepare('INSERT INTO customer_actual VALUES(?,?,?,?,?)');
$cid = 6;
$address1 = 'F road 6';
$address2 = 'F town';
$contact = 'Mr. F';
$discount = 10;
$stmt->execute(array($cid, $address1, $address2, $contact, $discount));
$stmt = $con->prepare('INSERT INTO order_main VALUES(?,?,?)');
$oid = 6;
$customer = 6;
$status = 'In progress';
$stmt->execute(array($oid, $customer, $status));
$stmt = $con->prepare('INSERT INTO order_line VALUES(?,?,?,?,?)');
$olid = 8;
$order = 6;
$item = 'X stuff';
$quantity = 1;
$price = '72.00';
$stmt->execute(array($olid, $order, $item, $quantity, $price));
?>

list_pdo.php:

<?php
if($argc == 2) {
    $con = new PDO($argv[1]);
}
if($argc == 4) {
    $con = new PDO($argv[1], $argv[2], $argv[3]);
}
$stmt = $con->prepare('SELECT customer.id AS cid, name, phone, source ' .
                      'FROM customer JOIN customer_potential ON customer.id = customer_potential.id ' .
                      'ORDER BY cid');
$stmt->execute();
while($row = $stmt->fetch()) {
    $name = $row['name'];
    $phone = $row['phone'];
    $source = $row['source'];
    echo "**future customer** $name, $phone, $source\r\n";
}
$stmt = $con->prepare('SELECT customer.id AS cid,name,address1,address2,phone,order_main.id AS oid,status,SUM(quantity*price) AS sumval ' .
                      'FROM customer JOIN customer_actual ON customer.id = customer_actual.id ' .
                      '              JOIN order_main ON customer.id = order_main.customer ' .
                      '              JOIN order_line ON order_main.id = order_line.order_main ' .
                      'GROUP BY customer.id,name,address1,address2,phone,order_main.id,status ' .
                      'ORDER BY cid, oid');
$stmt->execute();
$last_cid = 0;
while($row = $stmt->fetch()) {
    $cid = $row['cid'];
    $name = $row['name'];
    $address1 = $row['address1'];
    $address2 = $row['address2'];
    $phone = $row['phone'];
    $oid = $row['oid'];
    $status = $row['status'];
    $sumval = $row['sumval'];
    if($cid != $last_cid) {
        echo "$name, $address1, $address2, $phone\r\n";
        $last_cid = $cid;
    }
    echo "  $oid, $status, $sumval\r\n";
}
?>

Run:

$ php list_pdo.php mysql:host=localhost;dbname=test root "hemmeligt"
$ php add_pdo.php mysql:host=localhost;dbname=test root "hemmeligt"
$ php list_pdo.php mysql:host=localhost;dbname=test root "hemmeligt"
$ php list_pdo.php sqlite:/disk2/arne/art/dbx/test.db
$ php add_pdo.php sqlite:/disk2/arne/art/dbx/test.db
$ php list_pdo.php sqlite:/disk2/arne/art/dbx/test.db

Conclusion:

Standard API's are the preferred option for non-native languages.

And the API's are usually much simpler than the C/C++ API's.

Portability:

Problem:

There are two aspects of application portability across databases:

Differences in API are pretty obvious.

    stmt = mysql_stmt_init(con);
    stat = mysql_stmt_prepare(stmt, sqlstr, strlen(sqlstr));
    stat = mysql_stmt_execute(stmt);

and:

    stat = sqlite3_prepare(con, sqlstr, strlen(sqlstr), &sel, NULL);
    stat = sqlite3_step(stmt);

are totally different code.

But even if the API used is the same, then different databases can have different SQL dialect and they can handle things differently.

ResultSet rs = stmt.executeQuery("SELECT TOP 3 * FROM sometable");

and:

ResultSet rs = stmt.executeQuery("SELECT * FROM sometable LIMIT 3");

both use the same API, but the SQL is not portable - the first works with MS SQLServer etc. - the second works with MySQL etc..

Known portability issues are:

So a standard database API make it possible to write portable database access code - it does not guarantee that database access code will be portable.

JDBC solution:

JDBC provide two mechanisms to help navigate around non-portable SQL:

This enable developers to do "defensive programming" aka write code that will work with most/all databases.

That sounds a bit theoretical, but let us see some practical examples.

The examples are not quite realistic but should be good enough to illustrate problems that are seen in the real world.

Problem: Find rows where text field match abbreviation.

Database specific SQL in various SQL dialects:

WHERE CONCAT(SUBSTRING(term FROM 1 FOR 1), UPPER(SUBSTRING(term FROM LENGTH(term) FOR 1))) = ?
WHERE CONCAT(LEFT(term, 1), UPPER(RIGHT(term, 1))) = ?
WHERE SUBSTR(term, 1, 1) || UPPER(SUBSTR(term, LENGTH(term), 1)) = ?
WHERE SUBSTRING(term FROM 1 FOR 1) || UPPER(SUBSTRING(term FROM CHAR_LENGTH(term) FOR 1)) = ?

JDBC logic:

if driver supports CONCAT, LEFT, RIGHT and UCASE escape functions {
    use "WHERE {fn concat({fn left(term,1)}, {fn ucase({fn right(term,1)})})} = ?"
} else {
    fallback to read all rows and compare client side
}

JDBC code:

    public void findAbbreviation(Connection con) throws SQLException {
        String s = "IN";
        if(Arrays.asList(con.getMetaData().getStringFunctions().split(",")).containsAll(Arrays.asList("CONCAT", "LEFT", "RIGHT", "UCASE"))) {
            System.out.println("String (JDBC):");
            PreparedStatement pstmt = con.prepareStatement("SELECT id,term FROM abbr " +
                                                           "WHERE {fn concat({fn left(term,1)}, {fn ucase({fn right(term,1)})})} = ?");
            pstmt.setString(1, s);
            ResultSet rs = pstmt.executeQuery();
            while(rs.next()) {
                int id = rs.getInt(1);
                String term = rs.getString(2);
                System.out.printf("%d : %s\n", id, term);
            }
            rs.close();
            pstmt.close();
        } else {
            System.out.println("String (JDBC fallback client side):");
            Statement stmt = con.createStatement();
            ResultSet rs = stmt.executeQuery("SELECT id,term FROM abbr");
            while(rs.next()) {
                int id = rs.getInt(1);
                String term = rs.getString(2);
                String term2 = term.substring(0, 1) + term.substring(term.length() - 1).toUpperCase();
                if(term2.equals(s)) {
                    System.out.printf("%d : %s\n", id, term);
                }
            }
            rs.close();
            stmt.close();
        }
    }

Doing it client side will cost performance wise.

Problem: Find rows where timestamp field is in the future.

Database specific SQL in various SQL dialects:

WHERE dt > CURRENT_TIMESTAMP
WHERE dt > NOW()
WHERE dt > DATETIME()
WHERE dt > LOCALTIMESTAMP

JDBC logic:

if driver supports NOW escape function }
    use "WHERE dt > {fn now()}"
} else {
    fallback to send and use client time
}

JDBC code:

    public void later(Connection con) throws SQLException {
        if(Arrays.asList(con.getMetaData().getTimeDateFunctions().split(",")).contains("NOW")) {
            System.out.println("Later (JDBC):");
            Statement stmt = con.createStatement();
            ResultSet rs = stmt.executeQuery("SELECT id,dt FROM ts WHERE dt > {fn now()}");
            while(rs.next()) {
                int id = rs.getInt(1);
                if(rs.getObject(2) instanceof Timestamp) {
                    Timestamp dt = rs.getTimestamp(2);
                    System.out.printf("%d %s\n", id, dt);
                } else if(rs.getObject(2) instanceof String) {
                    String dt = rs.getString(2);
                    System.out.printf("%d %s\n", id, dt);
                } else {
                    System.out.println("Error: Unknown type");
                }
            }
            rs.close();
            stmt.close();
        } else {
            System.out.println("Later (JDBC fallback client side):");
            PreparedStatement pstmt = con.prepareStatement("SELECT id,dt FROM ts WHERE dt > ?");
            pstmt.setTimestamp(1, new Timestamp(System.currentTimeMillis()));
            ResultSet rs = pstmt.executeQuery();
            while(rs.next()) {
                int id = rs.getInt(1);
                if(rs.getObject(2) instanceof Timestamp) {
                    Timestamp dt = rs.getTimestamp(2);
                    System.out.printf("%d %s\n", id, dt);
                } else if(rs.getObject(2) instanceof String) {
                    String dt = rs.getString(2);
                    System.out.printf("%d %s\n", id, dt);
                } else {
                    System.out.println("Error: Unknown type");
                }
            }
            rs.close();
            pstmt.close();
        }
    }

Using client side time can give problems if client and server are in different time zones.

Problem: Find rows where timestamp field is in current month.

Database specific SQL in various SQL dialects:

WHERE EXTRACT(MONTH FROM dt) = EXTRACT(MONTH FROM CURRENT_TIMESTAMP) AND EXTRACT(YEAR FROM dt) = EXTRACT(YEAR FROM CURRENT_TIMESTAMP)
WHERE MONTH(dt) = MONTH(NOW()) AND YEAR(dt) = YEAR(NOW())
WHERE STRFTIME('%m',dt) = STRFTIME('%m',DATE()) AND STRFTIME('%Y',dt) = STRFTIME('%Y', DATE())
WHERE EXTRACT(MONTH FROM dt) = EXTRACT(MONTH FROM LOCALTIMESTAMP) AND EXTRACT(YEAR FROM dt) = EXTRACT(YEAR FROM LOCALTIMESTAMP)
WHERE MONTH(dt) = MONTH(CURRENT_TIMESTAMP) AND YEAR(dt) = YEAR(CURRENT_TIMESTAMP)

JDBC logic:

if driver supports NOW, MONTH and YEAR escape functions {
    use "WHERE {fn month(dt)} = {fn month({fn now()})} AND {fn year(dt)} = {fn year({fn now()})}"
} else {
    fallback to have client side provide timestamp interval
}

JDBC code:

    public void thisMonth(Connection con) throws SQLException {
        if(Arrays.asList(con.getMetaData().getTimeDateFunctions().split(",")).containsAll(Arrays.asList("NOW","MONTH","YEAR"))) {
            System.out.println("This month(JDBC):");
            Statement stmt = con.createStatement();
            ResultSet rs = stmt.executeQuery("SELECT id,dt FROM ts WHERE {fn month(dt)} = {fn month({fn now()})} AND {fn year(dt)} = {fn year({fn now()})}");
            while(rs.next()) {
                int id = rs.getInt(1);
                if(rs.getObject(2) instanceof Timestamp) {
                    Timestamp dt = rs.getTimestamp(2);
                    System.out.printf("%d %s\n", id, dt);
                } else if(rs.getObject(2) instanceof String) {
                    String dt = rs.getString(2);
                    System.out.printf("%d %s\n", id, dt);
                } else {
                    System.out.println("Error: Unknown type");
                }
            }
            rs.close();
            stmt.close();
        } else {
            System.out.println("This month (JDBC fallback client side):");
            PreparedStatement pstmt = con.prepareStatement("SELECT id,dt FROM ts WHERE dt BETWEEN ? AND ?");
            Calendar start = new GregorianCalendar();
            start.set(Calendar.DAY_OF_MONTH, 1);
            start.set(Calendar.HOUR_OF_DAY, 0);
            start.set(Calendar.MINUTE, 0);
            start.set(Calendar.SECOND, 0);
            pstmt.setTimestamp(1, new Timestamp(start.getTime().getTime()));
            Calendar end = new GregorianCalendar();
            pstmt.setTimestamp(2, new Timestamp(end.getTime().getTime()));
            ResultSet rs = pstmt.executeQuery();
            while(rs.next()) {
                int id = rs.getInt(1);
                if(rs.getObject(2) instanceof Timestamp) {
                    Timestamp dt = rs.getTimestamp(2);
                    System.out.printf("%d %s\n", id, dt);
                } else if(rs.getObject(2) instanceof String) {
                    String dt = rs.getString(2);
                    System.out.printf("%d %s\n", id, dt);
                } else {
                    System.out.println("Error: Unknown type");
                }
            }
            rs.close();
            pstmt.close();
        }
    }

Using client side time can give problems if client and server are in different time zones.

Problem: find auto generated id's.

Database specific SQL in various SQL dialects:

SELECT aid.CURRVAL FROM aid
SELECT LAST_INSERT_ID()
SELECT LAST_INSERT_ROWID()
SELECT (CURRENT_VALUE OF aid_id) FROM aid
SELECT IDENTITY_VAL_LOCAL() FROM aid
SELECT SCOPE_IDENTITY()
SELECT IDENTITY() FROM aid

JDBC logic:

if driver supports Statement getGeneratedKeys {
    just INSERT and get ResultSet from Statement with generated keys
} else {
    INSERT and SELECT MAX(id) in transaction with transaction isolation level serializable
}

JDBC code:

    public void getAutoIds(Connection con) throws SQLException {
        DatabaseMetaData md = con.getMetaData();
        if(md.supportsGetGeneratedKeys()) {
            System.out.println("Id (JDBC):");
            PreparedStatement pstmt = con.prepareStatement("INSERT INTO aid (val) VALUES(?)", PreparedStatement.RETURN_GENERATED_KEYS);
            for(int i = 0; i < 3; i++) {
                pstmt.setString(1, "Bla bla bla");
                pstmt.executeUpdate();
                ResultSet rs = pstmt.getGeneratedKeys();
                if(rs.next()) {
                    if(rs.getObject(1) instanceof Integer) {
                        System.out.println(rs.getInt(1));
                    } else if(rs.getObject(1) instanceof Long) {
                        System.out.println(rs.getLong(1));
                    } else if(rs.getObject(1) instanceof BigDecimal) {
                        System.out.println(rs.getBigDecimal(1));
                    } else {
                        System.out.println("Error: Unknown type");
                    }
                } else {
                    System.out.println("Error: No value");
                }
                rs.close();
            }
            pstmt.close();
        } else if(md.supportsTransactionIsolationLevel(Connection.TRANSACTION_SERIALIZABLE)) {
            System.out.println("Id (JDBC fallback to transaction):");
            boolean old_status = con.getAutoCommit();
            int old_level = con.getTransactionIsolation();
            con.setAutoCommit(false);
            con.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
            Statement stmt = con.createStatement();
            PreparedStatement pstmt = con.prepareStatement("INSERT INTO aid (val) VALUES(?)");
            for(int i = 0; i < 3; i++) {
                pstmt.setString(1, "Bla bla bla");
                pstmt.executeUpdate();
                ResultSet rs = stmt.executeQuery("SELECT MAX(id) FROM aid");
                if(rs.next()) {
                    System.out.println(rs.getBigDecimal(1));
                } else {
                    throw new RuntimeException("Ooops");
                }
                rs.close();
                con.commit();
            }
            pstmt.close();
            stmt.close();
            con.setTransactionIsolation(old_level);
            con.setAutoCommit(old_status);
        } else {
            System.out.println("*Statement.getGeneratedKeys() not supported and transacion isolation level serializzable not supported");
        }
    }
Problem: limit number of rows returned from query.

Database specific SQL in various SQL dialects:

FETCH FIRST 3 ROW ONLY
LIMIT 3
-- not possible

JDBC logic:

try {
    use "{limit 3}"
} catch database exception {
    fallback to just query and stop reading after 3 rows
}

JDBC code:

    public void limit(Connection con) throws SQLException {
        Statement stmt = con.createStatement();
        try {
            ResultSet rs = stmt.executeQuery("SELECT id FROM customer {limit 3}");
            System.out.println("Limit (JDBC):");
            while(rs.next()) {
                int id = rs.getInt(1);
                System.out.printf("%d\n", id);
            }
            rs.close();
        } catch(SQLException ex) {
            ResultSet rs = stmt.executeQuery("SELECT id FROM customer");
            System.out.println("Limit (JDBC fallback client side):");
            int n = 0;
            while(rs.next() && n < 3) {
                int id = rs.getInt(1);
                System.out.printf("%d\n", id);
                n++;
            }
            rs.close();
        }
        stmt.close();
    }

The fallback of just stop reading will put more load on the database.

Problem: read value from database, modify value and write value back to database in a way safe for concurrent access.

Database specific SQL in various SQL dialects:

JDBC logic:

if driver supports SELECT FOR UPDATE and transactions {
    enable transactions at lowest transaction isolation level
    SELECT FOR UPDATE and UPDATE in transaction with retry logic
} else if driver supports transaction isolation level serializable {
    enable transactions at transaction isolation level serializable
    SELECT and UPDATE in transaction with retry logic
} else {
    problem
}

For more details about why this logic see here here.

JDBC code:

    public static void counter(Connection con) throws SQLException {
        DatabaseMetaData md = con.getMetaData();
        boolean retry;
        do {
            retry = false;
            try {
                if(md.supportsSelectForUpdate() && md.supportsTransactions()) {
                    int level;
                    if(md.supportsTransactionIsolationLevel(Connection.TRANSACTION_READ_UNCOMMITTED)) {
                        level = Connection.TRANSACTION_READ_UNCOMMITTED;
                    } else if(md.supportsTransactionIsolationLevel(Connection.TRANSACTION_READ_COMMITTED)) {
                        level = Connection.TRANSACTION_READ_COMMITTED;
                    } else if(md.supportsTransactionIsolationLevel(Connection.TRANSACTION_REPEATABLE_READ)) {
                        level = Connection.TRANSACTION_REPEATABLE_READ;
                    } else if(md.supportsTransactionIsolationLevel(Connection.TRANSACTION_SERIALIZABLE)) {
                        level = Connection.TRANSACTION_SERIALIZABLE;
                    } else {
                        throw new RuntimeException("No suitable transaction isolation level");
                    }
                    System.out.printf("Counter (JDBC) - SELECT FOR UPDATE (%s)\n", til.get(level));
                    boolean old_status = con.getAutoCommit();
                    int old_level = con.getTransactionIsolation();
                    con.setAutoCommit(false);
                    con.setTransactionIsolation(level);
                    Statement stmt = con.createStatement();
                    ResultSet rs = stmt.executeQuery("SELECT val FROM counter WHERE id = 1");
                    if(!rs.next()) throw new RuntimeException("No rows in counter table");
                    int val = rs.getInt(1);
                    rs.close();
                    stmt.close();
                    val++;
                    PreparedStatement pstmt = con.prepareStatement("UPDATE counter SET val = ? WHERE id = 1");
                    pstmt.setInt(1, val);
                    pstmt.executeUpdate();
                    pstmt.close();
                    con.commit();
                    con.setTransactionIsolation(old_level);
                    con.setAutoCommit(old_status);
                } else if(md.supportsTransactionIsolationLevel(Connection.TRANSACTION_SERIALIZABLE)) {
                    System.out.println("Counter (JDBC fallback to transaction) - transaction isolation level serializable");
                    boolean old_status = con.getAutoCommit();
                    int old_level = con.getTransactionIsolation();
                    con.setAutoCommit(false);
                    con.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
                    Statement stmt = con.createStatement();
                    ResultSet rs = stmt.executeQuery("SELECT val FROM counter WHERE id = 1");
                    if(!rs.next()) throw new RuntimeException("No rows in counter table");
                    int val = rs.getInt(1);
                    rs.close();
                    stmt.close();
                    val++;
                    PreparedStatement pstmt = con.prepareStatement("UPDATE counter SET val = ? WHERE id = 1");
                    pstmt.setInt(1, val);
                    pstmt.executeUpdate();
                    pstmt.close();
                    con.commit();
                    con.setTransactionIsolation(old_level);
                    con.setAutoCommit(old_status);
                } else {
                    System.out.println("Not possible");
                }
            } catch(SQLException ex) {
                retry = true;
            }
        } while(retry);
    }

Does writing database independent code require more code than writing code targetting only one database?

Yes. There is no free lunch. Staying database independent require some effort.

Note though that it requires less code to write database independent code than to write 5 different sets of code for 5 different databases.

So does it work?

Reasonable well!

string functions date and time functions get generated ids limit atomic counter update
Works with JDBC mechanisms
(no need for database specific solution)
Rdb
MySQL
Mimer
HSQLDB
Rdb
MySQL
Mimer
HSQLDB
MySQL
SQLite
H2
HSQLDB
Rdb
Derby
H2
Rdb
MySQL
Mimer
Derby
H2
HSQLDB
Works with JDBC fallback/workaround
(works but a database specific solution may be preferable)
SQLite
Derby
H2
SQLite
Derby
H2
Rdb
Mimer
Derby
MySQL
SQLite
Mimer
HSQLDB
SQLite
Database specific solution required (none) (none) (none) (none) (none)

A different question is whether database independence is needed.

It is usually a long and expensive process to switch database, so it does not happen that often.

But fundamental changes in IT strategy happen for various reasons (new CIO, mergers/acquisitions/spinoffs, changes in vendors strategy or pricing policy etc.).

And history show that many companies end up switching database even though they expected never to have to.

So it can be a very good investment to keep ones application code database independent.

ORM:

Using an ORM enables the application to work on an object view of the data instead of on a relational model.

And typical will ORM usage mean no SQL usage. Updates and simple queries loading just one object does typical not require any query langauge usage and complex queries typical use another query language than SQL.

Note that usage of ORM obviously only makes sense when an object view of data makes sense. In some cases the relational model makes more sense.

JPA:

JPA is the standard ORM API for Java.

The most common JPA implementation is Hibernate, but other exist like OpenJPA and EclipseLink.

JPA use JPQL as query language.

JPA can be used not only by Java but also by other JVM languages like Jython, Groovy, Kotlin, Scala etc..

With JPA the database connections are defined in META-INF/persistence.xml:

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
             version="2.0">
   <persistence-unit name="Rdb">
      <provider>org.hibernate.ejb.HibernatePersistence</provider>
      <class>Customer</class>
      <class>CustomerActual</class>
      <class>CustomerPotential</class>
      <class>Order</class>
      <class>OrderLine</class>
      <exclude-unlisted-classes/>
      <properties>
          <property name="hibernate.show_sql" value="false"/>
          <property name="hibernate.connection.driver_class" value="oracle.rdb.jdbc.rdbThin.Driver"/>
          <property name="hibernate.connection.url" value="jdbc:rdbThin://localhost:1701/dka4:[rdb]test"/>
          <property name="hibernate.connection.username" value="arne"/>
          <property name="hibernate.connection.password" value="hemmeligt"/>
          <property name="hibernate.connection.pool_size" value="5"/>
          <property name="hibernate.dialect" value="org.hibernate.dialect.RdbDialect"/>
      </properties>
   </persistence-unit>
   <persistence-unit name="Mimer">
      <provider>org.hibernate.ejb.HibernatePersistence</provider>
      <class>Customer</class>
      <class>CustomerActual</class>
      <class>CustomerPotential</class>
      <class>Order</class>
      <class>OrderLine</class>
      <exclude-unlisted-classes/>
      <properties>
          <property name="hibernate.show_sql" value="false"/>
          <property name="hibernate.connection.driver_class" value="com.mimer.jdbc.Driver"/>
          <property name="hibernate.connection.url" value="jdbc:mimer:/test"/>
          <property name="hibernate.connection.username" value="SYSADM"/>
          <property name="hibernate.connection.password" value="hemmeligt"/>
          <property name="hibernate.connection.pool_size" value="5"/>
          <property name="hibernate.dialect" value="org.hibernate.dialect.MimerSQLDialect"/>
      </properties>
   </persistence-unit>
   <persistence-unit name="MySQL">
      <provider>org.hibernate.ejb.HibernatePersistence</provider>
      <class>Customer</class>
      <class>CustomerActual</class>
      <class>CustomerPotential</class>
      <class>Order</class>
      <class>OrderLine</class>
      <exclude-unlisted-classes/>
      <properties>
          <property name="hibernate.show_sql" value="false"/>
          <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
          <property name="hibernate.connection.url" value="jdbc:mysql://localhost/Test"/>
          <property name="hibernate.connection.username" value="root"/>
          <property name="hibernate.connection.password" value="hemmeligt"/>
          <property name="hibernate.connection.pool_size" value="5"/>
      </properties>
   </persistence-unit>
   <persistence-unit name="SQLite">
      <provider>org.hibernate.ejb.HibernatePersistence</provider>
      <class>Customer</class>
      <class>CustomerActual</class>
      <class>CustomerPotential</class>
      <class>Order</class>
      <class>OrderLine</class>
      <exclude-unlisted-classes/>
      <properties>
          <property name="hibernate.show_sql" value="false"/>
          <property name="hibernate.connection.driver_class" value="org.sqlite.JDBC"/>
          <property name="hibernate.connection.url" value="jdbc:sqlite:test.db"/>
          <property name="hibernate.connection.pool_size" value="5"/>
          <property name="hibernate.dialect" value="org.hibernate.dialect.SQLiteDialect"/>          
      </properties>
   </persistence-unit>
   <persistence-unit name="Derby">
      <provider>org.hibernate.ejb.HibernatePersistence</provider>
      <class>Customer</class>
      <class>CustomerActual</class>
      <class>CustomerPotential</class>
      <class>Order</class>
      <class>OrderLine</class>
      <exclude-unlisted-classes/>
      <properties>
          <property name="hibernate.show_sql" value="false"/>
          <property name="hibernate.connection.driver_class" value="org.apache.derby.jdbc.EmbeddedDriver"/>
          <property name="hibernate.connection.url" value="jdbc:derby:test"/>
          <property name="hibernate.connection.pool_size" value="5"/>
          <property name="hibernate.dialect" value="org.hibernate.dialect.DerbyDialect"/>
      </properties>
   </persistence-unit>
   <persistence-unit name="H2">
      <provider>org.hibernate.ejb.HibernatePersistence</provider>
      <class>Customer</class>
      <class>CustomerActual</class>
      <class>CustomerPotential</class>
      <class>Order</class>
      <class>OrderLine</class>
      <exclude-unlisted-classes/>
      <properties>
          <property name="hibernate.show_sql" value="false"/>
          <property name="hibernate.connection.driver_class" value="org.h2.Driver"/>
          <property name="hibernate.connection.url" value="jdbc:h2:test;FILE_LOCK=FS"/>
          <property name="hibernate.connection.pool_size" value="5"/>
          <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
      </properties>
   </persistence-unit>
   <persistence-unit name="HSQLDB">
      <provider>org.hibernate.ejb.HibernatePersistence</provider>
      <class>Customer</class>
      <class>CustomerActual</class>
      <class>CustomerPotential</class>
      <class>Order</class>
      <class>OrderLine</class>
      <exclude-unlisted-classes/>
      <properties>
          <property name="hibernate.show_sql" value="false"/>
          <property name="hibernate.connection.driver_class" value="org.hsqldb.jdbc.JDBCDriver" />
          <property name="hibernate.connection.url" value="jdbc:hsqldb:file:test"/>
          <property name="hibernate.connection.pool_size" value="5"/>
          <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
      </properties>
   </persistence-unit>
</persistence>

Most of it is rather self-explanatory, but two items is worth mentioning.

<property name="hibernate.show_sql" value="false"/>

If changed to true then Hibernate will log all the executed SQL statements. This is great for verifying that Hibernate is actually doing what you are expecting it to do.

<property name="hibernate.dialect" value="xxx.xxx.xxx.XxxxDialect"/>

This tells Hibernate which SQL dialect to use, which enables Hibernate to generate different SQL for different databases. Note that if not specified then Hibernate tries to auto-detect.

Customer.java:

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Table;

@Entity
@Table(name="customer")
@Inheritance(strategy=InheritanceType.JOINED)
public class Customer {
    private int id;
    private String name;
    private String phone;
    public Customer() {
        this(0, "", "");
    }
    public Customer(int id, String name, String phone) {
        super();
        this.id = id;
        this.name = name;
        this.phone = phone;
    }
    @Id
    @Column(name="id")
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    @Column(name="name")
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Column(name="phone")
    public String getPhone() {
        return phone;
    }
    public void setPhone(String phone) {
        this.phone = phone;
    }
}

CustomerPotential.java:

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;

@Entity
@Table(name="customer_potential")
@PrimaryKeyJoinColumn(name="id")
public class CustomerPotential extends Customer {
    private String source;
    public CustomerPotential( ) {
        this(0, "", "", "");
    }
    public CustomerPotential(int id, String name, String phone, String source) {
        super(id, name, phone);
        this.source = source;
    }
    @Column(name="source")
    public String getSource() {
        return source;
    }
    public void setSource(String source) {
        this.source = source;
    }
}

CustomerActual.java:

import java.util.Set;
import java.util.TreeSet;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;

@Entity
@Table(name="customer_actual")
@PrimaryKeyJoinColumn(name="id")
public class CustomerActual extends Customer {
    private String address1;
    private String address2;
    private String contact;
    private int discount;
    private Set<Order> orders;
    public CustomerActual() {
        this(0, "", "", "", "", "", 0);
    }
    public CustomerActual(int id, String name, String phone, String address1, String address2, String contact, int discount) {
        super(id, name, phone);
        this.address1 = address1;
        this.address2 = address2;
        this.contact = contact;
        this.discount = discount;
        this.orders = new TreeSet<Order>();
    }
    @Column(name="address1")
    public String getAddress1() {
        return address1;
    }
    public void setAddress1(String address1) {
        this.address1 = address1;
    }
    @Column(name="address2")
    public String getAddress2() {
        return address2;
    }
    public void setAddress2(String address2) {
        this.address2 = address2;
    }
    @Column(name="contact")
    public String getContact() {
        return contact;
    }
    public void setContact(String contact) {
        this.contact = contact;
    }
    @Column(name="discount")
    public int getDiscount() {
        return discount;
    }
    public void setDiscount(int discount) {
        this.discount = discount;
    }
    @OneToMany(mappedBy = "customer", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @OrderBy("id")
    public Set<Order> getOrders() {
        return orders;
    }
    public void setOrders(Set<Order> orders) {
        this.orders = orders;
    }
}

Order.java:

import java.math.BigDecimal;
import java.util.Set;
import java.util.TreeSet;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.Table;
import javax.persistence.Transient;

@Entity
@Table(name="order_main")
public class Order {
    private int id;
    int customer;
    private String status;
    private Set<OrderLine> orderLines;
    public Order() {
        this(0, 0, "");
    }
    public Order(int id, int customer, String status) {
        this.id = id;
        this.customer = customer;
        this.status = status;
        this.orderLines = new TreeSet<OrderLine>();
    }
    @Id
    @Column(name="id")
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    @Column(name="customer")
    public int getCustomer() {
        return customer;
    }
    public void setCustomer(int customer) {
        this.customer = customer;
    }
    @Column(name="status")
    public String getStatus() {
        return status;
    }
    public void setStatus(String status) {
        this.status = status;
    }
    @OneToMany(mappedBy = "order", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @OrderBy("id")
    public Set<OrderLine> getOrderLines() {
        return orderLines;
    }
    public void setOrderLines(Set<OrderLine> orderLines) {
        this.orderLines = orderLines;
    }
    @Transient
    public BigDecimal getTotalPrice() {
        BigDecimal res = BigDecimal.ZERO;
        for(OrderLine ol : orderLines) {
            res = res.add(ol.getTotalPrice());
        }
        return res;
    }
}

OrderLine.java:

import java.math.BigDecimal;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;

@Entity
@Table(name="order_line")
public class OrderLine {
    private int id;
    private int order;
    private String item;
    private int quantity;
    private BigDecimal price;
    public OrderLine() {
        this(0, 0, "", 0, BigDecimal.ZERO);
    }
    public OrderLine(int id, int order, String item, int quantity, BigDecimal price) {
        this.id = id;
        this.order = order;
        this.item = item;
        this.quantity = quantity;
        this.price = price;
    }
    @Id
    @Column(name="id")
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    @Column(name="order_main")
    public int getOrder() {
        return order;
    }
    public void setOrder(int order) {
        this.order = order;
    }
    @Column(name="item")
    public String getItem() {
        return item;
    }
    public void setItem(String item) {
        this.item = item;
    }
    @Column(name="quantity")
    public int getQuantity() {
        return quantity;
    }
    public void setQuantity(int quantity) {
        this.quantity = quantity;
    }
    @Column(name="price")
    public BigDecimal getPrice() {
        return price;
    }
    public void setPrice(BigDecimal price) {
        this.price = price;
    }
    @Transient
    public BigDecimal getTotalPrice() {
        return price.multiply(new BigDecimal(quantity));
    }
}

AddJPA.java:

import java.math.BigDecimal;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class AddJPA {
    public static void perform(String unit) {
        System.out.printf("%s:\n", unit);
        EntityManagerFactory emf = Persistence.createEntityManagerFactory(unit);
        EntityManager em = emf.createEntityManager();
        em.getTransaction().begin();
        CustomerActual ca = new CustomerActual(6, "F company", "6666-6666", "F road 6", "F town", "Mr. F", 10);
        Order o = new Order(6, 6, "In progress");
        OrderLine ol = new OrderLine(8, 6, "X stuff", 1, new BigDecimal("72.00"));
        o.getOrderLines().add(ol);
        ca.getOrders().add(o);
        em.persist(ca);
        em.getTransaction().commit();
        em.close();
        emf.close();
    }
    public static void main(String[] args) throws Exception {
        Logger.getLogger("org.hibernate").setLevel(Level.OFF);
        if(args.length == 1) {
            perform(args[0]);
        }
    }
}

ListJPA.java:

import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.TypedQuery;

public class ListJPA {
    public static void perform(String unit) {
        System.out.printf("%s:\n", unit);
        EntityManagerFactory emf = Persistence.createEntityManagerFactory(unit);
        EntityManager em = emf.createEntityManager();
        TypedQuery<CustomerPotential> qp = em.createQuery("SELECT cp FROM CustomerPotential AS cp ORDER BY cp.id", CustomerPotential.class);
        List<CustomerPotential> resp = qp.getResultList();
        for(CustomerPotential cp : resp) {
            System.out.printf("**future customer** %s, %s, %s\n", cp.getName(), cp.getPhone(), cp.getSource());
        }
        TypedQuery<CustomerActual> qa = em.createQuery("SELECT DISTINCT ca FROM CustomerActual AS ca " +
                                                       "JOIN FETCH ca.orders o " +
                                                       "JOIN FETCH o.orderLines ol " +
                                                       "ORDER BY ca.id", CustomerActual.class);
        List<CustomerActual> resa = qa.getResultList();
        for(CustomerActual ca : resa) {
            System.out.printf("%s, %s, %s, %s\n", ca.getName(), ca.getAddress1(), ca.getAddress2(), ca.getPhone());
            for(Order o : ca.getOrders()) {
                System.out.printf("  %d, %s, %.2f\n", o.getId(), o.getStatus(), o.getTotalPrice());
            }
        }
        em.close();
        emf.close();
    }
    public static void main(String[] args) throws Exception {
        Logger.getLogger("org.hibernate").setLevel(Level.OFF);
        if(args.length == 1) {
            perform(args[0]);
        }
    }
}

Build and run:

$ define/nolog java$filename_controls 8
$ define/nolog decc$efs_charset true
$ hibpath = "/javalib/antlr-2_7_6.jar:/javalib/cglib-2_2.jar:/javalib/commons-collections-3_1.jar:/javalib/dom4j-1_6_1.jar:/javalib/hibernate-jpa-2_0-api-1_0_0_final.jar:/javalib/hibernate3.jar:/javalib/javassist-3_12_0_ga.jar:/javalib/jta-1_1.jar:/javalib/slf4j-api-1_6_1.jar:/javalib/slf4j-jdk14-1_6_1.jar"
$ javac -cp 'hibpath' ListJPA.java AddJPA.java Customer.java CustomerActual.java CustomerPotential.java Order.java OrderLine.java
$ java -cp .:'hibpath':/javalib/rdbthinv5.jar:/javalib/rdbhib.jar "ListJPA" "Rdb"
$ java -cp .:'hibpath':/javalib/rdbthinv5.jar:/javalib/rdbhib.jar "AddJPA" "Rdb"
$ java -cp .:'hibpath':/javalib/rdbthinv5.jar:/javalib/rdbhib.jar "ListJPA" "Rdb"
$! java -cp .:'hibpath':/javalib/mimjdbc3.jar "ListJPA" "Mimer"
$! java -cp .:'hibpath':/javalib/mimjdbc3.jar "AddJPA" "Mimer"
$! java -cp .:'hibpath':/javalib/mimjdbc3.jar "ListJPA" "Mimer"
$ java -cp .:'hibpath':/javalib/mysql-connector-java-5_1_36-bin.jar "ListJPA" "MySQL"
$ java -cp .:'hibpath':/javalib/mysql-connector-java-5_1_36-bin.jar "AddJPA" "MySQL"
$ java -cp .:'hibpath':/javalib/mysql-connector-java-5_1_36-bin.jar "ListJPA" "MySQL"
$ java -cp .:'hibpath':/javalib/sqlite-jdbc-3_14_1-vms.jar:/javalib/sqlitehib.jar "ListJPA" "SQLite"
$ java -cp .:'hibpath':/javalib/sqlite-jdbc-3_14_1-vms.jar:/javalib/sqlitehib.jar "AddJPA" "SQLite"
$ java -cp .:'hibpath':/javalib/sqlite-jdbc-3_14_1-vms.jar:/javalib/sqlitehib.jar "ListJPA" "SQLite"
$ java -cp .:'hibpath':/javalib/derby.jar "ListJPA" "Derby"
$ java -cp .:'hibpath':/javalib/derby.jar "AddJPA" "Derby"
$ java -cp .:'hibpath':/javalib/derby.jar "ListJPA" "Derby"
$ java -cp .:'hibpath':/javalib/h2-1_2_147.jar "ListJPA" "H2"
$ java -cp .:'hibpath':/javalib/h2-1_2_147.jar "AddJPA" "H2"
$ java -cp .:'hibpath':/javalib/h2-1_2_147.jar "ListJPA" "H2"
$ java -cp .:'hibpath':/javalib/hsqldb.jar "ListJPA" "HSQLDB"
$ java -cp .:'hibpath':/javalib/hsqldb.jar "AddJPA" "HSQLDB"
$ java -cp .:'hibpath':/javalib/hsqldb.jar "ListJPA" "HSQLDB"

Customer.groovy:

import javax.persistence.*

@Entity
@Table(name="customer")
@Inheritance(strategy=InheritanceType.JOINED)
class Customer {
    @Id
    @Column(name="id")
    int id
    @Column(name="name")
    String name
    @Column(name="phone")
    String phone
}

@Entity
@Table(name="customer_potential")
@PrimaryKeyJoinColumn(name="id")
class CustomerPotential extends Customer {
    @Column(name="source")
    String source
}

@Entity
@Table(name="customer_actual")
@PrimaryKeyJoinColumn(name="id")
class CustomerActual extends Customer {
    @Column(name="address1")
    String address1
    @Column(name="address2")
    String address2
    @Column(name="contact")
    String contact
    @Column(name="discount")
    int discount
    @OneToMany(mappedBy = "customer", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @OrderBy("id")
    Set<Order> orders = new TreeSet<Order>()
}

Order.groovy:

import javax.persistence.*

@Entity
@Table(name="order_main")
class Order implements Comparable<Order> {
    @Id
    @Column(name="id")
    int id
    @Column(name="customer")
    int customer
    @Column(name="status")
    String status
    @OneToMany(mappedBy = "order", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @OrderBy("id")
    Set<OrderLine> orderLines = new TreeSet<OrderLine>()
    @Transient
    BigDecimal getTotalPrice() {
        BigDecimal res = 0.00
        for(OrderLine ol in orderLines) {
            res = res + ol.getTotalPrice()
        }
        return res
    }
    @Override
    int compareTo(Order o) {
        return id <=> o.id
    }
}

@Entity
@Table(name="order_line")
class OrderLine implements Comparable<OrderLine> {
    @Id
    @Column(name="id")
    int id
    @Column(name="order_main")
    int order
    @Column(name="item")
    String item
    @Column(name="quantity")
    int quantity
    @Column(name="price")
    BigDecimal price
    @Transient
    BigDecimal getTotalPrice() {
        return price * quantity
    }
    @Override
    int compareTo(OrderLine o) {
        return id <=> o.id
    }
}

Note: implementing Comparable on M side is necessary to get Grrovy and Hibernate to work with the 1:M relations.

AddJPA.groovy:

import java.util.logging.*

import javax.persistence.*

void perform(String unit) {
    emf = Persistence.createEntityManagerFactory(unit)
    em = emf.createEntityManager()
    em.transaction.begin()
    ca = new CustomerActual(id:6, name:"F company", phone:"6666-6666", address1:"F road 6", address2:"F town", contact:"Mr. F", discount:10)
    o = new Order(id:6, customer:6, status:"In progress")
    ol = new OrderLine(id:8, order:6, item:"X stuff", quantity:1, price:72.00)
    o.orderLines.add(ol)
    ca.orders.add(o)
    em.persist(ca)
    em.transaction.commit()
    em.close()
    emf.close()
}

Logger.getLogger("org.hibernate").setLevel(Level.OFF)
if(args.length == 1) {
    perform(args[0])
}

ListJPA.groovy:

import java.util.logging.*

import javax.persistence.*

void perform(String unit) {
    emf = Persistence.createEntityManagerFactory(unit)
    em = emf.createEntityManager()
    qp = em.createQuery("SELECT cp FROM CustomerPotential AS cp ORDER BY cp.id", CustomerPotential.class)
    resp = qp.getResultList()
    for(cp in resp) {
        println("**future customer** ${cp.name}, ${cp.phone}, ${cp.source}")
    }
    qa = em.createQuery("SELECT DISTINCT ca FROM CustomerActual AS ca " +
                         "JOIN FETCH ca.orders o " +
                         "JOIN FETCH o.orderLines ol " +
                         "ORDER BY ca.id", CustomerActual.class)
    resa = qa.getResultList()
    for(ca in resa) {
        println("${ca.name}, ${ca.address1}, ${ca.address2}, ${ca.phone}")
        for(o in ca.orders) {
            println("  ${o.id}, ${o.status}, ${o.totalPrice}")
        }
    }
    em.close()
    emf.close()
}

Logger.getLogger("org.hibernate").setLevel(Level.OFF)
if(args.length == 1) {
    perform(args[0])
}

gr_jpa.com:

$ groovy_cp = ".:/javalib/javax_persistence-api-2_2.jar:/javalib/hibernate-core-5_6_5_Final.jar:/javalib/h2-2_2_220.jar:/javalib/hsqldb.jar:/javalib/derby.jar:/javalib/hibernate-commons-annotations-5_1_2_Final.jar:/javalib/javax_activation-api-1_2_0.jar:/javalib/jboss-transaction-api_1_2_spec-1_1_1_Final.jar:/javalib/istack-commons-runtime-3_0_7.jar:/javalib/stax-ex-1_8.jar:/javalib/txw2-2_3_1.jar:/javalib/jboss-logging-3_4_3_Final.jar:/javalib/antlr-2_7_7.jar:/javalib/byte-buddy-1_12_7.jar:/javalib/classmate-1_5_1.jar:/javalib/jandex-2_4_2_Final.jar"
$ groovyc Order.groovy Customer.groovy
$ groovy ListJPA.groovy "H2"
$ groovy AddJPA.groovy "H2"
$ groovy ListJPA.groovy "H2"
$ groovy ListJPA.groovy "HSQLDB"
$ groovy AddJPA.groovy "HSQLDB"
$ groovy ListJPA.groovy "HSQLDB"
$!!!! groovy ListJPA.groovy "Derby"
$!!!! groovy AddJPA.groovy "Derby"
$!!!! groovy ListJPA.groovy "Derby"
$ groovy ListJPA.groovy "RemoteMySQL"
$ groovy AddJPA.groovy "RemoteMySQL"
$ groovy ListJPA.groovy "RemoteMySQL"

Derby is outcommented because there are some problem with the VMS, Java, Groovy, Hibernate, Derby version combination I used for test.

MySQL is tested with remote database as I ran this on VMS x86-64 and MySQL/MariaDB is not yet available on VMS x86-64. It will be available at some point and then the code will work with local database.

Customer.java:

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Table;

@Entity
@Table(name="customer")
@Inheritance(strategy=InheritanceType.JOINED)
public class Customer {
    private int id;
    private String name;
    private String phone;
    public Customer() {
        this(0, "", "");
    }
    public Customer(int id, String name, String phone) {
        super();
        this.id = id;
        this.name = name;
        this.phone = phone;
    }
    @Id
    @Column(name="id")
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    @Column(name="name")
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Column(name="phone")
    public String getPhone() {
        return phone;
    }
    public void setPhone(String phone) {
        this.phone = phone;
    }
}

CustomerPotential.java:

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;

@Entity
@Table(name="customer_potential")
@PrimaryKeyJoinColumn(name="id")
public class CustomerPotential extends Customer {
    private String source;
    public CustomerPotential( ) {
        this(0, "", "", "");
    }
    public CustomerPotential(int id, String name, String phone, String source) {
        super(id, name, phone);
        this.source = source;
    }
    @Column(name="source")
    public String getSource() {
        return source;
    }
    public void setSource(String source) {
        this.source = source;
    }
}

CustomerActual.java:

import java.util.Set;
import java.util.TreeSet;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;

@Entity
@Table(name="customer_actual")
@PrimaryKeyJoinColumn(name="id")
public class CustomerActual extends Customer {
    private String address1;
    private String address2;
    private String contact;
    private int discount;
    private Set<Order> orders;
    public CustomerActual() {
        this(0, "", "", "", "", "", 0);
    }
    public CustomerActual(int id, String name, String phone, String address1, String address2, String contact, int discount) {
        super(id, name, phone);
        this.address1 = address1;
        this.address2 = address2;
        this.contact = contact;
        this.discount = discount;
        this.orders = new TreeSet<Order>();
    }
    @Column(name="address1")
    public String getAddress1() {
        return address1;
    }
    public void setAddress1(String address1) {
        this.address1 = address1;
    }
    @Column(name="address2")
    public String getAddress2() {
        return address2;
    }
    public void setAddress2(String address2) {
        this.address2 = address2;
    }
    @Column(name="contact")
    public String getContact() {
        return contact;
    }
    public void setContact(String contact) {
        this.contact = contact;
    }
    @Column(name="discount")
    public int getDiscount() {
        return discount;
    }
    public void setDiscount(int discount) {
        this.discount = discount;
    }
    @OneToMany(mappedBy = "customer", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @OrderBy("id")
    public Set<Order> getOrders() {
        return orders;
    }
    public void setOrders(Set<Order> orders) {
        this.orders = orders;
    }
}

Order.java:

import java.math.BigDecimal;
import java.util.Set;
import java.util.TreeSet;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.Table;
import javax.persistence.Transient;

@Entity
@Table(name="order_main")
public class Order {
    private int id;
    int customer;
    private String status;
    private Set<OrderLine> orderLines;
    public Order() {
        this(0, 0, "");
    }
    public Order(int id, int customer, String status) {
        this.id = id;
        this.customer = customer;
        this.status = status;
        this.orderLines = new TreeSet<OrderLine>();
    }
    @Id
    @Column(name="id")
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    @Column(name="customer")
    public int getCustomer() {
        return customer;
    }
    public void setCustomer(int customer) {
        this.customer = customer;
    }
    @Column(name="status")
    public String getStatus() {
        return status;
    }
    public void setStatus(String status) {
        this.status = status;
    }
    @OneToMany(mappedBy = "order", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @OrderBy("id")
    public Set<OrderLine> getOrderLines() {
        return orderLines;
    }
    public void setOrderLines(Set<OrderLine> orderLines) {
        this.orderLines = orderLines;
    }
    @Transient
    public BigDecimal getTotalPrice() {
        BigDecimal res = BigDecimal.ZERO;
        for(OrderLine ol : orderLines) {
            res = res.add(ol.getTotalPrice());
        }
        return res;
    }
}

OrderLine.java:

import java.math.BigDecimal;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;

@Entity
@Table(name="order_line")
public class OrderLine {
    private int id;
    private int order;
    private String item;
    private int quantity;
    private BigDecimal price;
    public OrderLine() {
        this(0, 0, "", 0, BigDecimal.ZERO);
    }
    public OrderLine(int id, int order, String item, int quantity, BigDecimal price) {
        this.id = id;
        this.order = order;
        this.item = item;
        this.quantity = quantity;
        this.price = price;
    }
    @Id
    @Column(name="id")
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    @Column(name="order_main")
    public int getOrder() {
        return order;
    }
    public void setOrder(int order) {
        this.order = order;
    }
    @Column(name="item")
    public String getItem() {
        return item;
    }
    public void setItem(String item) {
        this.item = item;
    }
    @Column(name="quantity")
    public int getQuantity() {
        return quantity;
    }
    public void setQuantity(int quantity) {
        this.quantity = quantity;
    }
    @Column(name="price")
    public BigDecimal getPrice() {
        return price;
    }
    public void setPrice(BigDecimal price) {
        this.price = price;
    }
    @Transient
    public BigDecimal getTotalPrice() {
        return price.multiply(new BigDecimal(quantity));
    }
}

add_jpa.py:

from sys import argv

from java.math import BigDecimal
from java.util.logging import Level
from java.util.logging import Logger

from javax.persistence import Persistence

import CustomerActual
import Order
import OrderLine

def perform(unit):
    print('%s:' % (unit))
    emf = Persistence.createEntityManagerFactory(unit)
    em = emf.createEntityManager()
    em.getTransaction().begin()
    ca = CustomerActual(6, 'F company', '6666-6666', 'F road 6', 'F town', 'Mr. F', 10)
    o = Order(6, 6, 'In progress')
    ol = OrderLine(8, 6, 'X stuff', 1, BigDecimal('72.00'))
    o.orderLines.add(ol)
    ca.orders.add(o)
    em.persist(ca)
    em.getTransaction().commit()
    em.close()
    emf.close()
    
Logger.getLogger('org.hibernate').setLevel(Level.OFF)
if len(argv) == 2:
    perform(argv[1])

list_jpa.py:

from sys import argv

from java.util.logging import Level
from java.util.logging import Logger

from javax.persistence import Persistence

import CustomerPotential
import CustomerActual

def perform(unit):
    print('%s:' % (unit))
    emf = Persistence.createEntityManagerFactory(unit)
    em = emf.createEntityManager()
    qp = em.createQuery('SELECT cp FROM CustomerPotential AS cp ORDER BY cp.id', CustomerPotential)
    resp = qp.getResultList();
    for cp in resp:
        print('**future customer** %s, %s, %s' % (cp.name, cp.phone, cp.source))
    qa = em.createQuery('SELECT DISTINCT ca FROM CustomerActual AS ca ' + \
                        'JOIN FETCH ca.orders o ' + \
                        'JOIN FETCH o.orderLines ol ' + \
                        'ORDER BY ca.id', CustomerActual)
    resa = qa.getResultList()
    for ca in resa:
        print('%s, %s, %s, %s' % (ca.name, ca.address1, ca.address2, ca.phone))
        for o in ca.orders:
            print('  %d, %s, %s' % (o.id, o.status, o.totalPrice))
    em.close()
    emf.close()
    
Logger.getLogger('org.hibernate').setLevel(Level.OFF)
if len(argv) == 2:
    perform(argv[1])

Build and run:

$ hibpath = "/javalib/antlr-2_7_6.jar:/javalib/cglib-2_2.jar:/javalib/commons-collections-3_1.jar:/javalib/dom4j-1_6_1.jar:/javalib/hibernate-jpa-2_0-api-1_0_0_final.jar:/javalib/hibernate3.jar:/javalib/javassist-3_12_0_ga.jar:/javalib/jta-1_1.jar:/javalib/slf4j-api-1_6_1.jar:/javalib/slf4j-jdk14-1_6_1.jar"
$ jython_libs_prefix = hibpath + ":"
$ define/nolog java$filename_controls 8
$ define/nolog decc$efs_charset true
$ javac -cp 'hibpath' Customer.java CustomerActual.java CustomerPotential.java Order.java OrderLine.java
$ define/nolog jython_libs "/javalib/rdbthinv5.jar:/javalib/rdbhib.jar"
$ jython list_jpa.py """Rdb"""
$ jython add_jpa.py """Rdb"""
$ jython list_jpa.py """Rdb"""
$! define/nolog jython_libs "/javalib/mimjdbc3.jar"
$! jython list_jpa.py """Mimer"""
$! jython add_jpa.py """Mimer"""
$! jython list_jpa.py """Mimer"""
$ define/nolog jython_libs "/javalib/mysql-connector-java-5_1_36-bin.jar"
$ jython list_jpa.py """MySQL"""
$ jython add_jpa.py """MySQL"""
$ jython list_jpa.py """MySQL"""
$ define/nolog jython_libs "/javalib/sqlite-jdbc-3_14_1-vms.jar:/javalib/sqlitehib.jar"
$ jython list_jpa.py """SQLite"""
$ jython add_jpa.py """SQLite"""
$ jython list_jpa.py """SQLite"""
$ define/nolog jython_libs "/javalib/derby.jar
$ jython list_jpa.py """Derby"""
$ jython add_jpa.py """Derby"""
$ jython list_jpa.py """Derby"""
$ define/nolog jython_libs "/javalib/h2-1_2_147.jar
$ jython list_jpa.py """H2"""
$ jython add_jpa.py """H2"""
$ jython list_jpa.py """H2"""
$ define/nolog jython_libs "/javalib/hsqldb.jar
$ jython list_jpa.py """HSQLDB"""
$ jython add_jpa.py """HSQLDB"""
$ jython list_jpa.py """HSQLDB"""

Mimer is outcommented because the combination of Hibernate and Mimer I used to test with can not handle queries with ORDER BY. In general MIMER works fine with JPA and I suspect that newer versions may also handle queries with ORDER BY fine.

A few notes about the data classes and the JPQL:

Conclusion:

ORM has become widely used in development today (Java - JPA, Hibernate, .NET - Entity Framework, NHibernate, PHP - Doctrine, Eloquent etc.), but not so much on VMS. Java and other JVM languages are probably the only language(s) that frequently use ORM.

People may prefer the object oriented view or they may prefer the relational view, but it is a fact that usage of ORM result in having to write much less code.

Comparison:

Code size:

from code examples above (not counting data definitions that are resuable between many programs):

Stack Lines for add Lines for list Lines total
Technology Language Database
Embedded SQL Cobol * (Rdb, Mimer) 61-65 148-152 209-217
Embedded SQL C * (Rdb, Mimer) 42-43 66-67 108-110
Database specific API C MySQL 128 116 244
Database specific API Pascal MySQL 64 51 115
Database specific API Basic MySQL 67 57 124
Database specific API PHP MySQL 32 41 73
Database specific API C SQLite 67 66 133
Database specific API Pascal SQLite 59 49 108
Database specific API PHP SQLite 44 37 81
Standard API - JDBC Java * (Rdb, MySQL, Mimer, SQLite, Derby, H2, HSQLDB) 44 56 100
Standard API - DB API 2.0 Python/Jython * (Rdb, MySQL, SQLite) / (Rdb, MySQL, Mimer, SQLite, Derby, H2, HSQLDB) 22 38-39 60-61
Standard API - PDO PHP * (MySQL, SQLite) 32 41 73
ORM - JPA Java * (Rdb, MySQL, Mimer, SQLite, Derby, H2, HSQLDB) 31 40 71
ORM - JPA Groovy * (Rdb, MySQL, Mimer, SQLite, Derby, H2, HSQLDB) 23 31 54
ORM - JPA Jython * (Rdb, MySQL, Mimer, SQLite, Derby, H2, HSQLDB) 30 33 63

Disclaimer: number of lines obviously depends on the coding style. The code here is intended to follow a common coding style for the particular languages as good as I can.

The conclusions are:

Summary:

Technology Portable across databases Language support Tooling requirement Code size Speeed (*)
Embedded SQL Mostly, exceptions include:
  • establishing connection
  • differences in SQL dialect
  • differences in mapping between SQL types and host language types
Static typed compiled languages A pre-compiler specific for combination of programming language and underlying database API
(example: Cobol and Rdb)
Medium Fast
Database API Database specific API No Usually C and languages able to call C libraries Just the library Large Fast
Standard API Yes or mostly depending on the API support for handling differences in SQL dialect Technology specific:
  • C and languages able to call C libraries
  • JVM languages
  • Python
Just the library Medium Fast
ORM Yes
(an ORM specific query language handles differences in SQL dialect)
Object oriented languages An ORM library specific for combination of technology and underlying database API
(example: JVM and JDBC)
Small Startup overhead and then fast
Warning: wrong use of ORM can result in very inefficient SQL being generated

*) speed is only the speed of the database access itself. Besides that there are generally startup overhead of starting a JVM and interpreted script languages are slower than AOT/JIT compiled languages executing code.

The final conclusion must be:

Article history:

Version Date Description
1.0 November 17th 2023 Initial version
1.1 December 12th 2023 Add Basic and Groovy examples

Other articles:

See list of all articles here

Comments:

Please send comments to Arne Vajhøj