PostgreSQLでのドキュメント指向APIの設計:全文検索と多数のドキュメントの保存(パート2)

この一連の記事最初の部分では 、適切な保存機能と、変更可能なドキュメント指向のテーブルをその場で作成できる別の機能を作成しました。 それらは適切に機能し、必要なことを正確に行いますが、さらに多くのことができます。 特に、全文検索をオンザフライでインデックス化し、トランザクション内に多くのドキュメントを保存したいです。



やってみましょう。



全文検索



ドキュメント指向のテーブルには、 tsvector型の検索フィールドがあり、GINインデックスを使用して高速化されています。 ドキュメントを保存するたびにこのフィールドを更新します。これを行うときに、APIからのノイズをあまり必要としません。



この点で、私はいくつかのコンベンションに頼ります。



通常、フルテキストインデックスを作成するとき、フィールドはかなり特定の名前で保存されます。 など:



インデックスを作成するキーを保存するときにドキュメントを確認し、 検索フィールドに保存します。 これは、 update_searchという関数を使用して実行できます。

create function update_search(tbl varchar, id int) returns boolean as $$ //get the record var found = plv8.execute("select body from " + tbl + " where id=$1",id)[0]; if(found){ var doc = JSON.parse(found.body); var searchFields = ["name","email","first","first_name", "last","last_name","description","title", "street", "city", "state", "zip", ]; var searchVals = []; for(var key in doc){ if(searchFields.indexOf(key.toLowerCase()) > -1){ searchVals.push(doc[key]); } }; if(searchVals.length > 0){ var updateSql = "update " + tbl + " set search = to_tsvector($1) where id =$2"; plv8.execute(updateSql, searchVals.join(" "), id); } return true; }else{ return false; } $$ language plv8;
      
      





これらの目的でjavascript(PLV8)を再利用し、IDベースのドキュメントを引き出しています。 その後、すべてのキーを調べて、保存したいキーがあるかどうかを確認し、キーがある場合は配列に入れます。



このアーカイブにヒットがある場合、これらのオブジェクトを連結し、Postgresに組み込まれているto_tsvector関数を使用してドキュメントの検索フィールドに保存します。



ありました! このスクリプトを実行すると、次の結果が得られます。



画像



理想-これをsave_document関数の最後に挿入するだけで、何かを保存するたびにトランザクション的に呼び出されます:



 create function save_document(tbl varchar, doc_string jsonb) returns jsonb as $$ var doc = JSON.parse(doc_string); var result = null; var id = doc.id; var exists = plv8.execute("select table_name from information_schema.tables where table_name = $1", tbl)[0]; if(!exists){ plv8.execute("select create_document_table('" + tbl + "');"); } if(id){ result = plv8.execute("update " + tbl + " set body=$1, updated_at = now() where id=$2 returning *;",doc_string,id); }else{ result = plv8.execute("insert into " + tbl + "(body) values($1) returning *;", doc_string); id = result[0].id; doc.id = id; result = plv8.execute("update " + tbl + " set body=$1 where id=$2 returning *",JSON.stringify(doc),id); } //run the search indexer plv8.execute("perform update_search($1, $2)", tbl,id); return result[0] ? result[0].body : null; $$ language plv8;
      
      





多くのドキュメントを保存する



現時点では、1つのドキュメントをsave_document関数に渡すことができますが、配列を渡すことができるようにしたいと思います。 引数の型を確認してこれを行うことができ、ループを開始します。



 create function save_document(tbl varchar, doc_string jsonb) returns jsonb as $$ var doc = JSON.parse(doc_string); var exists = plv8.execute("select table_name from information_schema.tables where table_name = $1", tbl)[0]; if(!exists){ plv8.execute("select create_document_table('" + tbl + "');"); } //function that executes our SQL statement var executeSql = function(theDoc){ var result = null; var id = theDoc.id; var toSave = JSON.stringify(theDoc); if(id){ result=plv8.execute("update " + tbl + " set body=$1, updated_at = now() where id=$2 returning *;",toSave, id); }else{ result=plv8.execute("insert into " + tbl + "(body) values($1) returning *;", toSave); id = result[0].id; //put the id back on the document theDoc.id = id; //resave it result = plv8.execute("update " + tbl + " set body=$1 where id=$2 returning *;",JSON.stringify(theDoc),id); } plv8.execute("select update_search($1,$2)", tbl, id); return result ? result[0].body : null; } var out = null; //was an array passed in? if(doc instanceof Array){ for(var i = 0; i < doc.length;i++){ executeSql(doc[i]); } //just report back how many documents were saved out = JSON.stringify({count : i, success : true}); }else{ out = executeSql(doc); } return out; $$ language plv8;
      
      





ここでjavascriptを使用する良い面は、そのようなルーチンに必要なロジックが非常に単純であることです(PLPGSQLとは対照的です)。 保存プロセス全体を別の関数として強調表示しました-これは結局JavaScriptです-重複を避けることができます。



次に、入力引数が配列であることを確認します。 そうであれば、そのメンバーを調べてexecuteSqlを呼び出し 、実行中に蓄積されたすべてを返します。



配列でない場合は、ドキュメントと同じようにすべてを行い、ドキュメント全体を返します。 結果:



画像



いいね! 最良の部分は、 それがすべてトランザクション内で発生することです。 私はそれが好きです!



奇数ノード



Nodeから完全に機能する場合にのみ! .NETとNodeの両方で試してみましたが、.NETはすべてNpgsqlライブラリを使用して(奇妙な)動作します。 Nodeからではなく。



要するに、node_pgドライバーは、入力パラメーターとして配列オブジェクトを見たときに非常に奇妙な変換を行います。 次のことに注意してください。



 var pg = require("pg"); var run = function (sql, params, next) { pg.connect(args.connectionString, function (err, db, done) { //throw if there's a connection error assert.ok(err === null, err); db.query(sql, params, function (err, result) { //we have the results, release the connection done(); pg.end(); if(err){ next(err,null); }else{ next(null, result.rows); } }); }); }; run("select * from save_document($1, $2)", ['customer_docs', {name : "Larry"}], function(err,res){ //works just fine }
      
      





これは通常のNode / PGコードです。 最後に、run関数はsave_document関数を呼び出してデータを渡すように構成されています。 PGは入力オブジェクトを認識すると、それを文字列に変換し、保存が正常に行われます。



配列を送信すると...



 run("select * from save_document($1, $2)", ['customer_docs', [{name : "Larry"}, {name : "Susie"}], function(err,res){ //crashes hard }
      
      





これが無効なJSONであることを伝えるエラーが返されます。 エラーメッセージ(Postgresから)は、これが不十分にフォーマットされたJSONによるものであることを報告します。



 {"{name : "Larry"}, ...}
      
      





何...はい、それはひどいです。 私は何が起こるかを定式化しようとしましたが、node_pgドライバーが外部配列を解析するように見えます-おそらくUnderscoresライブラリのflatdersメソッドを呼び出すことによって。 知りません これを回避するには、呼び出しを次のように変更する必要があります。



 run("select * from save_document($1, $2)", ['customer_docs', JSON.stringify([{name : "Larry"}, {name : "Susie"}]), function(err,res){ //Works fine }
      
      





どうぞ!



保管手順は非常にスムーズで、私を喜ばせます。 次の記事では、検索エンジンをセットアップし、全文検索機能を作成します。



All Articles