コンパイラかく語りき

import { Fun } from 'programming'

RethinkDBで「DBが存在して無ければ作る」「テーブルが存在して無ければ作る」

※ Nodeで書いてますが、ReQL自体は言語フリーなので他言語の方にも参考にはなると思います。

初期化時にありがちな、DBとテーブルを有無をチェックしながら作成するという処理。

DB作成

// 無ければDBを作成
r.dbList().contains('messages').do((containsDb) => {
  return r.branch(
    containsDb,
    {created: 0},
    r.dbCreate('messages')
  )
}).run(conn)

テーブル作成

// 無ければTableを作成
r.tableList().contains('messages').do((containsTable) => {
  return r.branch(
    containsTable,
    {created: 0},
    r.tableCreate('messages')
  )
}).run(conn)

リファレンス

dbList, tableList

文字通り、リストを取得します。返り値はArrayです。

dblist - ReQL command tablelist - ReQL command

contains

Sequence内に引数で渡した値がある場合はTrueを返します。あるいは、Boolean判定関数を引数に渡すことも可能です。 ※Sequence = Array, Stream, Tablesのようなリスト型

contains - ReQL command

do

引数に関数を設定すると、その関数を実行します。その際、直前のReQLの結果を引き継ぎ、関数の引数として渡すことができます。

do - ReQL command

branch

第一引数にBooleanを取り、trueなら第二引数の処理を、falseなら第三引数の処理を行います。if-then-else的な処理ができます。 引数はいくらでも設定でき、if-then-elseif-then-elseif-then…とつなぐこともできるようです。

branch - ReQL command

おまけ

ちなみに、上記の処理を、接続から起動までチェーンにした1例がこちら。

const async = require('async')

// 接続
const connectRethinkDB = (cb) => {
  r.connect({
    host: 'localhost',
    port: 28015,
    authKey: '',
    db: 'trialDB'
  }, cb)
}

// 無ければDBを作成
const createDB = (conn, cb) => {
  r.dbList().contains('messages').do((containsDb) => {
    return r.branch(
      containsDb,
      {created: 0},
      r.dbCreate('messages')
    )
  }).run(conn, (err) => {
    cb(err, conn)
  })
}

// 無ければTableを作成
const createTable = (conn, cb) => {
  r.tableList().contains('messages').do((containsTable) => {
    return r.branch(
      containsTable,
      {created: 0},
      r.tableCreate('messages')
    )
  }).run(conn, (err) => {
    cb(err, conn)
  })
}

// 複数処理を連続的に実行
async.waterfall([
  connectRethinkDB,
  createDB,
  createTable
], (err, conn) => {
  if(err) {
    console.log(err)
    process.exit(1)
    return
  }
  startExpress()
})

こちらのコードをガッツリお手本にしました。 https://github.com/rethinkdb/rethinkdb-example-nodejs/blob/master/todo-angular-express/app.js

node.jsでRethinkDBを扱うためのORM "Thinky" を試してみた

Thinkyというnode.jsでRethinkDBを扱うためのORMを試してみました。

RethiknDB: https://www.rethinkdb.com/ Thinky: https://thinky.io/

参考ページ

公式のドキュメントのクイックスタートというページを参考にしてみました。

https://thinky.io/documentation/

以下で示すコードは、その写経と若干の補足コメントです。

コード

パッケージインポート

// Thinkyを実行すると、RethinkDBの接続プールが作成されます。
const thinky = require('thinky')() 
// コンソール出力:"Creating a pool connected to localhost:28015"

// typeはスキーマ定義の型を指定するために使います。
const type = thinky.type

RethinkDBへのドライバも利用可

// thinkyはRethinkDBへのドライバの参照を持っています。
const r = v.r
r.now().run().then((time) => {
  return time // 2016-09-19T05:22:53.098Z (例)
})

モデルの作成

// モデルの作成:thinky.createModel(tableName, schema, options)
const Post = thinky.createModel("Post", {
  id: type.string(),
  title: type.string(),
  content: type.string(),
  idAuthor: type.string()
})

const Author = thinky.createModel("Author", {
  id: type.string(),
  name: type.string()
})
// 有効なスキーマについては -> https://thinky.io/documentation/schemas/

モデルのジョイン

// モデルのジョイン: belongsTo(OtherModel, fieldName, leftKey, rightKey[, options]))
Post.belongsTo(Author, "author", "idAuthor", "id")

ドキュメントの作成

// モデルを初期化して、ドキュメントを作成します。
const post = new Post({
  title: "Hello world",
  content: "This is an example"
})
const author = new Author({
  name: "Michel"
})

// ドキュメントをジョインさせます。
post.author = author

ドキュメントの保存

// ドキュメントの保存(ジョイン含む): SaveAll([modelToSave], [callback])
post.saveAll({author: true}).then((result) => {
  //
})
// 外部キーが自動的にセットされます。

データの取得

// PostをそのAuthorとともに取得します。
Post.get('d2b2ee18-c8d2-45a0-9e89-35a7e026aea5') // Idは適当
  .getJoin({author: true}).then((post) => {
      // 取得結果↓↓
      // model {
      //  author: model { id: '97f5faed-4c7a-449c-8570-4aca9a273bf9', name: 'Michel' },
      //  content: 'This is an example',
      //  id: 'd2b2ee18-c8d2-45a0-9e89-35a7e026aea5',
      //  idAuthor: '97f5faed-4c7a-449c-8570-4aca9a273bf9',
      //  title: 'Hello world'
      // }
  
    // Authorの名前を変更します。
    post.author.name = 'John'
    post.saveAll({author: true})
})

ファーストインプレッション

いいですね。 スキーマの型定義や、シンプルなデータのリトリーブが嬉しいです。

次回は、リレーション周りをもう少し詳しく見てみます。

おまけ

Thinkyのキャラクターがゆるくて可愛いです。

Screen Shot 2016-09-19 at 3.02.47 PM.png

RethinkDBのキャラもゆるいですよね。和みます。

Thinkyを使ったRethinkDB REST API

メモ。 参考にした動画:A Simple REST API with Express, RethinkDB, and Thinky

下準備

セットアップ

thinkyをrequireする時に、セットアップ情報をオブジェクトで渡して実行する。

const thinky = require('thinky')({
  host: 'localhost',
  port: 28015,
  db: 'People'
})

RethinkDBドライバへの参照

thinkyにはrethinkDBへの参照が紐付いているので、変数格納しておく。

const r = thinky.r

モデルの作成

thinky.createModel(‘TableName’, {schema})

const People = thinky.createModel('People', {
  firstName: String,
  lastName: String,
  coolnessFactor: Number,
  date: { _type: Date, default: r.now() }
})

thinky/#createmodel

インデックス作成

dateカラムがインデックスに適しているかどうかはさておき…

People.ensureIndex('date')

model/#ensureindex

REST API

get

const get = function (req, res) {
  People.get(req.params.id).run().then((person) => {
    res.json(person)
  }).error((err) => {
    res.json({ message: err })
  })
}

getAll

const getAll = function (req, res) {
  People.orderBy({ index: r.desc('date') }).run().then((people) => {
    res.json(people)
  }).error((err) => {
    res.json({ message: err })
  })
}

add

const add = function (req, res) {
  var person = new People(req.body)
  person.save().then((result) => {
    res.json(result)
  }).error((err) => {
    res.json({ message: err })
  })
}

delete

const delete = function (req, res) {
  People.get(req.params.id).run().then((person) => {
    person.delete().then((result) => {
      res.json(result)
    }).error((err) => {
      res.json({ message: err })
    })
  }).error((err) => {
    res.json({ message: err })
  })
}

update

const update = function (req, res) {
  People.get(req.params.id).run().then((person) => {
    if(req.body.firstName) {
      person.firstName = req.body.firstName
    }
  
    if(req.body.lastName) {
      person.lastName = req.body.lastName
    }
    
    if(req.body.coolnessFactor) {
      person.coolnessFactor = parseInt(req.body.coolnessFactor)
    }
    person.data = r.now()
    
    person.save().then((result) => {
      res.json(result)
    }).error((err) => {
      res.json({ message: err})
    })
  })
}

あとは、expressなりなんなりでrouteしてあげれば動きます。

RethinkDBでGeoJSON操作

RethinkDBでGeoJSONをゴニョゴニョする方法について。 サンプルコードはJavaScriptで書いてありますが、Ruby, Python, JavaでもOKです。

GeoJSONとは

JSONをベースとした、位置情報データフォーマットです。

ReQL - geojson

RethinkDBのクエリであるReQLには、geojsonというクエリがあります。

ReQL - geojson

geojsonを使うと、GeoJSONをRethinkDBのGeometry型データに変換することができます。 Geometry型については、以下に公式サイトのリンクを貼ります。

ReQL data types - Geometry data types

サポート対象

GeoJSONはいくつかの型を持ちますが、RethinkDBがサポートするのは Point, LineString, and Polygon のみです。 MultiPoint, MultiLineString, and MultiPolygonはサポートされていませんが、array型を用いて複数のPoint, LineString, Polygonを格納することはできるようです。

また、座標に関して、緯度経度の座標はサポートされていますが、デカルト座標や高度座標を持つGeoJSONをRethinkDBは受け付けません。

コードサンプル

var geoJson = {
    'type': 'Point',
    'coordinates': [ -122.423246, 37.779388 ]
};
r.table('geo').insert({
    id: 'sfo',
    name: 'San Francisco',
    location: r.geojson(geoJson)
}).run(conn, callback);

ReQL - toGeojson

一方、Geometry型をGeoJSONに変換するには、toGeojsonクエリを使います。

ReQL - toGeojson

コードサンプル

r.table('geo').get('sfo')('location').toGeojson.run(conn, callback);
// result passed to callback
{
    'type': 'Point',
    'coordinates': [ -122.423246, 37.779388 ]
}

RethinkDBでgeojsonをupdateする際の注意点

RethinkDB(ReQL)でのgeojsonメソッドについて。

insertならば以下のように問題なく動く。

r.table("hoge").insert({
  "location": r.geojson({
    type: "Point",
    coordinates: [-71.063611, 42.358056]
  })
})

updateで同じことをすると、エラーが出る。

r.table("hoge").update({
  "location": r.geojson({
    type: "Point",
    coordinates: [-71.063611, 42.358056]
  })
})

// Could not prove argument deterministic.  Maybe you want to use the non_atomic flag? in

updateの第二引数として、{non_atomic: true}を指定すればOK。

r.table("hoge").update({
  "location": r.geojson({
    type: "Point",
    coordinates: [-71.063611, 42.358056]
  }),
  {nonAtomic: true}
})

update - ReQL API

参考:https://github.com/rethinkdb/rethinkdb/issues/4778

Rethinkdbでデータをprepend, appendする方法

メモ。データをprepend, appendする方法。

r.table('shop').get("hoge_hoge_id").update({
 drinks : r.row('drinks').append('White wine')
})

append, prepend自体はDBのデータを変更しないことに注意。

参考: stackoverflow: How to append an element to an array in rethinkdb

リファレンス

MacにHadoopをインストールして、起動・実行する

環境

インストー

Homebrewから入れます。

brew install hadoop

バージョン

Apache Hadoop Releases

公式によると、2017/05/22時点では2.8.0が最新版らしいです。

インストール先

入りました。

/usr/local/Cellar/hadoop/2.8.0

f:id:chuck0523:20170522232404p:plain

名前衝突

ちなみに、nodeパッケージマネージャーのyarnをインストール済みの場合、名前衝突が起きます。 その結果、hadoop自体のインストールは終わりますが、brew linkが失敗している状態になります。 

設定

hadoopmac上で使い始める前にいくつかの設定を行います。

hadoop-env.shの編集

/usr/local/Cellar/hadoop/2.8.0/libexec/etc/hadoop/hadoop-env.shにて、以下を、

export HADOOP_OPTS="$HADOOP_OPTS -Djava.net.preferIPv4Stack=true"

以下のように変更。

export HADOOP_OPTS="$HADOOP_OPTS -Djava.net.preferIPv4Stack=true -Djava.security.krb5.realm= -Djava.security.krb5.kdc="

Core-site.xmlの編集

`/usr/local/Cellar/hadoop/2.8.0/libexec/etc/hadoop/core-site.xml<conficuration />を以下のように変更。

<configuration>  
<property>
     <name>hadoop.tmp.dir</name>
     <value>/usr/local/Cellar/hadoop/hdfs/tmp</value>
     <description>A base for other temporary directories.</description>
  </property>
  <property>
     <name>fs.default.name</name>                                     
     <value>hdfs://localhost:9000</value>                             
  </property>
</configuration>  

mapred-site.xmlの編集

/usr/local/Cellar/hadoop/2.8.0/libexec/etc/hadoop/mapred-site.xml<conficuration />を以下のように変更。

<configuration>
  <property>
    <name>mapred.job.tracker</name>
    <value>localhost:9010</value>
  </property>
</configuration>

ちなみに、自分の場合はファイル自体が無く、mapred-site.xml.templateというファイルがあったのでそれを複製しました。

hdfs-site.xmlの編集

/usr/local/Cellar/hadoop/2.8.0/libexec/etc/hadoop/hdfs-site.xml<conficuration />を以下のように変更。

<configuration>
  <property>
    <name>dfs.replication</name>
    <value>1</value>
  </property>
</configuration>

xx_profileの編集

~/.bash_profile~/.zshrc等にaliasを追記します。

alias hstart="/usr/local/Cellar/hadoop/2.6.0/sbin/start-dfs.sh;/usr/local/Cellar/hadoop/2.6.0/sbin/start-yarn.sh"
alias hstop="/usr/local/Cellar/hadoop/2.6.0/sbin/stop-yarn.sh;/usr/local/Cellar/hadoop/2.6.0/sbin/stop-dfs.sh"

ターミナル上で、aliasを再読込。

source ~/.profile

SSH Localhost

localhostsshできるようにします。

~/.ssh/id_rsa~/.ssh/id_rsa.pubがあるかどうかチェック。無ければ以下のコマンド。

ssh-keygen -t rsa

リモートログイン

Macの環境設定(system preference)をいじります。 環境設定 > 共有(Sharing) > リモートログイン(Remote Login)を有効にします。

SSH鍵認証

システムがログインを受け入れるようにします。

cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
ssh localhost

ログインが成功します。exitでログアウトです。

Hadoop起動

起動。

hstart

停止。

hstop

ブラウザで確認

無事にhadoopが起動していれば、以下のポートでそれぞれ様子を確認できます。

f:id:chuck0523:20170523005952p:plain

参考にした記事

ここまでの手順で参考にした記事です。

単語数カウント

さて、Hadoopの世界でのHello Worldとも言われる、単語数カウントをやってみます。

ディレクトリ・ファイル作成

まず、材料となるファイルをローカルに作っておきます。ちなみに、今は、/usr/local/Celler/hadoop/2.8.0にいる状態です。

mkdir input
touch input/a.txt input/b.txt

ファイルに書き込みをします。

echo "a a b b b c b c" > input/a.txt
echo "ab a b b c c c" > input/b.txt

次にHadoopを起動して、Hadoopファイルシステム上にディレクトリとファイルを作成します。

hstart

// 起動後…

hadoop fs -mkdir /user/hoge/input
hadoop fs -put input/a.txt input/
hadoop fs -put input/b.txt input/

putはローカルにあるファイルをhadoopファイルシステム上にコピーします。

その他のファイルシステム系のコマンドはhelpか以下のサイトからどうぞ。

HDFSシェルコマンド一覧

ちなみに、ディレクトリ構造についてはブラウザからも確認できます。

http://localhost:50070/explorer.html#/

f:id:chuck0523:20170523012638p:plain

実行

コマンドを実行します。

hadoop jar libexec/share/hadoop/mapreduce/hadoop-mapreduce-examples-2.8.0.jar wordcount input output

上記のコマンドを実行すると、inputディレクトリにおいたファイルを読んで、outputディレクトリ以下に結果を吐き出します。(outputディレクトリは自動生成されます。)

生成されたoutput/part-r-00000の中身は以下の通り。

a    3
ab  1
b   6
c   5

きちんと単語の数がカウントされていますね。

というわけで、MacHadoopをインストールして、起動・実行してみました。