sql

前回の選挙で、ゼロ票確認なんて言う名誉が有る事を知った。22日の早朝にチャレンジしようと したら、台風に阻まれてあえなく撤退。また、次回、早急にやってください。(一回選挙をやると、600だか700億円かかるそうだから、頻繁には出来ません。)

勝てると思った時に合戦を仕掛けるのが、喧嘩の必勝法。特権と言うか専権事項が親分には 有るから、覇者に有利に出来ている。これを潰したがってる所もあるね。 でも、そんな美味しい権利をむざむざ放棄するなんて事は、天地がひっくり返ってもないだろう。

大体、合戦前には、どことどこが同盟を結ぶとか、新たに徒党を立ち上げるとかやるよね。 あの、厚化粧の大年増(と目パチクリの老人が言ってた)も、子分を沢山用意して、ひと山当てようと頑張ってたし。

開票と言う合戦結果判定で、TV局が持ち切り。統計とかAIとか引っ張り出してきて、騒がしい事。 そんなの、3分でけりがつくようなスマートな方法は無いものかね。JRAは昔から3分以内に 決着が付くようにしてるぞ。

女房は、この騒がしい報道を避けるため、1週間ぐらい前から録画をため込んで、見ないように 我慢してた。日曜夜と月曜日の楽しみとか。そうだよな、それが正解。

オイラーは、分厚い『ペンタゴンの頭脳』なんて本を読んでいる。戦争屋が使う道具を開発する 部署。昔は、ARPAと呼ばれていたけど、今はDARPAとなってる。

ここで開発された有名なものとして、ステルス戦闘機、ドローン、GPSとか有るな。最近では、 AIを搭載した自立型殺人ロボット。あと、みんながお世話になってるインターネット。

銀河間コンピュータ・ネットワークのアイデアを思い付いた、リックライダーがその覚書を ペンタゴンに送った。それから10年、彼はそこを離れたが、後任に夢想家のサザーランドと テイラーが指名された。

テイラーはこの提案を各所に紹介して、大いなる賛同を得る。自信を得たテイラーは、予算を 付けてくれるように、局長に交渉に行く。

が、局長は、コンピュータが攻撃されたらネットがダウンしちゃうんじゃ、戦略的に使えんよと 疑問を投げる。テイラーは咄嗟に、そうならないようネットワークに冗長化を組み入れますと 返答。幾らかかる? 100万ドルです。難しいのかね? もうやり方は分かってますから。 宜しい、予算を付けよう。こうして、わずか20分で予算を確保。

開発が始まり、西海岸の大学や研究所が繋がった。(どこかの国と違い、大学も軍に協力するのが伝統)やがて東海岸にも繋がった。 多くのコンピュータを接続するには、プロトコルが必要。DARPAのプログラム技術者カーンがIPにまとめた。そして、サーフと協力して、上位プロトコルTCPも策定した。TCP/IPの誕生。

そのうちに、軍用ネットと切り離して、民間に公開された。なんたって、税金を使っての開発 だから、それを解放する使命が有ったんですな。GPSもそうだね。じっくりと楽しめましたよ。

qhs

前回お世話になったpdf解析のページの著者は、Haskellが得意との事。これはきっと、色々な スクリプトを書かれているに違いないと思って、色々読ませて頂いた。そしたら、面白い記事が 掲載されていた。

CSVファイルをSQLのクエリで集計できるqコマンドをHaskellで実装してみました!

面白いな。csvファイルをDatabaseに突っ込んで、それをSQLコマンドで拾い出すとか合計を 求めるとか、ファイルを結合して集計するとか。unixコマンドではちょいと実現出来そうも ない事が、SQLの力を借りて出来るようになる。これはもう試してみる鹿。

stackが入っていれば、簡単にコマンドを構築出来るよとの事。

    Process exited with code: ExitFailure 1
    Logs have been written to: /tmp/qhs/.stack-work/logs/sqlite-0.5.5.log

    [1 of 2] Compiling Main             ( /tmp/stack4275/sqlite-0.5.5/Setup.hs,
/tmp/stack4275/sqlite-0.5.5/.stack-work/dist/x86_64-linux-nopie/Cabal-1.24.2.0/setup/Main.o )
    [2 of 2] Compiling StackSetupShim   ( /home/sakae/.stack/setup-exe-src/setup-shim-mPHDZzAJ.hs, /tmp/stack4275/sqlite-0.5.5/.stack-work/dist/x86_64-linux-nopie/Cabal-1.24.2.0/setup/StackSetupShim.o )
    Linking /tmp/stack4275/sqlite-0.5.5/.stack-work/dist/x86_64-linux-nopie/Cabal-1.24.2.0/setup/setup ...
    Configuring sqlite-0.5.5...
    setup: Missing dependency on a foreign library:
    * Missing C library: sqlite3
    This problem can usually be solved by installing the system package that
    provides this library (you may need the "-dev" version). If the library is
    already installed but in a non-standard location then you can use the flags
    --extra-include-dirs= and --extra-lib-dirs= to specify where it is.

でも、見事にエラーだ。メッセージを見たら、またlinuxのいじめだよ。全く困ったものだ。stack様からの助言によればdevバージョンが足りないのでしょうとな。devって事は、Debian系か。

Fedora系はdevelだったな。Fedoraの変化の 激しさに嫌気が差して、Centを入れていたんだけど、こちらは余りパッケージも充実していな かったので、さよならしておいたよ。

deb9:~$ sudo apt install libsqlite3-dev

これをやったら、無事に出来たみたい。ちょっと試運転。血圧の測定データで試す。

deb9:bld$ qhs 'SELECT c1,c2 from am.dat' | head -2
16011504 121
16011604 131
deb9:bld$ qhs "SELECT avg(c2) FROM pm.dat"
119.128571428571

悪乗りして、標準偏差は求められないのですか? SQLite入門SQLite が認識できる SQL を調べてみたけど、そんなのネェー。 (と、小島よしお風)ひょっとして、ボラクル方言には、有ったりして。

deb9:qhs$ ps -awxl | qhs -H -O "SELECT UID, total(RSS) FROM - group by UID"
UID total(RSS)
0 181004.0
100 4140.0
105 3016.0
106 4120.0
110 101172.0
112 3468.0
1000 80392.0

total関数が有ったので、試してみた。誰がメモリーを一番使ってるか一目瞭然。

deb9:qhs$ id 110
uid=110(lightdm) gid=113(lightdm) groups=113(lightdm)

ソース拝見

動いたね、よかったねで終わってしまっては、もったいないので鑑賞したい。初め、/tmpの 下で、gitして動作を確かめて、永久保存版という事でHOMEDIRに移しておいた。そこで、ghci とかを起動すると、よう分からんエラーが発生。

これはきっと、生まれ故郷を内蔵してるに違いない。移動しちゃったんで、過疎ってて、何も ないぞと嘆いているんだろう(現実的に怖い)。元の生まれ故郷に移動。

deb9:~$ cd /tmp/qhs/src/
deb9:src$ stack ghci Main.hs
 :
Using main module: 1. Package `qhs' component exe:qhs with main-is file: /tmp/qhs/src/Main.hs
GHCi, version 8.0.2: http://www.haskell.org/ghc/  :? for help
[1 of 6] Compiling SQL              ( /tmp/qhs/src/SQL.hs, interpreted )
[2 of 6] Compiling Paths_qhs        ( /tmp/qhs/.stack-work/dist/x86_64-linux-nopie/Cabal-1.24.2.0/build/autogen/Paths_qhs.hs, interpreted )
[3 of 6] Compiling Parser           ( /tmp/qhs/src/Parser.hs, interpreted )
[4 of 6] Compiling Option           ( /tmp/qhs/src/Option.hs, interpreted )
[5 of 6] Compiling File             ( /tmp/qhs/src/File.hs, interpreted )
[6 of 6] Compiling Main             ( /tmp/qhs/src/Main.hs, interpreted )
Ok, modules loaded: File, Main, Option, Parser, Paths_qhs, SQL.
Loaded GHCi configuration from /tmp/ghci11048/ghci-script

ああ、きっと2番目のやつが、生まれ故郷をしっかり覚えている、鮭 の役割を果たしているの だな。これなら理屈が分かるけど、鮭は長旅をして、きちんと生まれ故郷に戻ってくる。 どうやって戻ってこれるのだろう? 今度、鮭に聞いてみたいぞ。

*Main> :bro
main :: IO ()
runCommand :: Option.Option -> IO ()
runQuery ::
  Option.Option
  -> SQLite.SQLiteHandle -> (String, Parser.TableNameMap) -> IO ()
fetchQuery :: Option.Option -> IO String
parseQuery :: String -> IO (Maybe (String, Parser.TableNameMap))
readFilesCreateTables ::
  Option.Option
  -> SQLite.SQLiteHandle -> Parser.TableNameMap -> IO ()
createTable ::
  SQLite.SQLiteHandle
  -> String -> String -> [String] -> [[String]] -> IO ()

もっと沢山のものから出来上がっているかと思ったら、意外に少ないなあ。

*Main> :i  Option.Option
data Option.Option
  = Option.Option {Option.skipHeader :: Bool,
                   Option.outputHeader :: Bool,
                   Option.delimiter :: Maybe String,
                   Option.tabDelimited :: Bool,
                   Option.outputDelimiter :: Maybe String,
                   Option.tabDelimitedOutput :: Bool,
                   Option.keepLeadingWhiteSpace :: Bool,
                   Option.gzipped :: Bool,
                   Option.queryFile :: Maybe String,
                   Option.query :: Maybe String}
        -- Defined at /tmp/qhs/src/Option.hs:10:1

これの使用例が、下記になるんだな。今も昔もオプションの解析とその取り回しは面倒。 C語のmain関数なんて、ほとんどが、オプションの解析に費やされている。

deb9:~$ qhs -h
qhs - SQL queries on CSV and TSV files

Usage: qhs [-H|--skip-header] [-O|--output-header] [-d|--delimiter DELIMITER]
           [-t|--tab-delimited] [-D|--output-delimiter OUTPUT_DELIMITER]
           [-T|--tab-delimited-output] [-k|--keep-leading-whitespace]
           [-z|--gzipped] [-q|--query-filename QUERY_FILENAME] [QUERY]

Available options:
  -h,--help                Show this help text
  -v,--version             Show the version of the command.
  -H,--skip-header         Skip the header row.
  -O,--output-header       Output the header line.
  -d,--delimiter DELIMITER Field delimiter. If not specified, automatically
                           detected.
  -t,--tab-delimited       Same as -d $'\t'.
  -D,--output-delimiter OUTPUT_DELIMITER
                           Field delimiter for output. If not specified, the
                           argument of -d DELIMITER is used.
  -T,--tab-delimited-output
                           Same as -D $'\t'.
  -k,--keep-leading-whitespace
                           Keep leading whitespace in values. The leading
                           whitespaces are stripped off by default.
  -z,--gzipped             Assuming the gzipped input.
  -q,--query-filename QUERY_FILENAME
                           Read query from the provided filename.

そして、下記は、名前を分かりやすくしてるんだな。

*Main> :i Parser.TableNameMap
type Parser.TableNameMap = Map.Map String String
        -- Defined at /tmp/qhs/src/Parser.hs:13:1
*Main> :i SQLite.SQLiteHandle
newtype SQLite.SQLiteHandle
  = Database.SQLite.SQLiteHandle (GHC.ForeignPtr.ForeignPtr ())
        -- Defined in ‘Database.SQLite’

ForeignPtrを扱う時に、/usr/include/sqlite3.h が、必要なんだけど、意図的に入れて やらないといけない。/usr/lib/x86_64-linux-gnu/libsqlite3.so.0しか入っていなくて、GHCは 使い方が分からず、文句を垂れたんだな。

test

testも頑張って、書かれたそうなので、試してみます。

deb9:qhs$ stack test
qhs-0.1.3: test (suite: spec)

File
  readFromFile
    should read from a test file
    should read from a gzipped file
     :
  detectSplitter
    should detect the column splitter space
    should detect the column splitter comma
     :
Main
  qhs
    should be executed correctly: basic
    should be executed correctly: columns
      :
SQL
  open, close
    should not throw exception
  createTable, insertRow, execute
    should create a table
    should take care of null values in a number column
    can create a table when name and column names contain spaces

Finished in 0.3170 seconds
66 examples, 0 failures

これが、sh版のテストスクリプト。

deb9:tests$ cat where.sh
qhs -H "SELECT foo,baz FROM basic.csv WHERE baz <> 'b2'"
deb9:tests$ sh where.sh
a0 a2
c0 c2
deb9:tests$ cat where.out
a0 a2
c0 c2

これらを駆動する、MainSpec.hs等が用意されてる。ちなみに、Main部では、

  describe "qhs" $ do

    let tests = [
          "basic", "columns", "stdin", "header", "where", "tab", "tab2",
          "count", "is_null", "not_null", "output_header", "spaces",
          "output_delimiter", "tab_delimited_output", "multiline",
          "query_file", "empty_query", "empty_query_file", "file_spaces",
          "gzip", "gzip_stdin", "avg", "sum", "avg_sum", "seq",
          "group", "group_sum", "concat", "join", "invalid", "version"
          ]

    forM_ tests $ \test -> do
      :

こんな具合になってた。

このHSpecは、RSpecにヒントを得て作られたそうなので、 スはスペックのス 【第 1 回】 RSpec の概要と、RSpec on Railsをみてみた。あの元気な方が、一生懸命に旗振りされてたな。

何となく、英語を読んでる気になって、実はrubyのメソッドの寄せ集めだったと言う、Web屋 さんに敷居を低くした設計だった。昔々、コボルと言う、事務屋さんがしり込みしないプログラミング言語が有ったけど(今も現役)それが、元祖だな。

detectSplitterSpec :: Spec
detectSplitterSpec =
  describe "detectSplitter" $ do

    it "should detect the column splitter space" $ do
      let (headLine, secondLine) = ("c0 c1 c2 c3 c4", "0 1 2 3 4")
      detectSplitter headLine secondLine ' ' `shouldBe` True
      detectSplitter headLine secondLine '\t' `shouldBe` True
        :

確かに、doが上手い具合にアクセントになってて、英語っぽく読めるな。(と、偉そうに言ってみるテスト)でも、慣れた人には、まどろっこしい感じがするぞ。

doc

-- | The words after these words are possibly table names.
isTableNamePrefix :: String -> Bool
isTableNamePrefix xs = map toUpper xs `elem` ["FROM", "JOIN"]

ソースを眺めていると、上記のような見慣れないコメントに遭遇した。これって、あの大御所が 提言した文芸的プログラミングの流れを汲むもの? コードと説明原稿が分離しちゃってると 説明書の更新が、なおざりになる。よって、説明とコードを一緒に書こうよというあれ。

そんなの、*.lhs だろ。あちらは、文書が主体で、コードはその中に紛れ込ませる方式。 コードの冒頭に、'> ' を書いて、区別するんだった。

今回のコメントマーク '-- | ' は、これ大事だから、htmlで見られるように抽出してねマーク なのかなあ? ええい、やってみれ。haddockってコマンドで、htmlを作ってくれるらしい。

deb9:qhs$ stack haddock
  :
Installing executable(s) in
/tmp/qhs/.stack-work/install/x86_64-linux-nopie/lts-8.10/8.0.2/bin
Completed 28 action(s).
Updating Haddock index for snapshot packages in
/home/sakae/.stack/snapshots/x86_64-linux-nopie/lts-8.10/8.0.2/doc

随分時間がかかったけど、ドキュメントのhtmlが生成された。

deb9:doc$ pwd
/home/sakae/.stack/snapshots/x86_64-linux-nopie/lts-8.10/8.0.2/doc
deb9:doc$ server
Serving HTTP on 0.0.0.0 port 8080 ...
  :

そこに移動して、Python製のサーバーを起動。ブラウザーみると、残念ながらqhsの説明は 収録されていなかった。でも、下記のように、お取り寄せしたモジュールの説明が出て きたぞ。

Database
     Database.SQLite3
         Database.SQLite3.Bindings
            Database.SQLite3.Bindings.Types
        Database.SQLite3.Direct

後は、これを頼りに、Webに載るコメントの書き方を調べればいいんだな。

上に出てきたDatabaseの階層は、direct-sqlite-2.3.21に有ったんで、ソースを取り寄せた。

Database.SQLite3のexecPrint付近をソースと対比させてみると

    -- * Simple query execution
    -- | <http://sqlite.org/c3ref/exec.html>
            :
-- | Like 'exec', but print result rows to 'System.IO.stdout'.
--
-- This is mainly for convenience when experimenting in GHCi.
-- The output format may change in the future.
execPrint :: Database -> Text -> IO ()
execPrint !db !sql =
    interruptibly db $
    execWithCallback db sql $ \_count _colnames -> T.putStrLn . showValues
      :

説明文とコメントが一致したね。

パッケージの決定

自分が使うパッケージを決定するとしたら、どういう手順を踏めば良いのだろう。 思いつくまま挙げてみると、まずはぐぐるして先駆者がどんなのを使ってるか調べる。 キーワードは、haskell sqlite3 ぐらいかな。 検索期間は、足が速い事を考慮して1年以内ぐらいかな。それ以内と言うと1月に なっちゃうんだけど、オイラーとしては半年ぐらいのリミットも欲しいぞ。

HDBCパッケージを使う人やシンプルパッケージを使う人がいて、何を選んだらよいか 迷う。

で、供給元で確認してみる。

Package search で、sqlite3で探して、DLsで人気の程を知る。persisetent-sqlite, yst, direct-sqlite, HDBC-sqlite3 あたりが人気の的のようだ。

これらの候補から絞りこむんだな。ystはYAMLだとか関係なさそうな感じ。HDBCはやりたい事 が出来ない感じ。一番人気のpersisetent-sqlite は何か偏りがありそう。消去法で行ったら、 direct-sqliteが残った。(ええ、正解を知ってるというバイアスがかかってるのは承知 してますよ)

ここから、Database.SQLite を選んでいるのが分からないなあ。元祖だから?

追跡

qhsがどんな風にコンパイルしてるか、追ってみる。

deb9:qhs$ stack build
:
direct-sqlite-2.3.21: download
direct-sqlite-2.3.21: configure
direct-sqlite-2.3.21: build
:
direct-sqlite-2.3.21: copy/register
:
sqlite-simple-0.4.13.0: download
sqlite-simple-0.4.13.0: configure
sqlite-simple-0.4.13.0: build
:
sqlite-0.5.5: download
sqlite-simple-0.4.13.0: copy/register
:
sqlite-0.5.5: configure
sqlite-0.5.5: build
:
sqlite-0.5.5: copy/register

qhs.cabalにdirect-sqliteが無いのに、勝手にインストールされてる。