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