UD/CRUD

Table of Contents

delete …

今FreeBSDのパッケージになってるrfcを更新してみたら、最新は、rfc9371だっ た。でも、古いものからの積み重ねになってんだよなあ。と言う事で、簡単な データベースを作成。これを肴にする。

sqlite3 test.db
create table rfc(id integer,name text);
insert into rfc(id,name) values(786,'mail');
insert into rfc(id,name) values(801,'TCP');
insert into rfc(id,name) values(1945,'HTTP');

前回は軽く触れたsqliteの動作ね。潜ってみたい。

#include <stdio.h>
#include "sqlite3.h"

int main(void){
    sqlite3 *db;
    char *filename="test.db";
    int rc; //result codes
    sqlite3_stmt *stmt;

    rc = sqlite3_open(filename, &db);

    rc = sqlite3_prepare_v2(db,"DELETE FROM rfc where id=2",
                                -1, &stmt, 0);
    if(rc != SQLITE_OK){
        printf("ERROR(%d) %s\n",rc, sqlite3_errmsg(db));
        sqlite3_close(db);
        return 2;
    }

    rc = sqlite3_step(stmt);    // exec delete
    if(rc != SQLITE_DONE){
        printf("ERROR(%d) %s\n",rc,sqlite3_errmsg(db));
    }

    rc = sqlite3_finalize(stmt);
    if(rc != SQLITE_OK){
        printf("ERROR(%d) %s\n",rc,sqlite3_errmsg(db));
    }
    sqlite3_close(db);
    return 0;
}    

CRUDのうちのdeleteをやる。有り難くコードを利用させて貰う。Uも貴重です よ。普通はCRだけの説明が多いですからね。

cc -g -O0 rfc_del.c -L`pwd`/.libs -lsqlite3

で、こんな風にコンパイルしたら、バイナリーは無事に作成されたけど、ライ ブラリーが見付からないと言う実行時エラー。訳ワカメだったの で、.libs/libsqlite3.so.8.6を、/usr/libに入れてしまった。 ライブラリィーは、/home/sakae/sqlite-autoconf-3410000 か ら、–enable-debug付きで 作ったものだ。

実行の前に説明を聞いておく。

sqlite> explain DELETE FROM rfc where id=2;
addr  opcode         p1    p2    p3    p4             p5  comment
----  -------------  ----  ----  ----  -------------  --  -------------
0     Init           0     12    0                    0   Start at 12
1     Null           0     1     0                    0   r[1]=NULL
2     OpenWrite      0     2     0     2              0   root=2 iDb=0; rfc
3     Explain        3     0     0     SCAN rfc       0
4     Rewind         0     11    0                    0
5       Column         0     0     2                    0   r[2]= cursor 0 column 0
6       Ne             3     10    2     BINARY-8       84  if r[2]!=r[3] goto 10
7       ReleaseReg     2     1     0                    0   release r[2] mask 0
8       Rowid          0     4     0                    0   r[4]=rfc.rowid
9       Delete         0     1     0     rfc            2
10    Next           0     5     0                    1
11    Halt           0     0     0                    0
12    Transaction    0     1     1     0              1   usesStmtJournal=0
13    TableLock      0     2     1     rfc            0   iDb=0 root=2 write=1
14    Integer        2     3     0                    0   r[3]=2
15    Goto           0     1     0                    0

説明ついでに、manから拾い出したのをのせておく。SQL文って、綺麗に清書し たのを沢山見るけど、一行野郎なんだね。

sqlite3 test.db 'select * from rfc;'

An Introduction To The SQLite C/C++ Interface まず、これを参照して全体 を把握する事だな。

try again in FreeBSD

FreeBSDで失敗して悔しいので再トライ。方針は、sqlite3系をちゃんとインス トールする。そして、禁断の設定をしてターゲットをコンパイル。

./configure --prefix=/home/sakae/MINE --enable-debug --enable-shared

こんな風にsqlite3を用意しる。自前のエリアに入れるんだな。

[sakae@fb ~/MINE/work]$ LD_LIBRARY_PATH=/home/sakae/MINE/lib cc -v -g -O0 rfc_del.c -I/home/sakae/MINE/include -L/home/sakae/MINE/lib -lsqlite3

ターゲットのコンパイル時にライブラリィーのエリアを指定。指定したからと 言って、-Lの指定は省略できない。

[sakae@fb ~/MINE/work]$ ldd a.out
a.out:
        libsqlite3.so.0 => /home/sakae/MINE/lib/libsqlite3.so.0 (0x20443000)
        libc.so.7 => /lib/libc.so.7 (0x206aa000)
        libz.so.6 => /lib/libz.so.6 (0x2087c000)
        libm.so.5 => /lib/libm.so.5 (0x20896000)
        libthr.so.3 => /lib/libthr.so.3 (0x208d2000)

これで目的のエリアにあるライブラリィーがリンクされた。勿論ちゃんと機能 するよ。

[sakae@fb ~/MINE/work]$ gdb -q a.out
Reading symbols from a.out...
(gdb) b sqlite3_step
Breakpoint 1 at 0x401ad0
(gdb) r
Starting program: /usr/home/sakae/MINE/work/a.out

Breakpoint 1, sqlite3_step (pStmt=0x20966b78) at sqlite3.c:88538
88538     int rc = SQLITE_OK;      /* Result from sqlite3Step() */
(gdb) bt 3
#0  sqlite3_step (pStmt=0x20966b78) at sqlite3.c:88538
#1  0x204cfff6 in sqlite3_exec (db=0x20934008,
    zSql=0x20969ba8 "SELECT*FROM\"main\".sqlite_master ORDER BY rowid",
    xCallback=0x20541820 <sqlite3InitCallback>, pArg=0xffbfdee0, pzErrMsg=0x0)
    at sqlite3.c:131919
#2  0x20541655 in sqlite3InitOne (db=0x20934008, iDb=0, pzErrMsg=0xffbfe8e4,
    mFlags=0) at sqlite3.c:137529
(More stack frames follow...)

但しemacsからgdbを起動した場合、

(gdb) b sqlite3_step
Breakpoint 1 at 0x401ad0
(gdb) r
Starting program: /usr/home/sakae/MINE/work/a.out

Breakpoint 1, 0x2047e616 in sqlite3_step () from /usr/local/lib/libsqlite3.so.0

と、なってしまう。

[sakae@fb ~/MINE/work]$ export LD_LIBRARY_PATH=/home/sakae/MINE/lib
[sakae@fb ~/MINE/work]$ mg rfc_del.c

こんな風に、場所を広告した上でemacsを使っても、その広告は、ターゲット までは届かずに、/usr/local/lib/libsqlte3の方が使われてしまっていた。相 性悪いな。

gud: how to set the LDLIBRARYPATH before run?

むむ、こんなのが有るぞ。

try ArchLinux

普通にemacsからgdbを起動すると、

Reading symbols from sqlite3...

This GDB supports auto-downloading debuginfo from the following URLs:
  <https://debuginfod.archlinux.org>
Enable debuginfod for this session? (y or [n]) Please answer y or [n].
 :
Enable debuginfod for this session? (y or [n]) n
Please answer y or [n].

山本リンダ風に、どうにも止まらないになってしまう。こんなの初めての経験。 これを脱するには、

M-x gud-gdb と起動すればいいらしい。

Run gud-gdb (like this): gdb --fullname sqlite3

以下は、そうやった時のセッション。

(gdb) b sqlite3_exec
Breakpoint 1 at 0x1274cf: file sqlite3.c, line 131888.
(gdb) r doctor.db 'select * from Procs;'
Starting program: /home/sakae/sqlite-autoconf-3410000/sqlite3 doctor.db 'select * from Procs;'

This GDB supports auto-downloading debuginfo from the following URLs:
  <https://debuginfod.archlinux.org>
Enable debuginfod for this session? (y or [n]) n
Debuginfod has been disabled.
To make this setting permanent, add 'set debuginfod enabled off' to .gdbinit.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".

Breakpoint 1, sqlite3_exec (db=0x5555557d9518,
    zSql=0x5555557e8068 "SELECT*FROM\"main\".sqlite_master ORDER BY rowid",
    xCallback=0x55555568410c <sqlite3InitCallback>, pArg=0x7fffffffbfe0,
    pzErrMsg=0x0) at sqlite3.c:131888
(gdb)

案内にもあるように、set debuginfod enabled off を記述しておけとな。

sqlite3 script

.open test.db
create table rfc(id integer,name text);
insert into rfc(id,name) values(786,'mail');
insert into rfc(id,name) values(801,'TCP');
insert into rfc(id,name) values(1945,'HTTP');

.mode box
.print 'orignl data'
select * from rfc;

.print 'update data for id:801'
update rfc set name='TCP/IP' where id=801;

.print 'delete data for mail'
delete from rfc where id=786;

.print 'after UD'
select * from rfc;

こんな具合にドット・コマンドとSQL文の混合スクリプトを作成した。

[sakae@fb ~/MINE/work]$ sqlite3
SQLite version 3.40.1 2022-12-28 14:03:47
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> .read ud.sql
orignl data
┌──────┬──────┐
│  id  │ name │
├──────┼──────┤
│ 786  │ mail │
│ 801  │ TCP  │
│ 1945 │ HTTP │
└──────┴──────┘
update data for id:801
delete data for mail
after UD
┌──────┬────────┐
│  id  │  name  │
├──────┼────────┤
│ 801  │ TCP/IP │
│ 1945 │ HTTP   │
└──────┴────────┘
sqlite>

起動して、中からスクリプトを読み込み(=実行)してみた。出来の悪いアップ デートとデリートの実例。

sqlite> .read ud.sql
Parse error near line 2: table rfc already exists
  create table rfc(id integer,name text);
               ^--- error here
orignl data
┌──────┬────────┐
│  id  │  name  │
├──────┼────────┤
│ 801  │ TCP/IP │
│ 1945 │ HTTP   │
│ 786  │ mail   │
│ 801  │ TCP    │
│ 1945 │ HTTP   │
└──────┴────────┘
 :

再度実行すると、既にDBは作成済なので、エラーと言われる。でも、実行でき ちゃった。

そういう難点を解消するには、起動時に、テーブルをdropしてしまえば良い。 そんな設定ファイルを用意して、sqlite3 –init xx.sql のように使えばよい。

Command Line Shell For SQLite に詳しい説明があるぞ。

csv to sqlite3

上のCLIの説明を見ていたら、簡単にCSVからデータを輸入できるようだ。

sqlite> .import --csv --skip 1 --schema temp C:/work/somedata.csv tab1

一応説明を参照しておく。

sqlite> .help import
.import FILE TABLE       Import data from FILE into TABLE
   Options:
     --ascii               Use \037 and \036 as column and row separators
     --csv                 Use , and \n as column and row separators
     --skip N              Skip the first N rows of input
     --schema S            Target table to be S.TABLE
     -v                    "Verbose" - increase auxiliary output
   Notes:
     *  If TABLE does not exist, it is created.  The first row of input
        determines the column names.
     *  If neither --csv or --ascii are used, the input mode is derived
        from the ".mode" output mode
     *  If FILE begins with "|" then it is a command that generates the
        input text.

オイラーが貯めこんでいる血圧データから、朝の最高血圧だけを、年月と共に 採取。その冒頭には、ヘッダーをつけておいた。

#! /bin/sh
cat current.csv  |
awk -F, \
  'BEGIN{printf("yy,mm,hi\n")}
   {if ($1 % 100 < 12)
      {printf("%s,%s,%s\n", substr($1,1,2), substr($1,3,2), $2)}
   }'

これを走らせて、一時ファイルのz.csvなんてのにリダイレクト。それから saliteに輸入しる。

$ sqlite3 my.db
SQLite version 3.41.0 2023-02-21 18:09:37
Enter ".help" for usage hints.
sqlite> .import --csv z.csv bld
sqlite> .mode table
sqlite> select * from bld limit 3;
+----+----+-----+
| yy | mm | hi  |
+----+----+-----+
| 12 | 01 | 118 |
| 12 | 01 | 128 |
| 12 | 01 | 122 |
+----+----+-----+

冒頭部分をちょいと確認。

sqlite> select yy, avg(hi) from bld group by yy;
+----+------------------+
| yy |     avg(hi)      |
+----+------------------+
| 12 | 119.644444444444 |
| 13 | 119.050279329609 |
| 14 | 122.433701657459 |
   :

次は年毎に平均を出してみた。無駄に桁数が多いな。こういう時は丸めればい いのか。ついでに、タイトルもasを使って置き換えてみた。roundは組み込み の関数。avgは組み込みの集計(aggregate)関数に分類されてた。

sqlite> select yy, round(avg(hi),2) as avg from bld group by yy;
+----+--------+
| yy |  avg   |
+----+--------+
| 12 | 119.64 |
| 13 | 119.05 |
| 14 | 122.43 |
   :
sqlite> select yy,mm,round(avg(hi),2) as avg from bld where yy=23 group by mm;
+----+----+--------+
| yy | mm |  avg   |
+----+----+--------+
| 23 | 01 | 135.71 |
| 23 | 02 | 136.75 |
+----+----+--------+

月毎の平均を求めると、何も指定しなければ、最初の年で終了してしまう。そ んな訳で、年をwhere句で強制指定してみた。

CREATE VIEW yma(yy, mm, avg) AS
  select L.yy, L.mm, round(avg(L.hi),2)
  from bld as L, bld as R
  where L.yy = R.yy
   and  L.mm = R.mm
  group by L.yy, R.mm;

次は、こんなビューを作成。普通にやるだけで十分なんだけど、SQLパズルの 復習って事で。自分でやってみると、よく原理が分った。 原テーブルに別名のLとRをつける。左のテーブルと右のテーブルだな。where 句で、左右のそれぞれの列が等しいって条件を加味してselectする。 その結果は、合成したようなものだから、VIEWで新しいテーブルとして登録。

結果を確認。

sqlite> select * from yma limit 9,6;
+----+----+--------+
| yy | mm |  avg   |
+----+----+--------+
| 12 | 10 | 119.81 |
| 12 | 11 | 122.5  |
| 12 | 12 | 124.67 |
| 13 | 01 | 124.03 |
| 13 | 02 | 119.79 |
| 13 | 03 | 116.81 |
+----+----+--------+

これの答合わせはpythonエリアにお住まいのう北極熊さんにお願いかな。

ああ、.import時に勝手に作成されちゃったテーブルは、どういう形だろ?

CREATE TABLE IF NOT EXISTS "bld"(
"yy" TEXT, "mm" TEXT, "hi" TEXT);

思った通りテキスト属性だった。それに対して、平均を計算するなんて、よき に計らえって方式。古くはawkやperlの流れをくんでいるな。pythonみたいに ツンツンしてない、大らかな所が好感だな。

tools

The sqlite3analyzer.exe Utility Program

sqlite3_analyzer.exe my.db
/** Disk-Space Utilization Report For my.db

Page size in bytes................................ 4096
Pages in the whole file (measured)................ 18
Pages in the whole file (calculated).............. 18
Pages that store data............................. 18         100.0%
Pages on the freelist (per header)................ 0            0.0%
Pages on the freelist (calculated)................ 0            0.0%
Pages of auto-vacuum overhead..................... 0            0.0%
Number of tables in the database.................. 2
Number of indices................................. 0
Number of defined indices......................... 0
Number of implied indices......................... 0
Size of the file in bytes......................... 73728
Bytes of user payload stored...................... 44627       60.5%
  :

このツールはMINGW64に紛れこんでいた。リナとか*BSDには入っていなかった ぞ。ソースは何処にあるのだろう? Windowsだけが優遇されてるの?

sqlite3_analyzer.exe sqlite3_analyzer.sh こんなのが同梱されてるんだけ どな。

$ pacman -Qi mingw-w64-x86_64-sqlite3
Name            : mingw-w64-x86_64-sqlite3
Version         : 3.41.0-1
Description     : A C library that implements an SQL database engine (mingw-w64)
Architecture    : any
URL             : https://www.sqlite.org
Licenses        : PublicDomain
Groups          : None
Provides        : mingw-w64-x86_64-sqlite=3.41.0
                  mingw-w64-x86_64-sqlite-analyzer=3.41.0
Depends On      : mingw-w64-x86_64-gcc-libs  mingw-w64-x86_64-readline
                  mingw-w64-x86_64-tcl  mingw-w64-x86_64-zlib
Optional Deps   : None
Required By     : mingw-w64-x86_64-python
Optional For    : None
Conflicts With  : mingw-w64-x86_64-sqlite-analyzer
Replaces        : mingw-w64-x86_64-sqlite-analyzer
Installed Size  : 12.25 MiB
Packager        : CI (msys2/msys2-autobuild/91ab3435/4335856811)
Build Date      : Sun Mar 5 20:52:15 2023
Install Date    : Mon Mar 6 15:12:42 2023
Install Reason  : Installed as a dependency for another package
Install Script  : No
Validated By    : Signature

python君の朋友なのか。その親分はgdbだったりするんだけどね。

etc

Debuginfod service - Arch Linux

Debuginfod service - Arch Linux

(gdb) b main
Breakpoint 1 at 0x52434: file shell.c, line 26946.
(gdb) r
Starting program: /tmp/sqlite-autoconf-3410000/sqlite3
Downloading separate debug info for /lib64/ld-linux-x86-64.so.2
Downloading separate debug info for system-supplied DSO at 0x7ffff7fc8000
Downloading separate debug info for /usr/lib/libedit.so.0
Downloading separate debug info for /usr/lib/libncursesw.so.6
Downloading separate debug info for /usr/lib/libz.so.1
Downloading separate debug info for /usr/lib/libm.so.6
Downloading separate debug info for /usr/lib/libc.so.6
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".

通常は使う事がない、デバッグ情報付きのライブラリィーを自動的にDLしてく れる、小粋なサービス。

[sakae@arch sqlite-autoconf-3410000]$ ldd sqlite3
        linux-vdso.so.1 (0x00007ffc76ff8000)
        libedit.so.0 => /usr/lib/libedit.so.0 (0x00007fb2fa48a000)
        libncursesw.so.6 => /usr/lib/libncursesw.so.6 (0x00007fb2fa416000)
        libz.so.1 => /usr/lib/libz.so.1 (0x00007fb2fa3fc000)
        libm.so.6 => /usr/lib/libm.so.6 (0x00007fb2fa314000)
        libc.so.6 => /usr/lib/libc.so.6 (0x00007fb2fa12d000)
        /lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007fb2fa75a000)

This year's Index

Home