2012/09/22


ちょっと前に PostgreSQL に関する記事を見た。最近 PostgreSQL がアツイらしい。
PostgreSQLに興味がある人向けにまとめてみた。|PostgreSQL|お仕事メモ|Pictnotes
  • Q. FDWってなによ?
  • A. FDW(Foreign Data Wrappe),外部データラッパっていうやつで、SQL/MED(Management of External Data)の規格の一つで簡単にいうと、PostgreSQLにQUERYを発行したら、あら不思議、外部の(たとえばMySQLとかCSV)データが取得できるという変態機能。
    twitterAPIに変更あるらしいから今後はわからにけど、twitterのデータを取ってくるとかもできる。というかラッパーが用意されてる。(ちなみに使ってみたら楽しかったw)
http://www.pictnotes.jp/memo/archives/6
それはワクテカ過ぎる...
だがしかし SQLite3 でも出来る!きっと出来る!
#include <string>
#include <sstream>
#include <sqlite3.h>
#include <sqlite3ext.h>
#include <curl/curl.h>
#include "picojson.h"

#ifdef _WIN32
# define EXPORT __declspec(dllexport)
#else
# define EXPORT
#endif

SQLITE_EXTENSION_INIT1;

typedef struct {
  char* data;   // response data from server
  size_t size;  // response size of data
} MEMFILE;

MEMFILE*
memfopen() {
  MEMFILE* mf = (MEMFILE*) malloc(sizeof(MEMFILE));
  if (mf) {
    mf->data = NULL;
    mf->size = 0;
  }
  return mf;
}

void
memfclose(MEMFILE* mf) {
  if (mf->data) free(mf->data);
  free(mf);
}

size_t
memfwrite(char* ptr, size_t size, size_t nmemb, void* stream) {
  MEMFILE* mf = (MEMFILE*) stream;
  int block = size * nmemb;
  if (!mf) return block; // through
  if (!mf->data)
    mf->data = (char*) malloc(block);
  else
    mf->data = (char*) realloc(mf->data, mf->size + block);
  if (mf->data) {
    memcpy(mf->data + mf->size, ptr, block);
    mf->size += block;
  }
  return block;
}

char*
memfstrdup(MEMFILE* mf) {
  char* buf;
  if (mf->size == 0return NULL;
  buf = (char*) malloc(mf->size + 1);
  memcpy(buf, mf->data, mf->size);
  buf[mf->size] = 0;
  return buf;
}

static int
my_connect(sqlite3 *db, void *pAux, int argc, const char * const *argv, sqlite3_vtab **ppVTab, char **c) {
  std::stringstream ss;
  ss << "CREATE TABLE " << argv[0]
    << "(id text, screen_name text, tweet text)";
  int rc = sqlite3_declare_vtab(db, ss.str().c_str());
  *ppVTab = (sqlite3_vtab *) sqlite3_malloc(sizeof(sqlite3_vtab));
  memset(*ppVTab, 0sizeof(sqlite3_vtab));
  return rc;
}

static int
my_create(sqlite3 *db, void *pAux, int argc, const char * const * argv, sqlite3_vtab **ppVTab, char **c) {
  return my_connect(db, pAux, argc, argv, ppVTab, c);
}

static int my_disconnect(sqlite3_vtab *pVTab) {
  sqlite3_free(pVTab);
  return SQLITE_OK;
}

static int
my_destroy(sqlite3_vtab *pVTab) {
  sqlite3_free(pVTab);
  return SQLITE_OK;
}

typedef struct {
  sqlite3_vtab_cursor base;
  int index;
  picojson::value* rows;
} cursor;

static int
my_open(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor) {
  MEMFILE* mf;
  CURL* curl;
  char* json;

  mf = memfopen();
  curl = curl_easy_init();
  curl_easy_setopt(curl, CURLOPT_URL, "http://api.twitter.com/1/statuses/public_timeline.json");
  curl_easy_setopt(curl, CURLOPT_WRITEDATA, mf);
  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, memfwrite);
  curl_easy_perform(curl);
  curl_easy_cleanup(curl);

  picojson::value* v = new picojson::value;
  std::string err;
  picojson::parse(*v, mf->data, mf->data + mf->size, &err);
  memfclose(mf);

  if (!err.empty()) {
    delete v;
    std::cerr << err << std::endl;
    return SQLITE_FAIL;
  }

  cursor *c = (cursor *)sqlite3_malloc(sizeof(cursor));
  c->rows = v;
  c->index = 0;
  *ppCursor = &c->base;
  return SQLITE_OK;
}

static int
my_close(cursor *c) {
  delete c->rows;
  sqlite3_free(c);
  return SQLITE_OK;
}

static int
my_filter(cursor *c, int idxNum, const char *idxStr, int argc, sqlite3_value **argv) {
  c->index = 0;
  return SQLITE_OK;
}

static int
my_next(cursor *c) {
  c->index++;
  return SQLITE_OK;
}

static int
my_eof(cursor *c) {
  return c->index >= c->rows->get<picojson::array>().size() ? 1 : 0;
}

static int
my_column(cursor *c, sqlite3_context *ctxt, int i) {
  picojson::value v = c->rows->get<picojson::array>()[c->index];
  picojson::object row = v.get<picojson::object>();
  const char* p = NULL;
  switch (i) {
  case 0:
    p = row["id"].to_str().c_str();
    break;
  case 1:
    p = row["user"].get<picojson::object>()["screen_name"].to_str().c_str();
    break;
  case 2:
    p = row["text"].to_str().c_str();
    break;
  }
  sqlite3_result_text(ctxt, strdup(p), strlen(p), free);
  return SQLITE_OK;
}

static int
my_rowid(cursor *c, sqlite3_int64 *pRowid) {
  *pRowid = c->index;
  return SQLITE_OK;
}

static int
my_bestindex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo) {
  return SQLITE_OK;
}

static const sqlite3_module module = {
  0,
  my_create,
  my_connect,
  my_bestindex,
  my_disconnect,
  my_destroy,
  my_open,
  (int (*)(sqlite3_vtab_cursor *)) my_close,
  (int (*)(sqlite3_vtab_cursor *, intchar const *, int, sqlite3_value **)) my_filter,
  (int (*)(sqlite3_vtab_cursor *)) my_next,
  (int (*)(sqlite3_vtab_cursor *)) my_eof,
  (int (*)(sqlite3_vtab_cursor *, sqlite3_context *, int)) my_column,
  (int (*)(sqlite3_vtab_cursor *, sqlite3_int64 *)) my_rowid,
  NULL// my_update
  NULL// my_begin
  NULL// my_sync
  NULL// my_commit
  NULL// my_rollback
  NULL// my_findfunction
  NULL// my_rename
};

static void
destructor(void *arg) {
  return;
}


extern "C" {

EXPORT int
sqlite3_extension_init(sqlite3 *db, char **errmsg, const sqlite3_api_routines *api) {
  SQLITE_EXTENSION_INIT2(api);
  sqlite3_create_module_v2(db, "twitter_public_timeline", &module, NULL, destructor);
  return 0;
}

}
picojson を使うので適当に持ってきて下さい。
このソースを sqlite3_twit.cxx として $ g++ -I. -g -o sqlite3_twit.dll -shared sqlite3_twit.cxx -lcurldll
の様にコンパイルする。unix な人は... 調べて下さい。
さて... $ sqlite3
SQLite version 3.7.14 2012-09-03 15:42:36
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
SQLiteのシェルに入ったら sqlite> select load_extension("sqlite3_twit.dll");
load_extension("sqlite3_twit.dll")
----------------------------------

拡張をロードして sqlite> create virtual table tbl using twitter_public_timeline(id, screen_name, text);
仮想テーブルを作る。あとは...
sqlite> select * from tbl;
                                  
id                  screen_name   tweet                                                                                                   
------------------  ------------  ---------------------------------------------------------------------------------------------           
248056468924928000  sosomustafa2  الحياة بسيطه جدا لدرجة أن الابتسامه قد تجعلك محبوب جدا ♡"
248056468727808000  nurulauliad   Sekalinya dateng, dateng semua. Sekalinya pergi, pergi semua. Itu lah sebab kenapa hidup penuh pilihan. 
248056468421632000  ZaldivarSkat  ¡HOY HOY HOY! Extreno de O.N.I.F.C de @RealWizKhalifa                                                  
248056468291584000  chimpungdila  Ciye! "@putuderism: Hey 1530 QL, i miss you so badly :'))"                                              
248056467767296000  44Velboo      268とかプロボウラー並の人がいる(^_^;)                                                     
248056467154944000  kevintumongg  Karepmu her RT @KurniawanHerdi: Mau banget ga mong? RT @kevintumonggi: @KurniawanHerdi folbeck her      
248056467045888000  rnbanyu       Detik detik                                                                                             
248056466739712000  iiiFoN        [HD] The Voice Thailand : Week2 (Full Version) 16 Sep 2012: http://t.co/mRIaKDuy via @youtube           
248056465582080000  CarbonQ8e     ياكـــم نفـــسٍ عـــن هـــوا الحـــب طابـــت                     
248056465166848000  WhoIsVARRI_   I'm just Boolin how i B Boolin !                                                                        
248056463069696000  iSwiftCyrus   Photo:  http://t.co/qAcDMm1c                                                                            
248056463048704000  IGORNOKALT    Baixei o ringtone do gummy bear :9                                                                      
248056462130176000  meowkikocel   #MentionSomeoneVerySpecial @Mistacey :)                                                                 
248056462109184000  xFatehx       just got back from NVBA , phhewww what a great match :D                                                 
248056461912064000  snags17       「倭」を日本の蔑称として使いたがるのは、「現代では」、中国人より韓国<e4><ba>
248056461803008000  hendraMirvan  RT @gadasianipar: RT @febrinacitra: Ket biyen nonton sketsa kok rak tau ngguyu blas to.... (‾▿‾") 
248056460972544000  _Azrie_       begitu lah gaya nya , online , tapi hanya memerhatikan TL.                                              
248056460557312000  imbaked_      This one of those morning were I smoke a blunt nd watch Kevin Hart                                      
248056459814912000  psanso        +  RT“@damiaborras: El rei diu que hem de remar tots… Deu tenir el Fortuna espatllat.”
sqlite> 
キターーー!
もちろん sqlite> select * from tbl where tweet like '%twitter%';

id                  screen_name   tweet
------------------  ------------  -----------
248059202780672000  Baby_Nandos_  New Twitter
where区だって使えるんだぜ!public timeline速過ぎるから毎回違うテーブル内容なんだぜ!

って事で、データベースは MySQL でも PostgreSQL でもなく、SQLite に決まり!
Posted at by




Windows を UNIX っぽく使おうとすると、必ずお目にかかるであろうこのメッセージ。
UNIX に慣れている人は、コマンドプロンプトを使いたがる。しかし全ての場合においてコンソールアプリケーションは有能では無いし、異常に長い引数は省略したい。Java VM で動く言語のクラスパス等はスクリプトに書きたいし、オフィシャルから Windows ユーザ向けに用意される物はだいたいバッチファイルだ。しかしながら Cygwin は標準提供物ではないし、嫌いだ。いや、大嫌いだ。
そこで私達は一般的に、バッチファイルという一見便利そげで実は非情なまでに我々に独特の仕様を強要するDSLを頻繁に使う事になる。
例えば Java VM 上で動く clojure を Windows 上で repl として使う場合、僕は以下の様なバッチファイルを「clj.bat」というファイル名にして使っている。
@echo off
set CLOJURE_EXT=%USERPROFILE%\.clojure.d
setlocal enabledelayedexpansion
if defined CLOJURE_EXT for %%E in ("%CLOJURE_EXT%\*") do set CP=!CP!;%%~fE
if not defined CLOJURE_JAVA set CLOJURE_JAVA=java
if exist .clojure for /F %%E in (".clojure\*") do set CP=!CP!;%%~fE
%CLOJURE_JAVA% %CLOJURE_OPTS% -cp "%CP%" clojure.main -i "%CLOJURE_EXT%\cljrc.clj" %1 %2 %3 %4 %5 %6 %7 %8 %9 --repl
これは別に大した物ではないし、僕にとって心地よく動作している。
しかしこの repl を終了しようと、UNIX ユーザでは当たり前の呪文、そう「CTRL-C」をタイプすると事態が急転する。
C:\temp>clj.bat
Clojure 1.4.0
user=>
バッチ ジョブを終了しますか (Y/N)?
お前は俺が終了しろと言ったのが聞こえないのか?

java は既に終了してしまってるのにこれ以上バッチファイルの実行を継続する理由など無い! start を絡ませればメッセージを出さなくする事も出来るが、その start を含んだコマンドをバッチファイルに書いてしまったら堂々巡りだ!我々はコマンドプロンプトからコマンド名で実行したんだ!
全世界の Windows ユーザがこの問題に悩んでいる。
How can I suppress the "terminate batch job" in cmd.exe - Stack Overflow

I'm looking for a mechanism for suppressing the "Terminate batch job? (Y/N)" invitation that I get whenever I press CTRL-C in a program started from a batch file...

http://stackoverflow.com/questions/1234571/how-can-i-suppress-the-terminate-batch-job-in-cmd-exe
中には cmd.exe にバイナリパッチを当てる人までいる。
several useful patches for cmd.exe

how to find what to patch using a recent version of IDA, it is quite easy to find where to patch cmd...

http://itsme.home.xs4all.nl/projects/misc/patching-cmdexe.html
本来、CTRL-C をタイプしてプロセスを終了するという自由な行動が、非人道的な cmd.exe の仕様により不自由な操作を強要されている。(© EzoeRyou)

これを解決しようと色々考えたが、以下の方法が良いのではないかと思う。
コマンドプロンプトをシェルの様に使う人はフルパスをタイプしないし、拡張子までタイプしない。例えば既述の「clj.bat」であれば「clj」とだけタイプする。拡張子を明示しない場合、Windows では PATHEXT 環境変数を ; で区切った順番に実行ファイルが探索される。通常、PATHEXT では「.bat」よりも「.exe」の方が先に来る。つまり「clj.bat」と同じ位置に「clj.exe」があれば、「clj.exe」が先に見つけられ実行される事になるのだ。
であれば、その exe からバッチファイルを起動し、プロセスグループに加えてやれば CTRL-C が伝播するはず。なお且つ対話形式で起動しない様にすれば「バッチ ジョブを終了しますか (Y/N)?」というふざけたメッセージをお目にかかる事はない。

よろしい、ならばコーディングだ。一番いい Vim を頼む。
#include <windows.h>
#include <string.h>
#include <stdio.h>

int
emsg() {
  char* p = NULL;
  DWORD err = GetLastError();
  if (err == 0return 0;
  FormatMessage(
    FORMAT_MESSAGE_ALLOCATE_BUFFER |
    FORMAT_MESSAGE_FROM_SYSTEM |
    FORMAT_MESSAGE_IGNORE_INSERTS,
    NULL, err, 0, (LPTSTR)(&p), 0NULL);
  fputs(p, stderr);
  LocalFree((LPVOID)p);
  return err;
}

int
main(int argc, char* argv[]) {
  STARTUPINFO si = {0};
  PROCESS_INFORMATION pi = {0};
  int r;
  char *p, *t;
  t = p = strdup(GetCommandLine());
  if (!p) return -1;
  if (*p == '"')
    do p++; while (*p && *p != '"');
  else
    while (*p && *p != ' ') p++;
  if (!strncasecmp(p-4".exe"4))
    memcpy(p-4".bat"4);
  else {
    char *comspec = getenv("COMSPEC"), *b;
    if (!comspec) comspec = "CMD";
    b = malloc(strlen(comspec) + 1 + 9 + strlen(t) + 5);
    *b = 0;
    strcat(b, comspec);
    strcat(b, " /c call ");
    strncat(b, t, (int) (p-t));
    strcat(b, ".bat");
    strcat(b, p);
    free(t);
    t = b;
  }
  si.cb = sizeof(si);
  if (!CreateProcess(
      NULL, t, NULLNULL, TRUE,
      CREATE_DEFAULT_ERROR_MODE | CREATE_NEW_PROCESS_GROUP,
      NULLNULL, &si, &pi))
    r = emsg();
  else {
    CloseHandle(pi.hThread);
    if (WaitForSingleObject(pi.hProcess, INFINITE) == WAIT_FAILED)
      r = emsg();
    else {
      DWORD code = 0;
      if (!GetExitCodeProcess(pi.hProcess, &code))
        r = emsg();
      else {
        r = (int) code;
        CloseHandle(pi.hProcess);
      }
    }
  }
  free(t);
  return r;
}
ソースファイルは「batter.c」として保存し、コンパイルする。
コマンドラインの第一引数に「.exe」が付いていたら「.bat」に書き換え、付いていなかったら付け足し、CREATE_NEW_PROCESS_GROUP を指定してプロセスを起動する。
これをコンパイルして出来上がった batter.exe を、例えば「clj.bat」が置いてあるフォルダに「clj.exe」というファイル名てコピーする。

では新しいコマンドプロンプトを起動し、「clj」とタイプしよう... C:\temp>clj
Clojure 1.4.0
user=>
そして CTRL-C だ。
^C
C:\temp>
やった。俺は「バッチ ジョブを終了しますか (Y/N)?」メッセージを倒した。
今後「clj.bat」を好き放題に編集しても構わない。

俺は勝った。
Posted at by



2012/09/14


そろそろWeb周りだけでなくDB周りも揃ってきて、本格的なアプリケーションが書ける様になってきました。
DBを扱うには、SQLを実行する様な物は標準でサポートしていましたが、gorp を使うと極力SQLを書かずにデータベースを扱う事が出来ます。
coopernurse/gorp - GitHub

Go Relational Persistence I hesitate to call gorp an ORM.

https://github.com/coopernurse/gorp
まずエンティティとなる構造体を宣言します。ここでは Person という構造にします。 type Person struct {
    Id int32
    Name string
}
次に、データベースマッパーを作ります。 db, err := sql.Open("sqlite3""./foo.db")
dbmap := &gorp.DbMap{Db: db, Dialect: gorp.SqliteDialect{}}
ドライバはSQLite3を使いましたが、MySQLとPostgreSQLもサポートしている様です。 なお、gorp では使えませんが、Go言語で使えるデータベースは以下のWikiにエントリされています。
SQLDrivers - go-wiki - SQL database drivers - Go Language Community Wiki - Google Project Hosting

SQL database drivers The database/sql and database/sql/driver packages are designed for using databa...

http://code.google.com/p/go-wiki/wiki/SQLDrivers
手前味噌ですが、SQLite3 と Oracle(OCI8)、Microsoft ADODB のドライバは僕が作った奴です。バグあったら教えて下さい。

さて、データベースマッパーが出来たらテーブルと構造体の紐付けを行います。 t := dbmap.AddTableWithName(Person{}, "person").SetKeys(true"Id")
t.ColMap("Id").Rename("id")
t.ColMap("Name").Rename("name")
person というテーブル名で Person 構造体を登録し、Id がキーである事を教えています。その後、カラム名のリネームも行っています。テーブルのフィールド名先頭が大文字とか気持ち悪いとか、UserName というフィールドが "USER_NAME" というカラムに割り当てたい場合に使います。
この後、テーブルを作ります。 dbmap.DropTables()
err = dbmap.CreateTables()
今回は1つしかテーブルを扱っていませんが、一度に複数登録して一括で作成する事が多いと思います。
ではデータを登録しましょう。dbmap.Insert でも登録出来ますが、ここではトランザクションを使いましょう。
tx, _ := dbmap.Begin()
for i := 0; i < 100; i++ {
    tx.Insert(&Person{0, fmt.Sprintf("mattn%03d", i)})
}
tx.Commit()
今度はデータを取得してみましょう。dbmap.Get でキーを指定して1行取得する事も出来ますし、Select を使う事も出来ます。
list, _ := dbmap.Select(Person{}, "select * from person")
for _, l := range list {
    p := l.(*Person)
    fmt.Printf("%d, %s\n", p.Id, p.Name)
}
なんだか便利になってきましたね。全体のコードは以下の通り。
package main

import (
    "database/sql"
    "github.com/coopernurse/gorp"
    _ "github.com/mattn/go-sqlite3"
    "fmt"
)

type Person struct {
    Id int32
    Name string
}

func main() {
    db, err := sql.Open("sqlite3""./foo.db")
    if err != nil {
        panic(err.Error())
    }
    dbmap := &gorp.DbMap{Db: db, Dialect: gorp.SqliteDialect{}}
    t := dbmap.AddTableWithName(Person{}, "person").SetKeys(true"Id")
    t.ColMap("Id").Rename("id")
    t.ColMap("Name").Rename("name")
    dbmap.DropTables()
    err = dbmap.CreateTables()
    if err != nil {
        panic(err.Error())
    }

    tx, _ := dbmap.Begin()
    for i := 0; i < 100; i++ {
        tx.Insert(&Person{0, fmt.Sprintf("mattn%03d", i)})
    }
    tx.Commit()

    list, _ := dbmap.Select(Person{}, "select * from person")
    for _, l := range list {
        p := l.(*Person)
        fmt.Printf("%d, %s\n", p.Id, p.Name)
    }
}
Go言語もだんだん仕事で使えそうな言語になってきましたね。

Posted at by