PGroonga を試す

Ruby on RailsでPostgreSQLとPGroongaを使って日本語全文検索を実現する方法 - ククログ(2015-11-09)

最近ちょっと PostgreSQL が楽しくなってきたところ、ちょうどクリアコードさんのブログで全文検索、PGroonga のチュートリアル記事が出てた。
今回、PostreSQL 9.5 beta 1 で試したので、ソースからインストールした。

pgroonga.github.io

あとはまるきり手順通り。すごい簡単にできた。
※ 追記: task に :environment をつけること。
コマンド実行時につけるというのもあるようだが、素直にコード中に記述した方がよいと思う。

f:id:yossk:20151110223258j:plain

PostgreSQL 設計・運用計画の鉄則

内部構造から学ぶPostgreSQL 設計・運用計画の鉄則 (Software Design plus)

内部構造から学ぶPostgreSQL 設計・運用計画の鉄則 (Software Design plus)

読んだ。最近、この本を読む前に、ストリーミングレプリケーションを試したばかり。
以下記事を読みながら試した。lets.postgresql.jp

期待通りの動作はしたのだが、正直ただ記事に沿って動かしただけで、ぼんやりしていた。
それがこの書籍を読んで、なるほど、そういうことかと合点がいった。

本番運用をするにあたり、どのように設計するのか、なにを監視をするのか
悩んでいたので、これは読んで良かった。

バックアップとリカバリについては、定期的に練習しないといざというときに余計な時間がかかりそうだ。
ITAC かなんかでも、定期的なリカバリの検証をしないといけないんじゃなかったっけ。
ほんと、やっとかないと自分が泣くことになりそう。

入門Redmine

読んだ。
いや、この本は「読んだ」で終わらせてはいけない。「使った」でないと。

著者は MyRedmine というサービスや、Redmine.JP を運営されている。
SlideShare にも Redmine 関連資料を上げている、Redmine 愛に溢れた方だ。

hosting.redmine.jp

Redmine.JP — Redmine日本語情報サイト

www.slideshare.net


見所はやはり、3版には無かった Redmine を使った体験談。
高専生が Redmine を使ってロボコンに挑んでみた」、なんてラノベか漫画であっても良いなぁ、と思った。
いや、ほんとにいいんじゃない?

Redmine、以前どこかで Trello の方がよい、みたいな話を聞いた。trello.com

Trello、確かに ToDo管理においてはとってもよい。その手軽さ、見やすさは Redmine を上回る。
ただ、言ってしまえば Trello はそこに特化しているので、個人的には Redmine の代替手段ではないと思っている。

Redmine の一番の強みは、過去/現在/未来の情報の一元管理、だと思ってる。
長期スパンの計画、過去のチケット情報、Wikiなどなど。
このあたりに魅力を感じず、今現在の、目の前のタスクをただこなせれば良い、ということであれば、
Redmine は 過剰なので、Trello を使えば良いと思う。

しかし、高専生でもやってることを、いい年こいた自分ときたら・・・。

UPDATE での Primary Key の更新について

達人に学ぶ SQL徹底指南書 (CodeZine BOOKS)

達人に学ぶ SQL徹底指南書 (CodeZine BOOKS)

勉強中。
SQL って、コーディング規約とかあまり見かけないなので、どう書いていいか困る。
Kindle で買って、今何ページ目か分からないけど、6% まで。

今のところ、違和感がある文・説明が以下。

UPDATE sometable
SET
    p_key = CASE
        WHEN p_key = 'a' THEN 'b'
        WHEN p_key = 'b' THEN 'a'
        ELSE p_key
        END
WHERE
    p_key IN ('a', 'b')
;

p_key はプライマリキーなのだが、PostgreSQLMySQL では重複エラーになる。
このことに対して、

本来、制約は文よる変更が終わった時点で施行されるものであるため、実行途中で一時的に重複が生じても問題はありません。事実、OracleDB2SQL Server では問題無く実行できます。

とあるが、これは本当にそうなのか?UPDATE が完了されるまでは、現時点での状態に合わせてチェックされるのが普通かな?と思ったので、PostgreSQLMySQL の挙動のほうが納得できる気がしている。
その他商用DBの方が独自の実装が多いような印象(ほんとに勝手なイメージだけど)がある。

何を正とするか、だけど、PostgreSQL なんかはSQL準拠頑張ってると聞いたこともあるし。
マニュアル見ても、指摘されている「おかしさ」については説明されていない。

https://www.postgresql.jp/document/9.4/html/sql-update.html

このコマンドは標準SQLに準拠しています。 ただしFROM句およびRETURNING句はPostgreSQLの拡張です。 UPDATEでWITHが使用可能であることも同様に拡張です。

たいしたことじゃないかもしれないが、モヤモヤするな。

PostgreSQL Client

MacPostgreSQL のクライアントは何がよいかなぁ。
勉強用なので、有料はちょっとね。
pgAdmin で良いかと思ったけど、ちょっと無理だった。
β版を使ったから、クエリウィンドウのサイズ変更が出来ないのと、日本語が満足にいれられない。
日本語使わなければいける。

環境は VagrantPostgreSQL に接続。
ポートフォワーディングの設定しとく。

f:id:yossk:20151105235009j:plain

さてどうしようかな。

f:id:yossk:20151105235213j:plain

f:id:yossk:20151105235253j:plain

f:id:yossk:20151105235308j:plain

これからはじめるTDD

tatsu-zine.com

C# で写経してみた。
Testプロジェクトは、頭に Test をつけたけど、後ろにつけるべきだったのかな。
注意点は、プロダクトの方にテストプロジェクトがアクセスするためには、プロダクト側の AssemblyInfo.cs に以下を記述すること。

[assembly: InternalsVisibleTo("TestKorekaraTdd")]

BowlingGame.cs

using System.Collections.Generic;
using System.Linq;

namespace KorekaraTdd
{
    class BowlingGame
    {
        private Frame spareFrame;
        private List<Frame> strikes = new List<Frame> { };
        private IList<Frame> frames = new List<Frame> { new Frame() };

        public int FrameScore(int frameNo)
        {
            return frames[frameNo - 1].Score;
        }

        public void RecordShot(int pins)
        {
            frames.Last().RecordShot(pins);
            CalcSpareBonus(pins);
            CalcStrikeBonus(pins);
            if (frames.Last().IsFinished())
            {
                frames.Add(new Frame());
            }
        }

        public int Score()
        {
            int total = 0;
            foreach(var frame in frames)
            {
                total += frame.Score;
            }
            return total;
        }

        private void AddStrikeBonus(int pins)
        {
            foreach(var strike in strikes)
            {
                if (strike.IsNeedBonus())
                {
                    strike.AddBonus(pins);
                }
            }
        }

        private void CalcSpareBonus(int pins)
        {
            if (spareFrame != null && spareFrame.IsNeedBonus())
            {
                spareFrame.AddBonus(pins);
            }

            if (frames.Last().IsSpare())
            {
               spareFrame = frames.Last();
            }
        }

        private void CalcStrikeBonus(int pins)
        {
            AddStrikeBonus(pins);
            if (frames.Last().IsStrike())
            {
                RecognizeStrikeBonus();
            }
        }

        private void RecognizeStrikeBonus()
        {
            strikes.Add(frames.Last());
        }
    }
}

Frame.cs

using System;

namespace KorekaraTdd
{
    class Frame
    {
        private int score = 0;
        private int bonusCount = 0;

        public int Score {
            get
            {
                return score + Bonus;
            }
            private set
            {
                score = value;
            }
        }

        public int Bonus { get; private set; }

        private int shotCount = 0;

        public Frame()
        {
            Score = 0;
            Bonus = 0;
        }

        public void AddBonus(int bonus)
        {
            Bonus += bonus;
            bonusCount++;
        }

        public bool IsFinished()
        {
            return Score >= 10 || shotCount > 1;
        }

        public bool IsSpare()
        {
            return Score >= 10 && shotCount > 1;
        }

        public bool IsStrike()
        {
            return Score >= 10 && shotCount == 1;
        }

        public bool IsNeedBonus()
        {
            if (IsSpare())
            {
                return bonusCount < 1;
            }

            if (IsStrike())
            {
                return bonusCount < 2;
            }

            return false;
        }

        public void RecordShot(int pins)
        {
            CheckPinsCount(pins);
            Score += pins;
            shotCount++;
        }

        private void CheckPinsCount(int pins)
        {
            if ((pins < 0 || 10 < pins) && Score + pins <= 10)
            {
                throw new ArgumentException();
            }
        }
    }
}

BowlingGameTest.cs

using System;
using KorekaraTdd;

namespace TestKorekaraTdd
{
    using NUnit.Framework;

    [TestFixture]
    public class BowlingGameTest
    {
        BowlingGame game;

        [SetUp]
        public void SetUp()
        {
            game = new BowlingGame();
        }

        [Test]
        public void 全ての投球がガター()
        {
            RecordManyShot(20, 0);
            Assert.That(game.Score, Is.EqualTo(0));
        }

        [Test]
        public void 全ての投球で1ピンだけ倒した()
        {
            RecordManyShot(20, 1);
            Assert.That(game.Score, Is.EqualTo(20));
        }

        [Test]
        public void スペアをとると次の投球のピン数を加算()
        {
            RecordSpare();
            game.RecordShot(4);
            RecordManyShot(17, 0);

            Assert.That(game.Score, Is.EqualTo(18));
            Assert.That(game.FrameScore(1), Is.EqualTo(14));
        }

        [Test]
        public void 直前の投球との合計が10品でもフレーム違いはスペアではない()
        {
            foreach (var i in new int[] { 2, 5, 5, 1 })
            {
                game.RecordShot(i);
            }
            RecordManyShot(16, 0);
            Assert.That(game.Score, Is.EqualTo(13));
        }

        [Test]
        public void ストライクをとると次の2投分のピン数を加算()
        {
            RecordStrike();
            foreach (var i in new int[] { 3, 3, 1 })
            {
                game.RecordShot(i);
            }
            RecordManyShot(15, 0);
            Assert.That(game.Score, Is.EqualTo(23));
            Assert.That(game.FrameScore(1), Is.EqualTo(16));
        }

        [Test]
        public void 連続ストライクすなわちダブル()
        {
            RecordStrike();
            RecordStrike();
            foreach (var i in new int[] { 3, 1 })
            {
                game.RecordShot(i);
            }
            RecordManyShot(14, 0);
            Assert.That(game.Score, Is.EqualTo(41));
            Assert.That(game.FrameScore(1), Is.EqualTo(23));
            Assert.That(game.FrameScore(2), Is.EqualTo(14));
        }

        [Test]
        public void 連続3回ストライクすなわちターキー()
        {
            for (var i = 0; i < 3; i++) { RecordStrike(); }
            foreach (var i in new int[] { 3, 1 })
            {
                game.RecordShot(i);
            }
            RecordManyShot(12, 0);
            Assert.That(game.Score, Is.EqualTo(71));
        }

        [Test]
        public void ストライクの後のスペア()
        {
            RecordStrike();
            RecordSpare();
            game.RecordShot(3);
            RecordManyShot(15, 0);
            Assert.That(game.Score, Is.EqualTo(36));
        }

        [Test]
        public void ダブル後のスペア()
        {
            RecordStrike();
            RecordStrike();
            RecordSpare();
            game.RecordShot(3);
            RecordManyShot(13, 0);
            Assert.That(game.Score, Is.EqualTo(61));
        }

        [Test]
        public void 全ての投球がガターの場合の第1フレームの得点()
        {
            RecordManyShot(20, 0);
            Assert.That(game.FrameScore(1), Is.EqualTo(0));
        }

        [Test]
        public void 全ての投球が1ピンだと全フレーム2点()
        {
            RecordManyShot(20, 1);
            Assert.That(game.FrameScore(1), Is.EqualTo(2));
        }

        private void RecordManyShot(int count, int pins)
        {
            for (var i = 0; i < count; i++)
            {
                game.RecordShot(pins);
            }
        }

        private void RecordStrike()
        {
            game.RecordShot(10);
        }

        private void RecordSpare()
        {
            RecordManyShot(2, 5);
        }
    }
}

FrameTest.cs

using System;
using KorekaraTdd;

namespace TestKorekaraTdd
{
    using NUnit.Framework;

    [TestFixture]
    class FrameTest
    {
        Frame frame;

        [SetUp]
        public void SetUp()
        {
            frame = new Frame();
        }

        [Test]
        public void 全ての投球がガター()
        {
            frame.RecordShot(0);
            frame.RecordShot(0);
            Assert.That(frame.Score, Is.EqualTo(0));
        }

        [Test]
        public void 全ての投球で1ピンだけ倒した()
        {
            frame.RecordShot(1);
            frame.RecordShot(1);
            Assert.That(frame.Score, Is.EqualTo(2));
        }

        [Test]
        public void 二投するとフレームは完了する()
        {
            frame.RecordShot(1);
            Assert.That(frame.IsFinished(), Is.False);
            frame.RecordShot(1);
            Assert.That(frame.IsFinished(), Is.True);
        }

        [Test]
        public void 十ピン倒した時点でフレームは完了する()
        {
            frame.RecordShot(10);
            Assert.That(frame.IsFinished, Is.True);
        }

        [Test]
        public void 二投目で10ピン倒すとスペア()
        {
            frame.RecordShot(5);
            Assert.That(frame.IsSpare(), Is.False);
            frame.RecordShot(5);
            Assert.That(frame.IsSpare(), Is.True);
        }

        [Test]
        public void 一投目で10ピン倒すとストライク()
        {
            Assert.That(frame.IsStrike(), Is.False);
            frame.RecordShot(10);
            Assert.That(frame.IsStrike(), Is.True);
        }

        [Test]
        public void ボーナス点を加算する()
        {
            frame.RecordShot(5);
            frame.RecordShot(5);
            frame.AddBonus(5);
            Assert.That(frame.Score, Is.EqualTo(15));
        }

        [Test]
        public void オープンフレームにはボーナス不要()
        {
            frame.RecordShot(3);
            frame.RecordShot(3);
            Assert.That(frame.IsNeedBonus(), Is.False);
        }

        [Test]
        public void スペアのボーナスは1投分で完了()
        {
            frame.RecordShot(5);
            frame.RecordShot(5);
            Assert.That(frame.IsNeedBonus(), Is.True);
            frame.AddBonus(5);
            Assert.That(frame.IsNeedBonus(), Is.False);
        }

        [Test]
        public void ストライクのボーナスは2投分で完了()
        {
            frame.RecordShot(10);
            frame.AddBonus(5);
            Assert.That(frame.IsNeedBonus(), Is.True);
            frame.AddBonus(5);
            Assert.That(frame.IsNeedBonus(), Is.False);
        }

        [Test]
        public void ピン数は0本以上()
        {
            Assert.Throws<ArgumentException>( delegate { frame.RecordShot(-1); });
        }
    }
}