Excelから関数型言語マスター3回目:WebにDBデータ表示【Mnesia編】 ...をやってみた

元記事様: https://qiita.com/piacerex/items/a7558adc6856e3577dc6

1月19日のEDI#11 で私が盛大に聞き違いを起こしたGigalixir、再来。「Excelから関数型言語マスター第8回目」で触れるっぽいので今は単語だけ。


「Mnesia」…Elixirにバンドルされている分散DB

読みは「エムネジア」。なんか、昔そんな大陸がありました~みたいな響きの名前。エムネジア大陸?


→【RDBMS】 Relational DataBase Management System。SQLを利用してリレーショナルデータベースを管理するシステム。逆にDBを管理しないでそのまま使うNoSQLもが存在する。

→【分散DB】ネットワーク上に複数存在するデータベースを、1つのデータベース管理システムが制御する形態のDBのこと。 具体的には、工場、支社、店舗など複数の場所にデータベースを分散して業務を統合する方式。 

→【インメモリ】データストレージ用のメモリに主に依存するDB。メモリ上に配置するので、ディスクやSSD上のDBより応答時間が短縮できる代わりに、処理やデータが失われてしまうリスクが上昇。通知や、ゲームのリーダーボード、リアルタイム分析などマイクロ秒の応答時間を必要としたり、トラフィックが急増する可能性があるアプリケーションに最適。

→【PaaS】 Platform as a Service   IaaSに OS + 実行環境を提供。ユーザーが用意するのはソフトウェアだけ。 読みはパース。 Gigalixirはこれにあたる。

→→【SaaS】 Software as a Service クラウド上のソフトウェアにユーザーはアクセスするだけ。読みはサース

→→【IaaS】 Infrastructure as a Service  仮想サーバやハードディスク、ファイアウォールなどのインフラを提供。実行環境はユーザー側で設定。読みはイアースかアイアース

DBを使うPhoenixのPJ作成、DB作成、起動

不明点なし

Mnesia用スキーマを作成

iex> :mnesia.create_schema( [ node() ] )
:ok
iex> :mnesia.start
:ok

→【スキーマ】データベースの構造を表現する設計図。以下の三層に分かれる。

→→【外部スキーマ】 実際にユーザーに見える部分のデータで、ビューに相当する層

→→【概念スキーマ】データベース管理者によって管理される対象で、論理的なデータを定義する層

→→【内部スキーマ】データベースのハードウェア部分に相当する、物理的配置を定義する層

複数列データと同じ構造のテーブル作成〜データ投入

順番は最初に入れ物(テーブル)を作って、

iex> :mnesia.create_table( :members, [ attributes: [ :id, :name, :age, :team, :position ], disc_copies: [ node() ] ] )

そこにデータを投入する

defmodule SampleDb do
 …
   def inserts() do
       :mnesia.transaction( fn -> :mnesia.write( { :members, 1, "enぺだーし", 49, "有限会社デライトシステムズ", "代表取締役、性能探求者" } ) end )
       :mnesia.transaction( fn -> :mnesia.write( { :members, 2, "ざっきー", 45, "公立大学法人 北九州市立大学", "准教授、カーネルハッカー" } ) end )
       :mnesia.transaction( fn -> :mnesia.write( { :members, 3, "つちろー", 34, "カラビナテクノロジー株式会社", "リードエンジニア、アプリマイスター" } ) end )
       :mnesia.transaction( fn -> :mnesia.write( { :members, 4, "piacere", 43, "株式会社DigiDockConsulting", "常務取締役CTO、福岡Elixirプログラマ、重力プログラマ、技術顧問" } ) end )
   end
end

コンパイルする

iex> recompile
iex> SampleDb.inserts

データが正しく格納されたことを確認

iex> :ets.i( :members )
<1   > {members,2,
        <<227,129,150,227,129,163,227,129,141,227,131,  ...
<2   > {members,4,<<"piacere">>,44,
        <<227,130,171,227,131,169,227  ...
<3   > {members,3,
        <<227,129,164,227,129,161,227,130,141,227,131,  ...
<4   > {members,1,
        <<101,110,227,129,186,227,129,160,227,131,188,  ...
EOT  (q)uit (p)Digits (k)ill /Regexp -->

要素がバイナリで読めないので、qキー(quitのq)で抜けて日本語で確認

iex> :mnesia.transaction( fn -> :mnesia.read( :members, 1 ) end )
{:atomic,
[
  {:members, 1, "enぺだーし", 49,
   "有限会社デライトシステムズ",
   "代表取締役、性能探求者"}
]}
iex> :mnesia.transaction( fn -> :mnesia.read( :members, 4 ) end )
{:atomic,
[
  {:members, 4, "piacere", 43, "株式会社DigiDockConsulting",
   "常務取締役CTO、福岡Elixirプログラマ、重力プログラマ、技術顧問"}
]}

できてますね。

DBアクセスモジュールを作る

DbMnesia.query()が呼ばれたタイミングで、:mnesia.startでMnesiaを起動し、:mnesia.wait_for_tables()でMnesiaテーブルが利用可能になるまで、ウェイト(最大1秒間待つ)してから、
#lib/util/db_mnesia.ex を作成して...


defmodule DbMnesia do
 def query(table_name) do
   :mnesia.start
   table_atom = table_name |> String.to_atom
   :mnesia.wait_for_tables([table_atom], 1000)
   columns = :mnesia.table_info(table_atom, :attributes)
     |> Enum.reduce([], fn item, acc -> acc ++ [Atom.to_string(item)] end)
   columns_spec = 1..Enum.count( columns )
     |> Enum.reduce({table_atom}, fn x, acc -> Tuple.append( acc, :"$#{ x }" )  end)
   rows = :mnesia.transaction( fn ->
     :mnesia.dirty_select(table_atom, [{columns_spec, [], [:"$$"]}]) end ) |> elem(1)
   %{
     columns: columns,
     command: :select,
     connection_id: 0,
       num_rows: Enum.count(rows),
     rows: rows
   }
 end
end

DBの起動からテーブルの利用可能時間まで考慮して命令自体を待機させる。そこまで考慮するんだ!面白い!

#lib/util/db.ex を作成して...

defmodule Db do
 def query(sql) when sql != "" do
   Regex.named_captures(~r/select( *)(?<columns>.*)( *)from( *)(?<tables>.*)/, sql)["tables"]
   |> DbMnesia.query
 end
 def columns_rows(result) do
   result
   |> rows
   |> Enum.map(fn row -> Enum.into(List.zip([columns(result), row]), %{}) end)
 end
 def rows(%{rows: rows} = _result ), do: rows
 def columns(%{columns: columns} = _result), do: columns
end
DbMnesiaモジュールのquery関数は、PostgreSQL/MySQL編のDb.queryと同じ形の結果を返すよう作っているため、Dbモジュールのquery以外の関数が、全て適用できます

汎用性の高さ=保守のしやすさということでもあるよなぁ。

DBデータをWeb表示

#lib/sample_db_web/templates/page/index.html.heex を開いて...

<%
result = Db.query( "select * from members" )
data = result |> Db.columns_rows
%>
<table border="1">
<%= for record <- data do %>
<tr>
   <td><%= record[ "name" ] %></td>
   <td><%= record[ "age" ] %></td>
   <td><%= record[ "team" ] %></td>
   <td><%= record[ "position" ] %></td>
</tr>
<% end %>
</table>

で、こうなると

スクリーンショット 2022-01-29 11.56.01


後はそれぞれのコマンドを細分化して理解すればソラ打ちできるようになるんではないかと思う。道は長い

参考: https://data.wingarc.com/saas-paas-iaas-11087,

https://aws.amazon.com/jp/nosql/in-memory/ 

 https://products.sint.co.jp/siob/blog/db-schema


この記事が気に入ったらサポートをしてみませんか?