1 /*
2  Copyright (c) 2013, 2019, Oracle and/or its affiliates. All rights reserved.
3 
4  This program is free software; you can redistribute it and/or modify
5  it under the terms of the GNU General Public License, version 2.0,
6  as published by the Free Software Foundation.
7 
8  This program is also distributed with certain software (including
9  but not limited to OpenSSL) that is licensed under separate terms,
10  as designated in a particular file or component or in included license
11  documentation.  The authors of MySQL hereby grant you an additional
12  permission to link the program and your derivative works with the
13  separately licensed software that they have included with MySQL.
14 
15  This program is distributed in the hope that it will be useful,
16  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  GNU General Public License, version 2.0, for more details.
19 
20  You should have received a copy of the GNU General Public License
21  along with this program; if not, write to the Free Software
22  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
23  */
24 
25 #include <string.h>
26 #include <uv.h>
27 
28 #include <NdbApi.hpp>
29 #include <mysql.h>
30 
31 #include "adapter_global.h"
32 #include "Record.h"
33 #include "NativeCFunctionCall.h"
34 #include "js_wrapper_macros.h"
35 #include "NdbWrappers.h"
36 #include "SessionImpl.h"
37 #include "EncoderCharset.h"
38 
39 using namespace v8;
40 
41 
42 /**** Dictionary implementation
43  *
44  * getTable(), listIndexes(), and listTables() should run in a uv background
45  * thread, as they may require network waits.
46  *
47  * Looking at NdbDictionaryImpl.cpp, any method that calls into
48  * NdbDictInterface (m_receiver) might block.  Any method that blocks
49  * will cause the ndb's WaitMetaRequestCount to increment.
50  *
51  * We assume that once a table has been fetched, all NdbDictionary::getColumn()
52  * calls are immediately served from the local dictionary cache.
53  *
54  * After all background calls return, methods that create JavaScript objects
55  * can run in the main thread.
56  *
57 */
58 
59 /*
60  * A note on getTable():
61  *   In addition to the user-visible fields, the returned value wraps some
62  *   NdbDictionary objects.
63  *   The TableMetadata wraps an NdbDictionary::Table
64  *   The ColumnMetadata objects each wrap an NdbDictionary::Column
65  *   The IndexMetadata objects for SECONDARY indexes wrap an NdbDictionary::Index,
66  *    -- but IndexMetadta for PK does *not* wrap any native object!
67  *   The ForeignKeyMetadata objects are literals and do *not* wrap any native object
68 */
69 Envelope NdbDictTableEnv("const NdbDictionary::Table");
70 Envelope NdbDictColumnEnv("const NdbDictionary::Column");
71 Envelope NdbDictIndexEnv("const NdbDictionary::Index");
72 
73 const char * getColumnType(const NdbDictionary::Column *);
74 bool getIntColumnUnsigned(const NdbDictionary::Column *);
75 Local<Value> getDefaultValue(v8::Isolate *, const NdbDictionary::Column *);
76 
getNdbDictTableEnvelope()77 Envelope * getNdbDictTableEnvelope() {
78   return & NdbDictTableEnv;
79 }
80 
81 /** Dictionary calls run outside the main thread may end up in
82     mysys error handling code, and therefore require a call to my_thread_init().
83     We must assume that libuv and mysys have compatible thread abstractions.
84     uv_thread_self() returns a uv_thread_t; we maintain a linked list
85     containing the uv_thread_t IDs of threads that have been initialized.
86 */
87 class ThdIdNode {
88 public:
89   uv_thread_t id;
90   ThdIdNode * next;
ThdIdNode(uv_thread_t _id,ThdIdNode * _next)91   ThdIdNode(uv_thread_t _id, ThdIdNode * _next) : id(_id), next(_next) {};
92 };
93 
94 ThdIdNode * initializedThreadIds = 0;
95 uv_mutex_t threadListMutex;
96 
require_thread_specific_initialization()97 void require_thread_specific_initialization() {
98   uv_thread_t thd_id = uv_thread_self();
99   bool isInitialized = false;
100   ThdIdNode * list;
101 
102   uv_mutex_lock(& threadListMutex);
103   {
104     list = initializedThreadIds;
105 
106     while(list) {
107       if(uv_thread_equal(& (list->id), & thd_id)) {
108         isInitialized = true;
109         break;
110       }
111       list = list->next;
112     }
113 
114     if(! isInitialized) {
115       my_thread_init();
116       initializedThreadIds = new ThdIdNode(thd_id, initializedThreadIds);
117     }
118   }
119   uv_mutex_unlock(& threadListMutex);
120 }
121 
122 
123 /*** DBDictionary.listTables()
124   **
125    **/
126 class ListTablesCall : public NativeCFunctionCall_2_<int, SessionImpl *, const char *>
127 {
128 private:
129   Ndb * ndb;
130   NdbDictionary::Dictionary * dict;
131   NdbDictionary::Dictionary::List list;
132   v8::Isolate * isolate;
133 
134 public:
135   /* Constructor */
ListTablesCall(const Arguments & args)136   ListTablesCall(const Arguments &args) :
137     NativeCFunctionCall_2_<int, SessionImpl *, const char *>(NULL, args),
138     list(),
139     isolate(args.GetIsolate())
140   {
141   }
142 
143   /* UV_WORKER_THREAD part of listTables */
run()144   void run() {
145     ndb = arg0->ndb;
146     dict = ndb->getDictionary();
147     return_val = dict->listObjects(list, NdbDictionary::Object::UserTable);
148   }
149 
150   /* V8 main thread */
151   void doAsyncCallback(Local<Object> ctx);
152 };
153 
154 
doAsyncCallback(Local<Object> ctx)155 void ListTablesCall::doAsyncCallback(Local<Object> ctx) {
156   DEBUG_MARKER(UDEB_DETAIL);
157   Handle<Value> cb_args[2];
158   const char * & dbName = arg1;
159 
160   DEBUG_PRINT("RETURN VAL: %d", return_val);
161   if(return_val == -1) {
162     cb_args[0] = NdbError_Wrapper(dict->getNdbError());
163     cb_args[1] = Null(isolate);
164   }
165   else {
166     cb_args[0] = Null(isolate); // no error
167     /* ListObjects has returned tables in all databases;
168        we need to filter here on database name. */
169     int * stack = new int[list.count];
170     unsigned int nmatch = 0;
171     for(unsigned i = 0; i < list.count ; i++) {
172       if(strcmp(dbName, list.elements[i].database) == 0) {
173         stack[nmatch++] = i;
174       }
175     }
176     DEBUG_PRINT("arg1/nmatch/list.count: %s/%d/%d", arg1,nmatch,list.count);
177 
178     Local<Array> cb_list = Array::New(isolate, nmatch);
179     for(unsigned int i = 0; i < nmatch ; i++) {
180       cb_list->Set(i, String::NewFromUtf8(isolate, list.elements[stack[i]].name));
181     }
182     cb_args[1] = cb_list;
183     delete[] stack;
184   }
185   ToLocal(& callback)->Call(ctx, 2, cb_args);
186 }
187 
188 
189 /* listTables() Method call
190    ASYNC
191    arg0: SessionImpl *
192    arg1: database name
193    arg2: user_callback
194 */
listTables(const Arguments & args)195 void listTables(const Arguments &args) {
196   DEBUG_MARKER(UDEB_DETAIL);
197   REQUIRE_ARGS_LENGTH(3);
198 
199   ListTablesCall * ncallptr = new ListTablesCall(args);
200 
201   DEBUG_PRINT("listTables in database: %s", ncallptr->arg1);
202   ncallptr->runAsync();
203 
204   args.GetReturnValue().SetUndefined();
205 }
206 
207 
208 class DictionaryNameSplitter {
209 public:
DictionaryNameSplitter()210   DictionaryNameSplitter()                      {};
211   char part1[65];
212   char part3[65];
213   void splitName(const char * src);
214   bool match(const char *db, const char *table);
215 };
216 
217 /* match() returns true if parts 1 and 3 of split name match db.table
218 */
match(const char * db,const char * table)219 inline bool DictionaryNameSplitter::match(const char *db, const char *table) {
220   return ((strncmp(db, part1, 65) == 0) && (strncmp(table, part3, 65) == 0));
221 }
222 
223 /* Convert a name of the form <database>/<schema>/<table> to database and table
224    which are allocated by the caller and must each be able to hold 65 characters
225 */
splitName(const char * src)226 void DictionaryNameSplitter::splitName(const char * src) {
227   char * dstp = part1;
228   int max_len = 64; // maximum database name
229   // copy first part of name to db
230   while (*src != '/' && *src != 0 && max_len-- > 0) {
231     *dstp++ = *src++;
232   }
233   src++;
234   *dstp = 0;
235   // skip second part of name /<schema>/
236   max_len = 65; // maximum  schema name plus trailing /
237   while (*src != '/' && max_len-- > 0) {
238     ++src;
239   }
240   ++src;
241   // copy third part of name to tbl
242   max_len = 64; // maximum table name
243   dstp = part3;
244   while (*src != 0 && max_len-- > 0) {
245     *dstp++ = *src++;
246   }
247   *dstp = 0;
248   DEBUG_PRINT("splitName for %s => %s %s", src, part1, part3);
249 }
250 
251 
252 /*** DBDictionary.getTable()
253   **
254    **/
255 class GetTableCall : public NativeCFunctionCall_3_<int, SessionImpl *,
256                                                    const char *, const char *>
257 {
258 private:
259   const NdbDictionary::Table * ndb_table;
260   Ndb * per_table_ndb;
261   Ndb * ndb;                /* ndb from DBSesssionImpl */
262   const char * dbName;      /* this is NativeCFunctionCall_3_  arg1 */
263   const char * tableName;   /* this is NativeCFunctionCall_3_  arg2 */
264   NdbDictionary::Dictionary * dict;
265   NdbDictionary::Dictionary::List idx_list;
266   NdbDictionary::Dictionary::List fk_list;
267   const NdbError * ndbError;
268   int fk_count;
269   DictionaryNameSplitter splitter;
270   v8::Isolate * isolate;
271 
272   Handle<Object> buildDBIndex_PK();
273   Handle<Object> buildDBIndex(const NdbDictionary::Index *);
274   Handle<Object> buildDBForeignKey(const NdbDictionary::ForeignKey *);
275   Handle<Object> buildDBColumn(const NdbDictionary::Column *);
276   bool splitNameMatchesDbAndTable(const char * name);
277 
278 public:
279   /* Constructor */
GetTableCall(const Arguments & args)280   GetTableCall(const Arguments &args) :
281     NativeCFunctionCall_3_<int, SessionImpl *, const char *, const char *>(NULL, args),
282     ndb_table(0), per_table_ndb(0), idx_list(), fk_list(), fk_count(0),
283     isolate(args.GetIsolate())
284   {
285     ndb = arg0->ndb;
286     dbName = arg1;
287     tableName = arg2;
288   }
289 
290   /* UV_WORKER_THREAD part of listTables */
291   void run();
292 
293   /* V8 main thread */
294   void doAsyncCallback(Local<Object> ctx);
295 };
296 
297 
splitNameMatchesDbAndTable(const char * name)298 inline bool GetTableCall::splitNameMatchesDbAndTable(const char * name) {
299   splitter.splitName(name);
300   return splitter.match(dbName, tableName);
301 };
302 
run()303 void GetTableCall::run() {
304   DEBUG_PRINT("GetTableCall::run() [%s.%s]", arg1, arg2);
305   require_thread_specific_initialization();
306   return_val = -1;
307 
308   /* dbName is optional; if not present, set it from ndb database name */
309   if(strlen(dbName)) {
310     ndb->setDatabaseName(dbName);
311   } else {
312     dbName = ndb->getDatabaseName();
313   }
314   dict = ndb->getDictionary();
315   ndb_table = dict->getTable(tableName);
316   if(ndb_table) {
317     /* Ndb object used to create NdbRecords and to cache auto-increment values */
318     per_table_ndb = new Ndb(& ndb->get_ndb_cluster_connection());
319     DEBUG_PRINT("per_table_ndb %s.%s %p\n", dbName, tableName, per_table_ndb);
320     per_table_ndb->init();
321 
322     /* List the indexes */
323     return_val = dict->listIndexes(idx_list, tableName);
324   }
325   if(return_val == 0) {
326     /* Fetch the indexes now.  These calls may perform network IO, populating
327        the (connection) global and (Ndb) local dictionary caches.  Later,
328        in the JavaScript main thread, we will call getIndex() again knowing
329        that the caches are populated.
330     */
331     for(unsigned int i = 0 ; i < idx_list.count ; i++) {
332       const NdbDictionary::Index * idx = dict->getIndex(idx_list.elements[i].name, tableName);
333       /* It is possible to get an index for a recently dropped table rather
334          than the desired table.  This is a known bug likely to be fixed later.
335       */
336       const char * idx_table_name = idx->getTable();
337       const NdbDictionary::Table * idx_table = dict->getTable(idx_table_name);
338       if(idx_table == 0 || idx_table->getObjectVersion() != ndb_table->getObjectVersion())
339       {
340         dict->invalidateIndex(idx);
341         idx = dict->getIndex(idx_list.elements[i].name, tableName);
342       }
343     }
344   }
345   else {
346     DEBUG_PRINT("listIndexes() returned %i", return_val);
347     ndbError = & dict->getNdbError();
348     return;
349   }
350   /* List the foreign keys and keep the list around for doAsyncCallback to create js objects
351    * Currently there is no listForeignKeys so we use the more generic listDependentObjects
352    * specifying the table metadata object.
353    */
354   return_val = dict->listDependentObjects(fk_list, *ndb_table);
355   if (return_val == 0) {
356     /* Fetch the foreign keys and associated parent tables now.
357      * These calls may perform network IO, populating
358      * the (connection) global and (Ndb) local dictionary caches.  Later,
359      * in the JavaScript main thread, we will call getForeignKey() again knowing
360      * that the caches are populated.
361      * We only care about foreign keys where this table is the child table, not the parent table.
362      */
363     for(unsigned int i = 0 ; i < fk_list.count ; i++) {
364       NdbDictionary::ForeignKey fk;
365       if (fk_list.elements[i].type == NdbDictionary::Object::ForeignKey) {
366         const char * fk_name = fk_list.elements[i].name;
367         int fkGetCode = dict->getForeignKey(fk, fk_name);
368         DEBUG_PRINT("getForeignKey for %s returned %i", fk_name, fkGetCode);
369         // see if the foreign key child table is this table
370         if(splitNameMatchesDbAndTable(fk.getChildTable())) {
371           // the foreign key child table is this table; get the parent table
372           ++fk_count;
373           DEBUG_PRINT("Getting ParentTable");
374           splitter.splitName(fk.getParentTable());
375           ndb->setDatabaseName(splitter.part1);  // temp for next call
376           const NdbDictionary::Table * parent_table = dict->getTable(splitter.part3);
377           ndb->setDatabaseName(dbName);  // back to expected value
378           DEBUG_PRINT("Parent table getTable returned %s", parent_table->getName());
379         }
380       }
381     }
382   }
383   else {
384     DEBUG_PRINT("listDependentObjects() returned %i", return_val);
385     ndbError = & dict->getNdbError();
386   }
387 }
388 
389 
390 /* doAsyncCallback() runs in the main thread.  We don't want it to block.
391    TODO: verify whether any IO is done
392          by checking WaitMetaRequestCount at the start and end.
393 */
doAsyncCallback(Local<Object> ctx)394 void GetTableCall::doAsyncCallback(Local<Object> ctx) {
395   const char *ndbTableName;
396   EscapableHandleScope scope(isolate);
397   DEBUG_PRINT("GetTableCall::doAsyncCallback: return_val %d", return_val);
398 
399   /* User callback arguments */
400   Handle<Value> cb_args[2];
401   cb_args[0] = Null(isolate);
402   cb_args[1] = Null(isolate);
403 
404   /* TableMetadata = {
405       database         : ""    ,  // Database name
406       name             : ""    ,  // Table Name
407       columns          : []    ,  // ordered array of DBColumn objects
408       indexes          : []    ,  // array of DBIndex objects
409       partitionKey     : []    ,  // ordered array of column numbers in the partition key
410       sparseContainer  : null     // default column for sparse fields
411     };
412   */
413   if(ndb_table && ! return_val) {
414     Local<Object> table = NdbDictTableEnv.wrap(ndb_table)->ToObject();
415 
416     // database
417     table->Set(SYMBOL(isolate, "database"), String::NewFromUtf8(isolate, arg1));
418 
419     // name
420     ndbTableName = ndb_table->getName();
421     table->Set(SYMBOL(isolate, "name"), String::NewFromUtf8(isolate, ndbTableName));
422 
423     // partitionKey
424     int nPartitionKeys = 0;
425     Handle<Array> partitionKeys = Array::New(isolate);
426     table->Set(SYMBOL(isolate, "partitionKey"), partitionKeys);
427 
428     // sparseContainer
429     table->Set(SYMBOL(isolate,"sparseContainer"), Null(isolate));
430 
431     // columns
432     Local<Array> columns = Array::New(isolate, ndb_table->getNoOfColumns());
433     for(int i = 0 ; i < ndb_table->getNoOfColumns() ; i++) {
434       const NdbDictionary::Column *ndb_col = ndb_table->getColumn(i);
435       Handle<Object> col = buildDBColumn(ndb_col);
436       columns->Set(i, col);
437       if(ndb_col->getPartitionKey()) { /* partition key */
438         partitionKeys->Set(nPartitionKeys++, String::NewFromUtf8(isolate, ndb_col->getName()));
439       }
440       if(     ! strcmp(ndb_col->getName(), "SPARSE_FIELDS")
441           && ( (! strncmp(getColumnType(ndb_col), "VARCHAR", 7)
442                   && (getEncoderCharsetForColumn(ndb_col)->isUnicode))
443               || (   ! strncmp(getColumnType(ndb_col), "VARBINARY", 9)
444                   || ! strncmp(getColumnType(ndb_col), "JSON", 4))))
445       {
446         table->Set(SYMBOL(isolate,"sparseContainer"),
447                    String::NewFromUtf8(isolate, ndb_col->getName()));
448       }
449     }
450     table->Set(SYMBOL(isolate, "columns"), columns);
451 
452     // indexes (primary key & secondary)
453     Local<Array> js_indexes = Array::New(isolate, idx_list.count + 1);
454     js_indexes->Set(0, buildDBIndex_PK());                   // primary key
455     for(unsigned int i = 0 ; i < idx_list.count ; i++) {   // secondary indexes
456       const NdbDictionary::Index * idx =
457         dict->getIndex(idx_list.elements[i].name, arg2);
458       js_indexes->Set(i+1, buildDBIndex(idx));
459     }
460     table->ForceSet(SYMBOL(isolate, "indexes"), js_indexes, ReadOnly);
461 
462     // foreign keys (only foreign keys for which this table is the child)
463     // now create the javascript foreign key metadata objects for dictionary objects cached earlier
464     Local<Array> js_fks = Array::New(isolate, fk_count);
465 
466     int fk_number = 0;
467     for(unsigned int i = 0 ; i < fk_list.count ; i++) {
468       NdbDictionary::ForeignKey fk;
469       if (fk_list.elements[i].type == NdbDictionary::Object::ForeignKey) {
470         const char * fk_name = fk_list.elements[i].name;
471         int fkGetCode = dict->getForeignKey(fk, fk_name);
472         DEBUG_PRINT("getForeignKey for %s returned %i", fk_name, fkGetCode);
473         // see if the foreign key child table is this table
474         if(splitNameMatchesDbAndTable(fk.getChildTable())) {
475           // the foreign key child table is this table; build the fk object
476           DEBUG_PRINT("Adding foreign key for %s at %i", fk.getName(), fk_number);
477           js_fks->Set(fk_number++, buildDBForeignKey(&fk));
478         }
479       }
480     }
481     table->ForceSet(SYMBOL(isolate, "foreignKeys"), js_fks, ReadOnly);
482 
483     // Autoincrement Cache Impl (also not part of spec)
484     if(per_table_ndb) {
485       table->Set(SYMBOL(isolate, "per_table_ndb"), Ndb_Wrapper(per_table_ndb));
486     }
487 
488     // User Callback
489     cb_args[1] = table;
490   }
491   else {
492     cb_args[0] = NdbError_Wrapper(* ndbError);
493   }
494 
495   ToLocal(& callback)->Call(ctx, 2, cb_args);
496 }
497 
498 
499 /*
500 DBIndex = {
501   name             : ""    ,  // Index name
502   isPrimaryKey     : true  ,  // true for PK; otherwise undefined
503   isUnique         : true  ,  // true or false
504   isOrdered        : true  ,  // true or false; can scan if true
505   columnNumbers    : []    ,  // an ordered array of column numbers
506 };
507 */
buildDBIndex_PK()508 Handle<Object> GetTableCall::buildDBIndex_PK() {
509   EscapableHandleScope scope(isolate);
510 
511   Local<Object> obj = Object::New(isolate);
512 
513   obj->ForceSet(SYMBOL(isolate, "name"), String::NewFromUtf8(isolate, "PRIMARY_KEY"));
514   obj->ForceSet(SYMBOL(isolate, "isPrimaryKey"), Boolean::New(isolate, true), ReadOnly);
515   obj->ForceSet(SYMBOL(isolate, "isUnique"),     Boolean::New(isolate, true), ReadOnly);
516   obj->ForceSet(SYMBOL(isolate, "isOrdered"),    Boolean::New(isolate, false), ReadOnly);
517 
518   /* Loop over the columns of the key.
519      Build the "columnNumbers" array and the "record" object, then set both.
520   */
521   int ncol = ndb_table->getNoOfPrimaryKeys();
522   DEBUG_PRINT("Creating Primary Key Record");
523   Record * pk_record = new Record(dict, ncol);
524   Local<Array> idx_columns = Array::New(isolate, ncol);
525   for(int i = 0 ; i < ncol ; i++) {
526     const char * col_name = ndb_table->getPrimaryKey(i);
527     const NdbDictionary::Column * col = ndb_table->getColumn(col_name);
528     pk_record->addColumn(col);
529     idx_columns->Set(i, v8::Int32::New(isolate, col->getColumnNo()));
530   }
531   pk_record->completeTableRecord(ndb_table);
532 
533   obj->Set(SYMBOL(isolate, "columnNumbers"), idx_columns);
534   obj->ForceSet(SYMBOL(isolate, "record"), Record_Wrapper(pk_record), ReadOnly);
535 
536   return scope.Escape(obj);
537 }
538 
539 
buildDBIndex(const NdbDictionary::Index * idx)540 Handle<Object> GetTableCall::buildDBIndex(const NdbDictionary::Index *idx) {
541   EscapableHandleScope scope(isolate);
542 
543   Local<Object> obj = NdbDictIndexEnv.newWrapper();
544   wrapPointerInObject(idx, NdbDictIndexEnv, obj);
545 
546   obj->ForceSet(SYMBOL(isolate, "name"), String::NewFromUtf8(isolate, idx->getName()));
547   obj->ForceSet(SYMBOL(isolate, "isPrimaryKey"), Boolean::New(isolate, false), ReadOnly);
548   obj->ForceSet(SYMBOL(isolate, "isUnique"),
549                 Boolean::New(isolate, idx->getType() == NdbDictionary::Index::UniqueHashIndex),
550                 ReadOnly);
551   obj->ForceSet(SYMBOL(isolate, "isOrdered"),
552                 Boolean::New(isolate, idx->getType() == NdbDictionary::Index::OrderedIndex),
553                 ReadOnly);
554 
555   /* Loop over the columns of the key.
556      Build the "columns" array and the "record" object, then set both.
557   */
558   int ncol = idx->getNoOfColumns();
559   Local<Array> idx_columns = Array::New(isolate, ncol);
560   DEBUG_PRINT("Creating Index Record (%s)", idx->getName());
561   Record * idx_record = new Record(dict, ncol);
562   for(int i = 0 ; i < ncol ; i++) {
563     const char *colName = idx->getColumn(i)->getName();
564     const NdbDictionary::Column *col = ndb_table->getColumn(colName);
565     idx_columns->Set(i, v8::Int32::New(isolate, col->getColumnNo()));
566     idx_record->addColumn(col);
567   }
568   idx_record->completeIndexRecord(idx);
569   obj->ForceSet(SYMBOL(isolate, "record"), Record_Wrapper(idx_record), ReadOnly);
570   obj->Set(SYMBOL(isolate, "columnNumbers"), idx_columns);
571 
572   return scope.Escape(obj);
573 }
574 
575 /*
576  * ForeignKeyMetadata = {
577   name             : ""    ,  // Constraint name
578   columnNames      : null  ,  // an ordered array of column numbers
579   targetTable      : ""    ,  // referenced table name
580   targetDatabase   : ""    ,  // referenced database name
581   targetColumnNames: null  ,  // an ordered array of target column names
582 };
583 */
buildDBForeignKey(const NdbDictionary::ForeignKey * fk)584 Handle<Object> GetTableCall::buildDBForeignKey(const NdbDictionary::ForeignKey *fk) {
585   EscapableHandleScope scope(isolate);
586   DictionaryNameSplitter localSplitter;
587   Local<Object> js_fk = Object::New(isolate);
588 
589   localSplitter.splitName(fk->getName());  // e.g. "12/20/fkname"
590   js_fk->Set(SYMBOL(isolate, "name"), String::NewFromUtf8(isolate, localSplitter.part3));
591 
592   // get child column names
593   unsigned int childColumnCount = fk->getChildColumnCount();
594   Local<Array> fk_child_column_names = Array::New(isolate, childColumnCount);
595   for (unsigned i = 0; i < childColumnCount; ++i) {
596     int columnNumber = fk->getChildColumnNo(i);
597     const NdbDictionary::Column * column = ndb_table->getColumn(columnNumber);
598     fk_child_column_names->Set(i, String::NewFromUtf8(isolate, column->getName()));
599   }
600   js_fk->Set(SYMBOL(isolate, "columnNames"), fk_child_column_names);
601 
602   // get parent table (which might be in a different database)
603   const char * fk_parent_name = fk->getParentTable();
604   localSplitter.splitName(fk_parent_name);
605   const char * parent_db_name = localSplitter.part1;
606   const char * parent_table_name = localSplitter.part3;
607   js_fk->Set(SYMBOL(isolate, "targetTable"), String::NewFromUtf8(isolate, parent_table_name));
608   js_fk->Set(SYMBOL(isolate, "targetDatabase"), String::NewFromUtf8(isolate, parent_db_name));
609   ndb->setDatabaseName(parent_db_name);
610   const NdbDictionary::Table * parent_table = dict->getTable(parent_table_name);
611   ndb->setDatabaseName(dbName);
612 
613   // get parent column names
614   unsigned int parentColumnCount = fk->getParentColumnCount();
615   Local<Array> fk_parent_column_names = Array::New(isolate, parentColumnCount);
616   for (unsigned i = 0; i < parentColumnCount; ++i) {
617     int columnNumber = fk->getParentColumnNo(i);
618     const NdbDictionary::Column * column = parent_table->getColumn(columnNumber);
619     fk_parent_column_names->Set(i, String::NewFromUtf8(isolate, column->getName()));
620   }
621   js_fk->Set(SYMBOL(isolate, "targetColumnNames"), fk_parent_column_names);
622 
623   return scope.Escape(js_fk);
624 }
625 
buildDBColumn(const NdbDictionary::Column * col)626 Handle<Object> GetTableCall::buildDBColumn(const NdbDictionary::Column *col) {
627   EscapableHandleScope scope(isolate);
628 
629   Local<Object> obj = NdbDictColumnEnv.wrap(col)->ToObject();
630 
631   NdbDictionary::Column::Type col_type = col->getType();
632   bool is_int = (col_type <= NDB_TYPE_BIGUNSIGNED);
633   bool is_dec = ((col_type == NDB_TYPE_DECIMAL) || (col_type == NDB_TYPE_DECIMALUNSIGNED));
634   bool is_binary = ((col_type == NDB_TYPE_BLOB) || (col_type == NDB_TYPE_BINARY)
635                    || (col_type == NDB_TYPE_VARBINARY) || (col_type == NDB_TYPE_LONGVARBINARY));
636   bool is_char = ((col_type == NDB_TYPE_CHAR) || (col_type == NDB_TYPE_TEXT)
637                    || (col_type == NDB_TYPE_VARCHAR) || (col_type == NDB_TYPE_LONGVARCHAR));
638   bool is_lob =  ((col_type == NDB_TYPE_BLOB) || (col_type == NDB_TYPE_TEXT));
639 
640   /* Required Properties */
641 
642   obj->ForceSet(SYMBOL(isolate, "name"),
643            String::NewFromUtf8(isolate, col->getName()),
644            ReadOnly);
645 
646   obj->ForceSet(SYMBOL(isolate, "columnNumber"),
647            v8::Int32::New(isolate, col->getColumnNo()),
648            ReadOnly);
649 
650   obj->ForceSet(SYMBOL(isolate, "columnType"),
651            String::NewFromUtf8(isolate, getColumnType(col)),
652            ReadOnly);
653 
654   obj->ForceSet(SYMBOL(isolate, "isIntegral"),
655            Boolean::New(isolate, is_int),
656            ReadOnly);
657 
658   obj->ForceSet(SYMBOL(isolate, "isNullable"),
659            Boolean::New(isolate, col->getNullable()),
660            ReadOnly);
661 
662   obj->ForceSet(SYMBOL(isolate, "isInPrimaryKey"),
663            Boolean::New(isolate, col->getPrimaryKey()),
664            ReadOnly);
665 
666   obj->ForceSet(SYMBOL(isolate, "columnSpace"),
667            v8::Int32::New(isolate, col->getSizeInBytes()),
668            ReadOnly);
669 
670   /* Implementation-specific properties */
671 
672   obj->ForceSet(SYMBOL(isolate, "ndbTypeId"),
673            v8::Int32::New(isolate, static_cast<int>(col->getType())),
674            ReadOnly);
675 
676   obj->ForceSet(SYMBOL(isolate, "ndbRawDefaultValue"),
677            getDefaultValue(isolate, col),
678            ReadOnly);
679 
680   if(is_lob) {
681     obj->ForceSet(SYMBOL(isolate, "ndbInlineSize"),
682             v8::Int32::New(isolate, col->getInlineSize()),
683             ReadOnly);
684 
685     obj->ForceSet(SYMBOL(isolate, "ndbPartSize"),
686             v8::Int32::New(isolate, col->getPartSize()),
687             ReadOnly);
688   }
689 
690 
691   /* Optional Properties, depending on columnType */
692   /* Group A: Numeric */
693   if(is_int || is_dec) {
694     obj->ForceSet(SYMBOL(isolate, "isUnsigned"),
695              Boolean::New(isolate, getIntColumnUnsigned(col)),
696              ReadOnly);
697   }
698 
699   if(is_int) {
700     obj->ForceSet(SYMBOL(isolate, "intSize"),
701              v8::Int32::New(isolate, col->getSizeInBytes()),
702              ReadOnly);
703   }
704 
705   if(is_dec) {
706     obj->ForceSet(SYMBOL(isolate, "scale"),
707              v8::Int32::New(isolate, col->getScale()),
708              ReadOnly);
709 
710     obj->ForceSet(SYMBOL(isolate, "precision"),
711              v8::Int32::New(isolate, col->getPrecision()),
712              ReadOnly);
713   }
714 
715   obj->ForceSet(SYMBOL(isolate, "isAutoincrement"),
716            Boolean::New(isolate, col->getAutoIncrement()),
717            ReadOnly);
718 
719   /* Group B: Non-numeric */
720   if(is_binary || is_char) {
721     obj->ForceSet(SYMBOL(isolate, "isBinary"),
722              Boolean::New(isolate, is_binary),
723              ReadOnly);
724 
725     obj->ForceSet(SYMBOL(isolate, "isLob"),
726              Boolean::New(isolate, is_lob),
727              ReadOnly);
728 
729     if(is_binary) {
730       obj->ForceSet(SYMBOL(isolate, "length"),
731                v8::Int32::New(isolate, col->getLength()),
732                ReadOnly);
733     }
734 
735     if(is_char) {
736       const EncoderCharset * csinfo = getEncoderCharsetForColumn(col);
737 
738       obj->ForceSet(SYMBOL(isolate, "length"),
739                v8::Int32::New(isolate, col->getLength() / csinfo->maxlen),
740                ReadOnly);
741 
742       obj->ForceSet(SYMBOL(isolate, "charsetName"),
743                String::NewFromUtf8(isolate, csinfo->name),
744                ReadOnly);
745 
746       obj->ForceSet(SYMBOL(isolate, "collationName"),
747                String::NewFromUtf8(isolate, csinfo->collationName),
748                ReadOnly);
749     }
750   }
751 
752   return scope.Escape(obj);
753 }
754 
755 
756 /* getTable() method call
757    ASYNC
758    arg0: Ndb *
759    arg1: database name
760    arg2: table name
761    arg3: user_callback
762 */
getTable(const Arguments & args)763 void getTable(const Arguments &args) {
764   DEBUG_MARKER(UDEB_DETAIL);
765   REQUIRE_ARGS_LENGTH(4);
766   GetTableCall * ncallptr = new GetTableCall(args);
767   ncallptr->runAsync();
768   args.GetReturnValue().SetUndefined();
769 }
770 
771 
getColumnType(const NdbDictionary::Column * col)772 const char * getColumnType(const NdbDictionary::Column * col) {
773 
774   /* Based on ndb_constants.h */
775   const char * typenames[NDB_TYPE_MAX] = {
776     "",               // 0
777     "TINYINT",        // 1  TINY INT
778     "TINYINT",        // 2  TINY UNSIGNED
779     "SMALLINT",       // 3  SMALL INT
780     "SMALLINT",       // 4  SMALL UNSIGNED
781     "MEDIUMINT",      // 5  MEDIUM INT
782     "MEDIUMINT",      // 6  MEDIUM UNSIGNED
783     "INT",            // 7  INT
784     "INT",            // 8  UNSIGNED
785     "BIGINT",         // 9  BIGINT
786     "BIGINT",         // 10 BIG UNSIGNED
787     "FLOAT",          // 11
788     "DOUBLE",         // 12
789     "",               // 13 OLDDECIMAL
790     "CHAR",           // 14
791     "VARCHAR",        // 15
792     "BINARY",         // 16
793     "VARBINARY",      // 17
794     "DATETIME",       // 18
795     "DATE",           // 19
796     "BLOB",           // 20
797     "TEXT",           // 21 TEXT
798     "BIT",            // 22
799     "VARCHAR",        // 23 LONGVARCHAR
800     "VARBINARY",      // 24 LONGVARBINARY
801     "TIME",           // 25
802     "YEAR",           // 26
803     "TIMESTAMP",      // 27
804     "",               // 28 OLDDECIMAL UNSIGNED
805     "DECIMAL",        // 29 DECIMAL
806     "DECIMAL"         // 30 DECIMAL UNSIGNED
807 #if NDB_TYPE_MAX > 31
808     ,
809     "TIME",          // 31 TIME2
810     "DATETIME",      // 32 DATETIME2
811     "TIMESTAMP",     // 33 TIMESTAMP2
812 #endif
813   };
814 
815   if(    (col->getType() == 20) && (col->getInlineSize() == 4000)
816       && (col->getPartSize() == 8100))
817   {
818     return "JSON";
819   }
820 
821   return typenames[col->getType()];
822 }
823 
824 
getIntColumnUnsigned(const NdbDictionary::Column * col)825 bool getIntColumnUnsigned(const NdbDictionary::Column *col) {
826   switch(col->getType()) {
827     case NdbDictionary::Column::Unsigned:
828     case NdbDictionary::Column::Bigunsigned:
829     case NdbDictionary::Column::Smallunsigned:
830     case NdbDictionary::Column::Tinyunsigned:
831     case NdbDictionary::Column::Mediumunsigned:
832       return true;
833 
834     default:
835       return false;
836   }
837 }
838 
getDefaultValue(v8::Isolate * isolate,const NdbDictionary::Column * col)839 Local<Value> getDefaultValue(v8::Isolate *isolate, const NdbDictionary::Column *col) {
840   Local<Value> v = Null(isolate);
841   unsigned int defaultLen = 0;
842 
843   const void* dictDefaultBuff = col->getDefaultValue(& defaultLen);
844   if(defaultLen) {
845     v = COPY_TO_BUFFER(isolate, (const char *) dictDefaultBuff, defaultLen);
846   }
847   return v;
848 }
849 
850 
851 /* arg0: TableMetadata wrapping NdbDictionary::Table *
852    arg1: Ndb *
853    arg2: number of columns
854    arg3: array of NdbDictionary::Column *
855 */
getRecordForMapping(const Arguments & args)856 void getRecordForMapping(const Arguments &args) {
857   DEBUG_MARKER(UDEB_DEBUG);
858   EscapableHandleScope scope(args.GetIsolate());
859   const NdbDictionary::Table *table =
860     unwrapPointer<const NdbDictionary::Table *>(args[0]->ToObject());
861   Ndb * ndb = unwrapPointer<Ndb *>(args[1]->ToObject());
862   unsigned int nColumns = args[2]->Int32Value();
863   Record * record = new Record(ndb->getDictionary(), nColumns);
864   for(unsigned int i = 0 ; i < nColumns ; i++) {
865     const NdbDictionary::Column * col =
866       unwrapPointer<const NdbDictionary::Column *>
867         (args[3]->ToObject()->Get(i)->ToObject());
868     record->addColumn(col);
869   }
870   record->completeTableRecord(table);
871 
872   args.GetReturnValue().Set(scope.Escape(Record_Wrapper(record)));
873 }
874 
875 
DBDictionaryImpl_initOnLoad(Handle<Object> target)876 void DBDictionaryImpl_initOnLoad(Handle<Object> target) {
877   Local<Object> dbdict_obj = Object::New(Isolate::GetCurrent());
878 
879   DEFINE_JS_FUNCTION(dbdict_obj, "listTables", listTables);
880   DEFINE_JS_FUNCTION(dbdict_obj, "getTable", getTable);
881   DEFINE_JS_FUNCTION(dbdict_obj, "getRecordForMapping", getRecordForMapping);
882 
883   target->Set(NEW_SYMBOL("DBDictionary"), dbdict_obj);
884 
885   uv_mutex_init(& threadListMutex);
886 }
887 
888