#!/usr/local/bin/prologcgi

:- dlib_require(odbc).

:-s_charset(_,sjis).
top_call:-
  get_params(X),
   html_call([
%	"content-Type: text/html; charset=utf-8
	"content-Type: text/html; charset=shift_jis

	<html><head><meta http-equiv='Content-Type' content='text/html; charset=shift_jis'>
	<title>ODBC接続機能</title></head> 
	<body><center><H2>ODBC接続機能(Windows Only)</H2> 
  	<a href='puttxt.exe?odbctest.cgi' target='source' >【このＣＧＩソースの表示】　</a> 
	<INPUT TYPE='button' VALUE='戻る' onClick='history.back()'><br><br> 
    ACCESS,SQLサーバなどのＯＤＢＣが設定されており、テスト用のテーブルとデータが<br> 
	格納されていることを前提とします。設定方法はこのＣＧＩソースに書かれています<br><br> 
	デモ用テーブルのカラム名　【　 山名 char, 地域 char, 標高 int, 緯度 int, 経度 int, 地図 char 　】<br> 
	これらを使って、条件句を入力してください。例：「標高>3000 and 地域 like '南ア%' order by 緯度」<br> 
	<form action 'odbctest.cgi' method='POST' > <h2> 
	  SELECT　*　FROM　山岳テーブル　WHERE　<input type='text' name='where' size='50' value=""",X,"""></h2> 
	<input type='submit' value='データ取得' ></form><br> " ,
    	  call(select_call(X) ),                   % Prolog Call
	"</center></body></html>"]).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
get_params(X):-get_param(where,X),!.
get_params('').

% ＳＱＬ文発行
select_call(''):-!,write(条件を入力してください),nl.
select_call(XA):- 
	write('<table border="1" >'),nl,
	connect_test,
	(atom(XA)->name(XA,X);XA=X),
%	write(XA),nl,write('<br>'),  %変換前
	check_list(X,XL),
%	name(T,XL),write(T),nl,      %変換後
	write_listtab('<tr bgcolor="#af621f"><td>',['No',山名,地域,標高,緯度,経度,地図]),nl,
	e_register(1,_,1),
	select_each(Items,select2,[XL]),
	e_register(1,R,R+1),
	write_listtab('<tr><td>',[R|Items]),
	fail.
select_call(X):-write('</table>'),nl.

% 体裁付き出力
write_listtab(_,[]):-!,write('</td></tr>'),nl.
write_listtab(X,[A|L]):-write(X),write(A),write_listtab('</td><td>',L).

% ＯＤＢＣ接続確認
connect_test:- select_each(X,select,["",""]),!.
connect_test:- write('ODBCの接続がされていません(Now Windows Only!)'),fail.

% 入力文字に全角記号、全角英数文字が含まれていた場合 RDB側でエラーとなるので半角英数に変換する
check_list([],[]):-!.
check_list([A|L],[B|R]):-
        assocp(A,B, "！”＃＄％＆’（）＝?｜?＾?「＋＊」＜＞？、．／＿　",
                    "!""#$%&'()=~|-^\[+*]<>?,./_ "),
        check_list(L,R).

assocp(A,B,[A|_],[B|_]):-!.
assocp(A,B,[_|L],[_|R]):-!,assocp(A,B,L,R).
assocp(A,B,_,_):-k_upper(A,B,"AＡＺaａｚ0０９"),!.
assocp(A,A,_,_).

k_upper(A,B,[Base,KA,KZ|_]):- A >=KA,A=<KZ,!,B is A-KA+Base.
k_upper(A,B,[_,_,_|L]):-  k_upper(A,B,L).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%  ODBC 機能の利用方法
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Ｓｔｅｐ１ << Windows ODBC データソースの設定 >>
%% 
%% Windowsのスタート→設定→コントロールパネル→管理ツール→データソース（ＯＤＢＣ）→システムＤＳＮタブ
%% で　Ａｃｃｅｓｓ，ＳＱＬサーバなどのＯＤＢＣ・ＤＳＮ(データソースネーム）を追加します。
%% 設定内容の詳細はＯＤＢＣに関するマニュアル、ヘルプなどをお調べください。
%%
%% ここでは、DSN、ユーザ、パスワードすべて、"test"という文字列で設定された前提の
%% プログラムとなっています。異なる場合は、次のステップのsql_connect_data/4を必要に応じて書き換えます

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%  Ｓｔｅｐ２ << ユーザー定義単位節　DSNのProlog定義 >>
%%
%% Step1で設定したＤＳＮをＰｒｏｌｏｇ単位節で定義し、データベースの接続時に利用します。
%%  これ以降がPrologプログラムです。
%%
%%  書式： sql_connect_data(DSN識別子,DSN,User,Password).
%%
%% 第一引数DSN識別子は、ＳＱＬ発行時に該当するsql_statement/3 の第一引数とユニフィケーション
%% されるものが採用されます。複数のＤＳＮを利用する場合、これを考慮した対応がとれるようにして
%% ください。ここの例ではひとつのＤＳＮしか利用していませんので、無名変数としていますが、
%% DSN を複数利用し、ＳＱＬ文を対応付けるには以下のようにでもすればよろしいでしょう。
%% 
%% sql_connect_data(sqlsv(_)  ,"sqlsv_dsn" ,....
%% sql_connect_data(access(_) ,"access_dsn",....
%%
%% sql_statement(sqlsv(1),  .....   sqlsv_dsn を利用してコネクトするＳＱＬ文
%% sql_statement(sqlsv(2),  .....   sqlsv_dsn を利用してコネクトするＳＱＬ文
%% sql_statement(access(1), .....   access_dsnを利用してコネクトするＳＱＬ文
%%

%%             DSN識別子 DSN名   User  Password
sql_connect_data(_,      "test","test","test").

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%  Ｓｔｅｐ３　<< ユーザー定義単位節　SQL文の定義 >>
%%
%%   select_each,exec_direct,などＳＱＬ呼び出しを行う述語で利用するＳＱＬ文とそのＳＱＬ文が利用する
%%   DSNとのリンク、select 時の取得データ型をあらかじめ準備するか動的にアサーションします。
%%
%%  書式： sql_statement(SQL識別子,出力定義リスト,SQL文リスト).
%%
%%           SQL識別子:      select_each,exec_direct,などＳＱＬ呼び出しを行う述語に指定され、
%%                           ＳＱＬ文を一意に選択するための識別子です。
%%           出力定義リスト: select 呼び出しのみ意味を持ち、select　アイテムの取得型をその順に記述します
%%                           RDB上の定義と異なってもいい場合もあります。（DB:integer => Prolog:atom)
%%                             ＜出力データ型の定義＞
%%                              char/文字数 　　　結果は文字数以内の長さのアトム
%%                              date　　　　　　　結果は年月日の数値３要素のリスト
%%                              long　　　　　　　結果はinteger
%%                              double　　　　　　結果はfloat
%%                              timestamp 　　　　結果は年月日時分秒シーケンス番号の数値７要素のリスト
%%           SQL文(リスト）: リストの要素を平坦結合したものがＳＱＬ文としてＲＤＢへ渡されます。
%%                           要素は文字列リスト、アトム、数値、date型年月日表現リスト(例: [2006,12,31] )
%%                           および変数で、変数は出現順に呼び出し元から与えられる入力値リストで埋められます


%%          SQL識別子 出力定義リスト    SQL文
sql_statement(create,   [],             ["create table 山岳テーブル ",
                                         "( 山名  char(16) primary key, 地域 char(10) ,標高 integer,",
                                         "  緯度  integer, 経度 integer, 地図 char(16) )"]).

sql_statement(insert,   [],             ["insert into 山岳テーブル", 	% 変数は呼び出し時に値を与えます
			                 " values ( '",_,"','",_,"',",_,",",_,",",_,",'",_,"')"]).

sql_statement(select,[char/20,long],    ["select 山名,標高 from 山岳テーブル ",_,_]).

sql_statement(select2,[char/20,char/20,long,long,long,char/20],["select * from 山岳テーブル where ",_]).


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Ｓｔｅｐ４　<< SQL文の実行 >>
%%
%%   ＜＜　public であるPrologソースコンパイルド組込述語　＞＞
%%
%%   select_each/3.  ＳＱＬ文を実行する。非決定性述語。バックトラックにより次解を返す。
%%        ?-select_each(?取得結果リスト,＋ＳＱＬ識別子,＋ＳＱＬ文の変数充当データリスト).
%%                   ?取得結果リスト　　　ＳＥＬＥＣＴ文の場合、取得データがリストで返る
%%                   ＋ＳＱＬ識別子　　　　sql_statement/3 を特定する識別子
%%                   ＋ＳＱＬ文の変数充当データリスト　ＳＱＬ文中の変数に充当するデータ
%% 
%%   exec_direct/2.  結果取得をともなわないＳＱＬ文(insert,delete,dorp など)を実行する
%%        ?-exec_direct(＋ＳＱＬ識別子,＋ＳＱＬ文の変数充当データリスト).
%% 
%%   exec_directs/3. 結果取得をともなわないＳＱＬ文(insert,delete)を複数の充当値で実行する
%%        ?-exec_directs(＋ＳＱＬ識別子, 変数充当データリストのリスト,-エラーリスト).
%%                   -エラーリスト    実行エラーとなった変数充当データリストのリスト
%%                   
%%   is_null/1.      引数である該当取得データがＮＵＬＬの時に成功する
%%   sql_cancels/1.  指定ＳＱＬ識別子の問い合わせをキャンセルする
%%   odbc_close/0.   現在接続されている全てのＯＤＢＣコネクションをクローズする。
%% 
%%  
%%   ★なお、前記の述語の部品を構成する次のものもPublicとなっている。ＳＱＬ処理の間に別の処理を
%%     はさむ必要があったときなどに利用できる。仕様詳細はソースコード（odbc_prolog.pl)参照のこと。
%%     odbc_open/3. select_each/4.   select_each/6.  fetch_one/4.
%%     exec_directs_pre/4.  exec_directs_post/2. exec_directs_main/4.
%%  
%%   ★また、ＯＤＢＣとの最下層のやり取りは、Ｃソースコンパイルド組込述語による。これらの仕様詳細は
%%     ソースコード（odbc.c) および仕様書(odbc.html)を参照のこと。ユーザ独自のＳＱＬ操作系を構成できる。


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% 本サンプルプログラム用の事前処理の方法
%%% 本ファイルをＰｒｏｌｏｇインタプリタにリコンサルトし、１）?４）の
%%% 順番にテーブル、データ生成、取得テストをおこないます。

%%%%%%%%%%%%%%%%%%%%%%%%
%%% １）テーブルの生成

%% ?- exec_direct(create,[]).
%% yes                 テーブルが生成されます

%%%%%%%%%%%%%%%%%%%%%%%%
%%% ２）データのインサート                SQL識別子   SQL文の変数部分へ順番に埋め込まれる値リスト

%% ?- insert_data(X),exec_direct(insert,X),fail.
%% no                  データがインサートされます

%% なお、これは次の方法でもよい。
%% ?-bagof(X,insert_data(X),L),exec_directs(insert,L,Err).

%%%%%%%%%%%%%%%%%%%%%%%%
%%% ３）データの条件なし取得　　　全件なので元ＳＱＬ文の変数部分に空を渡します

%% ?-  select_each(X,select,["",""]).
%%  X= [富士山,3776];
%%  X= [北岳,3192]
%% yes

%%%%%%%%%%%%%%%%%%%%%%%%
%%% ４）データの条件つき取得

%% ?- select_each(X,select,["where 標高 < ",3000]).
%%  X= [剣岳,2998];
%%  X= [水晶岳,2986]
%% yes

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% インサートデータ
%%%          山名         地域    標高 緯度   経度    地図 
insert_data([富士山,     富士周辺,3776,352127,1384350,富士山]).
insert_data([北岳,       南ア北部,3192,354017,1381431,仙丈ヶ岳]).
insert_data([奥穂高岳,   北ア南部,3190,361710,1373904,穂高岳]).
insert_data([間ノ岳,     南ア北部,3189,353834,1381353,間ノ岳]).
insert_data([槍ヶ岳,     北ア南部,3180,362020,1373903,槍ケ岳]).
insert_data([悪沢岳,     南ア南部,3141,352951,1381108,赤石岳]).
insert_data([赤石岳,     南ア南部,3120,352729,1380938,赤石岳]).
insert_data([涸沢岳,     北ア南部,3110,361734,1373900,穂高岳]).
insert_data([北穂高岳,   北ア南部,3106,361758,1373918,穂高岳]).
insert_data([大喰岳,     北ア南部,3106,361958,1373856,穂高岳]).
insert_data([前穂高岳,   北ア南部,3090,361644,1373949,穂高岳]).
insert_data([中岳,       北ア南部,3084,361936,1373859,穂高岳]).
insert_data([荒川岳,     南ア南部,3083,352936,1381013,赤石岳]).
insert_data([御嶽山,     御嶽周辺,3067,355323,1372900,御獄山]).
insert_data([農鳥岳,     南ア北部,3051,353719,1381358,間ノ岳]).
insert_data([塩見岳,     南ア南部,3047,353414,1381110,塩見岳]).
insert_data([仙丈ヶ岳,   南ア北部,3033,353501,1382220,仙丈ヶ岳]).
insert_data([南岳,       北ア南部,3033,361857,1373914,穂高岳]).
insert_data([乗鞍岳,     北ア南部,3026,360612,1373324,乗鞍岳]).
insert_data([立山,       北ア北部,3015,363422,1373722,立山]).
insert_data([聖岳,       南ア南部,3013,352510,1380834,赤石岳]).
insert_data([剣岳,       北ア北部,2998,363713,1373713,剣岳]).
insert_data([水晶岳,     北ア北部,2986,362524,1373621,薬師岳]).
insert_data([甲斐駒ケ岳, 南ア北部,2967,354517,1381423,甲斐駒ケ岳]).
insert_data([木曽駒ケ岳, 中ア,    2956,354711,1374827,木曽駒ケ岳]).
insert_data([白馬岳,     北ア北部,2932,364520,1374542,白馬岳]).
insert_data([薬師岳,     北ア北部,2926,362757,1373252,薬師岳]).
insert_data([野口五郎岳, 北ア北部,2924,362547,1373827,烏帽子岳]).
insert_data([鷲羽岳,     北ア北部,2924,362400,1373630,三ツ俣蓮岳]).
insert_data([大天井岳,   北ア北部,2922,362143,1374215,槍ヶ岳]).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
