sqlite3

普通のtake

前回nomに普通のtakeが無いと嘆いた。後で考えたらstdにきっと有るはずって思ったのだ。が、やはり無い。何でも、汎用性が無いので殿堂入り出来無かったらしい。あえて作るとすると、下記のようになるってさ。

fn take<T>(mut vec: Vec<T>, index: usize) -> Option<T> {
    if index < vec.len() {
        Some(vec.swap_remove(index))
    } else {
        None
    }
}
fn take<T>(vec: Vec<T>, index: usize) -> Option<T> {
    vec.into_iter().nth(index)
}

でも、これを前回のやつには、直接適用出来無い。IResultでガッツリと型が決められているからね。 ああ、takeってのは、こんなの。

gosh$ (take '(5 4 2 3 1) 3)
(5 4 2)

そこで、手始めに前回のline関数にフックをしてみた。unwrap()して、中身を取出し、後で再び組み立てる作戦ね。

fn line(s: &str) -> IResult<&str, Vec<i32>> {
    let (yet, res) = terminated(
        separated_list0(tag(","), num),
                        tag("\n"))(s).unwrap();
    Ok((dbg!(yet), dbg!(res)))
}

思わぬ伏兵が潜んでいた。

[src/main.rs:17] yet = "4,5,6\n"
[src/main.rs:17] res = [
    1,
    2,
    3,
]
[src/main.rs:17] yet = ""
[src/main.rs:17] res = [
    4,
    5,
    6,
]
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error(Error { input: "", code: Tag })', src/main.rs:16
:23

空文字列に対してtagすると、エラーに落るって挙動。サンプル実行でもそうなってたから、これは仕様なんだろうね。逃れられない宿命なのか? どなたか解決方法を教授願う。

Go 1.18

Go 1.18 Release Notes

ジェネリクスを導入した「Go 1.18」が公開

なんか、rustに対抗上やらざるを得なかったのかな。これでGo遅れてるも挽回出来る亊でしょう。

それよりオイラーの注目はファジング・テストってやつ。よく知らなかったので調べてみた。

ファジングテスト これは、どこかの会社の製品だな。

Google、ファジングツール評価を自動化できる「FuzzBench」をリリース

機が熟したって亊ですかね。

不正データを与えて、脆弱な所を炙り出しましょってやつだな。雑に言うとクラッシュ・ミーって亊。どこかのOSの試験でとっくにやってた。騒ぎを起こしても堅牢でいられるかテストだ。 Haskellにも同様の試験ツールが提供されてた。

fuzzって、最近遭遇したぞ。思い出せ …. 思い出した。

sakae@deb:/tmp/nom/fuzz$ ls
Cargo.lock  Cargo.toml  fuzz_targets/

中身は、数値演算の限界テストっぽい。走らせてみても途中でエラーになるなあ。i386なマシンでは環境が不足してんのかな?

cargo-fuzz

cargo-fuzz こんなのが有った。

sakae@deb:/tmp/csv$ cargo fuzz init
sakae@deb:/tmp/csv$ cargo fuzz add csv
sakae@deb:/tmp/csv$ cargo fuzz run csv
error: failed to run `rustc` to learn about target-specific information

Caused by:
  process didn't exit successfully: `rustc - --crate-name ___ --print=file-names -Cpasses=sancov-module -Cllvm-args=-sanitizer-coverage-level=4 -Cllvm-args=-sanitizer-coverage-trace-compares -Cllvm-args=-sanitizer-coverage-inline-8bit-counters -Cllvm-args=-sanitizer-coverage-pc-table --cfg fuzzing -Clink-dead-code -Zsanitizer=address -Cllvm-args=-sanitizer-coverage-stack-depth -Cdebug-assertions -C codegen-units=1 --target i686-unknown-linux-gnu --crate-type bin --crate-type rlib --crate-type dylib --crate-type cdylib --crate-type staticlib --crate-type proc-macro --print=sysroot --print=cfg` (exit status: 1)
  --- stderr
  error: the option `Z` is only accepted on the nightly compiler

Error: failed to build fuzz script: "cargo" "build" "--manifest-path" "/tmp/csv/fuzz/Cargo.toml" "--target" "i686-unknown-linux-gnu" "--release" "--bin" "csv"

夜の部を使わないといけないのかな?

Fuzzing with cargo-fuzz こちらを見ると、注意事項が載ってたりして。 で、ソースに多少手を入れないとダメみたい。魔法はなし。

sakae@deb:/tmp/nom$ cargo fuzz list
fuzz_arithmetic
sakae@deb:/tmp/nom$ cargo fuzz run fuzz_arithmetic
error: failed to run `rustc` to learn about target-specific information
   :
  error: the option `Z` is only accepted on the nightly compiler
sakae@deb:/tmp/nom$ cargo +nightly fuzz run fuzz_arithmetic
error: failed to run `rustc` to learn about target-specific information  
  :
  target_vendor="unknown"
  unix

  --- stderr
  error: address sanitizer is not supported for this target

夜の部を指定すると、状況は変ったけど、やはりエラー。よう解らん。

test-fuzz こちらは例によって、赤丸急上昇中かな。

悔しいので、最近Debian 11.3 になった64Bitマシンで確認。

     Running `fuzz/target/x86_64-unknown-linux-gnu/release/fuzz_arithmetic -artifact_prefix=/tmp/nom/fuzz/artifacts/fuzz_arithmetic/ /tmp/nom/fuzz/corpus/fuzz_arithmetic`
INFO: Running with entropic power schedule (0xFF, 100).
INFO: Seed: 721652679
INFO: Loaded 1 modules   (20986 inline 8-bit counters): 20986 [0x55a59381dcf0, 0x55a593822eea),
INFO: Loaded 1 PC tables (20986 PCs): 20986 [0x55a593822ef0,0x55a593874e90),
INFO:      347 files found in /tmp/nom/fuzz/corpus/fuzz_arithmetic
INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
INFO: seed corpus: files: 347 min: 1b max: 3357b total: 119629b rss: 34Mb
#348    INITED cov: 317 ft: 1688 corp: 308/110Kb exec/s: 0 rss: 49Mb
#6301   REDUCE cov: 317 ft: 1688 corp: 308/110Kb lim: 3412 exec/s: 6301 rss: 56Mb L: 38/3357 MS: 3 CopyPart-CMP-EraseBytes- DE: "-\x00"-
#11237  REDUCE cov: 317 ft: 1688 corp: 308/110Kb lim: 3456 exec/s: 11237 rss: 59Mb L: 6/3357 MS: 1 EraseBytes-
#16384  pulse  cov: 317 ft: 1688 corp: 308/110Kb lim: 3500 exec/s: 8192 rss: 64Mb
#32383  REDUCE cov: 317 ft: 1688 corp: 308/110Kb lim: 3665 exec/s: 6476 rss: 74Mb L: 59/3357 MS: 1 EraseBytes-
#32768  pulse  cov: 317 ft: 1688 corp: 308/110Kb lim: 3665 exec/s: 6553 rss: 75Mb

こんなのが永遠と出てくるけど、どう解釈したらいいの? 道具は手に入ったけど、豚に真珠状態。後でじっくり調査だな。

ezio

気分転換に、こんな楽しいのを見ている。こちら、rust本繋がりに辿りついたんだ。今更だけど、インターネットは何が出てくるか予想もつかない、ビックリ箱だ。

「ちょい使い」に便利なIOクレート ezio

短かくコードが書けるのは、大歓迎。で、紹介されてた辞書ソースに行ってみた。

無料 英和辞書データ ダウンロード

テキスト版とSQL版が提供されてるのね。SQLって、テキスト版数の数倍のサイズになるのかな? 確かめてみたいぞ、と、例によって大脱線モードです。

ejdict.sqlite3

sakae@deb:/tmp/t$ ls -l
total 5976
-rw-r--r-- 1 sakae sakae 6115328 Nov 14  2020 ejdict.sqlite3
-rw-r--r-- 1 sakae sakae    1752 Nov 14  2020 README.txt

txt版の方は、サイズが4434192だったから、1.38倍は肥満してますね。太っていい亊有るのか?

sakae@deb:/tmp/t$ sqlite3 ejdict.sqlite3
sqlite> .schema
CREATE TABLE items (
        item_id         INTEGER         PRIMARY KEY,
        word            TEXT,
        mean            TEXT,
        level           INTEGER DEFAULT 0
);
CREATE INDEX word_index on items(word);
sqlite> select * from items ;
  :
48421|zygote|(生物の)接合子,接合体|0
48422|zymurgy|(ワイン・イーストなどの)発酵化学部門,醸造学|0
48423|zzz|グーグー(いびきの音)|0

selectした結果を10箇だけ出したいって、どうやるんだっけ?

nom-sql

そういうのは、きっとパーサーを調べれば、分るんだろうな。 nom-sql

   Compiling nom v7.1.1
   Compiling nom-sql v0.0.11 (/tmp/nom-sql)
error[E0277]: expected a `FnMut<()>` closure, found `Vec<_>`
   --> src/common.rs:952:13
    |
933 |         fold_many0(
    |         ---------- required by a bound introduced by this call
...
952 |             Vec::new(),
    |             ^^^^^^^^^^ expected an `FnMut<()>` closure, found `Vec<_>`
    |
    = help: the trait `FnMut<()>` is not implemented for `Vec<_>`
    = note: wrap the `Vec<_>` in a closure with no arguments: `|| { /* code */ }`
note: required by a bound in `fold_many0`
   --> /home/sakae/.cargo/registry/src/github.com-1285ae84e5963aae/nom-7.1.1/src/multi/mod.rs:642:6
    |
642 |   H: FnMut() -> R,
    |      ^^^^^^^^^^^^ required by this bound in `fold_many0`

For more information about this error, try `rustc --explain E0277`.

nom="6"の指定だったけど、勝手に7.1.1にした弊害なのかな? 取り敢えずdocに当ってみると、

Arguments
    f The parser to apply.
    init A function returning the initial value.
    g The function that combines a result of f with the current accumulator.

fn parser(s: &str) -> IResult<&str, Vec<&str>> {
  fold_many0(
    tag("abc"),
    Vec::new,
    |mut acc: Vec<_>, item| {
      acc.push(item);
      acc
    }
  )(s)
}

この例とエラーになったソースを見比べると、initに相当する所のVec::new() の()が邪魔だった。邪魔を外して無事にコンパイル出来た。

gosh$ (fold + 0 '(1 2 3 4 5))
15

foldは畳み込み。goshの例だと、リストのデータを+って手続を使って簡約化しなさい。その時の初期値はZEROにしておけって亊だ。

ああ、畳み込みの一種である平均について、一言書いておきたい。ある貧乏な村がありました。村民は100名。みんな平等に年収100万円。そんな村に年収100億円のAmazonの創業者ジェフ・ベゾスが移住してきました。村人の平均年収は幾らになったでしょう?

100万 X 100人 + 100億 X 1人 を101人で割って、1億円。裕福な村になりました。こういう亊が、さりげなくニュースで伝えられているから注意ね。曰く、平均年収とか平均貯蓄高とかね。

話が逸れた。 nomのそれだと、引数の順番が違うけど、パーサーで処理したデータを、初期値Vecにしておいて、そこに追加しろって例になってる。もう、schemeでコードを書いてる気分だな。

assert_eq!(parser("abcabc"), Ok(("", vec!["abc", "abc"])));
assert_eq!(parser("abc123"), Ok(("123", vec!["abc"])));
assert_eq!(parser("123123"), Ok(("123123", vec![])));
assert_eq!(parser(""), Ok(("", vec![])));

上記のエラー中にFnMutってのが混っていた。これは何だろうと思って調べてみたら、とても重要な亊をやってた。これを知らないとRustの潜りと言われてもしょうがないぐらい大事だと思う。道草するのも良いものだ。

Rustのクロージャtraitについて調べた(FnOnce, FnMut, Fn)

クロージャ こちらの入門編も上質で、公式版に優るとも劣らない。心して嫁。

sqlの勉強

上で作った木箱のテスト部を見て、sqlの勉強と言う、まどろっこしい亊をやります。

sqlite> select * from items limit 3;
1|$|dollar[s] ドル記号(通貨単位)|0
2|@|単価…で / …につき|0
3|'em|=them|0
sqlite> select * from items limit 3 offset 5000;
5001|bowsprit|船首斜檣(しゃしょう),やりだし(船首から前方に突き出た円材)|0
5002|bowstring|弓のつる|0
5003|bowtie|ちょうネクタイ|0
sqlite> select count(word) from items where level > 0;
2416
sqlite> select count(word) from items where level == 1;
1590
sqlite> select count(word) from items where level == 2;
755
sqlite> select count(word) from items where level == 3;
71
sqlite> select * from items where word == "where";
47335|where|《疑問副詞》『どこに(で,へ)』,どういう状態に,どんな点で ... ところの』 / 場所|2
sqlite> select * from items where word like "rust%";
36886|rust|(金属の)『さび』 / さび病;さび菌 / さび色 / さびる ...
  :
36894|rustless|さびつかない / さびついていない|0

パーサーには、like がサポートされていないな。grepの代わりには便利に使えるんだけどね。

select sql driver

RustからSQLiteにデータを詰め込み、取り出すぞい 『実践 Rustプログラミング入門』サンプルプログラムを使ってみたよって記事。

Rust で SQLite を動かしてみる (サンプルコードを関数に切り出す) こちらは、sqliteってドライバーを使ってる。 About "Cookin' with Rust"でも使われているな。

多分、軽いでしょう。

vbox$ cargo tree
q v0.1.0 (/tmp/q)
└── sqlite v0.26.0
    ├── libc v0.2.121
    └── sqlite3-sys v0.13.0
        ├── libc v0.2.121
        └── sqlite3-src v0.3.0
            [build-dependencies]
            ├── cc v1.0.73
            └── pkg-config v0.3.24

こちらは、鉄板ぽい。追加でsqliteのモジュールが入るみたい。

vbox$ cargo tree
z v0.1.0 (/tmp/z)
└── rusqlite v0.27.0
    ├── bitflags v1.3.2
    ├── fallible-iterator v0.2.0
    ├── fallible-streaming-iterator v0.1.9
    ├── hashlink v0.7.0
    │   └── hashbrown v0.11.2
    │       └── ahash v0.7.6
    │           ├── getrandom v0.2.5
    │           │   ├── cfg-if v1.0.0
    │           │   └── libc v0.2.121
    │           └── once_cell v1.10.0
    │           [build-dependencies]
    │           └── version_check v0.9.4
    ├── libsqlite3-sys v0.24.1
    │   [build-dependencies]
    │   ├── pkg-config v0.3.24
    │   └── vcpkg v0.2.15
    ├── memchr v2.4.1
    └── smallvec v1.8.0

sqlite on rust

軽いsqliteを使ってみる。例によってリナの意地悪である、ヘッダーファイル無いよエラーに遭遇するかと思ったら、そういう無様な亊は無かった。理由は、ソースを取り寄せてコンパイルしてるから。

/tmp/sql/vendor/sqlite3-src/source:
total used in directory 9244 available 1 GiB
 :
-rw-r--r-- 1 sakae sakae  654331 Mar 30 06:03 shell.c
-rw-r--r-- 1 sakae sakae 8182289 Mar 30 06:03 sqlite3.c
-rw-r--r-- 1 sakae sakae   35437 Mar 30 06:03 sqlite3ext.h
-rw-r--r-- 1 sakae sakae  583202 Mar 30 06:03 sqlite3.h

こんなにソースは巨大なのか。しかも一つのファイルだ。まあ、それは、組み込みを意識して、一つのファイルにまとめてしまっているんだろう。

そして、出来上がったライブラリーとrust側の橋渡しをする為、sqlite3-sysが用意されてる。いわゆるFFIね。素直な構成だ。

SQlite の README.mdに、使いかたの例。CRUDと言う物語になってるかと思ったら、CRRR だったぞ。物語を構成するには、土台のテーブルをクリエートする必要がある。後は主役のREADを数種見せて、SQLって便利だよってアッピール。

アップデートとかデリートって、脇役っぽいから、ロールにも出てこない。詳しい亊は、 sqlite-rust manual を参照してね。

ああ、このsqliteを提供してるgitのIDが、ステンレス・スチールってなってる。rustと対比すると面白いな。こういうユーモアが大事。なんだか、どこかの親分が仕掛けた戦争のおかげで、ステンレスの材料になるニッケルが高騰してるとか。

親分、暗殺を恐れて、長机のはじっこに座っている。そんな亊なら、戦争なんてやらなければ良かったのに。

実習

早速、実験してみる。

use sqlite::Value;

fn main() {
    let connection = sqlite::open("ejdict.sqlite3").unwrap();
    let mut cursor = connection
        .prepare("SELECT word,level FROM items WHERE level > ?")
        .unwrap()
        .into_cursor();

    cursor.bind(&[Value::Integer(1)]).unwrap();

    while let Some(row) = cursor.next().unwrap() {
        print!("word = {}\t", row[0].as_string().unwrap());
        println!("level = {}", row[1].as_integer().unwrap());
    }
}

昔、これと同じような亊を、ruby + MySQL でやった覚えがあるぞ。あの時は、ゲリラ的に社内の掲示板を作った。エンジニア向けのやつ。最初は、FAQみたいな奴で今で言うとqiitaみたいな物ね。

そのうちに、百聞は一見にしかずとか言い出す人がいて、エクセル・アルバムで写真も載せられると有り難いって要望が出て来た。夜なべして、しこしことアップロード出来るようにしたよ。

あの頃は、エクセル方眼紙も回路図を書くのに普通に使われていて、ファイルサイズを気にしない人ばかりだったから、苦肉の策として1投稿に添付出来るファイルは3つまでとしたなあ。

今でも神エクセルなんてのが、使われているのか。進歩ないね。

今回は、レベルが何を表しているか、ちょいと探りを入れる積もりのコードにした。

word = able     level = 2
word = about    level = 3
word = account  level = 2
word = across   level = 2
word = act      level = 2
word = after    level = 3
word = again    level = 2
word = against  level = 2
word = air      level = 2
word = all      level = 3
 :

レベルの違いを説明せよ、って英語の試験に出そうだな。


This year's Index

Home