38e8812fc84e405748340805191974952aade52a
[node_zettair.git] / zettair.cpp
1 /*
2  * Copyright (C) 2018 Nick Downing <nick@ndcode.org>
3  * SPDX-License-Identifier: MIT
4  * 
5  * Permission is hereby granted, free of charge, to any person obtaining a copy
6  * of this software and associated documentation files (the "Software"), to
7  * deal in the Software without restriction, including without limitation the
8  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9  * sell copies of the Software, and to permit persons to whom the Software is
10  * furnished to do so, subject to the following conditions:
11  * 
12  * The above copyright notice and this permission notice shall be included in
13  * all copies or substantial portions of the Software.
14  * 
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21  * IN THE SOFTWARE.
22  */
23
24 #include <node.h>
25 #include <node_object_wrap.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <zettair/def.h>
29 #include <zettair/index.h>
30
31 namespace zettair {
32
33 static v8::Local<v8::Object> index_result_to_object(
34   v8::Isolate* isolate,
35   struct index_result* result
36 );
37 static v8::Local<v8::Object> index_results_to_object(
38   v8::Isolate* isolate,
39   struct index_result* results,
40   unsigned int num_results,
41   double/*unsigned long int*/ total_results
42 );
43
44 class IndexObject : public node::ObjectWrap {
45 public:
46   static void Init(v8::Local<v8::Object> exports);
47
48 private:
49   struct index* idx;
50
51   explicit IndexObject(struct index* idx0);
52   ~IndexObject();
53
54   static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
55   static void Search(const v8::FunctionCallbackInfo<v8::Value>& args);
56   static v8::Persistent<v8::Function> constructor;
57 };
58
59 v8::Persistent<v8::Function> IndexObject::constructor;
60
61 IndexObject::IndexObject(struct index* idx0) : idx(idx0) {
62 }
63
64 IndexObject::~IndexObject() {
65 }
66
67 void IndexObject::Init(v8::Local<v8::Object> exports) {
68   v8::Isolate* isolate = exports->GetIsolate();
69
70   // Prepare constructor template
71   v8::Local<v8::FunctionTemplate> tpl =
72     v8::FunctionTemplate::New(isolate, New);
73   tpl->SetClassName(v8::String::NewFromUtf8(isolate, "Index"));
74   tpl->InstanceTemplate()->SetInternalFieldCount(1);
75
76   // Prototype
77   NODE_SET_PROTOTYPE_METHOD(tpl, "search", Search);
78
79   constructor.Reset(isolate, tpl->GetFunction());
80   exports->Set(
81     v8::String::NewFromUtf8(isolate, "Index"),
82     tpl->GetFunction()
83   );
84 }
85
86 void IndexObject::New(const v8::FunctionCallbackInfo<v8::Value>& args) {
87   v8::Isolate* isolate = args.GetIsolate();
88
89   if (args.IsConstructCall()) {
90     // Invoked as constructor: `new Index(...)`
91     v8::String::Utf8Value prefix(args[0]);
92     struct index* idx = NULL;;
93     int lopts = INDEX_LOAD_NOOPT;
94     struct index_load_opt lopt;
95
96     lopts |= INDEX_LOAD_IGNORE_VERSION;  /* quick hack */
97     if (
98       (
99         idx = index_load(
100           args[0]->IsString() ? *prefix : "index",
101           MEMORY_DEFAULT,
102           lopts,
103           &lopt
104         )
105       ) == NULL
106     ) {
107       isolate->ThrowException(
108         v8::String::NewFromUtf8(isolate, "Unable to load index")
109       );
110       return;
111     }
112     IndexObject* obj = new IndexObject(idx);
113     obj->Wrap(args.This());
114     args.GetReturnValue().Set(args.This());
115   } else {
116     // Invoked as plain function `Index(...)`, turn into construct call.
117     const int argc = 1;
118     v8::Local<v8::Value> argv[argc] = { args[0] };
119     v8::Local<v8::Context> context = isolate->GetCurrentContext();
120     v8::Local<v8::Function> cons =
121       v8::Local<v8::Function>::New(isolate, constructor);
122     v8::Local<v8::Object> result =
123       cons->NewInstance(context, argc, argv).ToLocalChecked();
124     args.GetReturnValue().Set(result);
125   }
126 }
127
128 void IndexObject::Search(const v8::FunctionCallbackInfo<v8::Value>& args) {
129   v8::Isolate* isolate = args.GetIsolate();
130
131   v8::String::Utf8Value query(args[0]);
132   v8::String::Utf8Value optType(args[3]);
133   v8::Local<v8::Array> optArgsTuple;
134   if (args[4]->IsArray())
135     optArgsTuple = args[4].As<v8::Array>();
136   unsigned long startdoc = args[1]->IntegerValue();
137   unsigned long len = args[2]->IntegerValue();
138   IndexObject* Index = node::ObjectWrap::Unwrap<IndexObject>(args.Holder());
139   struct index_result* result;
140   unsigned int results;
141   double/*unsigned long int*/ total_results;
142   int est;
143   unsigned int accumulator_limit = args[5]->Uint32Value();
144   int opts = INDEX_SEARCH_NOOPT;
145   struct index_search_opt opt;
146
147   opt.u.okapi_k3.k1 = 1.2;
148   opt.u.okapi_k3.k3 = 1e10;
149   opt.u.okapi_k3.b = 0.75;
150
151   if (
152     (result = (struct index_result*)malloc(sizeof(*result) * len)) == NULL
153   ) {
154     isolate->ThrowException(
155       v8::String::NewFromUtf8(isolate, "Unable to allocate results")
156     );
157     return;
158   }
159   if (args[3]->IsString()) {
160     if (strcmp(*optType, "COSINE") == 0) {
161       opts = INDEX_SEARCH_COSINE_RANK;
162     } else if (strcmp(*optType, "OKAPI") == 0) {
163       opts = INDEX_SEARCH_OKAPI_RANK;
164     } else if (strcmp(*optType, "OKAPI_K3") == 0) {
165       if (optArgsTuple.IsEmpty()) {
166         isolate->ThrowException(
167           v8::String::NewFromUtf8(isolate, "Must supply args to search type")
168         );
169         free(result);
170         return;
171       }
172       opts = INDEX_SEARCH_OKAPI_RANK;
173       opt.u.okapi_k3.k1 = optArgsTuple->Get(0)->IntegerValue();
174       opt.u.okapi_k3.k3 = optArgsTuple->Get(1)->IntegerValue();
175       opt.u.okapi_k3.b = optArgsTuple->Get(2)->IntegerValue();
176     } else if (strcmp(*optType, "HAWKAPI") == 0) {
177       if (optArgsTuple.IsEmpty()) {
178         isolate->ThrowException(
179           v8::String::NewFromUtf8(isolate, "Must supply args to search type")
180         );
181         free(result);
182         return;
183       }
184       opts = INDEX_SEARCH_HAWKAPI_RANK;
185       opt.u.hawkapi.alpha = optArgsTuple->Get(0)->IntegerValue();
186       opt.u.hawkapi.k3 = optArgsTuple->Get(1)->IntegerValue();
187     } else if (strcmp(*optType, "DIRICHLET") == 0) {
188       opts = INDEX_SEARCH_DIRICHLET_RANK;
189       if (optArgsTuple.IsEmpty() || optArgsTuple->Length() == 0)
190         opt.u.dirichlet.mu = 2500.0;
191       else
192         opt.u.dirichlet.mu = optArgsTuple->Get(0)->NumberValue();
193     } else {
194       isolate->ThrowException(
195         v8::String::NewFromUtf8(isolate, "Unknown search type")
196       );
197       free(result);
198       return;
199     }
200   }
201   if (accumulator_limit != 0) {
202     opts |= INDEX_SEARCH_ACCUMULATOR_LIMIT;
203     opt.accumulator_limit = accumulator_limit;
204   }
205 #if 1 /* Nick */
206  opts |= INDEX_SEARCH_SUMMARY_TYPE;
207  opt.summary_type = INDEX_SUMMARISE_TAG;
208 #endif
209   if (!index_search(Index->idx, *query, startdoc, len,
210       result, &results, &total_results, &est, opts, &opt)) {
211     char err_buf[1024];
212     snprintf(err_buf, 1024, "Unable to perform search for query '%s'; "
213        "system error is '%s'\n", *query, strerror(errno));
214     isolate->ThrowException(
215       v8::String::NewFromUtf8(isolate, err_buf)
216     );
217     free(result);
218     return;
219   }
220   args.GetReturnValue().Set(
221     index_results_to_object(isolate, result, results, total_results)
222   );
223   free(result);
224   return;
225 }
226
227 static v8::Local<v8::Object> index_result_to_object(
228   v8::Isolate* isolate,
229   struct index_result* result
230 ) {
231   v8::EscapableHandleScope handle_scope(isolate);
232   v8::Local<v8::Object> result_object = v8::Object::New(isolate);
233   result_object->Set(
234     v8::String::NewFromUtf8(isolate, "docno"),
235     v8::Integer::New(isolate, result->docno)
236   );
237   result_object->Set(
238     v8::String::NewFromUtf8(isolate, "score"),
239     v8::Number::New(isolate, result->score)
240   );
241   result_object->Set(
242     v8::String::NewFromUtf8(isolate, "summary"),
243     v8::String::NewFromUtf8(isolate, result->summary)
244   );
245   result_object->Set(
246     v8::String::NewFromUtf8(isolate, "title"),
247     v8::String::NewFromUtf8(isolate, result->title)
248   );
249   result_object->Set(
250     v8::String::NewFromUtf8(isolate, "auxiliary"),
251     v8::String::NewFromUtf8(isolate, result->auxilliary)
252   );
253   return handle_scope.Escape(result_object);
254 }
255
256 static v8::Local<v8::Object> index_results_to_object(
257   v8::Isolate* isolate,
258   struct index_result* results,
259   unsigned int num_results,
260   double/*unsigned long int*/ total_results
261 ) {
262   v8::EscapableHandleScope handle_scope(isolate);
263   v8::Local<v8::Object> results_object = v8::Object::New(isolate);
264   v8::Local<v8::Array> results_array = v8::Array::New(isolate, num_results);
265   for (unsigned int i = 0; i < num_results; ++i)
266     results_array->Set(i, index_result_to_object(isolate, results + i));
267   results_object->Set(
268     v8::String::NewFromUtf8(isolate, "results"),
269     results_array
270   );
271   results_object->Set(
272     v8::String::NewFromUtf8(isolate, "total_results"),
273     v8::Number::New(isolate, total_results)
274   );
275   return handle_scope.Escape(results_object);
276 }
277
278 void InitAll(v8::Local<v8::Object> exports) {
279   IndexObject::Init(exports);
280 }
281
282 NODE_MODULE(NODE_GYP_MODULE_NAME, InitAll)
283
284 }