基於mysql的分頁程序完全解決方案(含普通分頁/分段分頁/原始分頁/微博的since_id類分頁)

Author: selfimpr

Blog: http://blog.csdn.net/lgg201

Mail: [email protected]

Copyright: 轉載請註明出處


0. 下載:

本程序可自由修改, 自由分發, 可在http://download.csdn.net/user/lgg201下載
1. 分頁的需求
信息的操縱和檢索是當下互聯網和企業信息系統承擔的主要責任. 信息檢索是從大量的數據中找到符合條件的數據以用戶界面展現給用戶.
符合條件的數據通常會有成千上萬條, 而用戶的單次信息接受量是很小的, 因此, 如果一次將所有符合用戶條件的數據展現給用戶, 對於多數場景, 其中大部分數據都是冗餘的.
信息檢索完成後, 是需要經過傳輸(從存儲介質到應用程序)和相關計算(業務邏輯)的, 因此, 我們需要一種分段的信息檢索機制來降低這種冗餘.
分頁應運而生.
2. 分頁的發展
基本的分頁程序, 將數據按照每頁記錄數(page_size)將數據分爲ceil(total_record / page_size)頁, 第一次爲用戶展現第一段的數據, 後續的交互過程中, 用戶可以選擇到某一頁對數據進行審閱.
後來, 主要是在微博應用出現後, 由於其信息變化很快, 而其特性爲基於時間線增加數據, 這樣, 基本的分頁程序不能再滿足需求了: a) 當獲取下一頁時, 數據集可能已經發生了很多變化, 翻頁隨時都可能導致數據重複或跳躍; b) 此類應用採用很多采用一屏展示多段數據的用戶界面, 更加加重了數據重複/跳躍對用戶體驗的影響. 因此, 程序員們開始使用since_id的方式, 將下一次獲取數據的點記錄下來, 已減輕上述弊端.
在同一個用戶界面, 通過用戶閱讀行爲自動獲取下一段/上一段數據的確比點擊"下一頁"按鈕的用戶體驗要好, 但同樣有弊端: a) 當用戶已經到第100頁時, 他要回到剛纔感興趣的第5頁的信息時, 並不是很容易, 這其實是一條設計應用的規則, 我們不能讓用戶界面的單頁屏數過多, 這樣會降低用戶體驗; b) 單從數據角度看, 我們多次讀取之間的間隔時間足夠讓數據發生一些變化, 在一次只展示一屏時, 我們很難發現這些問題(因此不影響用戶體驗), 然而當一頁展示100屏數據時, 這種變化會被放大, 此時, 數據重複/跳躍的問題就會再次出現; c) 從程序的角度看, 將大量的數據放置在同一個用戶界面, 必然導致用戶界面的程序邏輯受到影響. 基於以上考慮, 目前應用已經開始對分頁進行修正, 將一頁所展示的屏數進行的限制, 同時加入了頁碼的概念, 另外也結合since_id的方式, 以達到用戶體驗最優, 同時保證數據邏輯的正確性(降低誤差).
3. 分頁的討論
感謝xp/jp/zq/lw四位同事的討論, 基於多次討論, 我們分析了分頁程序的本質. 主要的結論點如下:
1) 分頁的目的是爲了分段讀取數據
2) 能夠進行分頁的數據一定是有序的, 哪怕他是依賴數據庫存儲順序. (這一點換一種說法更容易理解: 當數據集沒有發生變化時, 同樣的輸入, 多次執行, 得到的輸出順序保持不變)
3) 所有的分段式數據讀取, 要完全保證數據集的一致性, 必須保證數據集順序的一致性, 即快照
4) 傳統的分頁, 分段式分頁(每頁內分爲多段)歸根結底是對數據集做一次切割, 映射到mysql的sql語法上, 就是根據輸入求得limit子句, 適用場景爲數據集變化頻率低
5) since_id類分頁, 其本質是假定已有數據無變化, 將數據集的某一個點的id(在數據集中可以絕對定位該數據的相關字段)提供給用戶側, 每次攜帶該id讀取相應位置的數據, 以此模擬快照, 使用場景爲數據集歷史數據變化頻率低, 新增數據頻繁
6) 如果存在一個快照系統, 能夠爲每一個會話發起時的數據集產生一份快照數據, 那麼一切問題都迎刃而解
7) 在沒有快照系統的時候, 我們可以用since_id的方式限定數據範圍, 模擬快照系統, 可以解決大多數問題
8) 要使用since_id方式模擬快照, 其數據集排序規則必須有能夠唯一標識其每一個數據的字段(可能是複合的)
4. 實現思路
1) 提供SQL的轉換函數
2) 支持分段式分頁(page, page_ping, ping, ping_size), 傳統分頁(page, page_size), 原始分頁(offset-count), since_id分頁(prev_id, next_id)
3) 分段式分頁, 傳統分頁, 原始分頁在底層均轉換爲原始分頁處理
5. 實現定義
ping_to_offset
輸入:
page #請求頁碼, 範圍: [1, total_page], 超過範圍以邊界計, 即0修正爲1, total_page + 1修正爲total_page
ping #請求段號, 範圍: [1, page_ping], 超過範圍以邊界計, 即0修正爲1, page_ping + 1修正爲page_ping
page_ping #每頁分段數, 範圍: [1, 無窮]
count #要獲取的記錄數, 當前應用場景含義爲: 每段記錄數, 範圍: [1, 無窮]
total_record #總記錄數, 範圍: [1, 無窮]
輸出:
offset #偏移量
count #讀取條數
offset_to_ping
輸入:
offset #偏移量(必須按照count對齊, 即可以被count整除), 範圍: [0, 無窮]
page_ping #每頁分段數, 範圍: [1, 無窮]
count #讀取條數, 範圍: [1, 無窮]
輸出:
page #請求頁碼
ping #請求段號
page_ping #每頁分段數
count #要獲取的記錄數, 當前應用場景含義爲: 每段記錄數
page_to_offset
輸入:
page #請求頁碼, 範圍: [1, total_page], 超過範圍以邊界計, 即0修正爲1, total_page + 1修正爲total_page
total_record #總記錄數, 範圍: [1, 無窮]
count #要獲取的記錄數, 當前應用場景含義爲: 每頁條數, 範圍: [1, 無窮]
輸出:
offset #偏移量
count #讀取條數
offset_to_page
輸入:
offset #偏移量(必須按照count對齊, 即可以被count整除), 範圍: [0, 無窮]
count #讀取條數, 範圍: [1, 無窮]
輸出:
page #請求頁碼
count #要獲取的記錄數, 當前應用場景含義爲: 每頁條數
sql_parser #將符合mysql語法規範的SQL語句解析得到各個組件
輸入:
sql #要解析的sql語句
輸出:
sql_components#SQL解析後的字段
sql_restore #將SQL語句組件集轉換爲SQL語句
輸入:
sql_components#要還原的SQL語句組件集
輸出:
sql #還原後的SQL語句
sql_to_count #將符合mysql語法規範的SELECT語句轉換爲獲取計數
輸入:
sql_components#要轉換爲查詢計數的SQL語句組件集
alias #計數字段的別名
輸出:
sql_components#轉換後的查詢計數SQL語句組件集
sql_add_offset
輸入:
sql_components#要增加偏移的SQL語句組件集, 不允許存在LIMIT組件
offset #偏移量(必須按照count對齊, 即可以被count整除), 範圍: [0, 無窮]
count #要獲取的記錄數, 範圍: [1, 無窮]
輸出:
sql_components#已增加LIMIT組件的SQL語句組件集
sql_add_since #增加since_id式的範圍
輸入:
sql_components#要增加範圍限定的SQL語句組件集
prev_id #標記上一次請求得到的數據左邊界
next_id #標記上一次請求得到的數據右邊界
輸出:
sql_components#增加since_id模擬快照的範圍限定後的SQL語句組件集
datas_boundary#獲取當前數據集的邊界
輸入:
sql_components#要讀取的數據集對應的SQL語句組件集
datas #結果數據集
輸出:
prev_id #當前數據集左邊界
next_id #當前數據集右邊界
mysql_paginate_query#執行分頁支持的SQL語句
輸入:
sql #要執行的業務SQL語句
offset #偏移量(必須按照count對齊, 即可以被count整除), 範圍: [0, 無窮]
count #讀取條數, 範圍: [1, 無窮]
prev_id #標記上一次請求得到的數據左邊界
next_id #標記上一次請求得到的數據右邊界
輸出:
datas #查詢結果集
offset #偏移量
count #讀取條數
prev_id #當前數據集的左邊界
next_id #當前數據集的右邊界
6. 實現的執行流程
分段式分頁應用(page, ping, page_ping, count):
total_record = sql_to_count(sql);
(offset, count)= ping_to_offset(page, ping, page_ping, count, total_record)
(datas, offset, count)= mysql_paginate_query(sql, offset, count, NULL, NULL);
(page, ping, page_ping, total_record, count)= offset_to_ping(offset, page_ping, count, total_record);
return (datas, page, ping, page_ping, total_record, count);
傳統分頁應用(page, count):
total_record = sql_to_count(sql);
(offset, count)= page_to_offset(page, count, total_record)
(datas, offset, count)= mysql_paginate_query(sql, offset, count, NULL, NULL);
(page, total_record, count)= offset_to_page(offset, count, total_record);
return (datas, page, total_record, count);
since_id分頁應用(count, prev_id, next_id):
total_record = sql_to_count(sql);
(datas, offset, count, prev_id, next_id)= mysql_paginate_query(sql, NULL, count, prev_id, next_id);
return (count, prev_id, next_id);
複合型分段式分頁應用(page, ping, page_ping, count, prev_id, next_id):
total_record = sql_to_count(sql);
(offset, count)= ping_to_offset(page, ping, page_ping, count, total_record)
(datas, offset, count, prev_id, next_id)= mysql_paginate_query(sql, offset, count, prev_id, next_id);
(page, ping, page_ping, total_record, count)= offset_to_ping(offset, page_ping, count, total_record);
return (datas, page, ping, page_ping, total_record, count, prev_id, next_id);
複合型傳統分頁應用(page, count, prev_id, next_id):
total_record = sql_to_count(sql);
(offset, count)= page_to_offset(page, count, total_record)
(datas, offset, count, prev_id, next_id)= mysql_paginate_query(sql, offset, count, prev_id, next_id);
(page, total_record, count)= offset_to_page(offset, count, total_record);
return (datas, page, total_record, count, prev_id, next_id);
mysql_paginate_query(sql, offset, count, prev_id, next_id)
need_offset = is_null(offset);
need_since = is_null(prev_id) || is_null(next_id);
sql_components= sql_parser(sql);
if ( need_offset ) :
sql_components= sql_add_offset(sql_components, offset, count);
endif
if ( need_since ) :
sql_components= sql_add_since(sql_components, prev_id, next_id);
endif
sql = sql_restore(sql_components);
datas = mysql_execute(sql);
(prev_id, next_id)= datas_boundary(sql_components, datas);
ret = (datas);
if ( need_offset ) :
append(ret, offset, count);
endif
if ( need_since ) :
append(ret, prev_id, next_id);
endif
return (ret);
7. 測試點
1) 傳統分頁
2) 分段分頁
3) 原始分頁
4) since_id分頁
5) 複合型傳統分頁
6) 複合型分段分頁
7) 複合型原始分頁
8. 測試數據構建
DROP DATABASE IF EXISTS `paginate_test`;
CREATE DATABASE IF NOT EXISTS `paginate_test`;
USE `paginate_test`;


DROP TABLE IF EXISTS `feed`;
CREATE TABLE IF NOT EXISTS `feed` (
`feed_id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '微博ID', 
`ctime` INT NOT NULL COMMENT '微博創建時間', 
`content` CHAR(20) NOT NULL DEFAULT '' COMMENT '微博內容', 
`transpond_count` INT NOT NULL DEFAULT 0 COMMENT '微博轉發數'
) COMMENT '微博表';


DROP TABLE IF EXISTS `comment`;
CREATE TABLE IF NOT EXISTS `comment` (
`comment_id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '評論ID', 
`content` CHAR(20) NOT NULL DEFAULT '' COMMENT '評論內容', 
`feed_id` INT NOT NUL COMMENT '被評論微博ID'
) COMMENT '評論表';


DROP TABLE IF EXISTS `hot`;
CREATE TABLE IF NOT EXISTS `hot` (
`feed_id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '微博ID', 
`hot` INT NOT NULL DEFAULT 0 COMMENT '微博熱度'
) COMMENT '熱點微博表';
9. 測試用例:
1) 搜索最熱微博(SELECT f.feed_id, f.content, h.hot FROM feed AS f JOIN hot AS h ON f.feed_id = h.feed_id ORDER BY hhot DESC, f.feed_id DESC)
2) 搜索熱評微博(SELECT f.feed_id, f.content, COUNT(c.*) AS count FROM feed AS f JOIN comment AS c ON f.feed_id = c.feed_id GROUP BY c.feed_id ORDER BY count DESC, f.feed_id DESC)
3) 搜索熱轉微博(SELECT feed_id, content, transpond_count FROM feed ORDER BY transpond_count DESC, feed_id DESC)
4) 上面3種場景均測試7個測試點
10. 文件列表
readme.txt 當前您正在閱讀的開發文檔
page.lib.php 分頁程序庫
test_base.php 單元測試基礎函數
test_convert.php不同分頁之間的轉換單元測試
test_parse.phpSQL語句解析測試

test_page.php分頁測試


下面是源代碼:

page.lib.php

<?php
/*
 * 分頁程序核心庫
 * 1. 各種分頁的轉換
 * 2. SQL語句解析
 * 3. SQL語句修改
 * 4. SQL語句還原
 * 5. 自動的分頁支持
 * author: selfimpr
 * blog: http://blog.csdn.net/lgg201
 * mail: [email protected]
 */
#分頁術語
define('TERM_DATAS',						'datas');			#分頁得到數據
define('TERM_COUNT',						'count');			#期望的分段記錄數
define('TERM_TOTAL_RECORD',					'total_record');	#總記錄數
define('TERM_OFFSET',						'offset');			#偏移量
define('TERM_PAGE',							'page');			#頁碼
define('TERM_PING',							'ping');			#段號
define('TERM_PAGE_PING',					'page_ping');		#每頁段數
define('TERM_PREV_ID',						'prev_id');			#範圍左標記
define('TERM_NEXT_ID',						'next_id');			#範圍右標記

#sql語法解析錯誤
define('E_SQL_SELECT_PARSER',				'SQL-SELECT語法解析錯誤: %s');	#SQL語法解析錯誤

#SQL語法解析特殊字符
define('SPACES',					" \t\f\r\n");
define('QUOTES',					'"\'');
define('COMMA',						',');
define('LBRACKET',					'(');
define('RBRACKET',					')');
define('DOT',						'.');
define('SPACE',						' ');

#SQL語法解析後的組件
define('CP_SELECT',						'_select');
define('CP_OPTIONS',					'_options');
define('CP_FIELDS',						'_fields');
define('CP_FROM',						'_from');
define('CP_TABLES',						'_tables');
define('CPK_TABLES_TABLE',				'_table');
define('CPK_TABLES_ALIAS',				'_alias');
define('CPK_TABLES_CONDITION',			'_condition');
define('CPK_TABLES_SEPARATER',			'_separater');
define('CP_WHERE',						'_where');
define('CP_CONDITIONS',					'_conditions');
define('CP_GROUP_BY',					'_group_by');
define('CP_GROUPS',						'_groups');
define('CPK_GROUPS_FIELD',				'_field');
define('CPK_GROUPS_ORDER',				'_order');
define('CP_HAVING',						'_having');
define('CP_FILTERS',					'_filters');
define('CP_ORDER_BY',					'_order_by');
define('CP_ORDERS',						'_orders');
define('CPK_ORDERS_FIELD',				'_field');
define('CPK_ORDERS_ORDER',				'_order');
define('CP_LIMIT',						'_limit');
define('CP_OFFSET',						'_offset');
define('CP_COUNT',						'_count');

#SQL語法中的關鍵字
define('KW_COUNT',						'COUNT');
define('KW_SELECT',						'SELECT');
define('KW_ALL',						'ALL');
define('KW_DISTINCT',					'DISTINCT');
define('KW_DISTINCTROW',				'DISTINCTROW');
define('KW_HIGH_PRIORITY',				'HIGH_PRIORITY');
define('KW_STRAIGHT_JOIN',				'STRAIGHT_JOIN');
define('KW_SQL_SMALL_RESULT',			'SQL_SMALL_RESULT');
define('KW_SQL_BIG_RESULT',				'SQL_BIG_RESULT');
define('KW_SQL_BUFFER_RESULT',			'SQL_BUFFER_RESULT');
define('KW_SQL_CACHE',					'SQL_CACHE');
define('KW_SQL_NO_CACHE',				'SQL_NO_CACHE');
define('KW_SQL_CALC_FOUND_ROWS',		'SQL_CALC_FOUND_ROWS');
define('KW_FROM',						'FROM');
define('KW_WHERE',						'WHERE');
define('KW_JOIN',						'JOIN');
define('KW_LEFT',						'LEFT');
define('KW_RIGHT',						'RIGHT');
define('KW_INNER',						'INNER');
define('KW_OUTER',						'OUTER');
define('KW_CROSS',						'CROSS');
define('KW_AS',							'AS');
define('KW_ON',							'ON');
define('KW_GROUP',					'GROUP');
define('KW_ORDER',					'ORDER');
define('KW_BY',						'BY');
define('KW_GROUP_BY',				KW_GROUP . SPACE . KW_BY);
define('KW_HAVING',					'HAVING');
define('KW_ORDER_BY',				KW_ORDER . SPACE . KW_BY);
define('KW_ASC',					'ASC');
define('KW_DESC',					'DESC');
define('KW_LIMIT',					'LIMIT');
define('KW_AND',					'AND');
define('KW_OR',						'OR');
define('KWS_EQ',					'=');
define('KWS_LT',					'<');
define('KWS_GT',					'>');
define('KWS_LE',					'<=');
define('KWS_GE',					'>=');

define('ORDER_ALIAS_PREFIX',		'__o_');
define('SINCE_ID_SEPARATER_0',		'|');
define('SINCE_ID_SEPARATER_1',		':');
define('DIRECT_PREV',				'PREV');
define('DIRECT_NEXT',				'NEXT');
define('COUNT_DEFAULT_ALIAS',		'__c');

#當前解析器需要處理的SELECT選項
define('ENABLE_OPTIONS',				'_enable_options');
$GLOBALS[ENABLE_OPTIONS]	= array(
	KW_ALL, KW_DISTINCT, KW_DISTINCTROW, 
	KW_HIGH_PRIORITY, KW_STRAIGHT_JOIN, KW_SQL_SMALL_RESULT, 
	KW_SQL_BIG_RESULT, KW_SQL_BUFFER_RESULT, KW_SQL_CACHE, 
	KW_SQL_NO_CACHE, KW_SQL_CALC_FOUND_ROWS, 
);

function p_datas($info) {
	return $info[TERM_DATAS];
}
#讀取每段記錄數
function p_count($info) {
	return intval($info[TERM_COUNT]);
}
#讀取總記錄數
function p_total_record($info) {
	return intval($info[TERM_TOTAL_RECORD]);
}
#讀取偏移量
function p_offset($info) {
	return intval($info[TERM_OFFSET]);
}
#讀取頁碼
function p_page($info) {
	return intval($info[TERM_PAGE]);
}
#讀取分段號
function p_ping($info) {
	return intval($info[TERM_PING]);
}
#讀取每頁分段數
function p_page_ping($info) {
	return intval($info[TERM_PAGE_PING]);
}
#讀取範圍左標識
function p_prev_id($info) {
	return strval($info[TERM_PREV_ID]);
}
#讀取範圍右標識
function p_next_id($info) {
	return strval($info[TERM_NEXT_ID]);
}

#分段分頁到偏移量轉換
function ping_to_offset($page, $ping, $page_ping, $count, $total_record) {
	$ping		= min($page_ping, max(1, $ping));
	$total_ping	= ceil($total_record / $count);
	$total_page	= ceil($total_ping / $page_ping);
	$page		= min($total_page, max(1, $page));
	$real_ping	= ($page - 1) * $page_ping + $ping;
	$real_ping	= min($total_ping, max(1, $real_ping));
	$offset		= ($real_ping - 1) * $count;
	return array(
		TERM_OFFSET	=> intval($offset), 
		TERM_COUNT	=> intval($count), 
	);
}
#偏移量到分段分頁轉換
function offset_to_ping($offset, $page_ping, $count) {
	$real_ping	= ($offset / $count) + 1;
	$ping		= ($real_ping - 1) % $page_ping + 1;
	$page		= ($real_ping - $ping) / $page_ping + 1;
	return array(
		TERM_PAGE			=> intval($page), 
		TERM_PING			=> intval($ping), 
		TERM_PAGE_PING		=> intval($page_ping), 
		TERM_COUNT			=> intval($count), 
	);
}
#傳統分頁到偏移量轉換
function page_to_offset($page, $count, $total_record) {
	$total_page	= ceil($total_record / $count);
	$page		= min($total_page, max(1, $page));
	$offset		= ($page - 1) * $count;
	return array(
		TERM_OFFSET	=> intval($offset), 
		TERM_COUNT	=> intval($count), 
	);
}
#偏移量到傳統分頁轉換
function offset_to_page($offset, $count) {
	$page	= floor($offset / $count) + 1;
	return array(
		TERM_PAGE	=> intval($page), 
		TERM_COUNT	=> intval($count), 
	);
}


#------------------------------------------------SQL語法解析器
/* 解析器要處理的SELECT語法
SELECT
    [ALL | DISTINCT | DISTINCTROW ]
      [HIGH_PRIORITY]
      [STRAIGHT_JOIN]
      [SQL_SMALL_RESULT] [SQL_BIG_RESULT] [SQL_BUFFER_RESULT]
      [SQL_CACHE | SQL_NO_CACHE] [SQL_CALC_FOUND_ROWS]
    select_expr [, select_expr ...]
    [FROM table_references
    [WHERE where_condition]
    [GROUP BY {col_name | expr | position}
      [ASC | DESC], ... [WITH ROLLUP]]
    [HAVING where_condition]
    [ORDER BY {col_name | expr | position}
      [ASC | DESC], ...]
    [LIMIT {[offset,] row_count | row_count OFFSET offset}]
*/
#符合mysql語法規範的SELECT語句
function sql_parser($sql) {
	$tokens		= sql_tokens($sql);
	$components	= sql_analysis($tokens);
	return $components;
}
#sql_parser的逆向函數
/* sql_components的結構
array(
	CP_SELECT		=> KW_SELECT, 									#SELECT關鍵字
	CP_OPTIONS		=> $GLOBALS[ENABLE_OPTIONS], 					#SELECT選項
	CP_FIELDS		=> array(										#查詢字段
		array('field_0_comp_0', 'field_0_comp_1', ...), 			#字段0的構成
		array('field_1_comp_0', 'field_2_comp_1', ...), 			#字段1的構成
		...
	), 
	CP_FROM			=> KW_FROM,										#FROM關鍵字
	CP_TABLES		=> array(										#目標表
		array(
			CPK_TABLES_TABLE		=> array('table_0_comp_0', 'table_0_comp_1', ...), 		#數據表描述
			CPK_TABLES_ALIAS		=> 'alias', 											#別名
			CPK_TABLES_CONDITION	=> array('cond_0_comp_0', 'cond_0_comp_1', ...), 		#條件
		), 
		array(
			CPK_TABLES_SEPARATER	=> array(KW_LEFT, KW_JOIN), 							#表0和表1的連接符(KW_LEFT, KW_RIGHT, KW_INNER, KW_CROSS, KW_OUTER, KW_JOIN, COMMA)
			CPK_TABLES_TABLE		=> array('table_1_comp_0', 'table_1_comp_1', ...), 		#數據表描述
			CPK_TABLES_ALIAS		=> 'alias', 											#別名
			CPK_TABLES_CONDITION	=> array('cond_1_comp_0', 'cond_1_comp_1', ...), 		#條件
		), 
	), 
	CP_WHERE		=> KW_WHERE,									#WHERE關鍵字
	CP_CONDITIONS	=> array(										#查詢條件(WHERE)
		array('cond_comp_0', 'cond_comp_1', ...), 					#條件構件
	), 
	CP_GROUP_BY		=> KW_GROUP_BY									#GROUP BY關鍵字
	CP_GROUPS		=> array(																#分組項
		array(
			CPK_GROUPS_FIELD => array('field_0_comp_0', 'field_0_comp_1', ...), 			#分組項0的字段構件
			CPK_GROUPS_ORDER => KW_ASC, 													#分組項0的排序規則(KW_ASC, KW_DESC)
		), 
		array(
			CPK_GROUPS_FIELD => array('field_1_comp_0', 'field_0_comp_1', ...), 			#分組項1的字段構件
			CPK_GROUPS_ORDER => KW_DESC, 													#分組項1的排序規則(KW_ASC, KW_DESC)
		), 
	), 
	CP_HAVING		=> KW_HAVING,									#HAVING關鍵字
	CP_FILTERS		=> array(										#過濾條件(HAVING)
		array('cond_comp_0', 'cond_comp_1', ...), 					#條件構件
	), 
	CP_ORDER_BY		=> KW_ORDER_BY,									#ORDER BY關鍵字
	CP_ORDERS		=> array(																#排序項
		array(
			CPK_ORDERS_FIELD => array('field_0_comp_0', 'field_0_comp_1', ...), 			#排序項0的字段構件
			CPK_ORDERS_ORDER => KW_ASC, 													#排序項0的排序規則(KW_ASC, KW_DESC)
		), 
		array(
			CPK_ORDERS_FIELD => array('field_1_comp_0', 'field_0_comp_1', ...), 			#排序項1的字段構件
			CPK_ORDERS_ORDER => KW_DESC, 													#排序項1的排序規則(KW_ASC, KW_DESC)
		), 
	), 
	CP_LIMIT		=> KW_LIMIT, 									#LIMIT關鍵字
	CP_OFFSET		=> 0,											#偏移量
	CP_COUNT		=> 10, 											#最大記錄數
);
TODO 實現還原
 */
function sql_restore($sc) {
	$r	= array();
	sr_restore_select($sc, $r);
	sr_restore_options($sc, $r); 
	sr_restore_fields($sc, $r); 
	sr_restore_tables($sc, $r); 
	sr_restore_conditions($sc, $r); 
	sr_restore_groups($sc, $r); 
	sr_restore_filters($sc, $r); 
	sr_restore_orders($sc, $r); 
	sr_restore_offset_count($sc, $r);
	return implode_exclude_bracket(SPACE, $r);
}
#將SQL語句轉換爲計數SQL(不支持外連接)
function sql_to_count($sql, $alias = COUNT_DEFAULT_ALIAS) {
	$sc	= sql_parser($sql);
	$sc	= sc_to_count($sc, $alias);
	return sql_restore($sc);
}
#SQL語句增加since_id
function sql_add_since($sql, $prev_id, $next_id, &$need_reverse) {
	$sc	= sql_parser($sql);
	$sc	= sc_add_since($sc, $prev_id, $next_id, $need_reverse);
	return sql_restore($sc);
}
#爲SQL語句設置偏移量
function sql_add_offset($sql, $offset, $count) {
	$sc	= sql_parser($sql);
	$sc	= sc_add_offset($sql, $offset, $count);
	return sql_restore($sc);
}
#對外的單一接口提供
/* 
	1) 傳統分頁
	2) 分段分頁
	3) 原始分頁
	4) since_id分頁
	5) 複合型傳統分頁
	6) 複合型分段分頁
	7) 複合型原始分頁
 */
function mysql_paginate_query($conn, $sql, $count, $offset = NULL, $prev_id = NULL, $next_id = NULL) {
	#修正偏移量
	$offset	= correct_offset($offset);
	$sc		= sql_parser($sql);

	#增加偏移量
	sc_add_offset($sc, $offset, $count);
	
	#增加since條件
	sc_add_since($sc, $prev_id, $next_id, $need_reverse);

	#結構化SQL到SQL語句
	$sql	= sql_restore($sc);
	#執行查詢
	$datas	= mysql_geta_all($conn, $sql);
	if ( $need_reverse )
		$datas	= array_reverse($datas);
	#獲取新的數據邊界
	if ( $prev_id || $next_id )
		sc_datas_boundary($sc, $datas, $prev_id, $next_id);
	
	return array(
		TERM_DATAS		=> $datas, 
		TERM_OFFSET		=> $offset, 
		TERM_COUNT		=> $count, 
		TERM_PREV_ID	=> $prev_id, 
		TERM_NEXT_ID	=> $next_id, 
	);
}
#獲取Top N數據
function mysql_paginate_top($conn, $sql, $count) {
	#獲取總記錄數
	$total_record	= mysql_get_total_record($conn, $sql);
	#讀取數據
	$data_info	= mysql_paginate_query($conn, $sql, 
				$count, NULL, 
				NULL, NULL);
	return array(
		TERM_DATAS			=> p_datas($data_info), 
		TERM_COUNT			=> p_count($data_info), 
		TERM_TOTAL_RECORD	=> $total_record, 
	);
}
#傳統方式結合since_id分頁
#(TODO 分段分頁/傳統分頁/原始分頁與since_id分頁聯合使用時, 記錄數是since_id條件附加之前的記錄數, 因此會導致頁碼數據錯亂, 目前不對此進行處理)
function mysql_paginate_tradition_since_id($conn, $sql, $page, 
		$count, $prev_id = NULL, $next_id = NULL, $total_record = NULL) {
	#修正總記錄數
	mysql_correct_total_record($conn, $sql, $total_record);
	#分頁信息轉換爲偏移量
	$offset_info	= page_to_offset($page, $count, $total_record);
	#讀取數據
	$data_info	= mysql_paginate_query($conn, $sql, 
				p_count($offset_info), p_offset($offset_info), 
				$prev_id, $next_id);
	#修正爲頁碼信息
	$page_info		= offset_to_page(p_offset($data_info), p_count($data_info));
	return array(
		TERM_DATAS			=> p_datas($data_info), 
		TERM_PAGE			=> p_page($page_info), 
		TERM_TOTAL_RECORD	=> $total_record, 
		TERM_COUNT			=> p_count($page_info), 
		TERM_PREV_ID		=> p_prev_id($data_info), 
		TERM_NEXT_ID		=> p_next_id($data_info), 
	);
}
#單純的傳統分頁
function mysql_paginate_tradition($conn, $sql, $page, $count, $total_record = NULL) {
	$info	= mysql_paginate_tradition_since_id($conn, $sql, $page, $count, NULL, NULL, $total_record);
	unset($info[TERM_PREV_ID]);
	unset($info[TERM_NEXT_ID]);
	return $info;
}
#分段分頁結合since_id分頁
#(TODO 分段分頁/傳統分頁/原始分頁與since_id分頁聯合使用時, 記錄數是since_id條件附加之前的記錄數, 因此會導致頁碼數據錯亂, 目前不對此進行處理)
function mysql_paginate_ping_since_id($conn, $sql, $page, $ping, 
		$page_ping, $count, $prev_id = NULL, $next_id = NULL, $total_record = NULL) {
	#修正總記錄數
	mysql_correct_total_record($conn, $sql, $total_record);
	#分段信息轉換爲offset
	$offset_info	= ping_to_offset($page, $ping, $page_ping, $count, $total_record);
	#讀取數據
	$data_info		= mysql_paginate_query($conn, $sql, 
					p_count($offset_info), p_offset($offset_info), 
					$prev_id, $next_id);
	#修正爲分段信息
	$ping_info		= offset_to_ping(p_offset($data_info), 
					$page_ping, p_count($data_info));
	return array(
		TERM_DATAS			=> p_datas($data_info), 
		TERM_PAGE			=> p_page($ping_info), 
		TERM_PING			=> p_ping($ping_info), 
		TERM_PAGE_PING		=> p_page_ping($ping_info), 
		TERM_COUNT			=> p_count($ping_info), 
		TERM_TOTAL_RECORD	=> $total_record, 
		TERM_PREV_ID		=> p_prev_id($data_info), 
		TERM_NEXT_ID		=> p_next_id($data_info), 
	);
}
#單純的分段分頁
function mysql_paginate_ping($conn, $sql, $page, $ping, $page_ping, $count, $total_record = NULL) {
	$info	= mysql_paginate_ping_since_id($conn, $sql, $page, $ping, 
			$page_ping, $count, NULL, NULL, $total_record);
	unset($info[TERM_PREV_ID]);
	unset($info[TERM_NEXT_ID]);
	return $info;
}
#原始分頁結合since_id分頁
#(TODO 分段分頁/傳統分頁/原始分頁與since_id分頁聯合使用時, 記錄數是since_id條件附加之前的記錄數, 因此會導致頁碼數據錯亂, 目前不對此進行處理)
function mysql_paginate_raw_since_id($conn, $sql, $offset, $count, 
		$prev_id = NULL, $next_id = NULL, $total_record = NULL) {
	#修正總記錄數
	mysql_correct_total_record($conn, $sql, $total_record);
	$offset			= min($total_record, max(0, $offset));
	#讀取數據
	$data_info		= mysql_paginate_query($conn, $sql, 
					$count, $offset, $prev_id, $next_id);
	return array(
		TERM_DATAS			=> p_datas($data_info), 
		TERM_OFFSET			=> p_offset($data_info), 
		TERM_COUNT			=> p_count($data_info), 
		TERM_TOTAL_RECORD	=> $total_record, 
		TERM_PREV_ID		=> p_prev_id($data_info), 
		TERM_NEXT_ID		=> p_next_id($data_info), 
	);
}
#單純的原始分頁
function mysql_paginate_raw($conn, $sql, $offset, $count, $total_record = NULL) {
	$info	= mysql_paginate_raw_since_id($conn, $sql, $offset, $count, NULL, NULL, $total_record);
	unset($info[TERM_PREV_ID]);
	unset($info[TERM_NEXT_ID]);
	return $info;
}
#單純的since_id分頁
function mysql_paginate_since_id($conn, $sql, $count, $prev_id = NULL, $next_id = NULL) {
	#獲取總記錄數
	$total_record	= mysql_get_total_record($conn, $sql);
	#讀取數據
	$data_info	= mysql_paginate_query($conn, $sql, $count, NULL,
				is_null($prev_id) ? TRUE : $prev_id,
				is_null($next_id) ? TRUE : $next_id);
	return array(
		TERM_DATAS			=> p_datas($data_info), 
		TERM_PREV_ID		=> p_prev_id($data_info), 
		TERM_NEXT_ID		=> p_next_id($data_info), 
		TERM_COUNT			=> p_count($data_info), 
		TERM_TOTAL_RECORD	=> $total_record, 
	);
}
#獲取總記錄數
function mysql_get_total_record($conn, $sql) {
	$sql	= sql_to_count($sql, COUNT_DEFAULT_ALIAS);
	$row	= mysql_geta($conn, $sql);
	$total_record	= $row[COUNT_DEFAULT_ALIAS];
	return $total_record;
}
#修正總記錄數
function mysql_correct_total_record($conn, $sql, &$total_record = NULL) {
	if ( !is_numeric($total_record) ) 
		$total_record	= intval(mysql_get_total_record($conn, $sql));
	$total_record	= intval($total_record);
}



#-------------------------------------------------------------------------以下部分是基礎函數, 非外部接口
#----------------------------------------SQL語句解析基礎函數
#將sql字符串處理爲tokens
/*
 * 1. 忽略空白元素
 * 2. 將逗號/左括號/右括號作爲詞法單元處理
 * 3. 對字符串字面量特殊處理(防止轉義等帶來的影響)
 * 語法描述
SELECT		::= UNIT SEPARATER UNIT [SEPARATER UNIT [...]]
UNIT		::= WORD | LITERAL
SEPARATER	::= COMMA | SPACES | LBRACKET | RBRACKET
LITERAL		::= LQUOTE WORD RQUOTE
WORD		::= CHARACTER [CHARACTER [...]]
LQUOTE		::= "'" | "\""
RQUOTE		::= "'" | "\""
CHARACTER	::= #非特殊含義字符集
LBRACKET	::= "("
RBRACKET	::= ")"
COMMA		::= ","
SPACES		::= " " | "\t" | "\f" | "\r" | "\n"
 */
function sql_tokens($s) {
	$r	= array();
	$l	= strlen($s);
	while ( $l > 0 ) {
		if ( sp_is_quote($s) ) {
			sp_read_string($s, $l, $r);
		} else if ( sp_is_space($s) ) {
			sp_read_spaces($s, $l);
		} else if ( sp_is_comma($s) ) {
			sp_read_comma($s, $l, $r);
		} else if ( sp_is_lbracket($s) ) {
			sp_read_lbracket($s, $l, $r);
		} else if ( sp_is_rbracket($s) ) {
			sp_read_rbracket($s, $l, $r);
		} else {
			sp_read_word($s, $l, $r);
		}
	}
	return $r;
}
#對解析得到的詞法單元進行語法分析
function sql_analysis($t) {
	$r	= array();
	$i	= 0;
	$l	= count($t);
	#讀取SELECT關鍵字
	spa_read_select($t, $i, $l, $r);
	#讀取SELECT選項
	spa_read_options($t, $i, $l, $r);
	#讀取查詢的目標字段
	spa_read_fields($t, $i, $l, $r);
	if ( spa_is_from($t, $i) ) {
		#讀取FROM關鍵字
		spa_read_from($t, $i, $l, $r);
		#讀取查詢的目標表
		spa_read_tables($t, $i, $l, $r);
	}
	if ( spa_is_where($t, $i) ) {
		#讀取WHERE關鍵字
		spa_read_where($t, $i, $l, $r);
		#讀取查詢條件
		spa_read_conditions($t, $i, $l, $r);
	}
	if ( spa_is_group_by($t, $i) ) {
		#讀取GROUP BY關鍵字
		spa_read_group_by($t, $i, $l, $r);
		#讀取分組信息
		spa_read_groups($t, $i, $l, $r);
	}
	if ( spa_is_having($t, $i) ) {
		#讀取HAVING關鍵字
		spa_read_having($t, $i, $l, $r);
		#讀取結果集過濾條件
		spa_read_filters($t, $i, $l, $r);
	}
	if ( spa_is_order_by($t, $i) ) {
		#讀取ORDER BY關鍵字
		spa_read_order_by($t, $i, $l, $r);
		#讀取排序信息
		spa_read_orders($t, $i, $l, $r);
	}
	if ( spa_is_limit($t, $i) ) {
		#讀取LIMIT關鍵字
		spa_read_limit($t, $i, $l, $r);
		#讀取偏移量和最大記錄數
		spa_read_offset_count($t, $i, $l, $r);
	}
	return $r;
}
#----------------------------------錯誤處理
#sql語法解析錯誤處理
function sql_parser_error() {
	$msgs	= func_get_args();
	trigger_error(sprintf(E_SQL_SELECT_PARSER, implode("\t", $msgs)), E_USER_ERROR);
}
#------------------------------------詞法分析基礎函數
#詞法分析: 檢查是否空白字符
function sp_is_space($s) {
	return strpos(SPACES, $s[0]) !== FALSE;
}
#詞法分析: 檢查是否引號
function sp_is_quote($s) {
	return strpos(QUOTES, $s[0]) !== FALSE;
}
#詞法分析: 檢查是否逗號
function sp_is_comma($s) {
	return $s[0] === COMMA;
}
#詞法分析: 檢查是否左括號
function sp_is_lbracket($s) {
	return $s[0] === LBRACKET;
}
#詞法分析: 檢查是否右括號
function sp_is_rbracket($s) {
	return $s[0] === RBRACKET;
}
#詞法分析: 檢查是否單詞字符(認爲所有非上面列出的5種特殊字符, 均爲單詞字符)
function sp_is_wordchar($s) {
	return !sp_is_quote($s) && !sp_is_space($s) && !sp_is_comma($s) && !sp_is_lbracket($s) && !sp_is_rbracket($s);
}
#詞法分析: 讀取詞法單元的基礎函數
function sp_read_base($f, &$s, &$l, &$r = NULL, $single = FALSE) {
	$i	= -1;
	while ( ++ $i < $l ) 
		#檢查是否符合詞法單元條件
		if ( !$f($s[$i]) || ($single && $i > 0) )
			break;
	if ( !is_null($r) )
		$r[]	= substr($s, 0, $i);
	$s	= substr($s, $i);
	$l	-= $i;
}
#詞法分析: 讀取空白單元
function sp_read_spaces(&$s, &$l, &$r = NULL) {
	sp_read_base('sp_is_space', $s, $l, $r);
}
#詞法分析: 讀取逗號單元
function sp_read_comma(&$s, &$l, &$r = NULL) {
	sp_read_base('sp_is_comma', $s, $l, $r, TRUE);
}
#詞法分析: 讀取單詞單元
function sp_read_word(&$s, &$l, &$r = NULL) {
	sp_read_base('sp_is_wordchar', $s, $l, $r);
}
#詞法分析: 讀取左括號單元
function sp_read_lbracket(&$s, &$l, &$r = NULL) {
	sp_read_base('sp_is_lbracket', $s, $l, $r, TRUE);
}
#詞法分析: 讀取右括號單元
function sp_read_rbracket(&$s, &$l, &$r = NULL) {
	sp_read_base('sp_is_rbracket', $s, $l, $r, TRUE);
}
#詞法分析: 讀取字符串字面量單元
function sp_read_string(&$s, &$l, &$r = NULL) {
	if ( !sp_is_quote($s) ) 
		sql_parser_error(__FILE__, __LINE__, __FUNCTION__);
	$q	= $s[0];
	$i	= 1;
	$e	= FALSE;
	while ( $i < $l ) {
		#讀取字符並下移指針
		$c	= $s[$i ++];
		#轉義狀態直接將設置爲非轉義狀態
		if ( $e ) $e = FALSE;
		#終止引號跳出處理
		else if ( $c === $q ) break;
		#轉義字符設置轉義狀態
		else if ( $c === '\\' ) $e = TRUE;
	}
	if ( $i > $l || $e ) 
		sql_parser_error(__FILE__, __LINE__, __FUNCTION__);
	if ( !is_null($r) )
		$r[]	= substr($s, 0, $i);
	$s	= substr($s, $i);
	$l	-= $i;
}

#--------------------------------------------語法分析基礎函數-簡單語法單元檢查
#語法分析: 檢查是否逗號
function spa_is_comma($t, $i, &$n = NULL) {
	$n	= 1;
	return sp_is_comma($t[$i]);
}
#語法分析: 檢查是否左括號
function spa_is_lbracket($t, $i, &$n = NULL) {
	$n	= 1;
	return sp_is_lbracket($t[$i]);
}
#語法分析: 檢查是否右括號
function spa_is_rbracket($t, $i, &$n = NULL) {
	$n	= 1;
	return sp_is_rbracket($t[$i]);
}
#語法分析: 檢查是否句點
function spa_is_dot($t, $i, &$n = NULL) {
	$n	= 1;
	return strtoupper($t[$i]) === DOT;
}
#語法分析: 檢查是否SELECT
function spa_is_select($t, $i, &$n = NULL) {
	$n	= 1;
	return strtoupper($t[$i]) === KW_SELECT;
}
#語法分析: 檢查是否SELECT選項
function spa_is_option($t, $i, &$n = NULL) {
	$n	= 1;
	return in_array(strtoupper($t[$i]), $GLOBALS[ENABLE_OPTIONS]);
}
#語法分析: 檢查是否FROM
function spa_is_from($t, $i, &$n = NULL) {
	$n	= 1;
	return strtoupper($t[$i]) === KW_FROM;
}
#語法分析: 檢查是否WHERE
function spa_is_where($t, $i, &$n = NULL) {
	$n	= 1;
	return strtoupper($t[$i]) === KW_WHERE;
}
#語法分析: 檢查是否JOIN
/* 實現語法:
join_table:
	  table_reference [INNER | CROSS] JOIN table_factor [join_condition]
	| table_reference {LEFT|RIGHT} [OUTER] JOIN table_reference join_condition
 */
function spa_is_join($t, $i, &$n = NULL) {
	$t_i0	= strtoupper($t[$i]);
	$t_i1	= strtoupper($t[$i + 1]);
	$t_i2	= strtoupper($t[$i + 2]);
	if ( $t_i0 !== KW_JOIN && $t_i1 !== KW_JOIN && $t_i2 !== KW_JOIN )
		return FALSE;
	if ( $t_i0 === KW_JOIN ) 
		$n	= 1;
	else if ( $t_i1 === KW_JOIN ) {
		if ( $t_i0 !== KW_INNER && $t_i0 !== KW_CROSS && $t_i0 !== KW_LEFT && $t_i0 !== KW_RIGHT )
			return FALSE;
		$n	= 2;
	} else if ( $t_i2 === KW_JOIN ) {
		if ( $t_i0 !== KW_LEFT && $t_i0 !== KW_RIGHT || $t_i1 !== OUTER ) 
			return FALSE;
		$n	= 3;
	}
	return TRUE;
}
#語法分析: 檢查是否AS
function spa_is_as($t, $i, &$n = NULL) {
	$n	= 1;
	return strtoupper($t[$i]) === KW_AS;
}
#語法分析: 檢查是否ON
function spa_is_on($t, $i, &$n = NULL) {
	$n	= 1;
	return strtoupper($t[$i]) === KW_ON;
}
#語法分析: 檢查是否GROUP BY
function spa_is_group_by($t, $i, &$n = NULL) {
	$n	= 2;
	return strtoupper($t[$i]) === KW_GROUP && strtoupper($t[$i + 1]) === KW_BY;
}
#語法分析: 檢查是否HAVING
function spa_is_having($t, $i, &$n = NULL) {
	$n	= 1;
	return strtoupper($t[$i]) === KW_HAVING;
}
#語法分析: 檢查是否ORDER BY
function spa_is_order_by($t, $i, &$n = NULL) {
	$n	= 2;
	return strtoupper($t[$i]) === KW_ORDER && strtoupper($t[$i + 1]) === KW_BY;
}
#語法分析: 檢查是否ASC
function spa_is_asc($t, $i, &$n = NULL) {
	$n	= 1;
	return strtoupper($t[$i]) === KW_ASC;
}
#語法分析: 檢查是否DESC
function spa_is_desc($t, $i, &$n = NULL) {
	$n	= 1;
	return strtoupper($t[$i]) === KW_DESC;
}
#語法分析: 檢查是否LIMIT
function spa_is_limit($t, $i, &$n = NULL) {
	$n	= 1;
	return strtoupper($t[$i]) === KW_LIMIT;
}
#---------------------------------------語法分析-語法邊界檢查
#CP_FIELDS語法單元中單條field自身結束而非整個語法單元結束的檢查
function spa_end_field_self($t, $i, $l) {
	return spa_is_comma($t, $i) || $i >= $l;
}
#CP_FIELDS語法單元結束標記檢查
function spa_end_fields($t, $i, $l) {
	return spa_is_from($t, $i) || spa_is_where($t, $i) || spa_is_group_by($t, $i) || spa_is_having($t, $i)
		|| spa_is_order_by($t, $i) || spa_is_limit($t, $i) || $i >= $l;
}
#CP_FIELDS語法單元中的單條field結束標記檢查
function spa_end_field($t, $i, $l) {
	return spa_end_field_self($t, $i, $l) || spa_end_fields($t, $i, $l);
}
#CP_TABLES語法單元中單表自身結束而非整個語法單元結束的檢查
function spa_end_table_self($t, $i, $l) {
	return spa_is_comma($t, $i) || spa_is_join($t, $i) || $i >= $l;
}
#CP_TABLES語法單元結束標記檢查
function spa_end_tables($t, $i, $l) {
	return spa_is_where($t, $i) || spa_is_group_by($t, $i) || spa_is_having($t, $i)
		|| spa_is_order_by($t, $i) || spa_is_limit($t, $i) || $i >= $l;
}
#CP_TABLES語法單元中的單表結束標記檢查
function spa_end_table($t, $i, $l) {
	return spa_end_table_self($t, $i, $l) || spa_end_tables($t, $i, $l);
}
#CP_CONDITIONS語法單元中單條條件自身結束而非整個語法單元結束的檢查
function spa_end_condition_self($t, $i, $l) {
#TODO 暫時不對where條件做詳細處理
	return $i >= $l;
}
#CP_CONDITIONS語法單元的結束標記檢查
function spa_end_conditions($t, $i, $l) {
	return spa_is_group_by($t, $i)  || spa_is_having($t, $i)|| spa_is_order_by($t, $i)
		|| spa_is_limit($t, $i) || $i >= $l;
}
#CP_CONDITIONS語法單元中的單條條件結束標記檢查
function spa_end_condition($t, $i, $l) {
	return spa_end_condition_self($t, $i, $l) || spa_end_conditions($t, $i, $l);
}
#CP_GROUPS語法單元中單個分組標記自身結束而非整個語法單元結束的檢查
function spa_end_group_self($t, $i, $l) {
	return spa_is_comma($t, $i) || $i >= $l;
}
#CP_GROUPS語法單元中的結束標記檢查
function spa_end_groups($t, $i, $l) {
	return spa_is_having($t, $i) || spa_is_order_by($t, $i) || spa_is_limit($t, $i) || $i >= $l;
}
#CP_GROUPS語法單元中的單個分組標記結束標記檢查
function spa_end_group($t, $i, $l) {
	return spa_end_group_self($t, $i, $l) || spa_end_groups($t, $i, $l);
}
#CP_FILTERS語法單元中單條條件自身結束而非整個語法單元結束的檢查
function spa_end_filter_self($t, $i, $l) {
#TODO 暫時不對having條件做詳細處理
	return $i >= $l;
}
#CP_FILTERS語法單元中的結束標記檢查
function spa_end_filters($t, $i, $l) {
	return spa_is_order_by($t, $i) || spa_is_limit($t, $i) || $i >= $l;
}
#CP_FILTERS語法單元中的單條條件結束標記檢查
function spa_end_filter($t, $i, $l) {
	return spa_end_filter_self($t, $i, $l) || spa_end_filters($t, $i, $l);
}
#CP_ORDERS語法單元中單條排序規則自身結束而非整個語法單元結束的檢查
function spa_end_order_self($t, $i, $l) {
	return spa_is_comma($t, $i) || $i >= $l;
}
#CP_ORDERS語法單元中的結束標記檢查
function spa_end_orders($t, $i, $l) {
	return spa_is_limit($t, $i) || $i >= $l;
}
#CP_ORDERS語法單元中的單條排序規則結束標記檢查
function spa_end_order($t, $i, $l) {
	return spa_end_filter_self($t, $i, $l) || spa_end_orders($t, $i, $l);
}
#-----------------------------------------------語法分析-各個語法單元的讀取
#將括號內容作爲整體讀取(這裏主要做棧的處理)
function spa_read_bracket($t, &$i, $l, $include_bracket = FALSE) {
	$tmp	= array();
	if ( $include_bracket )
		$tmp[]	= $t[$i];
	$i ++;
	$stack	= 0;
	while ( !spa_is_rbracket($t, $i) || $stack > 0 ) {
		if ( spa_is_lbracket($t, $i) )
			$stack ++;
		else if ( spa_is_rbracket($t, $i) )
			$stack --;
		$tmp[]	= $t[$i ++];
	}
	if ( !spa_is_rbracket($t, $i) )
		sql_parser_error(__FILE__, __LINE__, __FUNCTION__);
	if ( $include_bracket )
		$tmp[]	= $t[$i];
	$i ++;
	return $tmp;
}
#讀取SELECT關鍵字
function spa_read_select($t, &$i, $l, &$r) {
	if ( !spa_is_select($t, $i) )
		sql_parser_error(__FILE__, __LINE__, __FUNCTION__);
	$r[CP_SELECT]	= $t[$i ++];
}
#讀取SELECT選項
function spa_read_options($t, &$i, $l, &$r) {
	while ( spa_is_option($t, $i) ) 
		$r[CP_OPTIONS][]	= $t[$i ++];
}
#讀取查詢字段
function spa_read_fields($t, &$i, $l, &$r) {
	while ( !spa_end_fields($t, $i, $l) ) {
		spa_read_field($t, $i, $l, $r);
		spa_read_field_separater($t, $i, $l, $r);
	}
	#SELECT語句至少需要一個查詢字段
	if ( count($r[CP_FIELDS]) < 1 ) 
		sql_parser_error(__FILE__, __LINE__, __FUNCTION__);
}
#讀取單個查詢字段
function spa_read_field($t, &$i, $l, &$r) {
	$tmp	= array();
	while ( !spa_end_field($t, $i, $l) ) 
		if ( spa_is_lbracket($t, $i) ) {
			$tmp	= array_merge($tmp, spa_read_bracket($t, $i, $l, TRUE));
		} else 
			$tmp[]	= $t[$i ++];
	$r[CP_FIELDS][]	= $tmp;
}
#讀取查詢字段分隔符
function spa_read_field_separater($t, &$i, $l, &$r) {
	if ( spa_is_comma($t, $i, $n) ) {
		#$r[CP_FIELDS][]	= array_slice($t, $i, $n);
		$i	+= $n;
	}
}
#讀取哦FROM關鍵字
function spa_read_from($t, &$i, $l, &$r) {
	if ( spa_is_from($t, $i) )
		$r[CP_FROM]	= $t[$i ++];
}
#讀取要查詢的表
function spa_read_tables($t, &$i, $l, &$r) {
	while ( !spa_end_tables($t, $i, $l) ) {
		$tmp	= array();
		spa_read_table_separater($t, $i, $l, $tmp);
		spa_read_table_name($t, $i, $l, $tmp);
		spa_read_table_alias($t, $i, $l, $tmp);
		spa_read_table_condition($t, $i, $l, $tmp);
		$r[CP_TABLES][]	= $tmp;
	}
}
#讀取表名
function spa_read_table_name($t, &$i, $l, &$r) {
	if ( spa_is_lbracket($t, $i) ) {
		$r[CPK_TABLES_TABLE]	= sql_analysis(spa_read_bracket($t, $i, $l));
	} else {
		$r[CPK_TABLES_TABLE]	= array($t[$i ++]);
		if ( spa_is_dot($t, $i) ) 
			array_push($r[CPK_TABLES_TABLE], $t[$i ++], $t[$i ++]);
	}
}
#讀取表的別名
function spa_read_table_alias($t, &$i, $l, &$r) {
	if ( spa_is_as($t, $i) )
		$i ++;
	if ( !spa_is_on($t, $i) && !spa_end_table($t, $i, $l) ) 
		$r[CPK_TABLES_ALIAS]	= $t[$i ++];
}
#讀取表的連接條件
function spa_read_table_condition($t, &$i, $l, &$r) {
	if ( !spa_is_on($t, $i) ) return ;
	$i ++;
	while ( !spa_end_table($t, $i, $l) ) 
		if ( sp_is_lbracket($t, $i) )
			$r[CPK_TABLES_CONDITION]	= array_merge($r[CPK_TABLES_CONDITION], spa_read_bracket($t, $i, $l, TRUE));
		else 
			$r[CPK_TABLES_CONDITION][]	= $t[$i ++];
}
#讀取表的連接符
function spa_read_table_separater($t, &$i, $l, &$r) {
	if ( spa_is_comma($t, $i, $n) || spa_is_join($t, $i, $n) ) {
		$r[CPK_TABLES_SEPARATER]	= array_slice($t, $i, $n);
		$i	+= $n;
	}
}
#讀取WHERE關鍵字
function spa_read_where($t, &$i, $l, &$r) {
	if ( spa_is_where($t, $i) )
		$r[CP_WHERE]	= $t[$i ++];
}
#讀取查詢條件(WHERE)
function spa_read_conditions($t, &$i, $l, &$r) {
	while ( !spa_end_conditions($t, $i, $l) ) {
		$tmp	= array();
		spa_read_condition($t, $i, $l, $tmp);
		$r[CP_CONDITIONS][]	= $tmp;
	}
}
#讀取單條查詢條件(WHERE)
function spa_read_condition($t, &$i, $l, &$r) {
#TODO 對條件進行細節處理
	$tmp	= array();
	while( !spa_end_condition($t, $i, $l) ) 
		if ( spa_is_lbracket($t, $i) ) 
			$tmp	= array_merge($tmp, spa_read_bracket($t, $i, $l, TRUE));
		else 
			$tmp[]	= $t[$i ++];
	$r	= $tmp;
}
#讀取GROUP BY關鍵字
function spa_read_group_by($t, &$i, $l, &$r) {
	if ( spa_is_group_by($t, $i) )
		$r[CP_GROUP_BY]	= $t[$i ++] . SPACE . $t[$i ++];
}
#讀取分組項
function spa_read_groups($t, &$i, $l, &$r) {
	while ( !spa_end_groups($t, $i, $l) ) {
		$tmp	= array();
		spa_read_group_field($t, $i, $l, $tmp);
		spa_read_group_order($t, $i, $l, $tmp);
		$r[CP_GROUPS][]	= $tmp;
		spa_read_group_separater($t, $i, $l, $r[CP_GROUPS]);
	}
}
#讀取單條分組項
function spa_read_group_field($t, &$i, $l, &$r) {
	$tmp	= array();
	while ( !spa_end_group($t, $i, $l) && !spa_is_asc($t, $i) && !spa_is_desc($t, $i) ) 
		if ( spa_is_lbracket($t, $i) ) 
			$tmp	= array_merge($tmp, spa_read_bracket($t, $i, $l, TRUE));
		else 
			array_push($tmp, $t[$i ++]);
	$r[CPK_GROUPS_FIELD]	= $tmp;
}
#讀取分組項排序
function spa_read_group_order($t, &$i, $l, &$r) {
	if ( spa_is_desc($t, $i) || spa_is_asc($t, $i) )
		$r[CPK_GROUPS_ORDER]	= $t[$i ++];
}
#讀取分組項分隔符
function spa_read_group_separater($t, &$i, $l, &$r) {
	if ( spa_is_comma($t, $i, $n) ) {
		#$r[]	= array_slice($t, $i, $n);
		$i		+= $n;
	}
}
#讀取HAVING關鍵字
function spa_read_having($t, &$i, $l, &$r) {
	if ( spa_is_having($t, $i) )
		$r[CP_HAVING]	= $t[$i ++];
}
#讀取過濾條件(HAVING)
function spa_read_filters($t, &$i, $l, &$r) {
	while ( !spa_end_filters($t, $i, $l) ) {
		$tmp	= array();
		spa_read_filter($t, $i, $l, $tmp);
		$r[CP_FILTERS][]	= $tmp;
	}
}
function spa_read_filter($t, &$i, $l, &$r) {
#TODO 對條件進行細節處理
	$tmp	= array();
	while ( !spa_end_filter($t, $i, $l) )
		if ( spa_is_lbracket($t, $i) )
			$tmp	= array_merge($tmp, spa_read_bracket($t, $i, $l, TRUE));
		else 
			$tmp[]	= $t[$i ++];
	$r	= $tmp;
}
#讀取ORDER BY關鍵字
function spa_read_order_by($t, &$i, $l, &$r) {
	if ( spa_is_order_by($t, $i) ) 
		$r[CP_ORDER_BY]	= $t[$i ++] . SPACE . $t[$i ++];
}
#讀取排序項
function spa_read_orders($t, &$i, $l, &$r) {
	while ( !spa_end_orders($t, $i, $l) ) {
		$tmp	= array();
		spa_read_order_field($t, $i, $l, $tmp);
		spa_read_order_order($t, $i, $l, $tmp);
		$r[CP_ORDERS][]	=  $tmp;
		spa_read_order_separater($t, $i, $l, $r[CP_ORDERS]);
	}
}
#讀取排序項字段
function spa_read_order_field($t, &$i, $l, &$r) {
	$tmp	= array();
	while ( !spa_end_order($t, $i, $l) && !spa_is_asc($t, $i) && !spa_is_desc($t, $i) ) 
		if ( spa_is_lbracket($t, $i) ) 
			$tmp	= array_merge($tmp, spa_read_bracket($t, $i, $l, TRUE));
		else
			array_push($tmp, $t[$i ++]);
	$r[CPK_ORDERS_FIELD]	= $tmp;
}
#讀取排序項順序
function spa_read_order_order($t, &$i, $l, &$r) {
	if ( spa_is_asc($t, $i) || spa_is_desc($t, $i) ) 
		$r[CPK_ORDERS_ORDER]	= $t[$i ++];
}
#讀取排序項分隔符
function spa_read_order_separater($t, &$i, $l, &$r) {
	if ( spa_is_comma($t, $i, $n) ) {
		#$r[]	= array_slice($t, $i, $n);
		$i		+= $n;
	}
}
#讀取LIMIT關鍵字
function spa_read_limit($t, &$i, $l, &$r) {
	if ( spa_is_limit($t, $i, $l) )
		$r[CP_LIMIT]	= $t[$i ++];
}
#讀取偏移參數
function spa_read_offset_count($t, &$i, $l, &$r) {
	$offset	= 0;
	if ( spa_is_comma($t, $i + 1) ) {
		$offset	= $t[$i];
		$i		+= 2;
	}
	$count	= $t[$i ++];
	$r[CP_OFFSET]	= $offset;
	$r[CP_COUNT]	= $count;
}
#-------------------------------------------SQL還原
#遞歸的將$pieces所有子項按照順序用$glue連接
function sr_recursive_implode($glue, $pieces) {
	if ( !is_array($pieces) ) return $pieces;
	foreach ( $pieces as &$piece ) 
		if ( is_array($piece) )
			$piece	= sr_recursive_implode($glue, $piece);
	return implode_exclude_bracket($glue, $pieces);
}
function implode_exclude_bracket($glue, $pieces) {
	$ret	= '';
	if ( is_array($pieces) ) {
		$tmp	= array();
		foreach ( $pieces as $piece ) {
			if ( $piece === LBRACKET || $piece === RBRACKET ) {
				$ret	.= implode($glue, $tmp) . $piece;
				$tmp	= array();
			} else {
				$tmp[]	= $piece;
			}
		}
		$ret	.= implode($glue, $tmp);
	}
	return $ret;
}
#檢查給定數據$a是否非空數組, 如果指定了$k則檢查$a[$k]
function is_no_empty_array($a, $k = NULL) {
	if ( !is_null($k) ) 
		if ( !array_key_exists($k, $a) ) return FALSE;
		else $a = $a[$k];
	return is_array($a) && !empty($a);
}
function sr_restore_select($sc, &$r) {
	if ( array_key_exists(CP_SELECT, $sc) ) 
		array_push($r, KW_SELECT);
}
#還原選項
function sr_restore_options($sc, &$r) {
	if ( is_no_empty_array($sc, CP_OPTIONS) )
		array_push($r, sr_recursive_implode(SPACE, $sc[CP_OPTIONS]));
}
#還原查詢字段
function sr_restore_fields($sc, &$r) {
	if ( is_no_empty_array($sc, CP_FIELDS) ) {
		$tmp	= array();
		foreach ( $sc[CP_FIELDS] as $field ) 
			array_push($tmp, sr_recursive_implode(SPACE, $field));
		array_push($r, implode_exclude_bracket(COMMA, $tmp));
	}
}
#還原表
function sr_restore_tables($sc, &$r) {
	if ( is_no_empty_array($sc, CP_TABLES) ) {
		$ret_arr	= array();
		foreach ( $sc[CP_TABLES] as $table ) {
			$tmp	= array();
			if ( array_key_exists(CPK_TABLES_SEPARATER, $table) )
				$tmp[]	= sr_recursive_implode(SPACE, $table[CPK_TABLES_SEPARATER]);
			if ( array_key_exists(CPK_TABLES_TABLE, $table) ) 
				if ( array_key_exists(CP_SELECT, $table[CPK_TABLES_TABLE]) )
					$tmp[]	= LBRACKET . sql_restore($table[CPK_TABLES_TABLE]) . RBRACKET;
				else
					$tmp[]	= sr_recursive_implode(SPACE, $table[CPK_TABLES_TABLE]);
			if ( array_key_exists(CPK_TABLES_ALIAS, $table) )
				$tmp[]	= KW_AS . SPACE . strval($table[CPK_TABLES_ALIAS]);
			if ( array_key_exists(CPK_TABLES_CONDITION, $table) )
				$tmp[]	= KW_ON . SPACE . sr_recursive_implode(SPACE, $table[CPK_TABLES_CONDITION]);
			$ret_arr[]	= implode_exclude_bracket(SPACE, $tmp);
		}
		array_push($r, KW_FROM . SPACE . implode_exclude_bracket(SPACE, $ret_arr));
	}
}
#還原條件
function sr_restore_conditions($sc, &$r) {
	if ( is_no_empty_array($sc, CP_CONDITIONS) ) 
		array_push($r, KW_WHERE . SPACE . sr_recursive_implode(SPACE, $sc[CP_CONDITIONS]));
}
#還原分組項
function sr_restore_groups($sc, &$r) {
	if ( is_no_empty_array($sc, CP_GROUPS) ) {
		$tmp	= array();
		foreach ( $sc[CP_GROUPS] as $group ) {
			$g	= sr_recursive_implode(SPACE, $group[CPK_GROUPS_FIELD]);
			if ( array_key_exists(CPK_GROUPS_ORDER, $group) )
				$g	.= SPACE . $group[CPK_GROUPS_ORDER];
			$tmp[]	= $g;
		}
		array_push($r, KW_GROUP_BY . SPACE . implode_exclude_bracket(COMMA, $tmp));
	}
}
#還原過濾條件
function sr_restore_filters($sc, &$r) {
	if ( is_no_empty_array($sc, CP_FILTERS) ) 
		array_push($r, KW_HAVING . SPACE . sr_recursive_implode(SPACE, $sc[CP_FILTERS]));
}
#還原排序項
function sr_restore_orders($sc, &$r) {
	if ( is_no_empty_array($sc, CP_ORDERS) ) {
		$tmp	= array();
		foreach ( $sc[CP_ORDERS] as $order ) {
			$o	= sr_recursive_implode(SPACE, $order[CPK_ORDERS_FIELD]);
			if ( array_key_exists(CPK_ORDERS_ORDER, $order) )
				$o	.= SPACE . $order[CPK_ORDERS_ORDER];
			$tmp[]	= $o;
		}
		array_push($r, KW_ORDER_BY . SPACE . implode_exclude_bracket(COMMA, $tmp));
	}
}
#還原偏移量
function sr_restore_offset_count($sc, &$r) {
	if ( array_key_exists(CP_COUNT, $sc) ) {
		$count	= intval($sc[CP_COUNT]);
		$offset	= intval(array_key_exists(CP_OFFSET, $sc) ? $sc[CP_OFFSET] : 0);
		array_push($r, KW_LIMIT . SPACE . $offset . COMMA . $count);
	}
}

#------------------------------------------------分頁相關SQL重組子邏輯: sql_to_count相關
#將結構化SQL轉換爲計數的結構化SQL(不支持外連接)
function sc_to_count($sc, $alias = COUNT_DEFAULT_ALIAS) {
	#count字段構建
	$count_field	= sc_build_count_field($sc, $alias);
	#刪除排序項
	sc_delete_orders($sc);
	#合併HAVING條件到WHERE
	$sc[CP_FIELDS]	= array($count_field);
	return $sc;
}
#構造count查詢字段
function sc_build_count_field(&$sc, $alias = COUNT_DEFAULT_ALIAS) {
	#構造count統計查詢
	$count_field	= is_no_empty_array($sc, CP_GROUPS)
					? sc_build_count_by_group($sc)
					: sc_build_count_normal($sc);
	#別名構建
	if ( !empty($alias) ) 
		array_push($count_field, KW_AS, $alias);
	return $count_field;
}
#根據分組構造count統計
function sc_build_count_by_group(&$sc) {
	$count_field	= array(
		KW_COUNT . LBRACKET, 
		KW_DISTINCT, 
		tidy_fields_to_one(read_group_fields($sc)),
		RBRACKET, 
	);
	unset($sc[CP_GROUP_BY]);
	unset($sc[CP_GROUPS]);
	return $count_field;
}
#無分組的count構造
function sc_build_count_normal(&$sc) {
	return array(
		KW_COUNT . LBRACKET, 
		'*', 
		RBRACKET
	);
}
#刪除排序相關項
function sc_delete_orders(&$sc) {
	unset($sc[CP_ORDER_BY]);
	unset($sc[CP_ORDERS]);
}
#讀取分組項中的字段信息
function read_group_fields($sc) {
	$tmp	= array();
	foreach ( $sc[CP_GROUPS] as $group ) 
		$tmp[]	= $group[CPK_GROUPS_FIELD];
	return $tmp;
}
#將多個字段信息整理成一個逗號分隔的大項(用於外層嵌套括號構成複雜字段)
function tidy_fields_to_one($fields) {
	$tmp	= array();
	foreach ( $fields as $field ) {
		array_push($tmp, $field);
		array_push($tmp, COMMA);
	}
	array_pop($tmp);
	return $tmp;
}

#------------------------------------------------分頁相關SQL重組子邏輯: sql_add_since相關
#結構化SQL增加since_id
function sc_add_since(&$sc, $prev_id, $next_id, &$need_reverse) {
	$need_reverse	= FALSE;
	if ( is_null($prev_id) && is_null($next_id) ) 
		return $sc;
	#將排序項拷貝到查詢字段中, 修正排序項爲新的查詢字段的別名
	cp_orders_to_fields($sc);
	$need_prev	= !is_null($prev_id) && !is_bool($prev_id);
	$need_next	= !is_null($next_id) && !is_bool($next_id);
	#如果是向前查找, 需要對排序項進行逆序, 並對返回結果進行逆序
	if ( $need_prev && !$need_next ) {
		sc_apply_since_id($sc, $prev_id, DIRECT_PREV);
		sc_orders_reverse($sc);
		$need_reverse	= TRUE;
	#如果是向後查找, 直接追加條件
	} else if ( !$need_prev && $need_next ) {
		sc_apply_since_id($sc, $next_id, DIRECT_NEXT);
	#如果是閉區間查找, 則反向追加條件
	} else if ( $need_prev && $need_next ) {
		sc_apply_since_id($sc, $prev_id, DIRECT_NEXT);
		sc_apply_since_id($sc, $next_id, DIRECT_PREV);
	}
	return $sc;
}
#將排序項複製到查詢字段, 並自動設置別名, 使用別名作爲排序項
function cp_orders_to_fields(&$sc) {
	#讀取排序項中的字段信息
	$o_fields	= read_order_fields($sc);
	#將排序項轉換成查詢字段格式
	$a_fields	= convert_orders_to_fields($sc, $o_fields, $n_o_fields);
	#將排序項轉換而來的查詢字段合併到原始查詢字段
	sc_merge_having_to_where($sc);
	#將排序項字段合併到查詢字段
	$sc[CP_FIELDS]	= array_merge($sc[CP_FIELDS], $a_fields);
	#設置新的排序項爲別名
	$sc[CP_ORDERS]	= $n_o_fields;
}
#將id描述的規則按照方向direction應用到sc中
function sc_apply_since_id(&$sc, $id, $direction) {
	$infos		= explain_since_id($id);
	if ( !array_key_exists(CP_FILTERS, $sc) ) {
		$sc[CP_HAVING]	= KW_HAVING;
		$sc[CP_FILTERS]	= array();
	} else {
		array_unshift($sc[CP_FILTERS], LBRACKET);
		array_push($sc[CP_FILTERS], RBRACKET, KW_AND);
	}
	$filters	= id_info_to_filter($sc, $infos, $direction);
	array_unshift($filters, LBRACKET);
	array_push($filters, RBRACKET);
	sc_append_filter($sc, $filters);
}
#讀取排序項中的字段信息
function read_order_fields($sc) {
	$tmp	= array();
	foreach ( $sc[CP_ORDERS] as $group ) 
		$tmp[]	= $group[CPK_ORDERS_FIELD];
	return $tmp;
}
#將排序項轉換爲查詢字段格式, 並輸出它們的別名的排序項數據
function convert_orders_to_fields($sc, $o_fields, &$n_o_fields = NULL) {
	$tmp	= array();
	$i		= 0;
	$n_o_fields	= array();
	foreach ( $o_fields as &$o_field ) {
		$alias		= ORDER_ALIAS_PREFIX . $i;
		array_push($o_field, KW_AS, $alias);
		array_push($n_o_fields, array(
			CPK_ORDERS_FIELD	=> array($alias), 
			CPK_ORDERS_ORDER	=> array_key_exists(CPK_ORDERS_ORDER, $sc[CP_ORDERS][$i])
								? $sc[CP_ORDERS][$i][CPK_ORDERS_ORDER]
								: KW_ASC, 
		));
		$i ++;
	}
	return $o_fields;
}
#將HAVING條件合併到WHERE
function sc_merge_having_to_where(&$sc) {
	if ( array_key_exists(CP_FILTERS, $sc) ) {
		$conditions	= array();
		if ( array_key_exists(CP_CONDITIONS, $sc) ) {
			$conditions	= $sc[CP_CONDITIONS];
			array_unshift($conditions, LBRACKET);
			array_push($conditions, RBRACKET);
		}
		array_push($conditions, KW_AND);
		array_push($conditions, LBRACKET);
		$conditions	= array_merge($conditions, $sc[CP_FILTERS]);
		array_push($conditions, RBRACKET);
		$sc[CP_CONDITIONS]	= $conditions;
		unset($sc[CP_HAVING]);
		unset($sc[CP_FILTERS]);
	}
}
#將since_id還原爲數組形式
function explain_since_id($id) {
	$tmp	= array();
	$items	= explode(SINCE_ID_SEPARATER_0, $id);
	foreach ( $items as $item ) {
		$item	= explode(SINCE_ID_SEPARATER_1, $item);
		$tmp[]	= array($item[0], $item[1]);
	}
	return $tmp;
}
#將since_id的數組描述轉換爲過濾條件
function id_info_to_filter($sc, $infos, $direction) {
	$tmp	= array();
	$i	= -1;
	$l	= count($infos);
	while ( ++ $i < $l ) {
		$order	= find_order_from_orders($sc, $infos[$i][0]);
		$sign	= $direction === DIRECT_PREV
				? (strtoupper($order) === KW_ASC ? KWS_LT : KWS_GT)
				: (strtoupper($order) === KW_ASC ? KWS_GT : KWS_LT);
		$t		= array();
		$j		= -1;
		while ( ++ $j < $i ) 
			array_push($t, $infos[$j][0], KWS_EQ, $infos[$j][1], KW_AND);
		array_unshift($t, LBRACKET);
		array_push($t, $infos[$i][0], $sign, $infos[$i][1], RBRACKET, KW_OR);
		$tmp	= array_merge($tmp, $t);
	}
	array_pop($tmp);
	return $tmp;
}
#在結構化SQL中追加一些過濾條件
function sc_append_filter(&$sc, $filters) {
	if ( empty($filters) ) return ;
	$sc[CP_HAVING]	= KW_HAVING;
	$original	= is_no_empty_array($sc, CP_FILTERS)
				? $sc[CP_FILTERS]
				: array();
	$sc[CP_FILTERS]	= array_merge($original, $filters);
}
#從結構化SQL的分組項中找到字段排序(TODO 這裏的處理只適用於簡單字段)
function find_order_from_orders($sc, $name) {
	foreach ( $sc[CP_ORDERS] as $o ) 
		if ( sr_recursive_implode(SPACE, $o[CPK_ORDERS_FIELD]) == $name ) 
			return array_key_exists(CPK_ORDERS_ORDER, $o) ? $o[CPK_ORDERS_ORDER] : KW_ASC;
	return KW_ASC;
}
#將結構化SQL中的所有ORDER項逆序
function sc_orders_reverse(&$sc) {
	if ( is_no_empty_array($sc, CP_ORDERS) ) {
		foreach ( $sc[CP_ORDERS] as &$item ) {
			if ( array_key_exists(CPK_ORDERS_ORDER, $item) && strtoupper($item[CPK_ORDERS_ORDER]) === KW_DESC )
				$item[CPK_ORDERS_ORDER]	= KW_ASC;
			else 
				$item[CPK_ORDERS_ORDER]	= KW_DESC;
		}
	}
}

#------------------------------------------------分頁相關SQL重組子邏輯: mysql_paginate_query相關
#修正偏移量值
function correct_offset($offset) {
	return is_numeric($offset) ? intval($offset) : 0;
}
#爲結構化的SQL增加偏移量部分
function sc_add_offset(&$sc, $offset, $count) {
	$sc[CP_LIMIT]	= KW_LIMIT;
	$sc[CP_OFFSET]	= $offset;
	$sc[CP_COUNT]	= $count;
	return $sc;
}
#使用結構化SQL和輸出數據構造數據邊界
function sc_datas_boundary($sc, &$datas, &$prev_id, &$next_id) {
	$prev	= $datas[0];
	$next	= $datas[count($datas) - 1];
	$orders	= orders_to_presentable($sc[CP_ORDERS]);
	$prev_id	= build_since_id($orders, $prev);
	$next_id	= build_since_id($orders, $next);
	delete_order_alias_datas($orders, $datas);
}
#將排序項轉換爲可讀形式
function orders_to_presentable($orders) {
	$tmp	= array();
	foreach ( $orders as $order ) {
		$a	= sr_recursive_implode(SPACE, $order[CPK_ORDERS_FIELD]);
		$o	= array_key_exists(CPK_ORDERS_ORDER, $order)
			? strtoupper($order[CPK_ORDERS_ORDER])
			: KW_ASC;
		$tmp[$a]	= $o;
	}
	return $tmp;
}
#根據排序項可視結構和數據項構造標識
function build_since_id($orders, $data) {
	$id	= array();
	foreach ( $orders as $f => $o ) {
		if ( is_array($data) && array_key_exists($f, $data) ) {
			$id[]	= $f . SINCE_ID_SEPARATER_1 . $data[$f];
		}
	}
	return implode(SINCE_ID_SEPARATER_0, $id);
}
#刪除排序項增加上去的字段
function delete_order_alias_datas($orders, &$datas) {
	foreach ( $orders as $f => $o ) 
		foreach ( $datas as &$data ) 
			if ( array_key_exists($f, $data) ) unset($data[$f]);
}

#-------------------------------------------------數據庫操作
#查詢單條記錄
function mysql_geta($conn, $sql) {
	$rs		= mysql_query($sql, $conn);
	if ( mysql_errno($conn) )
		mysql_error_handler(mysql_errno($conn), mysql_error($conn));
	$row	= mysql_fetch_array($rs, MYSQL_ASSOC);
	if ( mysql_errno($conn) )
		mysql_error_handler(mysql_errno($conn), mysql_error($conn));
	return $row;
}
#查詢多條記錄
function mysql_geta_all($conn, $sql) {
	$rs		= mysql_query($sql, $conn);
	if ( mysql_errno($conn) )
		mysql_error_handler(mysql_errno($conn), mysql_error($conn));
	$rows	= array();
	while ( $row = mysql_fetch_array($rs, MYSQL_ASSOC) )
		if ( mysql_errno($conn) )
			mysql_error_handler(mysql_errno($conn), mysql_error($conn));
		else
			array_push($rows, $row);
	return $rows;
}

test_base.php

<?php
/*
 * 分頁程序單元測試基礎庫
 * 1. 斷言環境設置
 * 2. 定義mysql的錯誤處理
 * author: selfimpr
 * blog: http://blog.csdn.net/lgg201
 * mail: [email protected]
 */
require dirname(__FILE__) . '/page0.lib.php';
assert_options(ASSERT_ACTIVE, TRUE);
assert_options(ASSERT_WARNING, TRUE);
assert_options(ASSERT_CALLBACK, 'assert_handler');

function assert_info($file = NULL, $line = NULL, $sign = NULL) {
	static $msg;
	if ( func_num_args() > 1 )
		$msg	= sprintf(chr(10) . 'assert error at %s[%s]: %s', $file, $line, $sign);
	else 
		return $msg;
}
function assert_handler() {
	echo assert_info();
}
function mysql_error_handler($errno, $errstr) {
	echo $errstr . chr(10);
}

test_convert.php

<?php
/*
 * 分頁程序 分頁之間的轉換測試
 * author: selfimpr
 * blog: http://blog.csdn.net/lgg201
 * mail: [email protected]
 */
require dirname(__FILE__) . '/test_base.php';
#ping_to_offset單元測試
function test_ping_to_offset() {
	$total_record	= 92;
	$count			= 10;
	$page_ping		= 3;
	$info			= ping_to_offset(-1, 1, $page_ping, $count, $total_record);
	assert(p_offset($info) === 0);
	$info			= ping_to_offset(3, 1, $page_ping, $count, $total_record);
	assert(p_offset($info) === 60);
	$info			= ping_to_offset(4, 1, $page_ping, $count, $total_record);
	assert(p_offset($info) === 90);
	$info			= ping_to_offset(5, 1, $page_ping, $count, $total_record);
	assert(p_offset($info) === 90);
	$info			= ping_to_offset(2, 3, $page_ping, $count, $total_record);
	assert(p_offset($info) === 50);
	$info			= ping_to_offset(4, 2, $page_ping, $count, $total_record);
	assert(p_offset($info) === 90);
}
#offset_to_ping單元測試
function test_offset_to_ping() {
	$page_ping	= 3;
	$count		= 10;
	$info		= offset_to_ping(0, $page_ping, $count);
	assert(p_page($info) === 1);
	assert(p_ping($info) === 1);
	$info		= offset_to_ping(10, $page_ping, $count);
	assert(p_page($info) === 1);
	assert(p_ping($info) === 2);
	$info		= offset_to_ping(20, $page_ping, $count);
	assert(p_page($info) === 1);
	assert(p_ping($info) === 3);
	$info		= offset_to_ping(30, $page_ping, $count);
	assert(p_page($info) === 2);
	assert(p_ping($info) === 1);
	$info		= offset_to_ping(40, $page_ping, $count);
	assert(p_page($info) === 2);
	assert(p_ping($info) === 2);
	$info		= offset_to_ping(50, $page_ping, $count);
	assert(p_page($info) === 2);
	assert(p_ping($info) === 3);
	$info		= offset_to_ping(60, $page_ping, $count);
	assert(p_page($info) === 3);
	assert(p_ping($info) === 1);
	$info		= offset_to_ping(70, $page_ping, $count);
	assert(p_page($info) === 3);
	assert(p_ping($info) === 2);
	$info		= offset_to_ping(90, $page_ping, $count);
	assert(p_page($info) === 4);
	assert(p_ping($info) === 1);
}
#page_to_offset單元測試
function test_page_to_offset() {
	$total_record	= 92;
	$count			= 10;
	$info			= page_to_offset(-1, $count, $total_record);
	assert(p_offset($info) === 0);
	$info			= page_to_offset(1, $count, $total_record);
	assert(p_offset($info) === 0);
	$info			= page_to_offset(3, $count, $total_record);
	assert(p_offset($info) === 20);
	$info			= page_to_offset(9, $count, $total_record);
	assert(p_offset($info) === 80);
	$info			= page_to_offset(10, $count, $total_record);
	assert(p_offset($info) === 90);
	$info			= page_to_offset(12, $count, $total_record);
	assert(p_offset($info) === 90);
}
#offset_to_page單元測試
function test_offset_to_page() {
	$count	= 10;
	$info	= offset_to_page(0, $count);
	assert(p_page($info) === 1);
	$info	= offset_to_page(30, $count);
	assert(p_page($info) === 4);
	$info	= offset_to_page(80, $count);
	assert(p_page($info) === 9);
	$info	= offset_to_page(90, $count);
	assert(p_page($info) === 10);
}

test_ping_to_offset();
test_offset_to_ping();
test_page_to_offset();
test_offset_to_page();

test_parse.php

<?php
/*
 * 分頁程序 SQL解析測試(由於處理簡單, 可能會有某些SQL處理不了, 需要使用此工具先確認可用)
 * author: selfimpr
 * blog: http://blog.csdn.net/lgg201
 * mail: [email protected]
 */
require dirname(__FILE__) . '/test_base.php';

function test_sql_parser($sql) {
	$components	= sql_parser($sql);
	echo '測試SQL轉換:' . chr(10);
	echo sql_restore($components) . chr(10) . chr(10);
}
function test_sql_to_count($sql) {
	echo '測試SQL附加COUNT:' . chr(10);
	echo sql_to_count($sql) . chr(10) . chr(10);
}
function test_sql_add_since($sql, $prev_id = NULL, $next_id = NULL) {
	echo '測試SQL附加since_id條件:' . chr(10);
	echo sql_add_since($sql, $prev_id, $next_id, $need_reverse) . chr(10);
	if ( $need_reverse )
	echo '只按照prev_id附加since_id時, 需要對查詢結果逆序' . chr(10);
	echo chr(10);
}

#樣例SQL, 不關心語義正確性
$sql	= 'SELECT ALL HIGH_PRIORITY STRAIGHT_JOIN SQL_SMALL_RESULT SQL_BUFFER_RESULT SQL_CACHE SQL_CALC_FOUND_ROWS '
		. ' if(a.feed_id < 100, 100, 200) as t, f.feed_id d, f.content, `h`.`hot`, 3 + "\"\Hello FROM ,* ", *, \'world\''
		. ' FROM feed AS f INNER JOIN comment c ON f.feed_id = c.feed_id AND (1 + (2 -1)) * 3 > 4 LEFT JOIN (SELECT * FROM (SELECT * FROM hot) AS t) ON f.feed_id = h.feed_id, hot AS h1, hot h2, hot'
		. ' WHERE 1 = 2 AND hot IS NOT NULL OR (1 > 2 AND 3 < 4)'
		. ' GROUP by h.hot desc, f.feed_id, f.ctime ASc, (f.ctime + 1) + 3 desc'
		. ' HAVING 1 = 2 AND 2 AND (feed.feed_id < 5)'
		. ' ORDER by hot desc, f.feed_id * (100 + 2) asC, c.comment_id desc'
		. ' LIMIT 1, 3'
		;
if ( $argc > 1 )
	$sql	= $argv[1];
test_sql_parser($sql);
test_sql_to_count($sql);
test_sql_add_since($sql, $argv[2] ? $argv[2] : '__o_0:1948|__o_1:333333', $argv[3]);

echo '原始SQL:' . chr(10);
echo $sql . chr(10);

test_page.php

<?php
/*
 * 分頁程序 分頁測試
 * author: selfimpr
 * blog: http://blog.csdn.net/lgg201
 * mail: [email protected]
 */
require dirname(__FILE__) . '/test_base.php';

#初始化數據量
define('FEED_ID_MIN',				1000);
define('FEED_COUNT',				972);
define('COMMENT_ID_MIN',			1000);
define('COMMENT_MAX_COUNT',			10);
define('HOT_MIN',					1);
define('HOT_MAX',					100);
define('TRANSPOND_COUNT_MAX',		100);

#數據庫信息
$db_host	= '127.0.0.1';
$db_port	= '3306';
$db_user	= 'paginate_test';
$db_pass	= 'paginate_test';
$db_db		= 'paginate_test';
$db_charset	= 'UTF-8';

#全局數據變量名
define('ALL_DATAS',					'_all_datas');

#涉及到的key
define('K_HC_FEEDS',				'_hc_feed');
define('K_TC_FEEDS',				'_tc_feed');
define('K_CC_FEEDS',				'_cc_feed');
define('K_FEED_ID',					'feed_id');

#轉發比較函數
function comp_transpond_count($a, $b) {
	$tc	= $b['transpond_count'] - $a['transpond_count'];
	$ic	= $b['feed_id'] - $a['feed_id'];
	return $tc != 0 ? $tc : $ic;
}
#評論比較函數
function comp_comment_count($a, $b) {
	$cc	= $b['comment_count'] - $a['comment_count'];
	$ic	= $b['feed_id'] - $a['feed_id'];
	return $cc != 0 ? $cc : $ic;
}
#熱點比較函數
function comp_hot($a, $b) {
	$hc	= $b['hot'] - $a['hot'];
	$ic	= $b['feed_id'] - $a['feed_id'];
	return $hc != 0 ? $hc : $ic;
}

#數據庫初始化臨時文件
$tmp_file	= '/tmp/__paginate_test_tmp.sql';
#數據庫創建腳本
$db_init	= <<<doc
DROP DATABASE IF EXISTS `paginate_test`;
CREATE DATABASE IF NOT EXISTS `paginate_test`;
USE `paginate_test`;

DROP TABLE IF EXISTS `feed`;
CREATE TABLE IF NOT EXISTS `feed` (
	`feed_id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '微博ID', 
	`ctime` INT NOT NULL COMMENT '微博創建時間', 
	`content` CHAR(100) NOT NULL DEFAULT '' COMMENT '微博內容', 
	`transpond_count` INT NOT NULL DEFAULT 0 COMMENT '微博轉發數'
) COMMENT '微博表';

DROP TABLE IF EXISTS `comment`;
CREATE TABLE IF NOT EXISTS `comment` (
	`comment_id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '評論ID', 
	`content` CHAR(100) NOT NULL DEFAULT '' COMMENT '評論內容', 
	`feed_id` INT NOT NULL COMMENT '被評論微博ID'
) COMMENT '評論表';

DROP TABLE IF EXISTS `hot`;
CREATE TABLE IF NOT EXISTS `hot` (
	`feed_id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '微博ID', 
	`hot` INT NOT NULL DEFAULT 0 COMMENT '微博熱度'
) COMMENT '熱點微博表';
doc;

#初始化數據庫
function init_db() {
	global $db_host, $db_port, $db_user, $db_pass, $db_db, $db_init, $tmp_file;
	$datas	= generate_data();
	file_put_contents($tmp_file, $db_init . chr(10) . data_to_sql($datas));
	`mysql -u$db_user -p$db_pass -h$db_host -P$db_port $db_db -e 'SOURCE $tmp_file' && rm $tmp_file`;
	$tc_datas	= $datas;
	$cc_datas	= $datas;
	$hc_datas	= $datas;
	foreach ( $cc_datas as $k => $v ) 
		if ( count($v['comments']) <= 0 ) 
			unset($cc_datas[$k]);
	usort($tc_datas, 'comp_transpond_count');
	usort($cc_datas, 'comp_comment_count');
	usort($hc_datas, 'comp_hot');
	$GLOBALS[ALL_DATAS]	= array(
		K_TC_FEEDS	=> $tc_datas, 
		K_CC_FEEDS	=> $cc_datas, 
		K_HC_FEEDS	=> $hc_datas, 
	);
}
#生成測試數據
function generate_data() {
	$i		= -1;
	$j		= 0;
	$feeds	= array();
	while ( ++ $i < FEED_COUNT ) {
		$feed_id			= FEED_ID_MIN + $i;
		$ctime				= time();
		$transpond_count	= rand(0, TRANSPOND_COUNT_MAX);
		$hot				= rand(HOT_MIN, HOT_MAX);
		$comments			= array();
		$comment_count		= rand(0, COMMENT_MAX_COUNT);
		$k					= -1;
		while ( ++ $k < $comment_count ) {
			$comment_id		= COMMENT_ID_MIN + $j ++;
			$comments[]	= array(
				'content'		=> sprintf('cid: %d, fid: %d, ccnt: %d, tcnt: %d, hot: %d', $comment_id, $feed_id, $comment_count, $transpond_count, $hot), 
				'comment_id'	=> $comment_id, 
			);
		}
		$feeds[]	= array(
			'feed_id'			=> $feed_id, 
			'content'			=> sprintf('fid: %d, ccnt: %d, tcnt: %d, hot: %d', $feed_id, $comment_count, $transpond_count, $hot), 
			'comments'			=> $comments, 
			'comment_count'		=> $comment_count, 
			'transpond_count'	=> $transpond_count, 
			'hot'				=> $hot, 
			'ctime'				=> $ctime, 
		);
	}
	return $feeds;
}
#將生成的測試數據轉換爲sql語句
function data_to_sql($feeds) {
	$feed_sql		= 'INSERT INTO feed(feed_id, ctime, content, transpond_count) VALUES ';
	$comment_sql	= 'INSERT INTO comment(comment_id, content, feed_id) VALUES ';
	$hot_sql		= 'INSERT INTO hot(feed_id, hot) VALUES ';
	foreach ( $feeds AS $feed ) {
		$feed_sql	.= sprintf('(%d, %d, "%s", %d), ', $feed['feed_id'], $feed['ctime'], $feed['content'], $feed['transpond_count']);
		if ( !empty($feed['comments']) )
			foreach ( $feed['comments'] as $comment ) 
				$comment_sql	.= sprintf('(%d, "%s", %d), ', $comment['comment_id'], $comment['content'], $feed['feed_id']);
		$hot_sql	.= sprintf('(%d, %d), ', $feed['feed_id'], $feed['hot']);
	}
	return substr($feed_sql, 0, strlen($feed_sql) - 2) . ";\n" . substr($comment_sql, 0, strlen($comment_sql) - 2) .  ";\n" . substr($hot_sql, 0, strlen($hot_sql) - 2);
}
#格式化打印測試數據
function print_feeds($feeds) {
	foreach ( $feeds as $feed ) {
		printf("\tfeed_id: %d, transpond_count: %d, hot: %d, content: '%s'\n", $feed['feed_id'], $feed['transpond_count'], $feed['hot'], $feed['content']);
		foreach ( $feed['comments'] as $comment ) {
			printf("\t\tcomment_id: %d, content: '%s'\n", $comment['comment_id'], $comment['content']);
		}
	}
}
#格式化打印所有測試數據
function print_all_feeds($all_feeds) {
	foreach ( $all_feeds as $key => $feeds ) {
		printf("order type: %s\n", $key);
		print_feeds($feeds);
		printf("\n\n");
	}
}
#基本斷言函數
function assert_base($info, $datas, $offset, $count, $file, $line, $sign) {
	assert_info($file, $line, $sign . ':count');
	assert(intval($count) === count(p_datas($info)));
	assert_info($file, $line, $sign . ':datas');
	if ( is_array(p_datas($info)) )
	foreach ( p_datas($info) as $data ) 
		assert(intval($data[K_FEED_ID]) == intval($datas[$offset ++][K_FEED_ID]));
}
#斷言熱門數據
function assert_hot($info, $offset, $count, $file, $line) {
	assert_base($info, $GLOBALS[ALL_DATAS][K_HC_FEEDS], $offset, $count, $file, $line, __FUNCTION__);
}
#斷言熱轉數據
function assert_transpond($info, $offset, $count, $file, $line) {
	assert_base($info, $GLOBALS[ALL_DATAS][K_TC_FEEDS], $offset, $count, $file, $line, __FUNCTION__);
}
#斷言熱評數據
function assert_comment($info, $offset, $count, $file, $line) {
	assert_base($info, $GLOBALS[ALL_DATAS][K_CC_FEEDS], $offset, $count, $file, $line, __FUNCTION__);
}

#初始化數據庫連接
function init_conn() {
	global $db_host, $db_port, $db_user, $db_pass, $db_db, $db_charset;
	static $conn;
	if ( is_null($conn) ) {
		$conn	= mysql_connect($db_host . ':' . $db_port, $db_user, $db_pass);
		mysql_select_db($db_db, $conn);
		mysql_set_charset($db_charset, $conn);
	}
	return $conn;
}

#單純傳統分頁測試
function test_tradition($sql, $assert, $total_record) {
	$total_page	= ceil($total_record / 10);
	$remain		= $total_record % 10 ? $total_record % 10 : 10;
	$info	= mysql_paginate_tradition(init_conn(), $sql, -1, 10);
	$assert($info, 0, 10, __FILE__, __LINE__);
	$info	= mysql_paginate_tradition(init_conn(), $sql, 1, 10);
	$assert($info, 0, 10, __FILE__, __LINE__);
	$info	= mysql_paginate_tradition(init_conn(), $sql, 2, 10);
	$assert($info, 10, 10, __FILE__, __LINE__);
	$info	= mysql_paginate_tradition(init_conn(), $sql, 2, 10);
	$assert($info, 10, 10, __FILE__, __LINE__);
	$info	= mysql_paginate_tradition(init_conn(), $sql, $total_page - 1, 10);
	$assert($info, ($total_page - 2) * 10, 10, __FILE__, __LINE__);
	$info	= mysql_paginate_tradition(init_conn(), $sql, $total_page, 10);
	$assert($info, ($total_page - 1) * 10, $remain, __FILE__, __LINE__);
	$info	= mysql_paginate_tradition(init_conn(), $sql, $total_page + rand(1, 10), 10);
	$assert($info, ($total_page - 1) * 10, $remain, __FILE__, __LINE__);
}
#單純分段分頁測試
function test_ping($sql, $assert, $total_record) {
	$total_ping	= ceil($total_record / 10);
	$total_page	= ceil($total_ping / 3);
	$remain		= $total_record % 10 ? $total_record % 10 : 10;
	$remain_ping	= $total_ping % 3 ? $total_ping % 3 : 3;
	$info	= mysql_paginate_ping(init_conn(), $sql, -1, -1, 3, 10);
	$assert($info, 0, 10, __FILE__, __LINE__);
	$info	= mysql_paginate_ping(init_conn(), $sql, -1, 2, 3, 10);
	$assert($info, 10, 10, __FILE__, __LINE__);
	$info	= mysql_paginate_ping(init_conn(), $sql, 1, 3, 3, 10);
	$assert($info, 20, 10, __FILE__, __LINE__);
	$info	= mysql_paginate_ping(init_conn(), $sql, 1, 4, 3, 10);
	$assert($info, 20, 10, __FILE__, __LINE__);
	$info	= mysql_paginate_ping(init_conn(), $sql, 5, 2, 3, 10);
	$assert($info, 130, 10, __FILE__, __LINE__);
	$info	= mysql_paginate_ping(init_conn(), $sql, $total_page, $remain_ping, 3, 10);
	$assert($info, ($total_ping - 1) * 10, $remain, __FILE__, __LINE__);
	$info	= mysql_paginate_ping(init_conn(), $sql, $total_page + rand(1, 10), $remain_ping, 3, 10);
	$assert($info, ($total_ping - 1) * 10, $remain, __FILE__, __LINE__);
}
#單純原始分頁測試
function test_raw($sql, $assert, $total_record) {
	$remain		= $total_record % 10 ? $total_record % 10 : 10;
	$info	= mysql_paginate_raw(init_conn(), $sql, -1, 10);
	$assert($info, 0, 10, __FILE__, __LINE__);
	$info	= mysql_paginate_raw(init_conn(), $sql, 100, 10);
	$assert($info, 100, 10, __FILE__, __LINE__);
	$info	= mysql_paginate_raw(init_conn(), $sql, ($total_record - $remain), 10);
	$assert($info, ($total_record - $remain), $remain, __FILE__, __LINE__);
}
#單純since_id分頁測試
function test_since_id($sql, $assert) {
	#第一頁
	$info_0	= mysql_paginate_since_id(init_conn(), $sql, 10);
	$assert($info_0, 0, 10, __FILE__, __LINE__);
	#無數據(第一頁之前
	$info_1	= mysql_paginate_since_id(init_conn(), $sql, 10, p_prev_id($info_0));
	$assert($info_1, 0, 0, __FILE__, __LINE__);
	#第二頁
	$info_2	= mysql_paginate_since_id(init_conn(), $sql, 10, NULL, p_next_id($info_0));
	$assert($info_2, 10, 10, __FILE__, __LINE__);
	#第三頁
	$info_3	= mysql_paginate_since_id(init_conn(), $sql, 10, NULL, p_next_id($info_2));
	$assert($info_3, 20, 10, __FILE__, __LINE__);
	#第四頁
	$info_4	= mysql_paginate_since_id(init_conn(), $sql, 10, NULL, p_next_id($info_3));
	$assert($info_4, 30, 10, __FILE__, __LINE__);
	#第三頁
	$info_5	= mysql_paginate_since_id(init_conn(), $sql, 10, p_prev_id($info_4), NULL);
	$assert($info_5, 20, 10, __FILE__, __LINE__);
	#第二頁(第二頁至第三頁之間的前10條)
	$info_5	= mysql_paginate_since_id(init_conn(), $sql, 10, p_next_id($info_0), p_prev_id($info_4));
	$assert($info_5, 10, 10, __FILE__, __LINE__);
}
#傳統分頁複合since_id分頁測試
function test_tradition_since_id($sql, $assert, $total_record) {
	$total_page	= ceil($total_record / 10);
	$remain		= $total_record % 10 ? $total_record % 10 : 10;
	$info	= mysql_paginate_tradition_since_id(init_conn(), $sql, -1, 10);
	$assert($info, 0, 10, __FILE__, __LINE__);
	$info	= mysql_paginate_tradition_since_id(init_conn(), $sql, 2, 10);
	$assert($info, 10, 10, __FILE__, __LINE__);
	$info	= mysql_paginate_tradition_since_id(init_conn(), $sql, $total_page - 1, 10);
	$assert($info, ($total_page - 2) * 10, 10, __FILE__, __LINE__);
	$info	= mysql_paginate_tradition_since_id(init_conn(), $sql, $total_page, 10);
	$assert($info, ($total_page - 1) * 10, $remain, __FILE__, __LINE__);
	#第一頁
	$info_0	= mysql_paginate_tradition_since_id(init_conn(), $sql, 1, 10, TRUE, TRUE);
	$assert($info_0, 0, 10, __FILE__, __LINE__);
	#無數據(第一頁之前)
	$info_1	= mysql_paginate_tradition_since_id(init_conn(), $sql, 1, 10, p_prev_id($info_0));
	$assert($info_1, 0, 0, __FILE__, __LINE__);
	#第二頁
	$info_2	= mysql_paginate_tradition_since_id(init_conn(), $sql, 1, 10, NULL, p_next_id($info_0));
	$assert($info_2, 10, 10, __FILE__, __LINE__);
	#第三頁
	$info_3	= mysql_paginate_tradition_since_id(init_conn(), $sql, 1, 10, NULL, p_next_id($info_2));
	$assert($info_3, 20, 10, __FILE__, __LINE__);
	#第二頁
	$info_4	= mysql_paginate_tradition_since_id(init_conn(), $sql, 1, 10, p_prev_id($info_3));
	$assert($info_4, 10, 10, __FILE__, __LINE__);
	#第四頁
	$info_5	= mysql_paginate_tradition_since_id(init_conn(), $sql, 1, 10, NULL, p_next_id($info_3));
	$assert($info_5, 30, 10, __FILE__, __LINE__);
	#第三頁
	$info_6	= mysql_paginate_tradition_since_id(init_conn(), $sql, 2, 10, NULL, p_next_id($info_0));
	$assert($info_6, 20, 10, __FILE__, __LINE__);
	#第八頁
	$info_7	= mysql_paginate_tradition_since_id(init_conn(), $sql, 7, 10, NULL, p_next_id($info_0));
	$assert($info_7, 70, 10, __FILE__, __LINE__);
	#最後一頁
	$info_8	= mysql_paginate_tradition_since_id(init_conn(), $sql, $total_page - 1, 10, NULL, p_next_id($info_0));
	$assert($info_8, ($total_page - 1) * 10, $remain, __FILE__, __LINE__);
	#(TODO 分段分頁/傳統分頁/原始分頁與since_id分頁聯合使用時, 記錄數是since_id條件附加之前的記錄數, 因此會導致頁碼數據錯亂, 目前不對此進行處理)
	#越界訪問
	$info_8	= mysql_paginate_tradition_since_id(init_conn(), $sql, $total_page, 10, NULL, p_next_id($info_0));
	$assert($info_8, 0, 0, __FILE__, __LINE__);
}
#分段分頁複合since_id分頁測試
function test_ping_since_id($sql, $assert, $total_record) {
	$total_ping	= ceil($total_record / 10);
	$total_page	= ceil($total_ping / 3);
	$remain		= $total_record % 10 ? $total_record % 10 : 10;
	$remain_ping	= $total_ping % 3 ? $total_ping % 3 : 3;
	$info	= mysql_paginate_ping_since_id(init_conn(), $sql, -1, -1, 3, 10);
	$assert($info, 0, 10, __FILE__, __LINE__);
	$info	= mysql_paginate_ping_since_id(init_conn(), $sql, -1, 2, 3, 10);
	$assert($info, 10, 10, __FILE__, __LINE__);
	$info	= mysql_paginate_ping_since_id(init_conn(), $sql, 1, 3, 3, 10);
	$assert($info, 20, 10, __FILE__, __LINE__);
	$info	= mysql_paginate_ping_since_id(init_conn(), $sql, 1, 4, 3, 10);
	$assert($info, 20, 10, __FILE__, __LINE__);
	$info	= mysql_paginate_ping_since_id(init_conn(), $sql, 5, 2, 3, 10);
	$assert($info, 130, 10, __FILE__, __LINE__);
	$info	= mysql_paginate_ping_since_id(init_conn(), $sql, $total_page, $remain_ping, 3, 10);
	$assert($info, ($total_ping - 1) * 10, $remain, __FILE__, __LINE__);
	$info	= mysql_paginate_ping_since_id(init_conn(), $sql, $total_page + rand(1, 10), $remain_ping, 3, 10);
	$assert($info, ($total_ping - 1) * 10, $remain, __FILE__, __LINE__);
	#第一頁第一段
	$info_0	= mysql_paginate_ping_since_id(init_conn(), $sql, 1, 1, 3, 10, TRUE, TRUE);
	$assert($info_0, 0, 10, __FILE__, __LINE__);
	#無數據(第一頁之前)
	$info_1	= mysql_paginate_ping_since_id(init_conn(), $sql, 1, 1, 3, 10, p_prev_id($info_0));
	$assert($info_1, 0, 0, __FILE__, __LINE__);
	#第一頁第二段
	$info_2	= mysql_paginate_ping_since_id(init_conn(), $sql, 1, 1, 3, 10, NULL, p_next_id($info_0));
	$assert($info_2, 10, 10, __FILE__, __LINE__);
	#第一頁第三段
	$info_3	= mysql_paginate_ping_since_id(init_conn(), $sql, 1, 1, 3, 10, NULL, p_next_id($info_2));
	$assert($info_3, 20, 10, __FILE__, __LINE__);
	#第一頁第二段
	$info_4	= mysql_paginate_ping_since_id(init_conn(), $sql, 1, 1, 3, 10, p_prev_id($info_3));
	$assert($info_4, 10, 10, __FILE__, __LINE__);
	#第一頁第一段
	$info_5	= mysql_paginate_ping_since_id(init_conn(), $sql, 1, 1, 3, 10, NULL, p_next_id($info_3));
	$assert($info_5, 30, 10, __FILE__, __LINE__);
	#第一頁第三段
	$info_6	= mysql_paginate_ping_since_id(init_conn(), $sql, 1, 2, 3, 10, NULL, p_next_id($info_0));
	$assert($info_6, 20, 10, __FILE__, __LINE__);
	#第七頁第三段
	$info_7	= mysql_paginate_ping_since_id(init_conn(), $sql, 7, 2, 3, 10, NULL, p_next_id($info_0));
	$assert($info_7, 200, 10, __FILE__, __LINE__);
	#倒數第二頁最後一段
	$info_9	= mysql_paginate_ping_since_id(init_conn(), $sql, $total_page - 1, 2, 3, 10, NULL, p_next_id($info_0));
	$assert($info_9, ($total_ping - $remain_ping - 1) * 10, 10, __FILE__, __LINE__);
	#(TODO 分段分頁/傳統分頁/原始分頁與since_id分頁聯合使用時, 記錄數是since_id條件附加之前的記錄數, 因此會導致頁碼數據錯亂, 目前不對此進行處理)
	#越界訪問
	$info_10	= mysql_paginate_ping_since_id(init_conn(), $sql, $total_page + rand(1, 10), 3, 3, 10, NULL, p_next_id($info_0));
	$assert($info_10, 0, 0, __FILE__, __LINE__);
}
#原始分頁複合since_id分頁測試
function test_raw_since_id($sql, $assert, $total_record) {
	$remain		= $total_record % 10 ? $total_record % 10 : 10;
	$info	= mysql_paginate_raw_since_id(init_conn(), $sql, -1, 10);
	$assert($info, 0, 10, __FILE__, __LINE__);
	$info	= mysql_paginate_raw_since_id(init_conn(), $sql, 100, 10);
	$assert($info, 100, 10, __FILE__, __LINE__);
	$info	= mysql_paginate_raw_since_id(init_conn(), $sql, $total_record - $remain, 10);
	$assert($info, $total_record - $remain, $remain, __FILE__, __LINE__);
	#第一頁
	$info_0	= mysql_paginate_raw_since_id(init_conn(), $sql, 0, 10, TRUE, TRUE);
	$assert($info_0, 0, 10, __FILE__, __LINE__);
	#無數據(第一頁之前)
	$info_1	= mysql_paginate_raw_since_id(init_conn(), $sql, 0, 10, p_prev_id($info_0));
	$assert($info_1, 0, 0, __FILE__, __LINE__);
	#第二頁
	$info_2	= mysql_paginate_raw_since_id(init_conn(), $sql, 0, 10, NULL, p_next_id($info_0));
	$assert($info_2, 10, 10, __FILE__, __LINE__);
	#第三頁
	$info_3	= mysql_paginate_raw_since_id(init_conn(), $sql, 0, 10, NULL, p_next_id($info_2));
	$assert($info_3, 20, 10, __FILE__, __LINE__);
	#第二頁
	$info_4	= mysql_paginate_raw_since_id(init_conn(), $sql, 0, 10, p_prev_id($info_3));
	$assert($info_4, 10, 10, __FILE__, __LINE__);
	#第四頁
	$info_5	= mysql_paginate_raw_since_id(init_conn(), $sql, 0, 10, NULL, p_next_id($info_3));
	$assert($info_5, 30, 10, __FILE__, __LINE__);
	#最後一頁
	$info_6	= mysql_paginate_raw_since_id(init_conn(), $sql, $total_record - $remain - 10, 10, NULL, p_next_id($info_0));
	$assert($info_6, $total_record - $remain, $remain, __FILE__, __LINE__);
	#(TODO 分段分頁/傳統分頁/原始分頁與since_id分頁聯合使用時, 記錄數是since_id條件附加之前的記錄數, 因此會導致頁碼數據錯亂, 目前不對此進行處理)
	#越界訪問
	$info_7	= mysql_paginate_raw_since_id(init_conn(), $sql, 970, 10, NULL, p_next_id($info_0));
	$assert($info_7, 0, 0, __FILE__, __LINE__);
}

#初始化數據
init_db();

$h_sql	= 'SELECT f.feed_id, f.content, h.hot FROM feed AS f JOIN hot AS h ON f.feed_id = h.feed_id ORDER BY h.hot DESC, f.feed_id DESC';
$c_sql	= 'SELECT f.feed_id, f.content, COUNT(c.comment_id) AS count FROM feed AS f JOIN comment AS c ON f.feed_id = c.feed_id GROUP BY c.feed_id ORDER BY COUNT(c.comment_id) DESC, f.feed_id DESC';
$t_sql	= 'SELECT feed_id, content, transpond_count FROM feed ORDER BY transpond_count DESC, feed_id DESC';

$data_infos	= array(
	array($c_sql, 'assert_comment', count($GLOBALS[ALL_DATAS][K_CC_FEEDS])), 
	array($h_sql, 'assert_hot', count($GLOBALS[ALL_DATAS][K_HC_FEEDS])), 
	array($t_sql, 'assert_transpond', count($GLOBALS[ALL_DATAS][K_TC_FEEDS])), 
);
$use_cases	= array(
	'test_tradition', 'test_ping', 'test_raw', 'test_since_id', 
	'test_tradition_since_id', 'test_ping_since_id', 'test_raw_since_id', 
);

foreach ( $data_infos as $data_info ) {
	foreach ( $use_cases as $use_case )
		call_user_func_array($use_case, $data_info);
}

發佈了123 篇原創文章 · 獲贊 1149 · 訪問量 130萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章