2015年12月5日土曜日

PR-S300SE の内線設定に見知らぬ MAC アドレス

なんだかわからんが、PR-S300SE で SIP クライアントの設定をしていたら
なんだか知らない MAC アドレスが登録されてて怖い。

内線 3 番に勝手に登録されてて、ダイジェスト認証も外れている

arp したら BBユニットの MAC らしく、何で勝手に登録するのか?
しかもダイジェスト認証外すとかどうなってんのこれ?

まだ気持ち悪い。どうしよう

BB ユニットをルータにした状態でホームゲートウェイ(PR-S300SE)にアクセス

今日からソフトバンク光になった。光コラボはいろいろ不満がある人も多い
ようだけど(ソフトバンク光は特に)、スマート割でスマホの月額が結構安く
なるのでまぁいろいろありそうだが我慢しようと決めて今日の開通に至った。

今までは PR-S300SE をルータにして i-revo access というマイナーなプロバ
イダに繋いでいた。ソフトバンク光は必須で BB ユニットなる抱き合わせルー
タを契約する必要があり、それで Yahoo!BB に接続することを推奨している。
最初は「BB ユニットなんて使わねーよ!」的に考えていたんだけど、 ネット
でいろいろ調べると結構恩恵があることがわかり、ちょっと使ってみることに
した。恩恵ってのは以下。
  • IPv6 IPoE + IPv4 ハイブリッド接続で高速化
    うちはフレッツ光ハイスピードタイプなので最大 200Mbps だったが、
    Pv6 IPoE +IPv4 にすることにより工事なしでギガになる。
    ちなみにハイスピードタイプからギガタイプにするには 2000 円の工
    事費がかかる。
  • 11ac のアクセスポイントゲッツ
    ちょうど 11ac のアクセスポイントをもう一台買おうかと思っていたと
    ころだったんだけど、BB ユニット(E-WMTA2.3)で代用できちゃう。
  • 地デジパック
    抱き合わせで Wi-fi 地デジパックってのに契約してしまって(2年縛り
    だってのに後で気づいた...)、せっかくだからスマホでテレビを見るこ
    とにする。
  • VPNは問題なし
    恩恵ではないが心配ごとがひとつクリアされた。
    BB ユニットに VPN
    パススルー機能がついていないことは最初からわかってて、VPN どう
    すんべと思っていたんだけど、ネットでいろいろ調べるうちに

    (PR-S300SE=PPPoE ブリッジ) + (BB ユニット=ルータ)な構成でも
    IPSec は通せることがわかった。固定 IP じゃない(v4)ので DDNS はど
    うにかせにゃあかんが...
 だがしかし、実際モノが来て設定してみると以下の不便が発生。
  •  SIP クライアントが設定できない...
  • というか、PR-S300SE の設定は PC 直結しなくちゃならないのでは...
もともとフレッツの頃からひかり電話を家の固定電話にしている(これも早
くやめちゃって SMARTalk の 050 を家の固定にしたいんだけど家族からの

反対もあり...)。んで、スマホ
の SIP クライアント経由で電話をかけたりしている。

今回のソフトバンク光では、
光電話(N)っつーオプションでこのひかり電話
をそのまま引き継げる(BBフォンを抱き合わせされたが)ので、このあたり
何も心配していなかったんだけど、よくよく考えてみたら下記構成だと 
PR-S300SE と通信できないじゃん。
PR-S300SE
    |  (PPPoE ブリッジ)
BBユニット  [ルータ]
    | (無線 or 有線)

 PC,スマホ等
当たり前じゃん orz...  
じゃ、こうすんべということで以下構成をやってみた。
           PR-S300SE
     (有線) |   |  (PPPoE ブリッジ)
BBユニット  [ルータ]
                 | (無線 or 有線)

          PC,スマホ等



BB ユニットの WAN 側ポートはそのまま PR-S300SE からの接続をして
いるが、さらに BB ユニットの LAN 側ポートから PR-S300SE のポート
に LAN ケーブルを接続。ループ覚悟で
で、PR-S300SE には LAN と同じサブネットの IP アドレス(v4)を固定で
割り当てる。

おぉ、できた!(ブラウザから PR-S300SE の設定画面にアクセス OK)と
思いきや数分後に BB ユニットの DNS サーバが全く応答しなくなる。
IP 直なら OK なので 8.8.8.8 をスタティックに設定すんべと思ったがク
ライアントの台数も多いので結構面倒。DNS だけダメなので原因もつか
めていないので不安になり別の方法を模索することにした。

結果、2 ちゃんのおかげで解決。PR-S300SE に振られる IPv6 グローバ
ルアドレスを経由してアクセス可能であることがわかった。PR-S300SE 
の IPv6 アドレスは以下で確認できる。

「情報」→「DHCPv6サーバ払い出し状況」の 「SIP サーバアドレス」
 

最初の状態に戻して、家中 PC のブラウザから http://[2400:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx]/
にアクセスしたところ(firefox では v6 アドレスはカギカッコで括る)
画面出ました!あとは IPv6 対応の SIP クライアントを探せば良いだけ
だ。

と、家中 PC の IPv6 でちょっと手こずったのでメモ。
 手元の Fedora が DHCP サーバから IPv6 のアドレスをゲットする際
に下記エラーが発生して取れない。


 IPv6: wlan0: IPv6 duplicate address 2400:xxxx:xxxx:xxxx:yyyy:yyyy:yyyy:yyyy detected!

いろいろ調べると kernel のバグっぽいが、とりあえず以下でごまかし
する方法がネットにあったのでそれで回避してる。
echo 0 | sudo tee /proc/sys/net/ipv6/conf/*/accept_dad
 DAD を無効にしてるだけだが、とりあえずはまぁいいや。根本解決は
後回し。




2015年11月26日木曜日

xfreerdp で SSl 接続時に I/O Error

xfreerdp で 下記のエラーが発生

SSL_read: I/O error

xfreerdp のオプションに下記を追加して解決。

 --sec rdp

2015年11月25日水曜日

he name com.redhat.oddjob_mkhomedir was not provided

org.freedesktop.DBus.Error.ServiceUnknown: The name com.redhat.oddjob_mkhomedir was not provided by any .service files, pam_oddjob_mkhomedir

oddjobd が動いていないのが原因

# systemctl start oddjobd.service
# systemctl enable oddjobd.service

2015年11月20日金曜日

最近の Fedora やら RHEL7 やらの hostname

RHEL7 だとホスト名の変更には hostnamectl を使う必要がある。

hostnamectl で指定できるホスト名は "static", "transient", "pretty" の 3 種ある。

static はいわゆる kernel hostname で /etc/hostname に指定するもの。
transient は DHCP や mDNS のような起動時に割り当てられるホスト名。

これら 2 つは internet domain names の表記ルールに従う。

pretty は自由形式でおそらくコメントみたいなもの。

2015年11月18日水曜日

named-chroot (スレーブ) on SELinux でゾーンファイルが作れない。

CentOS7 にスレーブの DNS を立ててるんだけど、以下のエラーでゾーンファイルを
作ってくれない。

dumping master file: slaves/tmp-iwPDEqo4j7: open: permission denied

というか、named が chroot 環境のときだけ上記が出る模様。
SELinux のせいというのは FAQ なんだけど、なんで chroot のときだけ出るんだろ..

下記で解決なんだが...
setsebool -P named_write_master_zones true

...と、思ったが上記は方法が強引すぎる。
ここはディレクトリのセキュリティコンテキストを変えるのが正攻法。

cd /var/named/chroot/var/named
chcon -R system_u:object_r:named_conf_t:s0 data
chcon -R system_u:object_r:named_conf_t:s0 dynamic
chcon -R system_u:object_r:named_conf_t:s0 slaves
これで解決。

2015年11月16日月曜日

chronyc で ntp -q

chronyc で ntp -q がしたい。↓
chronyc sourcestats

簡単でよければ
chronyc activity

chronyc

2015年10月27日火曜日

Fedora で WN-G150UM

WN-G150UM のドライバはカーネルに標準で入っていて rtl8192cu.ko らしいが、
udev が反応してくれず ip コマンドで見えない。

アイ・オーデータのベンダーID とデバイス ID がコードに含まれてないらしい。

これは動的に /sys/bus/usb/drivers/rtl8192cu/new_id に追加出来るらしく、
modprobe 実行時に強制的にコマンドで追加してしまう方法が一般的のよう。
以下、http://beautifulajax.dip.jp/?p=1016 のマルパクです。

# echo 'install rtl8192cu /sbin/modprobe --ignore-install rtl8192cu ' \
# '$CMDLINE_OPTS; /bin/echo "04bb 094c"' \
# ' > /sys/bus/usb/drivers/rtl8192cu/new_id' \
# > /var/tmp/hogehoge.txt
# echo '# USB device 0x:0x (rtl8192cu)
# SUBSYSTEM=="usb", ACTION=="add", ATTR{idVendor}=="04bb", ' \
# 'ATTR{idProduct}=="094c", RUN+="/sbin/modprobe -qba rtl8192cu"' \
# > /etc/udev/rules.d/70-persistent-net.rules
上記のように /etc/modprobe.d/wireless.conf と /etc/udev/rules.d/70-persistent-net.rules
を追加。

再起動したくないときは以下実行。(wlp0s26u1u2 は私の環境のデバイス名)


# modprobe -ba rtl8192cu
# ip link set wlp0s26u1u2 up


2015年10月22日木曜日

長いワンライン作って cron に登録したら(ファイルに分けたくなかった)、実行時に以下エラーで弾かれる。
CROND[8535]: (root) CMDOUT (/bin/sh: -c: line 0: unexpected EOF while looking for 
CROND[8535]: (root) CMDOUT (/bin/sh: -c: line 1: syntax error: unexpected end of file)

ターミナルから実行すると普通に OK なんだけど、なんで、、、といろいろ調べたら、コマンド内に % 文字が含まれる場合はバックスラッシュでエスケープしなくちゃいけないらしい。で、直った。

2015年10月7日水曜日

paramiko で exec_command で stdin, stdout 同時に利用すると帰ってこない

python paramiko で exec_command() でコマンド実行する際に、標準入力と
標準出力を同時に使いたいんだけど、標準出力がどうも取れなくて固まって
しまう。下記のように stdin.channel.shutdown_write()  する必要があるぽい。

import paramiko
client = paramiko.SSHClient()
client.connect(hostname='host', ....)
stdin, stdout, stderr = client.exec_command(command)
stdin.write(mydata)
stdin.flush()
stdin.channel.shutdown_write()  # <--- これが必要
stdin.close()
ret = stdout.read()
これなら大丈夫なんだけど、これで良いのか?

xxd 知らんかった

16進からバイナリに戻したい。
python の binascii.unhexlify() でやったりしてたけど、xxd なんてあるのね。
Fedora の場合は vim-common パッケージに入ってた。

xxd -r -p
知らんかった恥ずかしい+忘れそうなのでメモ。

2015年9月25日金曜日

bourne shell で複数行数値を sum したい

paste で改行を+に書き換えて bc
例えば ls -l の結果からファイルサイズ合計を求めたいとき
(du 使えとかそういうのではなく)

ls -l | awk '{print $5}' | tail -n +2 | paste -sd+ | bc

2015年9月5日土曜日

libvirt の KVM で既存の仮想マシンディスクイメージを使ってインタンスを上げる件

自分の Fedora 内の libvirt で Windows 8 を上げていてこのネットワークは
ユーザモードになってる。でも、TAP で使いたかったので root ユーザ管理
のマシンにすべく、そのままのディスクイメージで root ユーザ管理に移行で
きないか調べてみた。

とりあえず、既存ディスクイメージをそのまま使うには以下のように virt-install
の --import オプションで良いらしい。こうすれば定義 xml だけを作ってく
れる模様。

virt-install --import --name win8pro --ram 4096 --disk path=/home/ryo/libvirt/images/win8pro.img,device=disk,bus=virtio,format=raw --os-type=windows --os-variant=win7 --network=network:default

すると、/etc/libvirt/qemu/ 下に win8pro.xml が作成されたので登録され
たようだ。

で、このまま virsh start win8pro をやってみたのですが、起動はするものの
Windows が修復モードで上がっちゃって、さらに PC をリフレッシュなるボタ
ンをクリックしてみると以下のエラーが返されてどうにもならなくなる。
Windowsがインストールされているドライブがロックされています。ドライブのロックを解除してやり直してください
なんじゃら、ディスクイメージファイルのパーミッションとかそのあたりを疑っ
たのだけど違うようで、 結局 xml ファイル内に定義されている uuid が旧仮想
マシンと違うことが原因だったようで。

なので、virsh で edit win8pro して uuid を既存のやつと同じにしたところ、
正常起動した。

というメモでした。

2015年8月19日水曜日

unar

unar なら windows の zip ファイル内にあるファイル名文字化けしないぞ。これからは unzip でなく unar を手癖にしないと。

2015年7月15日水曜日

libvirt で internal なネットワークを作って仮想マシンを接続

internal なネットワークを作成してそこに仮想マシンを接続したかった。net-create でネットワークを作って attach-interface 仮想マシンに接続。以下コマンド記録。
$ cat /var/tmp/mynet.xml
<network>
  <name>internal1</name>
  <bridge name='brint1' stp='on' delay='0' />
</network>
# virsh
Welcome to virsh, the virtualization interactive terminal.
Type:  'help' for help with commands
       'quit' to quit
virsh # net-create /var/tmp/mynet.xml
virsh # net-list
 Name                 State      Autostart     Persistent
----------------------------------------------------------
 default              active     yes           yes
 internal1            active     no            no
virsh # list
 Id    Name                           State
----------------------------------------------------
 2     vcent1                         running
virsh # attach-interface vcent1 network internal1
Interface attached successfully










2015年7月6日月曜日

BlueOnyx を手元の libvirt(KVM) にインストール

とりあえず BlueOnyx なるもの環境を作らないかん状況になったので、手元の KVM に入れようと libvirt と格闘中。

とりあえず現時点最新 stable iso をダウンロードして、そのまま以下実行したところエラーでインストールができない。

virt-install -n blueonyx5208r -r 1024 -f ~/libvirt/images/blueonyx5208r.img -s 50 --vcpus=1 --os-variant=rhel6 --cdrom=/var/tmp/BlueOnyx-5208R-CentOS-6.6-20150323.iso 
ERROR    Error validating install location: Checking installer location failed: Could not find media '/var/tmp/BlueOnyx-5208R-CentOS-6.6-20150323.iso'

/var/tmp/BlueOnyx-5208R-CentOS-6.6-20150323.iso にはちゃんとファイルもあるし、md5 も大丈夫だし file コマンドで確認しても bootable になってる。

もう考えるのもアレなので、iso を loopback マウントしてごまかすことに。

mount -o loop /var/tmp/BlueOnyx-5208R-CentOS-6.6-20150323.iso /mnt/tmp

んで以下実行。

 virt-install -n blueonyx5208r -r 1024 -f /home/ryo/libvirt/images/blueonyx5208r.img -s 50 --vcpus=1 --location /mnt/tmp/

おぉ、BlueOnyx のインストーラが走り始めた。が、CD-ROM イメージ内の ks/ ディレクトリ下の kickstart.cfg を使ってインストールすべきらしい。手動でパラメータ選択するのもアレなので一旦 Ctrl-C で中止。

virsh destroy, virsh undefine してからやりなおし。

virt-install -n blueonyx5208r -r 1024 -f /home/ryo/libvirt/images/blueonyx5208r.img -s 5 --vcpus=1 --extra-args "ks=http://10.9.3.182/blueonyx/ks/kickstart.cfg ksdevice=eth0" --location /mnt/tmp/

上記 10.9.3.182 は自分の IP アドレス(自分のマシンの Apache の DocumentRoot 下に /mnt/tmp(BlueOnyxのCDイメージルート)を blueonyx って名前で symlink してる)が、ks の読み込み時にドライバを要求されてしまう。

ちょっとごまかしで、以下のように--location に指定してるイメージルートを http 経由にしてみた。

virt-install -n blueonyx5208r -r 1024 -f /home/ryo/libvirt/images/blueonyx5208r.img -s 5 --vcpus=1 --extra-args "ks=http://10.9.3.182/blueonyx/ks/kickstart.cfg ksdevice=eth0" --location="http://10.9.3.182/blueonyx"

したら、おぉ、今度はちゃんと行けたらしい。

インストールもひと通り終わって、root/blueonyxでログインしたら今度は IP アドレスを求められる。DHCP クライアントはダメっぽい。

今回は一般ユーザからの virt-install で interface type='user' な環境なので、デフォルトのネットワークは以下。

https://libvirt.org/formatdomain.html#elementsNICS
Userspace SLIRP stack



       Provides a virtual LAN with NAT to the outside world. The virtual network has DHCP & DNS services and will give the guest VM addresses starting from 10.0.2.15. The default router will be 10.0.2.2 and the DNS server will be 10.0.2.3. This networking is the only option for unprivileged users who need their VMs to have outgoing access.


これを静的に設定してみる。






2015年6月25日木曜日

WI-U2-433DM on Fedora 20

仕事で使ってるのでなかなか ver. up できない Fedora 20 に WI-U2-433DM という 
BUFFALO の無線 LAN USB アダプタを装着して通信することに。

kernel 3.19.5-100.fc20.x86_64 な環境では標準ドライバが無い模様。なのでベンダー
提供のドライバをコンパイル。

http://www.edimax.com/edimax/mw/cufiles/files/download/Driver_Utility/transfer/Wireless/NIC/EW-7811UTC/EW-7811UTC_Linux_driver_1.0.1.6.zip

上記にドライバがあるらしいが、私の環境でコンパイルすると下記のエラー発生。
rtl8812AU_8821AU_linux_v4.2.2_7502.20130517/os_dep/linux/os_intfs.c:352:3: error: implicit declaration of function ‘create_proc_entry’

調べると create_proc_entry() は linux 3.10 までの関数で、以降は proc_create() だそうな。

最新のソースコードを探したところ github にあり。
git clone https://github.com/wuzzeb/rtl8812AU_8821AU_linux.git
が、これも make するとエラー。エラー内容は以下。
too many arguments to function ‘cfg80211_rx_mgmt’
これも引数の数が変わったらしい。仕方ないのでパッチ作成。パッチはここにアップロードしておきます。

あと、BUFFALO の情報を os_dep/linux/usb_intf.c に追加。これもこのパッチに含めておきます。

このパッチをあてて、make して root で make install すれば出来上がり。





2015年6月2日火曜日

Visual Studio 2013 Express で emacs Bind

基本的にEmacs emulationをインストールすればいいんだけど、Visual Studio 2013 でかつ Express だとかなり面倒。

extensions.vsixmanifest を編集するんだけど、<VisualStudo Version=12.0> にするのはいいとして、Edition をどうしたらいいかわからない。

と以下発見。

http://stackoverflow.com/questions/13884953/emacs-keybindings-in-visual-studio-2012-or-2013

<Edition>Express_All</Edition>

だそうで、うまくいきました。

.... と思ったらインストールはうまくいったんだけど、Emacs のキーバインドにならない。
くそ、、、どうしたらいいんじゃこれ

2015年5月13日水曜日

libvirt で NTFS のイメージを拡張

KVM(libvirt 管理) で動く Windows7 な仮想マシンのディスクが足りなく
なってきた。なので拡張したし。virt-resize を使うのがよさげ。

# yum install libguestfs-tools
現状こんな感じ。

$ virt-df -h win7pro-image.qcow2
Filesystem                                Size       Used  Available  Use%
win7pro-image.qcow2:/dev/sda1             100M        24M        76M   25%
win7pro-image.qcow2:/dev/sda2              30G        29G       1.1G   97%
$ virt-list-partitions -lh win7pro-image.qcow2
/dev/sda1 ntfs 100.0M
/dev/sda2 ntfs 29.9G


virt-resize は、イメージファイルをそのまま拡張するのではなくて、
新しいイメージにコピーする感じ。だからまず新しいイメージファイル
を作成。今回は 45GB のイメージにする(古いのは 30GB)。
$ qemu-img create -f qcow2 win7pro-image-new.qcow2 45G

sda2 の方を拡張したいので以下実行。古いファイルと新しいファイルを指定する

$ virt-resize --expand /dev/sda2 win7pro-image.qcow2 win7pro-image-new.qcow2

あとは virsh で当該ドメインを edit してイメージファイルのファイル名を変更していつものように virsh start


# Windows ディスク食い過ぎじゃね?...

2015年4月17日金曜日

CentOS7 でネットワーク設定が慣れない。

CentOS7 でネットワーク設定が慣れない。
# nmcli c show ens192
Error: ens192 - no such connection profile.

なんじゃこれ。単にデバイス ens192 をコネクションとして指定してもダメ?
どうも、connection-name が「有線接続1」とか日本語になってるから?

試しに nmtui で「有線接続1」を「ens192」に変更してみたところ通るようになった。

static route もこれで指定できるようになった。

nmcli con mod ens192 ipv4.routes "192.168.0.0/24 172.16.0.1"

2015年3月16日月曜日

PowerShell から AD のグループを作ろうとして Access Denied な件

Windows のことはあまり書きたくないが忘れそうなのでメモ。

Administrator 以外のアカウントで PowerShell から new-adgroup とかやるとアクセスが拒否されました的なエラーが発生してしまう。

Domain Admin を与えてもダメ。

いろいろ調べたら以下で解決。

https://social.technet.microsoft.com/Forums/scriptcenter/en-US/3b5d43b5-80a0-4141-8d20-ec09a21d200c/unable-to-create-security-group-with-powershell

以下を【無効】にする。

管理ツール→ローカルセキュリティポリシー→ローカルポリシー→セキュリティオプション→ユーザアカウント制御: 管理者承認モードですべての管理者を実行する

で再起動を忘れずに。

2015年2月27日金曜日

Multi-AZ な ELB 下の Shibboleth IdP on EC2

AWS で ELB とその下の EC2 を共に Multi-AZ にしている。
そんな環境で Shibboleth IdP を立ててるんだけど、どうやら ELB の IP アドレスが
DNS に 2 つ(Multi-AZ分)登録されていてラウンドロビンしてしまっている。

すると、Shibboleth IdP は以下のエラーを吐き認証が通らない。

Client sent a cookie from address 192.168.1.15 but the cookie was issued to address 192.168.2.17 

ELB がソース IP を自身のものに書き換えてしまうのは知っていたが、これが Multi-AZ
だとアクセスする毎に変わってしまう。DNS のラウンドロビンのせいで。

tomcat の server.xml の Engine タグ内に以下の設定を入れて回避。

      <Valve
          className="org.apache.catalina.valves.RemoteIpValve"
          internalProxies="192\.168\.\d+\.\d+"
          remoteIpHeader="x-forwarded-for"
          proxiesHeader="x-forwarded-by"
          protocolHeader="x-forwarded-proto" />

Apache の mod_remoteip で回避する方法もあるかも知れんが、今回は Cent6 であり、
Apache2.2 な世界なので標準で mod_remoteip が入っていないので tomcat で回避す
ることにした。


2015年2月8日日曜日

MySQL で sequence 作ってみたが... (再チャレンジ)

先日 MySQL の sequence をちょっと作ってみたが、あれからちょっと進化させてトランザクション外でも大丈夫なようにしてみた(SELECT FOR UPDATE はやめて、ワンライン update + last_insert_id() で済ますようにした)。

ただ、トランザクション内に nextval() や setval() を入れてしまうと、他のセッションで同一 sequence を nextval(), setval() しようとしたときにロックかかってしまう状態になる。

うーーんこのあたりなんとかならんか。トランザクション内でも特定のテーブルもしくは行だけはロックがかからないようにしたいのだが。


-- ### sequence テーブル定義 ###
CREATE TABLE sequence (
    sequence_name VARCHAR(50) NOT NULL,
    current_value BIGINT,
    increment BIGINT NOT NULL DEFAULT 1,
    start_value BIGINT,
    min_value BIGINT,
    max_value BIGINT,
    is_cycle BOOLEAN DEFAULT FALSE,
    PRIMARY KEY (sequence_name)
);

-- ### sequence テーブルデフォルト定義 ###
DELIMITER //
CREATE TRIGGER sequence_default BEFORE INSERT ON sequence
FOR EACH ROW
BEGIN
    IF NEW.increment > 0 THEN
        IF NEW.max_value IS NULL THEN
            SET NEW.max_value = POWER(2, 63) - 1;
        END IF;
        IF NEW.min_value IS NULL THEN
            SET NEW.min_value = 1;
        END IF;
        IF NEW.start_value IS NULL THEN
            SET NEW.start_value = NEW.min_value;
        END IF;
    ELSE
        IF NEW.max_value IS NULL THEN
            SET NEW.max_value = -1;
        END IF;
        IF NEW.min_value IS NULL THEN
            SET NEW.min_value = POWER(-2, 63) - 1;
        END IF;
        IF NEW.start_value IS NULL THEN
            SET NEW.start_value = NEW.max_value;
        END IF;
    END IF;
END
//
-- ### シーケンス関数内部利用のテンポラリテーブル定義 ###
DELIMITER //
CREATE PROCEDURE _create_tmp_sequence ()
BEGIN
    CREATE TEMPORARY TABLE IF NOT EXISTS tmp_sequence (
        tmp_sequence_name VARCHAR(50) KEY,
        tmp_current_value BIGINT
    );
END
//
DELIMITER ;
-- ### currval() ###
DELIMITER //
CREATE FUNCTION currval (seq VARCHAR(50))
     RETURNS BIGINT
     LANGUAGE SQL
     DETERMINISTIC
     CONTAINS SQL
     SQL SECURITY DEFINER
     COMMENT ''
BEGIN
     DECLARE mes varchar(128);
     DECLARE my_currval INT;
     CALL _create_tmp_sequence();
     SELECT tmp_current_value
     INTO my_currval
     FROM tmp_sequence
     WHERE tmp_sequence_name = seq;
 
     IF my_currval IS NULL THEN
         IF (SELECT 1 FROM sequence WHERE sequence_name = seq) THEN
             -- 一度も nextval() or setval() してない
             SET mes = concat('currval of sequence "',
                               seq,
                              '" is not yet defined in this session');
         ELSE
             -- そもそも sequence がない
             SET mes = concat('sequence "', seq, '" does not exist');
         END IF;
         SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = mes;
     END IF;
     RETURN my_currval;
END
//
DELIMITER ;
-- ### setval() ###
DELIMITER //
CREATE FUNCTION setval (seq VARCHAR(50), val INTEGER)
    RETURNS BIGINT
    LANGUAGE SQL
    DETERMINISTIC
    CONTAINS SQL
    SQL SECURITY DEFINER
    COMMENT ''
BEGIN
    DECLARE my_max_value BIGINT;
    DECLARE my_sequence_name varchar(50);
    DECLARE mes VARCHAR(128);
    CALL _create_tmp_sequence();

    SELECT sequence_name, max_value
    INTO my_sequence_name, my_max_value
    FROM sequence
    WHERE sequence_name = seq;
    IF my_sequence_name IS NULL THEN
        SET mes = concat('sequence "', seq, '" does not exist');
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = mes;
    END IF;
    IF val > my_max_value THEN
        SET mes = concat('setval: over maximum value of sequence ',
                         seq,
                         '(', my_max_value,')');
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = mes;
    END IF;
    UPDATE sequence
    SET current_value = last_insert_id(val)
    WHERE sequence_name = seq;
    REPLACE INTO tmp_sequence VALUES (seq, last_insert_id());
    RETURN last_insert_id();
 END
//
DELIMITER ;
DELIMITER //
CREATE FUNCTION nextval (seq VARCHAR(50))
    RETURNS BIGINT
    LANGUAGE SQL
    DETERMINISTIC
    CONTAINS SQL
    SQL SECURITY DEFINER
    COMMENT ''
 BEGIN
    DECLARE mes VARCHAR(128);
    DECLARE my_next_value BIGINT;
    DECLARE my_max_value BIGINT;
    CALL _create_tmp_sequence();

    UPDATE sequence
    SET current_value =
        last_insert_id(
             CASE WHEN (current_value is NULL) OR
                       (is_cycle && (current_value > (max_value - increment)))
                      THEN start_value
                  ELSE current_value + increment
             END)
    WHERE sequence_name = seq;
    IF (SELECT ROW_COUNT()) != 1 THEN
        SET mes = concat('sequence "', seq, '" does not exist');
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = mes;
    END IF;
    SELECT last_insert_id(), max_value
    INTO my_next_value, my_max_value
    FROM sequence
    WHERE sequence_name = seq;
    IF my_next_value > my_max_value THEN
        SET mes = concat('nextval: reached maximum value of sequence ',
                         seq,
                         '(', my_max_value,')');
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = mes;
    END IF;
    REPLACE INTO tmp_sequence VALUES (seq, my_next_value);
    RETURN my_next_value;
 END
//
DELIMITER ;

-- ## sequence 定義 (create sequence みたいなもん)
-- デフォルト(値は PostgreSQL チック)で良い場合
INSERT INTO sequence (sequence_name) VALUES ('myseq1');
-- 2 ずつ increment の場合
INSERT INTO sequence (sequence_name, incremetn) VALUES ('myseq2', 2);
-- 1 ずつ increment(デフォルト) かつ start を 10 から, 最大値 9999, 最小値 10
INSERT INTO sequence (sequence_name, start_value, min_value, max_value)
  VALUES ('myseq3', 10, 10, 9999);

2015年2月3日火曜日

MySQL で sequence 作ってみたが... イマイチ...

※ 2015/02/08 追記: 以下に例示した sequence のエミュレーションは全然ダメ。使わないでください、って誰も使うわけないかw
2015/02/08 の日記にちょっと進化させたやつを載せてますが、まだ少し難点あり。

MySQL で、複数の同時接続を考慮した sequence が必要だったので以下 URL を参考に作ってみた(like  PostgreSQL)。

参考: MySQL で シーケンス 機能実現Emulating nextval() function to get sequence in MySQL

が、どうしても外側でトランザクションを併用しなければならないというダサい実装になってしまう。

つーのも MySQL の SELECT FOR UPDATE はトランザクション内でしかロックが効かない
(InnoDBでしか確認してないが)、というのと、Function 内でトランザクションが使えない
ということから、今回作ったやつだと以下のような感じで nextval() を実行しなければな
らない...
begin; select nextval('myseq'); commit;
うーーん、あまりにもダサい気がする。一応ソースを載せておくのでどなたかご指摘を
いただけますと助かります(電車の中でうんこ座りで書いたのでひどいコード... という
言い訳をしておこうw)。

(上記参考のjlakeさんのコードの一部を使わせてもらってますm(__)m)


-- ### sequence テーブル定義 ###
CREATE TABLE sequence (
    sequence_name VARCHAR(50) NOT NULL,
    current_value BIGINT,
    increment BIGINT NOT NULL DEFAULT 1,
    start_value BIGINT,
    min_value BIGINT,
    max_value BIGINT,
    is_cycle BOOLEAN DEFAULT FALSE,
    PRIMARY KEY (sequence_name)
);

-- ### sequence テーブルデフォルト定義 ###
DELIMITER //
CREATE TRIGGER sequence_default BEFORE INSERT ON sequence
FOR EACH ROW
BEGIN
    IF NEW.increment > 0 THEN
        IF NEW.max_value IS NULL THEN
            SET NEW.max_value = POWER(2, 63) - 1;
        END IF;
        IF NEW.min_value IS NULL THEN
            SET NEW.min_value = 1;
        END IF;
        IF NEW.start_value IS NULL THEN
            SET NEW.start_value = NEW.min_value;
        END IF;
    ELSE
        IF NEW.max_value IS NULL THEN
            SET NEW.max_value = -1;
        END IF;
        IF NEW.min_value IS NULL THEN
            SET NEW.min_value = POWER(-2, 63) - 1;
        END IF;
        IF NEW.start_value IS NULL THEN
            SET NEW.start_value = NEW.max_value;
        END IF;
    END IF;
END
//
DELIMITER ;

-- ### シーケンス関数内部利用のテンポラリテーブル定義 ###
DELIMITER //
CREATE PROCEDURE _create_tmp_sequence ()
BEGIN
    CREATE TEMPORARY TABLE IF NOT EXISTS tmp_sequence (
        tmp_sequence_name VARCHAR(50) KEY,
        tmp_current_value INT
    );
END
//
DELIMITER ;

-- ### currval() ###
DELIMITER //
CREATE FUNCTION currval (seq VARCHAR(50))
     RETURNS INTEGER
     LANGUAGE SQL
     DETERMINISTIC
     CONTAINS SQL
     SQL SECURITY DEFINER
     COMMENT ''
BEGIN
     DECLARE mes varchar(128);
     DECLARE my_currval INT;
     CALL _create_tmp_sequence();
     SELECT tmp_current_value
     INTO my_currval
     FROM tmp_sequence
     WHERE tmp_sequence_name = seq;

     IF my_currval IS NULL THEN
         IF (SELECT 1 FROM sequence WHERE sequence_name = seq) THEN
             -- 一度も nextval() or setval() してない
             SET mes = concat('currval of sequence "',
                               seq,
                              '" is not yet defined in this session');
         ELSE
             -- そもそも sequence がない
             SET mes = concat('sequence "', seq, '" does not exist');
         END IF;
         SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = mes;
     END IF;
     RETURN my_currval;
END
//
DELIMITER ;

-- ### setval() ###
DELIMITER //
CREATE FUNCTION setval (seq VARCHAR(50), val INTEGER)
    RETURNS INTEGER
    LANGUAGE SQL
    DETERMINISTIC
    CONTAINS SQL
    SQL SECURITY DEFINER
    COMMENT ''
BEGIN
    DECLARE dummy INT;
    DECLARE mes VARCHAR(128);
    CALL _create_tmp_sequence();

    IF (SELECT 1 FROM sequence WHERE sequence_name = seq FOR UPDATE) THEN
        -- ロックして current_value の設定
        UPDATE sequence
        SET current_value = val
        WHERE sequence_name = seq;
    ELSE
        -- そもそも sequence がない
        SET mes = concat('sequence "', seq, '" does not exist');
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = mes;
    END IF;

    REPLACE INTO tmp_sequence VALUES (seq, val);
    RETURN val;
 END
//
DELIMITER ;
-- ### nextval() ###
DELIMITER //
CREATE FUNCTION nextval (seq VARCHAR(50))
    RETURNS INT
    LANGUAGE SQL
    DETERMINISTIC
    CONTAINS SQL
    SQL SECURITY DEFINER
    COMMENT ''
 BEGIN
    DECLARE my_sequence_name VARCHAR(50);
    DECLARE my_current_value BIGINT;
    DECLARE my_next_value BIGINT;
    DECLARE my_increment BIGINT;
    DECLARE my_min_value BIGINT;
    DECLARE my_max_value BIGINT;
    DECLARE my_start_value BIGINT;
    DECLARE my_cycle BOOLEAN;
    DECLARE mes VARCHAR(128);
    CALL _create_tmp_sequence();
    SELECT sequence_name, current_value, increment,
         min_value, max_value, is_cycle, start_value
    INTO my_sequence_name, my_current_value, my_increment,
         my_min_value, my_max_value, my_cycle, my_start_value
    FROM sequence
    WHERE sequence_name = seq
    FOR UPDATE;

    IF my_sequence_name IS NULL THEN
        -- そもそも sequence がない。
        SET mes = concat('sequence "', seq, '" does not exist');
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = mes;
    ELSEIF my_current_value IS NULL THEN
        -- sequence はあるが、まだ一度も使ったことがない -> start_value を設定
        SET my_next_value = my_start_value;
    ELSE
        -- increment 分加算
        SET my_next_value = my_current_value + my_increment;
    END IF;
    IF my_next_value > my_max_value THEN
        IF my_cycle THEN
            -- max_value を超えてしまったら min_value に戻す
            SET my_next_value = my_min_value;
        ELSE
            SET mes = concat('nextval: reached maximum value of sequence ',
                             seq,
                             '(', my_max_value,')');
   SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = mes;
        END IF;
    END IF;
    UPDATE sequence
    SET current_value = my_next_value
    WHERE sequence_name = seq;
    REPLACE INTO tmp_sequence VALUES (seq, my_next_value);
    RETURN my_next_value;
 END
//
DELIMITER ;
-- デフォルト(値は PostgreSQL チック)で良い場合
INSERT INTO sequence (sequence_name) VALUES ('myseq1');
-- 2 ずつ increment の場合
INSERT INTO sequence (sequence_name, incremetn) VALUES ('myseq2', 2);
-- 1 ずつ increment(デフォルト) かつ start を 10 から, 最大値 9999, 最小値 10
INSERT INTO sequence (sequence_name, start_value, min_value, max_value)
  VALUES ('myseq3', 10, 10, 9999);