Translate

2013年10月27日日曜日

DBI

まずは、接続と切断から試してみる
SQLiteを使うので、DBIとDBD::SQLiteがインストールされていること
<参考>
DBI [ver1.628]
DBD::SQLite [ver1.40]

覚えること
Noメソッド解説
1connectDBに接続
disconnectDBと切断
2doSQLを実行
prepareSQLを準備
executeSQLを実行
prepare_cached 準備した結果をcacheする
3fetch配列のリファレンスを返す
fetchrow_arrayref配列のリファレンスを返す
fetchrow_hashref列をキーに値をリファレンスで返す


■1.connect, disconnectメソッド
use strict;
use warnings;
use utf8;
use DBI;
use feature 'say';

my $database   = ':memory:';
my $connectStr = "dbi:SQLite:dbname=$database";
my $userName   = "";
my $passWord   = "";

#接続
my $dbh        = DBI->connect($connectStr,
                              $userName,
                              $passWord,
                              {RaiseError => 1,
                               PrintError => 0,
                               AutoCommit => 1});
#切断
$dbh->disconnect();

[第一引数]接続するための文字列は、
[SQLite]dbi:SQLite:DB名
[MySQL]dbi:mysql:DB名
[PostgreSQL]dbi:pg:DB名
[Oracle]dbi:oracle:DB名
尚、SQLiteはメモリ上にDBを作ることができ上記のように
:memory:と記載するとメモリ上に作成します。
当然ながら、プログラムが終了したら消えます。
サンプルコード等のお試しに最適です。


[第二引数]username
[第三引数]password
SQLiteではユーザー名、パスワードがないので、空文字のままにしています。
他のDBでは設定します。

[第四引数]オプション
ここは、いろいろあるのでDBIを参照するのがよいと思います。
http://search.cpan.org/search?query=DBI&mode=all

よく登場するオプションもメモ。
□RaiseError[デフォルト:偽]
エラーが発生すると自動的にdieして終了してくれます。
これを真にしないのであれば、各メソッド毎にエラー処理を書く必要があります。

□PrintError[デフォルト:真]
エラー時warnで出力してくれる機能。
デフォルトで真だけど、RaiseErrorを真にするのであれば
表示内容がかぶるのでこちらは偽にしておく。

□AutoCommti[デフォルト:真?]
全ての命令毎にCommitします。
ドライバによって、デフォルト値が異なるようですので明示的に指定した方がよいようです。

AutoCommitが真の時、
一つの命令毎にCommitされるため
トランザクションできなくなります。
トランザクションを行いたい時は、明示的に指示をします。

#ここまでAutoCommitエリア

#ここからトランザクション開始
$dbh->begin_work;
#何かしらの処理
$dbh->commit(); or $dbh->rollback();
#トランザクション区間終了

#ここからAutoCommitエリア

オプションに関しては、他にも必要とするのがあるのですが
まずは、DBの操作について話した後でないと検証ができないので
のちほどあらためて掲載します。


■2.do, prepare, execute, prepare_cached
□doメソッド
my $sql = "INSERT INTO sample (col1, col2) VALUES (?, ?)";
my @data = qw(data1 data2);
my $sth = $dbh->do($sql, undef, @data);

・ステートメントハンドルを作成しないため、SELECT文のように値を取得する場面では使えません。
・ステートメントハンドルを生成しない分、高速に動きます。
・INSERT, UPDATE, DELETEなどなど

□prepare, executeメソッド
my $sql = "SELECT * FROM sample WHERE col1 = ? AND col2 = ?";
my $sth = $dbh->prepare($sql);
my @data = qw(data1 data2);
my $rv = $sth->execute(@data);
・事前に準備(prepare)するため、何度もINSERTする際などループの外でprepareしておけば
その分高速に動きます。つまり、繰り返し同じSQLを実行する場合prepareしておけばよいということです。

□prepare_cachedメソッド
prepareした物を内部でハッシュに紐づけてキャッシュします。
ループ分のように連続して使用するのでなく、別の関数等で再び利用する場合
キャッシュしておけば、prepareの行為を省略できるのでその分だけ高速になります。
あちこちで同じSQLを使用するのであればキャッシュして使いましょう。

■3.fetch, fetchrow_arrayref, fetchrow_hashref
□fetch, fetchrow_arrayref
my $sql = "SELECT * FROM sample";
my $sth = $dbh->prepare($sql);
my $rv  = $sth->execute();

while (my $row = $sth->fetch()){
 my ($col1, $col2, col3) = @{$row};
 #処理
}
fetchとfetchrow_arrayrefは同じ結果となります。
つまり、配列のリファレンスを返してくれます。

□fetchrow_hashref
my $sql = "SELECT * FROM sample";
my $sth = $dbh->prepare($sql);
my $rv  = $sth->execute();

while (my $row = $sth->fetchrow_hashref()){
    say $row->{'col1'};
    say $row->{'col2'};
    say $row->{'col3'};
}


■DBI->connectのオプションについて
他のオプションについても紹介しときます。
□ShowErrorStatement
エラーとなったSQLを出力します。
通常、行数しか表示されないためその行まで移動しないと何が原因かわからないところを
SQLを表示するので何でエラーとなったか把握しやすくなります。

□DBから受け取る時、内部表現にしてくれる
MySQL→ mysql_enable_utf8
SQLite→ sqlite_unicode
PostgresSQL → pg_enable_utf8

出力時自動的に内部表現から戻す処理になっているのであれば
DBから受け取る際に内部表現にしとく必要がありますよね。。
その時に役立ちます。


■サンプルコード
地名を5つほど登録して出力するプログラムです。
use strict;
use warnings;
use utf8;
use DBI;
use feature 'say';
binmode STDOUT, ":utf8";

my $database   = ':memory:';
my $connectStr = "dbi:SQLite:dbname=$database";
my $userName   = "";
my $passWord   = "";
my $sql        = "";
my $dbh        = DBI->connect($connectStr,
         $userName,
         $passWord,
         {RaiseError => 1, 
          PrintError => 0, 
          AutoCommit => 1,
          sqlite_unicode => 1
         });
#CreateTable & InsertData
initialize($dbh);

$sql = "SELECT * FROM test";
my $sth = $dbh->prepare($sql);
my $rv  = $sth->execute();

#arrayrefで受け取る場合
#$sth->fetchrow_arrayref()
#$sth->fetch()は、同じメソッド
while (my $row = $sth->fetch()){
 my ($id, $area) = @{$row};
 say "$id:$area";
}

$sth->finish();
$dbh->disconnect();

sub initialize{
 my $dbh = shift;
    
    #テーブルを作成(idとstrの列のみ)
 my $sql = "CREATE TABLE IF NOT EXISTS test(id INTEGER PRIMARY KEY AUTOINCREMENT, area TEXT)";
 $dbh->do($sql);
 
    #str列に名前を登録
 my @list = qw(興部 椴法華村 音威子府 長万部 札幌);
 $sql = "INSERT INTO test (area) VALUES(?)";
 
 for my $area (@list){
  $dbh->do($sql, undef, $area);
 }
}

2013年8月18日日曜日

エラー情報は、発生行?呼出し行 warn/die or carp/croak

まずは、Debugモジュールを作成し、Printするメソッドを定義します。
ただし、Printする文字列がない時つまり引数が渡されなかったらエラーで終了としましょう!

use strict;                                                                              
use warnings;
use utf8;
use feature 'say';
binmode STDOUT, ":utf8";
binmode STDERR, ":utf8";

my $obj = Debug->new();
say $obj->Print("abc");
say $obj->Print("def");
say $obj->Print();
say $obj->Print("ghi");

package Debug;

sub new{                                                                                  
    return bless {}, shift;
}

sub Print{
    my ($self, $str) = @_;
    if (@_!=2){ die("引数を一つだけ指定してください") };

    return $str;
}

これを実行するとエラーで落ちます。
abc
def
引数を一つだけ指定してください at sample.pl line 22.

確かにエラーが発生した行の情報が表示されていて
これはこれで正しいのですが、あちこちから呼ばれる、データがトリガーとなって
エラーとなるケースでは呼び出しもとがどこなのか知りたいですよね。
つまり、呼び出しもとの行数を知りたい。
そんな時は、Carpモジュール。

use strict;                                                                              
use warnings;
use utf8;
use feature 'say';
binmode STDOUT, ":utf8";
binmode STDERR, ":utf8";

my $obj = Debug->new();
say $obj->Print("abc");
say $obj->Print("def");
say $obj->Print();
say $obj->Print("ghi");

package Debug;
use Carp;

sub new{                                                                                  
    return bless {}, shift;
}

sub Print{
    my ($self, $str) = @_;
    if (@_!=2){ croak("引数を一つだけ指定してください") };

    return $str;
}

実行結果:
abc
def
引数を一つだけ指定してください at sample.pl line 11.

引数を一つだけ指定してください。といわれ11行を確認すると
say $obj->Print();
ならば修正するのも楽ですね。

warnの代わりはcarpで
dieの代わりはcroakとなっています。

ロジックに問題の可能性があればwarnやdieを使い
呼び出し元がトリガーとなる場合carpやcroakを適宜使い分ければよいと思います。

2013年8月17日土曜日

マルチバイと文字列の文字列操作のお話

"abc"という文字列が何文字か調べてみましょう。

use strict;
use warnings;
use feature 'say';

my $str = "abc";
say $str. "は、". length($str). "文字です";

実行結果:
abcは、3文字です

問題ありませんね。
では、"あいうえお"が何文字か調べてみましょう。
use strict;
use warnings;
use feature 'say';

my $str = "abc";
say $str. "は、". length($str). "文字です";

実行結果:
あいうえおは、15文字です

はい、15文字ですn…
えぇぇぇぇぇ。それ困ります。

このようにマルチバイト文字列の場合見た目の文字数通りカウントできないため
文字列操作で文字数を絡める場合問題となります。
そのため、見た目の文字数と一致させるためには、
プログラム内部で使用する"内部文字列"という表現に変換してあげる必要があります。
use strict;
use warnings;
use feature 'say';
use utf8;

my $str = "あいうえお";
say $str. "は、". length($str). "文字です";

簡単です。use utf8;とつけただけです。

実行結果:
Wide character in say at t.pl line 7.
あいうえおは、5文字です

5文字と表示されているので正しくカウントされました。
ただ、Wide character in say 〜 としかられました。
これは、何かというと、プログラム内部で扱う文字表現のまま出力するとおこられるのです。
perlの場合互換性を大事にされているので昔のプログラムもそのまま動くように配慮されているため
バージョンがあがってから追加された機能はいろいろ一手間加えてあげないといけないわけです。

そんなわけで一手間加えてみます。
use strict;
use warnings;
use feature 'say';
use utf8;
binmode STDOUT, ":utf8";

my $str = "あいうえお";
say $str. "は、". length($str). "文字です";
binmode STDOUT, ":utf8";
を追加しました。
ターミナルへの出力(いわゆる標準出力)の際に問題となっているので
binmode STDOUTで標準出力を行うもの全てに対し処理が加えられます。
そして、ターミナルはutf8の文字列を受け付けているので:utf8として出力しています。
単純ですね。

内部文字列表現かそうじゃないかは、ぱっとみわからないので判断しづらいかもしれませんが
ともかく、perlに文字列を渡す時に内部文字列に変換して外に出す時に元に戻すということをすればよいわけです。

ここまでのまとめ


次。
エラー処理をしたい時warnやdieを使うと思いますが
use strict;
use warnings;
use feature 'say';
use utf8;
binmode STDOUT, ":utf8";

warn("警告");
die("すっごい致命的なんです");  

実行結果:
Wide character in warn at t.pl line 7.
警告 at t.pl line 7.
Wide character in die at t.pl line 8.
すっごい致命的なんです at t.pl line 8.

出ました!!
エラー系は標準出力と異なるため、STDOUTでは太刀打ちできません。

use strict;
use warnings;
use feature 'say';
use utf8;
binmode STDOUT, ":utf8";
binmode STDERR, ":utf8";

warn("警告");
die("すっごい致命的なんです");
binmode STDERR, ":utf8";
標準エラーを追加しました。

実行結果:
警告 at t.pl line 8.
すっごい致命的なんです at t.pl line 9.

いい感じですね。
まとめです。


さて、他に考えられるI/Oは…
標準入力を考えてみましょう
se strict;
use warnings;
use feature 'say';
use utf8;
binmode STDOUT, ":utf8";
binmode STDERR, ":utf8";

chomp(my $input = );
say $input;

"あいうえお"って入力すると・・・

実行結果:
あいうえお
あã

文字化けを起こしました。
何が起きているか確認してみましょう。

現時点で、外からやってきた文字列は内部の文字列表現になっていません。
それにもかかわらず、binmode STDOUT, ":utf8";があるため
文字列の出力の際に、内部表現だという前提で変換しているため、
おかしなことになったわけです。

もうすでに対策は薄々気づけそうですが

use strict;
use warnings;
use feature 'say';
use utf8;
binmode STDIN,  ":utf8";
binmode STDOUT, ":utf8";
binmode STDERR, ":utf8";

chomp(my $input = );
say $input;
binmode STDIN, ":utf8";
を追加しました

実行結果:
あいうえお
あいうえお

理由がわかれば、簡単ですね。


今度はファイルからのよみこみ書き込みを行ってみます。
まずは読み込み
use strict;
use warnings;
use feature 'say';
use utf8;
binmode STDIN,  ":utf8";
binmode STDOUT, ":utf8";
binmode STDERR, ":utf8";

open my $fh, '<', 'string.txt' or die "$!";
say <$fh>;
close $fh;

尚、string.txt には あいうえお と記載されているだけです。
1行取得して出力した結果は以下の通りです。

実行結果:
あã


また文字化けです。
当然ながら、perlに渡す前に内部表現に変換していないからです。
対応してみます。

use strict;
use warnings;
use feature 'say';
use utf8;
binmode STDIN,  ":utf8";
binmode STDOUT, ":utf8";
binmode STDERR, ":utf8";

open my $fh, '<:encoding(utf8)', 'string.txt' or die "$!";
say <$fh>;
close $fh;

'<' から '<:encoding(utf8)' へ変更しています。 string.txt はutf8で保存されています。 実行結果:

あいうえお

うまくいきました。
ファイルで書き込みを行う時も同じ操作になります。
'>:encoding(utf8)' と言った感じです。




残すは、他のソースコードとのやり取りですね。
つまり、他のモジュールへ文字列を渡したり受け取ったりする時です。
これは、モジュールごとに内部表現で渡すべきか、そうじゃないかは対応がまちまちなので
実際に試してみない限りなんとも言えません。
ケースバイケースで対応しましょ。
内部表現にしたい時は、use Encode; を使い decode_utf8('文字列')
内部表現からもとに戻す時は、use Enocde;を使いencode_utf8('文字列')
とすればよいです。

なお、encode_utf8 はenocde('utf8', '文字列') 別の書き方です。
違うエンコードの時は、encode('文字コード', '文字列')
decode('文字コード', '文字列')としてください。

最後のまとめです。
INとOUTで分けておきました。
これさえ注意すればなんとかなりますね。


基本的な考えとして、use utf8は必ずつける。
外から受け取る時は内部表現に変更する
外へ出す時は、内部表現をやめる
モジュール間でやりとりするときは、そのまま(内部表現)でやりとりする
という感じでよいのではないでしょうか?

2013年8月16日金曜日

リファレンス+do

ただ読み込むだけのもっとも簡単な設定ファイルの仕組みが欲しいのであれば
ファイルに直接リファレンスを書き込みそれをdoしてperlとして解釈するのが一番簡単。

まずは設定ファイルを作成する
ファイル名:config
database = 'fizz'
user     = 'bazz'
pass     = 'fizzbazz'

ファイル名:Sample.pl
use strict;
use warnings;
use utf8;  
use feature 'say';
binmode STDOUT, ":utf8";
use Dumpvalue; my $d = Dumpvalue->new();

my $configFile = 'config';
my $config = do $configFile or die "$!";

say $config->{database};
say $config->{user};
say $config->{pass};

$d->dumpValue($config);

実行結果:
fizz
bazz
fizzbazz
'database' => 'fizz'
'pass' => 'fizzbazz'
'user' => 'bazz'

個人でさくっと読み込みのみの気軽な物が・・・
となるとこれが一番ですかね。

Config::Simple

■簡単な設定ファイルを操作するモジュール

まずは設定ファイルを作成する
ファイル名:config
database = 'fizz'
user     = 'bazz'
pass     = 'fizzbazz'

ファイル名:Sample.pl
use strict;
use warnings;
use utf8;
use feature 'say';
use Config::Simple;
binmode STDOUT, ":utf8";

my $config = Config::Simple->new('./config');
say $config->param('database');
say $config->param('user');
say $config->param('pass'); 

実行結果:
fizz
bazz
fizzbazz

new する時に、ファイルパスを指定し、その後paramメソッドで値を取得することができるようです


ちょっとした物を記録しておき使うのであればこんな感じでよさそうですね。
ただ、同一のキー名を使用したい場合はセクションで区切ることで使い分けができます。
ファイル名:config
[001]
database = 'db001'
user     = 'user001'
pass     = 'pass001'

[002]
database = 'db002'
user     = 'user002'
pass     = 'pass002'

use strict;
use warnings;
use utf8;
use feature 'say';
use Config::Simple;
binmode STDOUT, ":utf8";

my $config = Config::Simple->new('./config');

say $config->param("001.database");
say $config->param("002.database");

実行結果:
db001
db002

今度は、paramメソッドで[セクション名].[キー名]を指定することで値にアクセスできます。

ちなみに、ハッシュ化することも可能で
オブジェクト->vars(); とすればできます。
use strict;
use warnings;
use utf8;  
use feature 'say';
use Config::Simple;
binmode STDOUT, ":utf8";
use Dumpvalue; my $d = Dumpvalue->new();

my $config = Config::Simple->new('./config01');
my $hash   = $config->vars();
$d->dumpValue($hash);

say $hash->{'001.user'};                                                           
say $hash->{'002.user'};

実行結果:
'001.database' => 'db001'
'001.pass' => 'pass001'
'001.user' => 'user001'
'002.database' => 'db002'
'002.pass' => 'pass002'
'002.user' => 'user002'
user001
user002


param関数とかじゃなくて、一発でハッシュに放り込んでくれると便利そうですね。
ということで他のConfig系のモジュールも探してみよう。

2013年8月14日水曜日

perlの変更点が知りたい時

perldoc.jpに翻訳されたものがあるのでそちらを見るとよいと思います。

自分もあれって何のバージョンから変更されたっけ??
となるので、たまに参照します。
ということで、おもだったとこのリンクでもはっておきます。

perl v5.18.0 での変更点
→5.16.0 リリースと 5.18.0 リリースの変更点

perl v5.16.0 での変更点
→5.14.0 リリースと 5.16.0 リリースの変更点

perl v5.14.0 での変更点
→5.12.0 リリースと 5.14.0 リリースの変更点

perl v5.12.0 での変更点
→5.10.0 リリースと 5.12.0 リリースの変更点

perl 5.10.0 での変更点
→5.8.8 リリースと 5.10.0 リリースとでの相異点


ラクダ本や、リャマ本を読んでようやく、スタートラインにたち、
ネットから最新情報を取得してがんばろう!
とした矢先に あれ?この記述なんだ??
みたいなことはよくあるものですよね。
おまけに、ググラビリティが低くて見つかりにくいのです…
そんなわけで、気になったものを簡単に羅列してみます。

■+{} 波括弧の前のプラス
波括弧には、
関数を囲うためとハッシュのリファレンスの2通り使われるため
perlにこれはハッシュのリファレンスのための波括弧なんですよ。
ってことを知らせるために、+{}としている

■use 5.010; or use feature ':5.10'; or use feature 'say'

print "値¥n";
とした場合、いちいち改行文字なんぞくっつけていたと思いますが
say "値";
say関数であれば、改行文字を自動的にくっつけてくれる便利さんなんです。
という話をどこかでみかけたと思います。
そして、say関数を使うにはどうすればよいの?
とあちこち眺めてみると上記の記述にでくわすわけです。

use feature 'say';
は、5.10で追加になったsayという関数のみ使えるようにする記述の仕方

use feature ':5.10';
は、5.10で追加になった機能すべて使えるようにする記述

use 5.0.10;
は、5.10で追加になった機能すべて使えるようにして
かつそれ未満のバージョンのperlで実行しようとしたらすぐにエラーとなり停止させれます。

つまり、明示的にsayって関数だけがつかいたいだけなんですよ!!
他、あるかしらんしー
sayだけあれば幸せだしー
という場合は、use feature 'say';
とかけば、他の人が読んでも 他のものはつかっていないことがはっきりします。

ただ、5.12以降を指定すると use strictしたのと同等になり
いちいちuse strict; と書かなくてすむので
use 5.0.12; のように記述するのを好む人もいると思います。

という感じですかね。