経路列挙モデルで階層構造
参考URL: SQLで木と階層構造のデータを扱う(2)―― 経路列挙モデル
ちょっと仕事で階層構造を持つテーブルをリファクタリングしたくなったので、いろいろ調べてみたところ経路列挙モデルが便利そうなので試してみた。試してみたDBはSQL Server 2008 R2。
まずテーブルはこれ。区切り文字は「/」にした。
;CREATE CREATE TABLE NODES ( ID int PRIMARY KEY , PATH NVARCHAR(256) NOT NULL , UNIQUE(PATH) , CONSTRAINT PATH_NAME_CHECK CHECK(RIGHT(PATH,LEN(STR(ID, 1))+1) = (STR(ID, 1)+'/')) );
IDでPATHを表現しているが、intなので区切り文字を含ませない制約はつけてない。その代わり、PATHの最後に自分のIDを含むような制約をつけるようにした。
経路列挙モデルで分かりにくいのは、IDをどうやって決めればいいのかってことだった。使ってないIDを自分で把握して、というのは現実的ではない。なのでINSERT時に最大値+1を計算して代入することにした。親がいない時と親がいる時でSQLを分けるのも分かりにくいので、CASEを使って頑張ってみた結果がこれ。2ヶ所のPARENT_IDをアプリから設定してあげればOK。
;INSERT INSERT INTO NODES ( ID , PATH ) select (CASE WHEN COUNT(ID)=0 THEN 1 ELSE MAX(ID)+1 END) AS ID2 , (CASE WHEN (SELECT COUNT(*) FROM NODES WHERE ID=/*PARENT_ID*/1) > 0 THEN (SELECT PATH FROM NODES WHERE ID=/*PARENT_ID*/1) ELSE '/' END) + CONVERT(nvarchar, CASE WHEN COUNT(ID)=0 THEN 1 ELSE MAX(ID)+1 END) + '/' AS PATH2 FROM NODES
SELECTは参考URLをSQL Server向けに修正しただけ。
;ルートを求める SELECT * FROM NODES WHERE ID = REPLACE(PATH,'/','')
;リーフを求める SELECT * FROM NODES P WHERE NOT EXISTS ( SELECT * FROM NODES C WHERE C.PATH LIKE P.PATH + '_%');
;ノードの深さ SELECT ID , PATH , LEN(PATH) - LEN(REPLACE(PATH, '/', '')) -1 AS LEVEL FROM NODES
;木の高さを求める SELECT MAX(LEN(PATH) - LEN(REPLACE(PATH, '/', '')) -1) AS HEIGHT FROM NODES;
;階層をインデントで表現する SELECT ID , PATH , REPLICATE(' ', LEN(PATH) - LEN(REPLACE(PATH, '/', '')) - 2) + STR(ID, 1) FROM NODES
;親から見た場合の子供 SELECT P.ID AS PARENT_ID , C.ID AS CHILD_ID , C.PATH AS CHILD_PATH FROM NODES P LEFT OUTER JOIN NODES C ON P.PATH = (SELECT MAX(PATH) FROM NODES WHERE C.PATH LIKE PATH + '_%');
;子から見た場合の親 SELECT CHILD.ID AS CHILD_ID , PARENT.ID AS PARENT_ID , CHILD.PATH AS CHILD_PATH FROM NODES CHILD LEFT OUTER JOIN NODES PARENT ON CHILD.PATH > PARENT.PATH AND PARENT.PATH = (SELECT MAX(PATH) FROM NODES WHERE CHILD.PATH LIKE PATH + '_%');
パスを列挙する(列持ちバージョン)はどちらもSQL Serverでは意図した結果にならなかった。特に使う予定もないので割愛。
次はデータの削除。今回の場合自分の配下も一緒に削除されて問題ないので、こんな感じでDELETE_IDを指定してDELETEする。
;部分木の削除:一般化 DELETE FROM NODES WHERE PATH LIKE (SELECT PATH FROM NODES WHERE ID = /*DELETE_ID*/4) + '%';
次にUPDATE。間に割りこむことはしないが、部分木の移動はあるので考えてみた結果がこれ。部分木の先頭ROOT_IDと新しい部分木の親NEW_ROOT_IDを指定してUPDATEする。一応部分木を独立させる場合にも対応してるはず。
UPDATE NODES SET PATH = REPLACE(PATH, (SELECT PATH FROM NODES WHERE ID=/*ROOT_ID*/7), (CASE WHEN (SELECT COUNT(ID) FROM NODES WHERE ID=/*NEW_ROOT_ID*/null)=0 THEN '/' ELSE (SELECT PATH FROM NODES WHERE ID=/*NEW_ROOT_ID*/null) END) + STR((SELECT ID FROM NODES WHERE ID=/*ROOT_ID*/7), 1) + '/') WHERE PATH LIKE (SELECT PATH FROM NODES WHERE ID=/*ROOT_ID*/7) + '%'
なんとなく必要なSQLは書けたので、既存DBを使ったDAOのテストでも書いて、それからテーブル変更かなー。
それにしても、そろそろSQLは一回SELECTしたデータを使いまわせるようにしてくれないかな。同じものをいろんなとこにコピペするのやだわー。
■
これの続き。
参考URL : http://mercurial.selenic.com/wiki/Subrepository
Mercurial 2.0 ( TortoiseHg 2.2 ) で仕様が変わったらしく、以前の方法だとうまくいかない。
[subpaths] subrepo_name = https://bitbucket.org/toruuetani/virtualenv-scaffold
たとえばメインリポジトリのクローン元が https://bitbucket.org/toruuetani/hoge の場合、以下のようにする。
[subpaths] https://bitbucket.org/toruuetani/hoge/subrepo_name = https://bitbucket.org/toruuetani/virtualenv-scaffold
ちなみに これで作ったバッチは以下のようになる。
@ECHO OFF SET ROOT_DIR=%~DP0 SET TOP_REPO=PATH_TO_TOP SET SECOND_REPO=PATH_TO_SECOND SET THIRD_REPO=PATH_TO_THIRD SET TOP_HGRC=%ROOT_DIR%\.hg\hgrc SET SECOND_HGRC=%ROOT_DIR%\SECOND_DIR\.hg\hgrc :CREATE_TOP hg init ECHO [paths]>%TOP_HGRC% ECHO default = %TOP_REPO%>>%TOP_HGRC% ECHO;>>%TOP_HGRC% ECHO [subpaths]>>%TOP_HGRC% ECHO %TOP_REPO%/subrepo_second = %SECOND_REPO%>>%TOP_HGRC% :CREATE_SECOND IF NOT EXIST %ROOT_DIR%SECOND_DIR MKDIR %ROOT_DIR%SECOND_DIR CD /D %ROOT_DIR%SECOND_DIR hg init ECHO [paths]>%SECOND_HGRC% ECHO default = %SECOND_REPO%>>%SECOND_HGRC% ECHO;>>%SECOND_HGRC% ECHO [subpaths]>>%SECOND_HGRC% ECHO %SECOND_REPO%/subrepo_third = %THIRD_REPO%>>%SECOND_HGRC% :UPDATE CD /D %ROOT_DIR% hg pull hg update -C :END PAUSE
py2exeでハマったことの解決法
環境は Windows + Python 2.7.2 + virtualenv 1.7
UAC で管理者権限を要求する
# -*- encoding: utf-8 -*- from distutils.core import setup import py2exe #python setup.py py2exe するために必要 setup( console = [{'script' : 'hoge.py', 'uac_info' : "requireAdministrator",}], )
実行時発生する ImportError を回避する
via http://stackoverflow.com/questions/1979486/py2exe-win32api-pyc-importerror-dll-load-failed
# -*- encoding: utf-8 -*- from distutils.core import setup import py2exe #python setup.py py2exe するために必要 py2exe_options = { 'dll_excludes': [ "mswsock.dll", "powrprof.dll" ], } setup( options = {'py2exe': py2exe_options}, )
VC2008 ランタイムの問題を回避する
Python 2.6 からは VC2008 でコンパイルされているため、 VC2008 ランタイムの問題が発生する。特に問題なのは、ほとんどの環境では SP1 までアップデートされるため、バージョンが 9.0.30729.XX になること。Python が要求するのは 9.0.21022.8 であり、このバージョンのランタイムがないと exe が起動できない。基本的に Windows Update すれば問題はなくなるはずだが、OSのクリーンインストール直後などでも動作できるようにしたい。開発マシンの C:\Windows\WinSxS には該当のDLLが存在するはずなので、これを集めてくるようにすれば問題ないはず。
# -*- encoding: utf-8 -*- from distutils.core import setup from glob import glob import os import py2exe #python setup.py py2exe するために必要 candidates = glob(r"C:\Windows\winsxs\Manifests\x86_microsoft.vc90.crt_*_9.0.21022.8_*.manifest") if not candidates: raise Exception("Could not find VC9 runtime( 9.0.21022.8 ) from 'C:\Windows\winsxs'.") dirname = os.path.splitext(os.path.basename(candidates[0]))[0] data_files = [("Microsoft.VC90.CRT", [candidates[0]] + glob(os.path.join(r"C:\Windows\winsxs", dirname, "*.*")))] setup( data_files=data_files, )
2012/01/14 追記
上記のままだとmanifest名が正しくないため、名前をリネームする必要がある。
from glob import glob import os import shutil build_dir = "/path/to/build_dir" manifests = glob(os.path.join(build_dir, r"Microsoft.VC90.CRT", "*.manifest")) shutil.move(manifests[0], os.path.join(build_dir, "Microsoft.VC90.CRT", "Microsoft.VC90.CRT.manifest"))
TortoiseHg 2.x 入門記事にMercurialコマンドリファレンス追記
最近書いてる TortoiseHg 入門記事に、Mercurialのコマンドリファレンスを追記した。
Windows+TortoiseHg 2.x で始めるMercurial
今回改めて Mercurial コマンドのヘルプを調べてみたけど、ほとんどオプションは使ってない。このあたりが Git との違いなんだろうな。
Sphinxドキュメントにソーシャルボタンを設置して、bitbucketに公開してみた
結果はこれ → http://toruuetani.bitbucket.org
リポジトリはこれ → https://bitbucket.org/toruuetani/toruuetani.bitbucket.org
ソーシャルボタンの設置
参考URL
- 今日のPython: Sphinx にソーシャルボタンを設置する
- はてなブックマークボタンの作成・設置について
- Twitter / ツイートボタン
- プラスワン ボタン
- Like Button - Facebook開発者
基本的には参考URLの通りにやれば問題ない。ただし、このままだと確認用にビルドしたドキュメントでもソーシャルボタンが設置され、表示に失敗する。それだけならまだいいが、ドキュメント上部に表示すると失敗するまで本来のドキュメントまで表示されない。なので確認用ビルド時と公開用ビルド時にソーシャルボタンの設置・非設置を切り替えられるようにしてみた。
まずはSphinxドキュメントが $/source にある場合、 $/source/_templates/layout.html を下記の内容で追加する。
はてなとfacebookについては、「http://toruuetani.bitbucket.org/」の部分を公開URLにあわせて修正すること。
{% extends "!layout.html" %} {%- block document %} <div class="documentwrapper"> {%- if render_sidebar %} <div class="bodywrapper"> {%- endif %} <div class="body"> {% if need_social_buttons %} <div style="height: 30px;"> <table cellpadding="5px"> <tbody> <tr><td> <!-- hatenaコード --> <a href="http://b.hatena.ne.jp/entry/http://toruuetani.bitbucket.org/{{ pagename }}{{ file_suffix }}" class="hatena-bookmark-button" data-hatena-bookmark-title="{{ title }}" data-hatena-bookmark-layout="standard" title="このエントリーをはてなブックマークに追加"> <img src="http://b.st-hatena.com/images/entry-button/button-only.gif" alt="このエントリーをはてなブックマークに追加" width="20" height="20" style="border: none;" /> </a><script type="text/javascript" src="http://b.st-hatena.com/js/bookmark_button.js" charset="utf-8" async="async"></script> </td><td> <!-- Twitterコード --> <a href="https://twitter.com/share" class="twitter-share-button" data-count="horizontal">Tweet</a> <script type="text/javascript" src="//platform.twitter.com/widgets.js"></script> </td><td> <!-- google+1 headerタグ用コード --> <script type="text/javascript" src="https://apis.google.com/js/plusone.js"> {lang: 'ja'} </script> <!-- google+1ボタン表示用コード --> <g:plusone></g:plusone> </td><td> <!-- facebookコード --> <div id="fb-root"></div> <script>(function(d, s, id) { var js, fjs = d.getElementsByTagName(s)[0]; if (d.getElementById(id)) {return;} js = d.createElement(s); js.id = id; js.src = "//connect.facebook.net/ja_JP/all.js#xfbml=1"; fjs.parentNode.insertBefore(js, fjs); }(document, 'script', 'facebook-jssdk'));</script> <div class="fb-like" data-href="http://b.hatena.ne.jp/entry/http://toruuetani.bitbucket.org/{{ pagename }}{{ file_suffix }}" data-send="false" data-layout="button_count" data-width="450" data-show-faces="true"></div> </td></tr></tbody></table> </div> {% endif %} {% block body %} {% endblock %} </div> {%- if render_sidebar %} </div> {%- endif %} </div> {%- endblock %}
本来なら body ブロックだけ用意すればいけるかと思ったが、ドキュメント側で body ブロックを完全にオーバーライドしてしまうらしい。なので Sphinx のデフォルトテンプレートから layout.html をコピーしてきて、ソーシャルボタン設置コードを追記した。さらにそのコードを if ブロックで切り替えられるようにして、その切り替えは、make.batにオプションを追記することで可能にした。
ポイントは %SPHINXBUILD% (sphinx-build) に渡すパラメータ「-A need_social_buttons=1」。「-A name=value」の形式で渡すと、Sphinxテンプレート内の変数をオーバーライドできる。boolean型の場合は0または1を渡せばいい。
REM ソーシャルボタン有効化オプション if "%1" == "html-publish" ( %SPHINXBUILD% -b html -A need_social_buttons=1 %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end )
こうすると、いつものように
make.bat html
するとソーシャルボタンを設置しないHTMLが生成されて、
make.bat html-publish
するとソーシャルボタンを設置するHTMLが生成される。
Sphinxドキュメントの公開
参考URL
次は作成したHTMLの公開。せっかく bitbucket のアカウントを持ってるので、 Sphinx ドキュメントの公開先に使ってみた。ほぼ参考URLの通りで、作成したHTMLもリポジトリに登録するのが注意点。
作成するリポジトリ名は、bitbucketのアカウント名.bitbucket.org とする。あとは通常のリポジトリと同じように使えばいい。
Sphinxでドキュメントをビルドすると、指定したビルドディレクトリ配下にターゲットに応じたディレクトリが作られて、その下に生成したドキュメントが配置される。HTMLを指定した場合は
ビルドディレクトリ/html/index.html
といった感じ。これを毎回リポジトリのルートディレクトリまでコピーするのも面倒なので、ビルドしたらルートディレクトリにHTMLをコピーするバッチファイルを用意した。DOC_DIRを各環境にあわせて変更すればそのまま使えるはず。あとは生成したHTMLをコミットして、bitbucketにpushすれば公開される。
@ECHO OFF SET ROOT_DIR=%~DP0 SET DOC_DIR=%ROOT_DIR%\doc CD /d %ROOT_DIR% PUSHD %DOC_DIR% IF "%1"=="publish" ( CALL make.bat html-publish ) ELSE ( CALL make.bat html ) POPD IF EXIST %DOC_DIR%\build\html\index.html ( XCOPY /S /Q /Y %DOC_DIR%\build\html\* %ROOT_DIR% )
TortoiseHg 2.x 入門記事にMercurial採用理由追記
以前書いてた TortoiseHg 入門記事を Ver.2.2 ベースで書きなおした記事に、なぜMercurialを採用するのか追記した。
Windows+TortoiseHg 2.x で始めるMercurial
ある程度分量があってはてなでは書きにくいことと、最近ドキュメントはSphinxで書いたほうが書きやすいので、すべてSphinxで書きなおした。
このあとはブランチの使い方に言及していく予定。果たしていつになるのか・・・