2008年6月30日 星期一

stored procedure


Stored procedure
http://dev.mysql.com/doc/refman/5.0/en/stored-procedures.html

什麼是stored procedure
stored procedure是由一群sql敘述及流程控制的語法組成,本質上就是mysql中的程式語言,只是stored procedure的程式語法,跟php的不一樣,能使用的環境只限於mysql資料庫中,專為處理資料庫的操作,為了處理資料庫中的資料,有一些語法與php這種泛用型的程式語言,語法方面就稍微有些不同。就像戰機有雖然都有戰鬥功能,但為了不同的目的,有不同設計,以發揮團隊作戰的整體力量,有的戰機是專用對付入侵的戰機,是屬於空優型戰機,有的戰機,武裝不強,但體型龐大,有匿蹤功能,可以攜帶大量彈藥轟炸敵軍,屬於轟炸機。
mysql的stored procedure的語法遵循SQL:2003的語法標準,IBM的DB2也一樣遵循這套標準,遵循相同的標準有相當明顯的好處,如果要轉換資料庫,stored procedure不同重新撰寫,除錯,可以輕易的進行轉換,不會因為換了資料庫,必須重頭學習另一種語法,所有的基礎重頭來過。
使用stored procedure可以減少php程式碼的數量並增加程式的效能,php程式中只要呼叫stored procedure,stored procedure便會執行stored procedure中所有的sql敘述,並將結果傳回php程式中,php不用再一一呼叫sql statement,執行各自獨立的sql statement,再一一判斷結果,進行處理,這些sql statement及判斷統統都包裝在stored procedure裡頭,php及mysql之間傳遞的資料數量大幅減少,由於php與mysql之間等待資料傳送的時間及次數減少,可以加快程式的速度,也由於只要呼叫stored procedure,而不用呼叫各自獨立的sql statement,也減少了程式碼的數量。
目前,stored procedure不支援遞迴呼叫,如果要使用遞迴呼叫,必須將mysql變數:max_sp_recursion_depth 設定為非零的數字,預設是零,不允許遞迴,設為1,允許遞迴一次,設為2,允許遞迴二次,以此類推。

stored procedure的管理
stored procedure都存在mysql資料庫中的proc資料表中(mysql.proc),這個資料表儲存所有的stroed procedure,這些資料透過管理工具,可以運作正常,但如果手動修改,就有可能發生不可預期的後果,絕對不要手動修改這些資料,而是要透過create procedure,alter proceudre或是drop procedure來進行管理。


要建立stored procedure就必須有create routine的權限

要修改stored procedure就必須有alter routine的權限

要執行stored procedure就必須有execute的權限

有create routine的權限就自動會有alter routine及execute的權限

建立stored procedure的語法
create [definer={usercurrent_user}] procedure sp_name([sp_parameter],[sp_parameter]...)
[characteristic]
stored procedure body
[characteristic]
language sql
目前沒有作用
[not] deterministic
如果沒設,預設為 not deterministic
{ CONTAINS SQL NO SQL READS SQL DATA MODIFIES SQL DATA }
目前沒有強制性,mysql只會當做註解
sql security {definerinvoker}
預設為definer
comment '註解內容'

修改
alter procedure sp_name ...

刪除
drop procedure sp_name;

執行stored procedure
call 預存程序名稱(參數);
以實際範例操作會比較清楚。
/**
*建立store procedure,名稱為sp_test
*/
delimiter //
create definer=current_user procedure sp_test(out version varchar(60),out day varchar(60))
language sql
not deterministic
contains sql
sql security definer
begin
select version() into version;
select curdate() into day;
end;
//
delimiter ;
/**
*執行stored procedure,stored procedure的名稱為sp_test
*/
call sp_test(@version,@day);
select @version,@day;

變數的處理
變數宣告
declare i int default 1;
變數的設定
set i=3;
select 4*5 into i;

例外處理
例外訊息列表
http://dev.mysql.com/doc/refman/5.0/en/error-messages-server.html
看下面的實例比較快
delimiter //
create procedure sp_h()
begin
declare no_table condition for 1146;
declare continue handler for no_table
begin
select 'hi!! world!';
end;
select * from a;
end
//
delimiter ;

cursor
只能讀
只能單向移動,不能跳過任一列
create procedure test()
begin
declare var_name varchar(30);
declare cur1 cursor for select name from a;
declare exit handler for sqlstate '02000' begin end;
open cur1;
repeat
fetch cur1 into var_name;
select var_name;
until 0 end repeat;
close cur1;
end;

宣告的順序
變數及condition
cursor
handler

流程控制
mysql的stored procedure流程控制語法跟一般的程式語言相同,有迴圈及判斷式的二類。迴圈及判斷式的用法與php的用法相同,只是語法不一樣,寫幾個具體範例熟悉一下迴圈及判斷式的語法會比較較快進入狀況,manual上的說明,範例太少了,自己出幾個題目,動手寫一些小程式會比較快熟悉stored procedure的迴圈判斷式的語法。
Mysql的stored procedure的iterate及leave是用在迴圈的語法,iterate的功能與php中的continue相同,leave就跟php中的break相同,都是控制迴圈進行的語法,讓程式能依照判斷式跳出迴圈或繼續重頭執行迴圈中的程式碼。
底下是使用stored procedure的語法寫的簡易程式,最好是使用記本先打好,再匯入mysql或是使用mysql的圖形化介面的管理工具,如navicat、Mysql Administrator,進行stored procedure的撰寫,才會比較快速有效率,不然是很難進行除錯的。工欲善其事,必先善其器,好的工具及方法,可以加快你進入stored procedure世界的速度。
程式很簡單,只要有程式基礎,看一下manual就知道程式是怎麼運作,但學習方法比較容易被忽略,如果只是看看過去,沒有實際撰寫的話,不容易深入了解stored procedure的運作。最好自己出題,實際寫幾個stored procedure,搭配manual上的說明,才能對stored procedure有完整的認識。
以loop迴圈寫的九九乘法表
delimiter //
create procedure sp_a()
begin
declare i int default 1;
declare j int default 1;
label_a: loop
if i<=9 then
set j=1;
label_b: loop
if j<=9 then
select i,j,i*j;
set j=j+1;
iterate label_b;
end if;
leave label_b;
end loop label_b;
set i=i+1;
iterate label_a;
end if;
leave label_a;
end loop label_a;
end;
//
delimiter ;


以repeat迴圈寫的九九乘法表
delimiter //
create procedure sp_b()
begin
declare i int default 1;
declare j int default 1;
repeat
set j=1;
repeat
select i,j,i*j;
set j=j+1;
until j>9 end repeat;
set i=i+1;
until i>9 end repeat;
end;
//
delimiter ;


以while迴圈寫的九九乘法表
delimiter //
create procedure sp_c()
begin
declare i int default 1;
declare j int default 1;
while i<=9 do
set j=1;
while j<=9 do
select i,j,i*j;
set j=j+1;
end while;
set i=i+1;
end while;
end;
//
delimiter ;


if判斷式的使用
delimiter //
create procedure sp_d(in age int)
begin
if age<18 then
select 'you are young man';
elseif age>=18 and age<40 then
select 'you have many life experiences';
else
select 'you ard old man';
end if;
end;
//
delimiter ;


case判斷式的使用
delimiter //
create procedure sp_e(in selector int)
begin
case selector
when 1 then
select 'yellow';
when 2 then
select 'black';
when 3 then
select 'red';
else
select 'no color selected';
end case;
end;
//
delimiter ;

2008年6月28日 星期六

看chm

在ubuntu上看chm檔的工具

apt-get install gnochm

大易輸入法

ubuntu上內定的輸入工具scim,大易輸入法是使用三碼輸入,跟我習慣的四碼輸入不太一樣,要重頭適應,實在是很痛苦.

上網找了一下資料,把scim換成gcin,就可以用四碼的大易輸入法了.但是標點符號,還是一樣沒辦法用大易四碼的方式打出來,輸入標點符號挺麻煩的.

scim換成gcin的步驟如下

apt-get install gcin
im-swith -s gcin
登出
重新登入

2008年6月24日 星期二

上傳檔案的設定

http://www.php.net/manual/en/features.file-upload.common-pitfalls.php

php相關設定
upload_max_filesize
memory_limit
max_execution_time
post_max_size

mysql相關設定
max_allowed_packet

撰寫上傳物件的問題

想要寫一個檔案上傳的物件,以方便以後程式碼的重複使用,不必重造輪子,做相同的事情,仔細思考很久,如果上傳的檔案都存在目錄裡,還必須考量到檔案備份、程式碼中檔案上傳的安全性,使用者上傳的檔案名稱也必須做管控,才能避免檔案重複,造成檔案彼此覆蓋的問題,因此決定將上傳物件,寫成將插入資料庫的型式,如果以物件導向的模式方式思考,其實應該實做一個interface或abstract,再分別撰寫要上傳至資料庫的物件,及上傳至目錄的物件,這樣才能顧及程式的擴展性及可維護性,不過現在沒想那麼多,先能用就好了。

檔案存到資料庫中,只要必份資料庫並小心權限設定,程式碼使用prepared statement操控資料庫,就可以避掉sql injection及檔案備份的問題,以後只要備份資料庫就可以了,而不用再備份檔案,也不用再花心思控管上傳檔案的安全性。比較省事。

上傳物件,在網路上有許多現成的程式碼可以參考,各種寫法看了讓人頭昏眼花,參考了php manual後,最後決定使用file_get_contents函數進行檔案的處理,這個函數是php manual上建議使用的函數,雖然php 6.0以後才會完整支援所有的參數,但轉換檔案為string時,這個函數的轉換效率是最好的。

如果使用fopen開啟檔案,再使用read()函數進行讀取,要記得加上參數'rb',如fopen(檔案,'rb'),因為windows系統對待binary跟文字檔是不同的,所以要記得加b,操作時才不會出錯。

測試上傳物件時,檔案比較大的物件上傳時,都會被切成只有64k,找程式碼找了半天,也找不到錯誤,後來,在mysql manual上找到原因了,blob的儲存上限是64k,mediumblob是16M,bigblob是4g,改欄位的資料型態就一切正常了。

另一個在測試時碰到的問題,就是中文檔名下載時會出會錯誤,只要使用urlencode()函數將檔名重新編碼,就不會有這個問題了。

2008年6月23日 星期一

多媒體播放器

在pchome上看到的二套多媒體播放器,中文檔名不會出現亂碼

看影片

smplayer



聽mp3

偏好設定的部份,取消「使用點陣字」勾選,就可顯示中文

audacious

2008年6月18日 星期三

Zend_Acl_Role的設計




/**

* 程式碼是以陳瑩光老師新版學務系統的認證模組改寫的。

*/




要實做Zend_Acl_Role的多重繼承架構,有四個區塊的觀念必須弄清楚,才能把整個架構完整的實做,這四個區塊都弄楚了,程式的實做就是水到渠成的事情。




第一個要弄清楚的區塊就是Zend_Acl_Role的多重繼承觀念,Zend_Acl_Role的繼承關係,跟Zend_Acl_Resource不一樣,Zend_Acl_Resource只能有一個parent,但Zend_Acl_Role卻可以有很多個parent,就像現實生活中的人物一樣,每個人都身兼多重身份,Zend_Acl_Role也可以同時繼承多個parent,與一個實例說明:假設一個Zend_Acl_Role的身份是專案經理,同時繼承了程式設計師、架構設計師及資料庫規畫師的身份,那麼專案經理就同時繼承了指定給這三個身份的所有資源。




第二個要弄清楚的區塊是資料庫結構,如果不使用資料庫,而將Zend_Acl_Role的資料寫在程式碼中,程式碼會變得很冗長,要一行一行coding,不是用幾個迴圈就可以解決的,也難以維護,如果資料非常龐雜,不但程式碼的行數會呈等比級數增加,也會很難搞清楚其中的繼承關係,程式也容易寫錯,為了一勞永逸,最好將這些資料都存入資料庫進行維護管理。存成xml或是ini檔也是可以解決部份的問題,不過資料如果太複雜,也會造成難以維護的結果,最好的解決辦法,就是使用資料庫。




第三個要弄清楚的就是陣列結構,使用陣列結構,只要從資料庫中抓一次資料,將資料轉為陣列結構,在後續的程序中直接使用陣列結構進行資料的操作,如果不使用陣列,那麼後續的程序,會不斷的讀取資料庫,雖然不能說這樣的作法有錯,但資料庫的資料都是存放在硬碟中的,每次讀取資料庫中的資料都會讀取硬碟,而陣列是存放在記憶體裡頭,在速度上,記憶體比硬碟快很多,因此,陣列的操作速度會比資料庫的操作速度快,為了加快速度,最好儘量使用陣列結構進行程式資料的存取。




第四個區塊就是程式的邏輯,這部份要花費很大的心思才能搞懂,如果弄懂了,程式的實做就很快了,只要照著你的思考順序,一步步的將解決方式寫出來就可以建構完整的程式碼了。




用freemind畫了四個區塊的架構圖,以後如果忘記了,不用看程式碼,也不用看說明,看這張圖就一目了然了。



以Zend Framework實做Zend_Acl_Role的雛型


class AclController extends Zend_Controller_Action{
public function init(){
$this->_helper->viewRenderer->setNoRender();
Zend_Loader::loadClass('Zend_Acl');
Zend_Loader::loadClass('Zend_Acl_Role');
Zend_Loader::loadClass('Zend_Acl_Resource');

$acl=new Zend_Acl();
Zend_Registry::set('acl',$acl);
}

public function roleAction(){

$baseDir=Zend_Registry::get('baseDir');

$config=new Zend_Config(require($baseDir.'/application/etc/db.php'));

$db=Zend_Db::factory($config->public);

$db->query('set names utf8');

$sql='select a.id,b.parent_id from acl_role as a left outer join acl_inheritance as b on a.id=b.child_id';
$stmt=$db->query($sql);
$rows=$stmt->fetchAll();

/**
* 將資料轉為所需的陣列結構
* Zend_Acl_Role註冊時所需的陣列結構
*/
$roles=array();
foreach($rows as $row){
if(!isset($roles[$row['id']]))
$roles[$row['id']]=array();
if(isset($row['parent_id']))
$roles[$row['id']][]=$row['parent_id'];
}

$acl=Zend_Registry::get('acl');
do{
$flag=false;

/**
* 註冊role
*/
foreach($roles as $key=>$role){
if(!$acl->hasRole($key) && (!isset($role) || $this->hasAllRole($role))){
$acl->addRole(new Zend_Acl_Role($key),$role);
}
/**
* 檢查第一維的key是否都己註冊為role
* 是---離開
* 否---繼續
*/
if(!$acl->hasRole($key))
$flag=true;
}
}while($flag);

}

public function hasAllRole(array &$role){
$acl=Zend_Registry::get('acl');
foreach($role as $parent_id){
if(!$acl->hasRole($parent_id))
return false;
}
return true;
}

}