LoginSignup
5

More than 3 years have passed since last update.

RubyでDBMSを実装 導入(1日目)

Last updated at Posted at 2019-12-01

この記事は RubyでDBMS Advent Calendar 2019 の1日目の記事です。

全体の概要

ブラックボックスなデータベースの内部実装を少しでも理解したいと思った筆者が、
25日かけてオレオレDBMSをスクラッチで実装していくAdvent Calendar 2019の企画となります。

言語はRuby使います。
このようなミドルウェアを書くとなると、最近だとパフォーマンスや並行処理の書きやすさの観点からGoやRustなどが多いのかもしれませんが、

  • 第一の目的はDBMSの内部実装を理解することであり、パフォーマンスは特に考慮しない
  • 筆者の現在の業務でのメイン言語がRuby(余計な部分で詰まらないようにしたい)
  • あまり例がないと思うので、弊社内の人間を始めとして多くのRubyistに読んでもらえたら嬉しい

などの理由でRubyで書いていくことにしました。
(Rust勉強したいのでいつかRustで書き直したりもしたい)

参考資料

CMU(カーネギーメロン大学)のDATABASE SYSTEMSという講義をメインに参考にしていきます。
非常に質の高い講義のスライド、ノート、動画がすべて無料で見れるためオススメです。

また、今年話題のO'Reillyのデータ指向アプリケーションデザインも適宜参考にするかもしれません。

成果物

こちらのGitHubリポジトリday01のように日付でディレクトリを切ってその日実装したコードを上げていく予定です。

本日の概要

初回なので、
サーバコマンド(MySQLのmysqldに相当)と、
クライアントコマンド(MySQLのmysqlに相当)を用意します。
また、RubyのRDBといいうことでRBDBと名付けました。

サーバコマンド

サーバはHTTPサーバとして立てます。
/exec?query=hogeというパスにクエリパラメータとしてクエリを渡すことで実行される仕様です。
Rubyの標準ライブラリに含まれるWEBrickを使ってチャチャっと作っていきます。
./rbdbdと実行ファイルを実行することで、以下のServerクラスのstartメソッドが呼ばれるようになっています。
(詳しくはソースを参照)
ひとまずqueryに渡された値をechoするだけにしておきます。

# frozen_string_literal: true

require 'webrick'

module Rbdb
  class Server
    def initialize(host: 'localhost', port: 40219)
      @host = host
      @port = port
    end

    def start
      server.mount_proc '/exec' do |req, res|
        query = req.query['query']
        # TODO:
        res.body = query
      end
      trap('INT') do |_|
        terminate
      end
      server.start
      terminate
    rescue Interrupt
      terminate
    end

    private

    def terminate
      server.shutdown
      puts "\ngood bye!"
    end

    def server
      @server ||= WEBrick::HTTPServer.new({
        DocumentRoot: '/',
        BindAddress: @host,
        Port: @port,
      })
    end
  end
end

実行してみます。

% ./bin/rbdbd
[2019-12-01 22:07:43] INFO  WEBrick 1.4.2
[2019-12-01 22:07:43] INFO  ruby 2.6.5 (2019-10-01) [x86_64-darwin18]
[2019-12-01 22:07:43] INFO  WEBrick::HTTPServer#start: pid=28968 port=40219
% curl "http://localhost:40219/exec?query=hoge"
hoge%

クライアントコマンド

次にクライアント側です。
./rbdbと実行すると、プロンプトを表示して入力を待ちます。
(遊び心で最初にアスキーアートを表示してみました)
exit, quit、あるいはCtrl+cで終了します。
こちらもひとまず、サーバからのレスポンスボディをそのまま表示するだけです。

# frozen_string_literal: true

require 'cgi'
require 'net/http'

module Rbdb
  class Client
    def initialize(host: 'localhost', port: 40219)
      @host = host
      @port = port
    end

    def start
      show_title
      show_prompt
      while query = gets.chomp do
        break if terminate_query?(query)
        res = exec_query(query)
        # TODO:
        puts res
        show_prompt
      end
      terminate
    rescue Interrupt
      terminate
    end

    private

    def show_title
      puts <<~'EOS'
              _         _ _
         _ __| |__   __| | |__
        | '__| '_ \ / _` | '_ \
        | |  | |_) | (_| | |_) |
        |_|  |_.__/ \__,_|_.__/

      EOS
    end

    def show_prompt
      print '>> '
    end

    def terminate_query?(query)
      query == 'exit' || query == 'quit'
    end

    def exec_query(query)
      Net::HTTP.get(@host, "/exec?query=#{CGI.escape(query)}", @port)
    end

    def terminate
      puts "\ngood bye!"
    end
  end
end

実行してみます。

% ./bin/rbdb
      _         _ _
 _ __| |__   __| | |__
| '__| '_ \ / _` | '_ \
| |  | |_) | (_| | |_) |
|_|  |_.__/ \__,_|_.__/

>> SELECT * FROM users;
SELECT * FROM users;
>> ^C
good bye!

まとめ

初日ということで、特にDBに関係ない内容となってしまいましたが、
明日からはサーバが受け取ったクエリを解析していく部分から実装していく予定です。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
5