Mainly Devel Notes

Twitter, GitHub, StackOverflow: @ovrmrw (short hand of "overmorrow" that means the day after tomorrow)

minimongoでバルクインサート用のjsonファイルをインポートしてLIKE検索までやってみた。

minimongo, Node.js

今回の記事のサンプルコードはGitHubにアップロードしてあります。→ovrmrw/minimongo_sample1

前置き

Webアプリ開発してるとDBを扱いたくなりますよね。きっとそうに違いない。
でもいちいちDBをインストールするのもめんどうですよね。きっとみんなそう思ってる。
で、

minimongoでIsomorphic Storage

を読んで、何もインストールしなくてもサンプルコードでDBを動かせるならちょっといいかもと思ってminomongoを試してみました。

mWater/minimongo

README通りに試してみると、おお、確かに動く!DBっぽいものが!

こうなるとテストデータを入手してそこそこのデータ量で試してみたくなりますね。そこで↓なんかいいんじゃないかと。

jsteemann/BulkInsertBenchmark

今回は上記リポジトリにあるnames_part_1000.json.gzを使ってみます。
ちなみにWindows環境では7zipで解凍できました。

ところがこのファイル、正規のJSONフォーマットではありません。

{"a":"xxx","b":"yyy","z":"zzz"}
{"a":"xxx","b":"yyy","z":"zzz"}
...

みたいな感じで、配列にもなっていないし、オブジェクトの区切りがカンマじゃなくて改行なんですね。
前にもバルクインサート用のデータで同じような形式を見たことがあるのですが、もしかしてこれバルクインサートの標準形?

とりあえずこのままではminimongoでインサートできないので、こういう正規のフォーマットに変換してあげないといけません。

[
{"a":"xxx","b":"yyy","z":"zzz"},
{"a":"xxx","b":"yyy","z":"zzz"}
]

そのサンプルコードがこちら↓

var splitter = '\n';
var lines = fs.readFileSync('./names_part_1000.json', 'utf-8').toString().split(splitter);
lines = _.filter(lines, function (line) {
  return line.indexOf('{') > -1 && line.indexOf('}') > -1
});
var json = "[" + splitter + lines.join(',' + splitter) + splitter + "]";

ファイルを読み込んで、改行コードでsplitして、空白行があれば除去して、配列の形に組み直す。
やってみたら意外と簡単でした。

実際にやってみよう

必要なパッケージをnpm installします

npm install minimongo fs lodash --save

バルクインサートするファイルを用意します

jsteemann/BulkInsertBenchmark
のdatasetsフォルダにnames_part_1000.json.gzがあるので、これを7zip等で解凍してnames_part_1000.jsonを取り出します。

JSファイルを書きます。

// bulkinsert.js

var splitter = '\n';

var minimongo = require("minimongo");
var fs = require('fs');
var _ = require('lodash');

var LocalDb = minimongo.MemoryDb;

var db = new LocalDb();

db.addCollection("names");

var lines = fs.readFileSync('./names_part_1000.json', 'utf-8').toString().split(splitter);
lines = _.filter(lines, function (line) {
  return line.indexOf('{') > -1 && line.indexOf('}') > -1
});

var json = "[" + splitter + lines.join(',' + splitter) + splitter + "]";

var obj = JSON.parse(json);

db.names.upsert(obj, function () {
  db.names.find({ 'name.first': 'Johnson' }, {})
    .fetch(function (res) {
      _.forEach(res, function (person) {
        console.log(person.name);
      });
    });
});

// ついでに変換後のjsonを保存。
fs.writeFileSync('./names_valid.json', JSON.stringify(obj, null, 2));

実行します

node bulkinsert.js

結果はこうなりました↓

{ first: 'Johnson', last: 'Diventura' }
{ first: 'Johnson', last: 'Wallick' }

ちなみにdb.names.find()は検索条件{ 'name.first': 'Johnson' }に一致するオブジェクトを配列で取得する関数ですが、 一件だけ取得するならdb.names.findOne()という関数があります。
find().fetch()fetchthenみたいなものだと思います。(多分)

結論としては…かなりイイね!

追記: LIKE検索

SQLでいうところのLIKE検索をしたいときは、{ 'name.first': 'Johnson' }のところを{ 'name.first': /john/i }のようにします。正規表現ですかね。

サンプルコードを一部書き換えてみます。

db.names.upsert(obj, function () {
  //db.names.find({ 'name.first': 'Johnson' }, {})
  db.names.find({ 'name.first': new RegExp('john', 'i') }, {})
    .fetch(function (res) {
      _.forEach(res, function (person) {
        console.log(person.name);
      });
    });
});

RegExp()なら文字列から正規表現を作れます。(さっき知った)

結果はこうなりました↓

{ first: 'Johnny', last: 'Kaster' }
{ first: 'Johnie', last: 'Bugler' }
{ first: 'Johnnie', last: 'Domine' }
{ first: 'Johnson', last: 'Diventura' }
{ first: 'Johnson', last: 'Wallick' }

/john/iは、文字列john(大文字小文字を問わない)が含まれるものを検索するという意味になります。
個人的にはLIKE検索ができないDBはDBじゃないのでこれでしばらくいじり倒したいと思います。


以上です、ありがとうございました。

Angular2がalpha.50になってインストールとかRxJSとか色々めんどくさい(beta.0になりました)

Angular2, npm3, RxJS, TypeScript, System.js

【beta.0対応版】

Angular2はalpha.47まではそこまでBreaking Changesが無かったような気がするのですが、ここ最近のアップデートはガンガンBreakingしています。

angular2-polyfills.jsの読み込みが必要になった

alpha.54からはangular2.dev.jsの前にポリフィルの読み込みが必要になりました。

<script src="../node_modules/angular2/bundles/angular2-polyfills.min.js"></script>
<script src="../node_modules/angular2/bundles/angular2.dev.js"></script>

これが足りないといくらブラウザをリロードしても真っ白な画面のままです。気を付けましょう。

npm3だとAngular2のインストールがめんどくさい

2.0.0-alpha.50からAngular2のインストール時に依存パッケージが自動インストールされなくなりました。(npm3だけらしい)
なのでこのように依存パッケージを先にインストールするようにnpm installを書きます。

npm i es6-promise@^3.0.2 es6-shim@^0.33.3  --save
npm i reflect-metadata@0.1.2 rxjs@5.0.0-beta.0 zone.js@0.5.10 --save --save-exact
npm i angular2 --save 

RxJSでイベントハンドラを書こうとするとめんどくさい

RxJS公式GitHubの説明通りにtsファイルの中でこのように書くと、

// hoge.ts

import Rx from 'rxjs/Rx'

Rx のところが赤い波線エラーになるかと思います。alpha.53まではちょこちょこいじってなんとかなっていたのですが…

いよいよalpha.54から、この↑書き方をするとAngular2では挙動がおかしくなりました。なのでこう書きます。

// hoge.ts

import {Observable} from 'rxjs/Observable'
import 'rxjs/add/observable/fromEvent'
import 'rxjs/add/observable/timer'
import 'rxjs/add/operator/map'
import 'rxjs/add/operator/filter'
import 'rxjs/add/operator/debounce'
import 'rxjs/add/operator/toPromise'

・・・こんな感じで使用するオペレーターを全て個別にimportします。
ぼくだってほんとうはこんなことしたくなかった。

オペレーターに関しては最初に読み込むJSファイル(例えばapp.jsとか)でまとめてimportしておけばルーティングで他のページに遷移しても有効みたいです。それだけが救いですね。

あとHTMLファイルで'Rx.js'を読み込んでおく必要があります。

<script src="../node_modules/rxjs/bundles/Rx.js"></script>

サンプルコード↓(beta.0で動作確認済み)

import {Component} from 'angular2/core'
import _ from 'lodash'
//import {Observable} from 'angular2/angular2' ← 実行時にオペレーターが見つからないエラー連発
//import {Observable} from 'angular2/core' ← 実行時にオペレーターが見つからないエラー連発
//import {Observable} from '@reactivex/rxjs' ← RxJSのバージョンアップによりフォルダ構成が変わった
import {Observable} from 'rxjs/Observable'
import 'rxjs/add/observable/fromEvent'
import 'rxjs/add/observable/timer' // 必要なら
import 'rxjs/add/operator/map'
import 'rxjs/add/operator/filter'
import 'rxjs/add/operator/debounce' // 必要なら
import 'rxjs/add/operator/toPromise' // 必要なら

@Component({
  selector: 'my-app',
  template: `
    <!-- 省略 -->
  `
})
export class Foo {
  initEventObservables(): void {
    Observable.fromEvent<MouseEvent>(document, 'click')
      .map(event => event.target.textContent)
      .filter(text => _.trim(text).length > 0)
      .subscribe(text => {
        alert('You clicked ' + text);
      });
  }
}