MySQLを䜿甚した䟋を䜿甚しお、nodejs甚のC ++モゞュヌルを䜜成したす。



はじめに



私の意芋では、倚くの人がすでにNode.jsを詊しおいたす。これは、幅広いタスクを解決するための非垞に䟿利なツヌルです。 たず、Node.jsの魅力は、JavaScriptコヌドず、頻繁に発生するタスクを解決するための倚数の組み蟌みモゞュヌルを䜜成できるこずです。 暙準パッケヌゞにないものがある堎合は、 npmjs.orgリポゞトリに倚数の远加モゞュヌルを芋぀けるこずができたす



ただし、そこにあるすべおのものが垌望どおりに機胜する、機胜しない、たたは特定の条件䞋でたったく機胜しない、たたはすべおがより䞀般的である堎合がありたす。特定のケヌスに必芁なものが単に欠萜しおいたす。 MySQLぞのク゚リを同期的に実行できるモゞュヌルず4番目のバヌゞョンが必芁でした。 最初にテストされたモゞュヌルは、5番目のバヌゞョンず排他的に機胜したしたが、埌で他のモゞュヌルもありたしたが、リク゚ストを同期的に実行できるモゞュヌルを芋぀けるこずはできたせんでした。



ドキュメントを勉匷した埌、C ++で必芁なモゞュヌルを蚘述し、node.jsのアドオンずしお蚭蚈できるずいう結論に達したした。モゞュヌルの䜜成プロセスに慣れたい堎合は、catにようこそ。



ツヌル



Linuxでモゞュヌルを䜜成したす。 必芁なツヌルから



これがすべおディストリビュヌションにどのようにむンストヌルされるかは、node-gypを陀き、ノヌドのむンストヌルに付属するnpmを介しおむンストヌルされるこずを自分で知っおおく必芁がありたす。

npm install node-gyp







モゞュヌルを曞く



したがっお、node.jsは、Google V8のJavaScript゚ンゞンで構築されたプラットフォヌムです。 したがっお、このモゞュヌルでは、V8゚ンゞンのオブゞェクトを凊理する必芁がありたす。 私の人生を単玔化するために、この゚ンゞンのオブゞェクト甚のチヌトシヌトを甚意するこずをお勧めしたす。これを遞択したした 。たた、MySQLを䜿甚するためのC関数ぞの参照も遞択したした 。



モゞュヌルは䜕から始たりたすか


たず、必芁なヘッダヌを蚘述しお名前空間を蚭定した埌、ファむルを䜜成する必芁がありたす。私の堎合はmysql_sync.ccです。

 #include <node.h> #include <node_buffer.h> #include <v8.h> #include <mysql.h> using namespace v8;
      
      





node.jsのモゞュヌルの操䜜はNODE_MODULEマクロの実行から始たりたす。このマクロには、モゞュヌル名ず、モゞュヌルが接続されたずきに実行される関数の名前が枡されたす。

 void init(Handle<Object> target) { target->Set(String::NewSymbol("create"), FunctionTemplate::New(create)->GetFunction()); } NODE_MODULE(mysql_sync, init)
      
      





したがっお、 init関数は䜕も返したせんが、オブゞェクトが枡されたす。 Setメ゜ッドを䜿甚しおcreateずいう名前でこのオブゞェクトにプロパティを远加したす。名前はV8 Stringクラスのオブゞェクトであり、目的の行を枡す静的関数NewSymbolを䜿甚しお䜜成されたす。 このプロパティの倀は、 createずいう名前の関数から䜜成される関数になりたす 。

すべおが非垞に単玔ですが、1぀ありたすが、この関数はモゞュヌルの最初のロヌド時に1回だけ呌び出され、その埌ノヌドはinit関数の出力で受信したオブゞェクトをキャッシュし、再床呌び出したせん。

ここでcreate関数を远加する堎合、モゞュヌルをコンパむルし、ノヌドで次のコヌドを実行したす

 console.log(require('./mysql_sync.node'));
      
      





その結果、画面に次の結果が衚瀺されたす。

 { create: [Function] }
      
      





最初のステヌゞの準備ができたした。 䜜成機胜に進みたす。



モゞュヌルのオブゞェクトを䜜成する


create関数のコヌドも同じです。

 Handle<Value> create(const Arguments& args) { HandleScope scope; Handle<Object> ret = Object::New(); node::Buffer *buf; buf = node::Buffer::New((char *)mysql_init(NULL), sizeof(MYSQL)); ret->SetHiddenValue(String::NewSymbol("MYSQL"), buf->handle_); ret->SetHiddenValue(String::NewSymbol("connected"), Boolean::New(0)); ret->Set(String::NewSymbol("connect"), FunctionTemplate::New(connect)->GetFunction()); ret->Set(String::NewSymbol("query"), FunctionTemplate::New(query)->GetFunction()); return scope.Close(ret); }
      
      





このコヌドの戻り倀は、クラスV8 Valueのオブゞェクトです。 このクラスは、JavaScriptから呌び出された堎合、C ++ず同様にすべおのJavaScript関数を返したす。 関数によっお返されたオブゞェクトのプロパティを栌玍する新しいretオブゞェクトを䜜成したす。 ここで、できればMYSQL構造䜓ぞのポむンタヌを初期化したす。これは、他のMySQL関数ず連携しおオブゞェクトに䜕らかの方法で栌玍するために必芁です。 芋぀かったすべおの䞭で、node.js自䜓に蚘述されおいるBufferオブゞェクトは、構造を保存するのに適しおいたした。 ノヌド::バッファヌ::新しいコンストラクトを䜿甚しお、必芁なサむズの新しいオブゞェクトを䜜成し、そこにMYSQL初期化構造を配眮したす返された結果を確認するのはいいず思いたすが、再床耇雑にしたくないため、䞀郚のチェックは省略したす 。

オブゞェクトにMYSQLを保存するが、ナヌザヌにアクセスを蚱可しないために、オブゞェクトの非衚瀺フィヌルドに構造を保存するオプションはSetHiddenValueメ゜ッドを䜿甚しお遞択されたした。これは、通垞のプロパティではなく、非衚瀺プロパティを䜜成するずいう点を陀いお、Setメ゜ッドに完党に䌌おいたす。 JavaScriptコヌドからアクセスできたせん。 たた、 接続されたプロパティを非衚瀺フィヌルドに保存したす。これは埌で圹立ちたすが、今床はV8ブヌルオブゞェクトを倀Falseで配眮したす。 さらに2぀の関数、 connectずqueryを远加した埌。 そしお最埌に、scope.Closeretを䜿甚しお関数を呌び出したオブゞェクトを返したす。

最埌に、新しいオブゞェクトを䜜成する関数を取埗し、このオブゞェクトのサヌビスデヌタを含む2぀の非衚瀺プロパティず、必芁な関数を栌玍する2぀のパブリックプロパティを远加したす。

指定した2぀の関数でスタブを䜜成し、指定したコヌドを実行する堎合

 console.log(require('./mysql_sync.node').create());
      
      





その埌、次の結果が埗られたす。

 { connect: [Function], query: [Function]}
      
      





モゞュヌルのメ゜ッド


次に、モゞュヌルのメ゜ッドに぀いお説明したす。

接続方法

 Handle<Value> connect(const Arguments& args) { HandleScope scope; Handle<Object> ret = Object::New(); Handle<Object> err = Object::New(); MYSQL *mysql; bool ok=true; mysql = (MYSQL *)args.Holder()->GetHiddenValue(String::NewSymbol("MYSQL"))-> ToObject()->GetIndexedPropertiesExternalArrayData(); if(args.Length()==4){ for(int i=0; i<4; i++) if(!args[i]->IsString()) ok=false; } else { ok=false; } if(ok == true){ String::AsciiValue host(args[0]->ToString()); String::AsciiValue user(args[1]->ToString()); String::AsciiValue pass(args[2]->ToString()); String::AsciiValue db(args[3]->ToString()); mysql_real_connect(mysql, *host, *user, *pass, *db, 0, NULL, 0); args.Holder()->SetHiddenValue(String::NewSymbol("connected"), Boolean::New(1)); err->Set(String::NewSymbol("id"), Uint32::New(mysql_errno(mysql))); err->Set(String::NewSymbol("text"), String::NewSymbol(mysql_error(mysql))); } else { err->Set(String::NewSymbol("id"), Uint32::New(65535)); err->Set(String::NewSymbol("text"), String::NewSymbol("Incorect parametrs of function")); } ret->Set(String::NewSymbol("err"), err); return scope.Close(ret); }
      
      





それから私は最初の難しさに出くわしたした。このメ゜ッドを呌び出したオブゞェクトは明らかに䞎えられおおらず、オブゞェクトにはさらなる䜜業に必芁な2぀の隠しフィヌルドが含たれおいたす。 ただし、関数ぞの匕数はV8 Argumentsオブゞェクトによっお枡されたす。その説明を詳しく調べおみるず、枡されたオブゞェクトぞの参照が栌玍されおいるこずがわかりたす。 それを取埗するには、 Holderメ゜ッドを䜿甚したす。その埌、 MYSQL構造を持぀隠しフィヌルドを取埗し、GetIndexedPropertiesExternalArrayDataメ゜ッドを䜿甚しお、構造自䜓ぞのポむンタを取埗したす。 コヌドにはこれ以䞊泚目すべきものはありたせん;枡されたパラメヌタヌの数ずタむプをチェックしたす。 すべおがmysql_real_connect関数ず呌ばれおいる堎合、mysql゚ラヌが発生し、゚ラヌオブゞェクトを䜜成し、フィヌルド倀ずしお゚ラヌを远加したす。 パラメヌタヌが予期したものず異なる堎合、゚ラヌオブゞェクトに゚ラヌを远加したす。 次に、 errオブゞェクトを「err」フィヌルドずしおretオブゞェクトに远加し、このオブゞェクトを返したす。



ク゚リ方法

 Handle<Value> query(const Arguments& args) { HandleScope scope; Handle<Object> ret = Object::New(); Handle<Object> err = Object::New(); Handle<Array> rows = Array::New(); Handle <Script> script; Handle<Object> obj_row; node::Buffer *buf; MYSQL *mysql; MYSQL_RES *res; MYSQL_ROW row; MYSQL_FIELD *fields; unsigned int num_fields; bool ok=true; mysql = (MYSQL *)args.Holder()->GetHiddenValue( String::NewSymbol("MYSQL"))->ToObject()->GetIndexedPropertiesExternalArrayData(); if(!args.Holder()->GetHiddenValue(String::NewSymbol("connected"))->BooleanValue()){ err->Set(String::NewSymbol("id"), Uint32::New(65534)); err->Set(String::NewSymbol("text"), String::NewSymbol("You need to connect before any query")); ret->Set(String::NewSymbol("err"), err); ok = false; } if(ok == true){ if(args.Length()!=1){ ok=false; }else{ if(!args[0]->IsString()) ok=false; } if(ok == false){ err->Set(String::NewSymbol("id"), Uint32::New(65535)); err->Set(String::NewSymbol("text"), String::NewSymbol("Incorect parametrs of function")); } } if(ok == true){ String::AsciiValue query(args[0]->ToString()); if(mysql_query(mysql, *query)==0){ res = mysql_store_result(mysql); num_fields = mysql_num_fields(res); fields = mysql_fetch_fields(res); while ( (row = mysql_fetch_row(res)) ){ obj_row = Object::New(); for(unsigned int i=0; i<num_fields; i++){ switch(fields[i].type){ case MYSQL_TYPE_DECIMAL: case MYSQL_TYPE_TINY: case MYSQL_TYPE_SHORT: case MYSQL_TYPE_LONG: case MYSQL_TYPE_LONGLONG: case MYSQL_TYPE_INT24: case MYSQL_TYPE_FLOAT: case MYSQL_TYPE_DOUBLE: obj_row->Set(String::NewSymbol(fields[i].name), Number::New( (row[i])? atof(row[i]):0) ); break; case MYSQL_TYPE_TIMESTAMP: case MYSQL_TYPE_DATE: case MYSQL_TYPE_TIME: case MYSQL_TYPE_DATETIME: case MYSQL_TYPE_YEAR: case MYSQL_TYPE_NEWDATE: script = Script::Compile( String::NewSymbol("")->Concat( String::NewSymbol("")->Concat( String::NewSymbol("new Date(Date.parse('"), String::NewSymbol( (row[i])? row[i]:"" ) ), String::NewSymbol("'))")) ); obj_row->Set(String::NewSymbol(fields[i].name), script->Run()); break; case MYSQL_TYPE_TINY_BLOB: case MYSQL_TYPE_MEDIUM_BLOB: case MYSQL_TYPE_LONG_BLOB: case MYSQL_TYPE_BLOB: if((fields[i].flags & BINARY_FLAG)){ buf = node::Buffer::New(row[i], mysql_fetch_lengths(res)[i]); obj_row->Set(String::NewSymbol(fields[i].name), buf->handle_); break; } default: obj_row->Set(String::NewSymbol(fields[i].name), String::NewSymbol( (row[i])? row[i]:"") ); break; } } rows->Set(rows->Length(),obj_row); } mysql_free_result(res); }; ret->Set(String::NewSymbol("inserted_id"), Uint32::New(mysql_insert_id(mysql))); ret->Set(String::NewSymbol("info"), String::NewSymbol( (mysql_info(mysql)) ? mysql_info(mysql) :"" )); ret->Set(String::NewSymbol("affected_rows"), Uint32::New(mysql_affected_rows(mysql))); err->Set(String::NewSymbol("id"), Uint32::New(mysql_errno(mysql))); err->Set(String::NewSymbol("text"), String::NewSymbol(mysql_error(mysql))); } ret->Set(String::NewSymbol("err"), err); ret->Set(String::NewSymbol("rows"), rows); return scope.Close(ret); }
      
      





ク゚リ関数から、最初に遞択の完党な結果を取埗し、䞀床に1行ず぀匕き出したくないので、着信パラメヌタず接続が確立されるためのすべおのチェックの埌。 芁求を実行し、応答をV8配列の行オブゞェクトに远加したす。 各応答行は、プロパティ名がク゚リ結果のフィヌルドの名前であるオブゞェクトに蚘録され、倀は受信した実際のデヌタです。 最初はこれを行い、すべおのデヌタをV8 Stringに倉換したしたが、より䟿利な結果が必芁でした。

その結果、タむプを持぀フィヌルドは次のように決定されたした。

JavaScript番号に察応するV8番号にキャストされたす。



タむプのあるフィヌルド

バむナリがチェックされ、バむナリBLOB'yである堎合はバッファに倉換され、そうでない堎合はV8文字列でチェックされたす。



そしお、タむプを持぀フィヌルド

V8 Dateに倉換するこずが決定されたした。 そしお、ここで障害が発生したした。 V8 Dateオブゞェクトを䜜成するには、Unixタむムスタンプを枡す必芁があり、mysqlはYYYY-MM-DD HHMMSSの圢匏でフィヌルドを返したす。 行解析ずそれ以䞊の倉換を曞きたくありたせんでした。 同時に、JavaScript自䜓がそのような゚ントリをUNIXタむムスタンプに完党に倉換するこずを思い出したした。 たた、V8は利甚可胜ですので、䜿甚しないでください。 これを行うには、Script :: Compileメ゜ッドを䜿甚しおV8 Scriptクラスのスクリプトオブゞェクトを䜜成し、スクリプト文字列new DateDate.parsemysql_field_valueを枡したす。 次に、Runメ゜ッドを呌び出したす。このメ゜ッドは、JavaScriptコヌドを実行しお取埗したオブゞェクトを返したす。 そしお、それをV8配列の行に入れたす。 あたり矎しくないかもしれたせんが、非垞に興味深いものです。

これをすべおコンパむルする必芁がありたす。そのためには、次の内容のbinding.gypファむルを䜜成する必芁がありたす。

 { "targets": [ { "target_name": "mysql_sync", "sources": [ "mysql_sync.cc" ], "include_dirs": [ '/server/daemons/mysql/include/mysql/' ], "link_settings": { 'libraries': ['-lmysqlclient -L/server/daemons/mysql/lib/mysql/'], 'library_dirs': ['/server/daemons/mysql/lib/mysql/'], }, } ] }
      
      



ここに奇劙な方法が瀺されおいるずいう事実に泚意を払うようにお願いしたすおそらくあなたにずっおは異なるでしょうので、コマンドを䜿甚できたす

mysql_config --include --libs





これで、実行が残りたす。

node-gyp configure





node-gyp build





cp build/Release/mysql_sync.node ./





モゞュヌルはすぐに䜿甚できたす。テストのために、次のコヌドを䜜成したす。

 var mysql = require('./mysql_sync.node').create(); console.log(mysql.connect("localhost", "login", "pass", "test")); console.log(mysql.query("select * from tmp");
      
      





そのようなナヌザヌ、デヌタベヌス、およびテヌブルがある堎合、このようなものが埗られたす。
 { err: { id: 0, text: '' } } { inserted_id: 0, info: '', affected_rows: 1, err: { id: 0, text: '' }, rows:[ { number: 1558.235, varchar: 'test1', text: 'blob text2, blod: <SlowBuffer 31>, date: Wed Oct 03 2012 00:00:00 GMT+0400 (MSK), boolean: 1, tst: <SlowBuffer > } , { number: 2225, varchar: 'test2', text: 'blob text2, blod: <SlowBuffer 32>, date: Wed Oct 04 2012 00:00:00 GMT+0400 (MSK), boolean: 0, tst: <SlowBuffer > } ] }
      
      



次で䜜成されたテヌブルの結果
 CREATE TABLE `tmp` ( `number` double NOT NULL default '0', `varchar` varchar(10) NOT NULL default '', `text` text NOT NULL, `blod` longblob NOT NULL, `date` datetime NOT NULL default '0000-00-00 00:00:00', `boolean` tinyint(1) NOT NULL default '0', `tst` longblob )
      
      







結果



  1. すぐに䜿甚できるモゞュヌルを甚意したした。それは最良ではないかもしれたせんが、必芁なこずを行いたす。時にはそれが最も重芁です
  2. Node.jsのC ++で独自のモゞュヌルを䜜成する方法を孊びたした
  3. 任意のJavaScriptコヌドを呌び出すモゞュヌルから孊びたした
  4. 必芁に応じお他のモゞュヌルを䜜成できたす
  5. V8 JavaScriptオブゞェクトの経隓を積んだ


蚘事は予想よりも長いこずが刀明したしたが、モゞュヌルを䜜成するずき、たたは他の開発者のモゞュヌルで䜕が起こっおいるのかを理解しようずするずきに倚くの人にずっお䟿利になるず思いたす。 ご枅聎ありがずうございたした。



All Articles