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
なんか、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本繋がりに辿りついたんだ。今更だけど、インターネットは何が出てくるか予想もつかない、ビックリ箱だ。
短かくコードが書けるのは、大歓迎。で、紹介されてた辞書ソースに行ってみた。
テキスト版と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 :
レベルの違いを説明せよ、って英語の試験に出そうだな。