2010年2月アーカイブ

前記事(http://www.igreks.jp/dev/2010/01/mysql.html)のようにALTER TABLEを使って更新するのもいいのだが、できれば構造のテンプレートに合わせて一発でコピーしたい。

ただ単にテーブルの構造とデータをコピーする場合、普通は、

---------------------------------------------------------------------------------------
CREATE TABLE sample_new LIKE sample_original
INSERT INTO sample_new SELECT * FROM sample_original
---------------------------------------------------------------------------------------


しかし、新しい構造のテーブルにあわせて、構造を変更しながらデータをコピーするといった方法が見つからないため、スクリプトの処理を合わせて実現してみる。
あんまりスマートじゃないけど・・・

まず、最新の構造のテンプレートを作っておく。


■最新のテーブル構造のテンプレート(tbl.pl)
---------------------------------------------------------------------------------------

package tbl;

sub conf{
my %tbl = (

'table_a' => {
'column' => '
id INT AUTO_INCREMENT PRIMARY KEY,
column_1 VARCHAR(36),
column_2 VARCHAR(255),
column_3 VARCHAR(255)
',
'option' => '
ENGINE = MyISAM DEFAULT CHARSET = utf8
'
},

'table_b' => {
'column' => '
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
column_1 INT,
column_2 VARCHAR(255),
column_3 VARCHAR(255)
',
'option' => '
ENGINE = MyISAM DEFAULT CHARSET = utf8
'
},



);
return \%tbl;
}
1;

---------------------------------------------------------------------------------------


テンプレートを読み込んで新しいテーブルを一時作成し、
そのテーブルに既存のテーブルのデータをインポートする。

■更新処理(tbl_update.pl)
---------------------------------------------------------------------------------------
#!/usr/bin/perl

package::main;

my $dbh;



# この辺でデータベースへの接続処理(詳細割愛)

print "Content-type: text/html\n\n";

print "<p>データベースの再構築を開始します</p>\n";

# 既存のテーブル名をリストで取得
my $sth = $dbh->prepare("SHOW TABLES LIKE 'hoge_%'") || die $dbh->errstr();
$sth->execute();
my @hoge_tbl = ();
while(my @tbl = $sth->fetchrow_array()){
push(@hoge_tbl,$tbl[0]);
}

# テンプレート読み込み
require "./tbl.pl";
my $tbl = &tbl::conf(); # ※リファレンスで受け取り

foreach(@hoge_tbl){

# 既存テーブルのカラム名をリストで取得
my @cols = ();
my $sth = $dbh->prepare("DESCRIBE $_") || die $dbh->errstr();
$sth->execute();
while(my $href = $sth->fetchrow_hashref()){
push(@cols,$href->{'Field'});  # unshiftしちゃだめよ
}
my $cols = join(',',@cols);

#####################################
# テーブル名の末尾に個別にIDとかが付いてなければこの処理はいらない
$_ =~ /hoge_(.+?)(_.+?)?$/;
my $name = $1;
######################################

# 新しい構造の空テーブルを「pre_***」という名前で一旦作成
my $sth = $dbh->prepare("
CREATE TABLE IF NOT EXISTS pre_$_ (
$$tbl{$name}{'column'}
) $$tbl{$name}{'option'}
") || die $dbh->errstr();
$sth->execute();

# 既存のテーブルのデータを、カラム名を明示的に指定して挿入
# これをしないとカラムの数が合いませんよ!と怒られる
my $sth = $dbh->prepare("INSERT INTO pre_$_ ($cols) SELECT * FROM $_"); # ※1
$sth->execute();

# 既存のテーブル削除
my $sth = $dbh->prepare("DROP TABLE IF EXISTS $_");
$sth->execute();

# 新しいテーブルのリネーム("pre_"をとる)
my $sth = $dbh->prepare("ALTER TABLE pre_$_ RENAME $_");
$sth->execute();

}

print "<p>データベースの再構築が完了しました。</p>\n";

exit;
---------------------------------------------------------------------------------------


以上。

新しいテーブルにカラムが追加されてる場合は問題ないが、カラムが削除された場合は、多分※1のところでエラーになる・・・

こういうの一発でできるSQLないのかなぁ。

配列関係の重複チェックは一度ハッシュ(%check)に入れると簡単


-----------------------------------------------------------------------------------------

my %hash = (
a => 'あああ',
b => 'いいい',
c => 'ううう',
d => 'あああ',
e => 'おおお',
f => 'あああ'
);

my %check = ();

for(values %hash){
die "$_ が重複してるよ!" if $check{$_};
$check{$_} = 1; #### 1とかaとか好きなの入れる
}

-----------------------------------------------------------------------------------------
LWPを使ったhttpレスポンスの取得には、HTTP::Request(::Common)モジュールを使ってリクエストのオブジェクトを作っておくと、レスポンス情報を確認するさまざまなメソッドを使用することができる。

HTTPモジュール群にはまず「HTTP::Message」があり、このオブジェクトには幾つかのヘッダとコンテント(ボディ)取得用のクラスが入ってるが、このクラスは抽象クラスである。
つまりHTTP::RequestとHTTP::Responseのための基本クラスとしてだけ使われ、それ自身のインスタンスは生成されることはない。

「HTTP::Response」はHTTP形式のレスポンスをカプセル化するクラスである。
このクラスのインスタンスは通常、LWP::UserAgentオブジェクトのrequestメソッドによって作成され返される。

また、「HTTP::Headers」は同様にHTTP形式のメッセージヘッダをカプセル化するクラスである。
このクラスのインスタンスは通常、HTTP::RequestやHTTP::Responseクラスのメンバー変数として、ライブラリ内部用に作成される。

つまり、

my $url = 'http://yahoo.co.jp';
my $ua = LWP::UserAgent->new();
my $req = &HTTP::Request::Common::GET($url);
my $res = $ua->request($req);

のように、レスポンス用オブジェクト($res)生成しておけば、上の3つのモジュールで定義されているメソッドは全てこのオブジェクト($res)から以下のように参照することができる。

---------------------------------------------------------------------------------------------------

(HTTP::Message)

■$res->protocol([$proto])
・・・そのメッセージに使われるHTTPプロトコルを設定する。protocol()は"HTTP/1.0"または"HTTP/1.1"のような文字列が返る。

■$res->content([$content])
・・・引数が与えられていれば、コンテントを設定する。引数が無ければ、コンテントは触れられずに返される。

■$res->add_content($data)
・・・前のコンテントの末尾にさらにデータを追加する。

■$res->content_ref
・・・コンテント文字列へのリファレンスを返す。
コンテントが巨大であれば、この方法が効率的。また、直接操作が出来る。
例:${$res->content_ref} =~ s/\bfoo\b/bar/g;

■$res->headers;
・・・HTTP::Headersオブジェクトを返す。

■$res->headers_as_string([$endl])
・・・HTTP::Headers->as_stringを呼び出す。

(HTTP::Headers)

■$res->header($field [=> $value],...)
・・・ヘッダの値を取得または設定する。
ヘッダ・フィールド名は大文字/小文字を区別しない。
引数が無い場合は複数の($field => $values)の組を受け取る。
$valueを複数設定することにより1回の呼び出しでたくさんのフィールドを更新することも可能。$valuesにはスカラまたはスカラのリストへのリファレンスを指定することができる。
$valueが未定義だったり指定されていなければヘッダは変更されない。

複数の値を持つフィールドは、スカラ・コンテキストでは","を区切り文字としてつなげられ返される。

(例)
 $header->header(MIME_Version => '1.0',
      User_Agent => 'My-Web-Client/0.01');
 $header->header(Accept => "text/html, text/plain, image/*");
 $header->header(Accept => [qw(text/html text/plain image/*)]);
 @accepts = $header->header('Accept');

■$res->scan(\&doit)
・・・ヘッダそれぞれにサブルーチンを適用する。コールバック・ルーチンは2つのパラメータ(フィールド名と1つの値)で呼び出される。
ヘッダが1つ以上の値を持っていれば、ルーチンはそれぞれの値につき1回呼ばれる。コールバック・ルーチンに渡されるフィールド名はHTTP仕様で提案されている大文字/小文字をもち、推奨されている"Good Practice"の順でやってくる。

■$res->as_string([$endl])
・・・フォーマットされたMIMEヘッダとしてヘッダ・フィールドを返す。
文字列を組み立てるために内部でscan()メソッドを使っているので、結果はHTTP仕様で提案されている大文字小文字で、ヘッダ・フィールドの順序の推奨されている"Good Practice"に従う。
長いヘッダの値はたたまれない。

オプションのパラメータには使用する行末シーケンスを指定できる。デフォルトは"\n"。
この場合、ヘッダに埋め込まれた"\n"文字は、この行末シーケンスで置きかえられる。

■$res->push_header($field, $val)
・・・指定されたヘッダに新しいフィールドの値を追加する。
ヘッダ・フィールド名は大文字小文字を区別しない。
同じフィールド名で、値を持たずに指定した場合、前の値が残る。
引数にはスカラやスカラのリストへのリファレンスを指定することが出来る。

(例)$header->push_header(Accept => 'image/jpeg');

■$res->remove_header($field,...)
・・・指定された名前を持つヘッダを削除する

(HTTP::Response)

■$res->code([$code])
・・・レスポンスコード(301など)を返す。

■$res->message([$message])
・・・レスポンスメッセージを返す。

■$res->request([$request])
・・・このレスポンスを発生したリクエストのリファレンスを返す。
ここまでの間に、リダイレクトや認証のリトライがある場合もあるので、$ua->request()メソッドに渡されたものと同じリクエストである必要はない。

■$res->previous([$previousResponse])
・・・最初のレスポンスがリダイレトクまたは認証されていなければ、レスポンスのチェーンを返す。

■$res->status_line
・・・文字列"$res->code + $res->message"を返す。
もしmessage属性が設定されていなければ、code(HTTP::Statusを参照にした)の公的な名前に置き換えられる。

■$res->base
・・・レスポンスのベースURLを返す。
戻り値はURIオブジェクトへのリファレンス。

ベースURLは以下のいずれかの情報源から1〜3の優先順で取得される。

 1. ドキュメント内容に埋め込まれたもの:例えばHTMLドキュメント内での
 2. レスポンスでの"Content-Base:"または"Content-Locatin:"ヘッダ
  古いHTTP実装との互換性のため、"Base:"ヘッダも探す。
 3. このレスポンスを要求したURL。
  これはレスポンスの前に、いくつかのリダイレクトを受信しているかもしれないため、$ua->request()メソッドに渡された元のURLでない場合もある。

■$res->as_string
・・・コンテントのプレーンテキストを返す。
主にデバッグのために使われる。引数は指定できない。

■$res->is_info
■$res->is_success
■$res->is_redirect
■$res->is_error
・・・レスポンスが情報的(informational)であるか、成功したか、リダイレクトであるか、エラーであるかを返す。
(偽ならおそらくundefか0を返す)

■$res->error_as_HTML()
・・・何のエラーが発生したかを示す完全なHTMLドキュメントが入っている文字列を返す。
このメソッドは$res->is_errorがTRUEのときだけ呼ぶべき。

■$res->current_age
・・・ section 13.2.3.によって指定されたレスポンスの"現在の年齢"("current age")を計算する。
レスポンスの年齢は元のサーバサーバーによって送信されてからの時間。
返される値は累計秒。

■$res->freshness_lifetime
・・・ section 13.2.4.で指定されたそのレスポンスの"新鮮期間"("freshness lifetime")を計算する。
"新鮮期間"はレスポンスが生成されてから終了するまでの時間の長さ。
返される値は累計秒。

■$res->is_fresh
・・・freshness_lifetime()とcurrent_age()の値をベースに、レスポンスが新鮮であればTRUEを返す。
レスポンスがもはや新鮮でなければ、もう一度取り出されるか、元のサーバによって再評価されるべきである。

■$res->fresh_until
・・・このエンティティがもはや新鮮ではない時刻を返す。


------------------------------------------------------------------------------------------------------

参考URL:
http://homepage3.nifty.com/hippo2000/perltips/HTTP/Headers.html
http://homepage3.nifty.com/hippo2000/perltips/HTTP/Message.html
http://homepage3.nifty.com/hippo2000/perltips/HTTP/Response.html
----------------------------------------------------------------------------------------------

#!/usr/bin/perl -w

use strict;
use LWP::UserAgent;
use HTTP::Request::Common;

# オブジェクト作成
my $ua = LWP::UserAgent->new();
my $url = 'http://yahoo.co.jp';
my $req = &HTTP::Request::Common::GET($url);

# レスポンスを得る
my $res = $ua->request($req);

# フィールド名を指定してヘッダを取得
my $con_type = $res->header('Content-Type');

#処理
if($con_type =~ /shift_jis/i){
  # (sjis用エンコーディング処理)
}
elsif($con_type =~ /euc-jp/i){
  # (ujis用エンコーディング処理)
}
elsif($con_type =~ /utf-8/i){
  # (utf8用エンコーディング処理)
}
  ・
  ・
  ・

----------------------------------------------------------------------------------------------


※charsetをダイレクトに返してくれるメソッドはどうやら無いらしい・・・
あったら教えてください。
多くの参考サイトや本では、LWPモジュール群を利用してのSSLアクセスには「Crypt::SSLeay」モジュールが必要と説明されているが、どうやら「Net::SSLaey」モジュールがあれば可能なようだ。

もちろんサーバにSSL環境(OpenSSLとかmodSSLとか)があるっていうのが前提ではあるが。

OpenSSLとLWPのつなぎ役をしてくれるのがCrypt::SSLeayだが、もともと、OpenSSLとSSLeayとの高度な機能インタフェースを提供するモジュールが「Net::SSLaey」モジュールである。


以下確認作業

■まず適当なスクリプトでPerlの@INCの中身を確認----------------------------------------------------------------------------------------------
#!/usr/bin/perl

print "Content-type: text/plain\n\n";

foreach(@INC){
print $_.'\n';
}

exit;
----------------------------------------------------------------------------------------------

■結果↓

----------------------------------------------------------------------------------------------
/usr/local/lib/perl5/site_perl/5.10.0/i386-linux-thread-multi
/usr/local/lib/perl5/site_perl/5.10.0
/usr/lib/perl5/vendor_perl/5.10.0/i386-linux-thread-multi
/usr/lib/perl5/vendor_perl/5.10.0
/usr/lib/perl5/vendor_perl
/usr/lib/perl5/5.10.0/i386-linux-thread-multi
/usr/lib/perl5/5.10.0
/usr/lib/perl5/site_perl
.
----------------------------------------------------------------------------------------------

■次に、Crypt::SSLeayとNet::SSLeayが入っているか確認↓

----------------------------------------------------------------------------------------------
(コマンド)
$ su
# cd /

# find -path *Crypt/SSL*

#
(↑Crypt::SSLeayは入っていない)

# find -path *Net/SSL*

./usr/lib/perl5/vendor_perl/5.10.0/i386-linux-thread-multi/Net/SSLeay
./usr/lib/perl5/vendor_perl/5.10.0/i386-linux-thread-multi/Net/SSLeay/Handle.pm
./usr/lib/perl5/vendor_perl/5.10.0/i386-linux-thread-multi/Net/SSLeay.pm
./usr/lib/perl5/vendor_perl/5.10.0/i386-linux-thread-multi/auto/Net/SSLeay
./usr/lib/perl5/vendor_perl/5.10.0/i386-linux-thread-multi/auto/Net/SSLeay/https_cat.al
./usr/lib/perl5/vendor_perl/5.10.0/i386-linux-thread-multi/auto/Net/SSLeay/do_https4.al
./usr/lib/perl5/vendor_perl/5.10.0/i386-linux-thread-multi/auto/Net/SSLeay/get_httpx3.al
./usr/lib/perl5/vendor_perl/5.10.0/i386-linux-thread-multi/auto/Net/SSLeay/get_http.al
./usr/lib/perl5/vendor_perl/5.10.0/i386-linux-thread-multi/auto/Net/SSLeay/put_http.al
./usr/lib/perl5/vendor_perl/5.10.0/i386-linux-thread-multi/auto/Net/SSLeay/want_read.al
   ・
   ・
   ・
(以下省略)

(↑@INCでは「/usr/lib/perl5/vendor_perl/5.10.0/i386-linux-thread-multi」にNet::SSLeayが入っている)
----------------------------------------------------------------------------------------------

■試しに名前を変えてみる↓

----------------------------------------------------------------------------------------------
# cd /usr/lib/perl5/vendor_perl/5.10.0/i386-linux-thread-multi/Net
# mv SSLeay.pm SSLeay.pm2
----------------------------------------------------------------------------------------------

■スクリプト↓でhttpsにリクエストしてみる

----------------------------------------------------------------------------------------------
#!/usr/bin/perl

use strict;
use LWP::UserAgent;
use HTTP::Request::Common;

my $ua = LWP::UserAgent->new();

my $url = "https://www.hogehoge.jp"; # 実在するセキュアサイト

$request = &HTTP::Request::Common::GET($url);
$response = $ua->request($request);

print "Content-type: text/plain\n\n";
print $response->status_line;

exit;
----------------------------------------------------------------------------------------------

■実行結果↓

----------------------------------------------------------------------------------------------

501 Protocol scheme 'https' is not supported

(httpsプロトコルはサポートしていません)
----------------------------------------------------------------------------------------------

■SSLeay.pmを元に戻す↓

----------------------------------------------------------------------------------------------
# mv SSLeay.pm2 SSLeay.pm
----------------------------------------------------------------------------------------------

■さっきのスクリプトをもう一度実行↓

----------------------------------------------------------------------------------------------

302 Found

(ファイルが存在します=接続成功)
----------------------------------------------------------------------------------------------


めでたしめでたし。

このアーカイブについて

このページには、2010年2月に書かれたブログ記事が新しい順に公開されています。

前のアーカイブは2010年1月です。

次のアーカイブは2010年3月です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

ウェブページ

Powered by Movable Type 4.22-ja