1 /*
2  Copyright (c) 2013, 2018, 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 <node.h>
26 #include <node_buffer.h>
27 #include <NdbApi.hpp>
28 
29 #include "adapter_global.h"
30 #include "js_wrapper_macros.h"
31 #include "JsWrapper.h"
32 #include "NdbRecordObject.h"
33 
34 
35 /************************************************************
36           Value Object, a.k.a "VO" or "Native-Backed Object"
37 
38  A VO consists of:
39     * a Buffer holding data read from NDB
40     * an NdbRecord (wrapped by a Record) describing the layout of the buffer
41     * the NdbRecordObject
42       Holds both the buffer and the record
43       Maintains a list of columns to be written back, in READ-MODIFY-UPDATE
44       Manages NULL values itself
45       Delegates management of non-NULL values to its Column Proxies
46     * Mutable per-instance Column Proxies
47       Proxy the JavaScript value of a column
48       read it from the buffer if it has not yet been read
49       write it back to the buffer when requested
50     * Immutable per-class Column Handlers
51       Encode and decode values for column based on record layout
52 
53 
54  The ValueObject defines setters and getters for the mapped fields and
55  directs them to the NdbRecordObject
56 
57  Rough Call Flow
58  ---------------
59  A user supplies a mapping for a table.  The TableMetadata is fetched, and
60  used to resolve the mapping and create a DBTableHandler (dbt).  The dbt
61  can then be used to build a JavaScript constructor for VOs.
62 
63  Step 1: call getRecordForMapping(), implemented in DBDictionaryImpl.cpp.
64   This takes as arguments some parts of the DBTableHandler, and returns
65   a Record over the set of mapped columns.
66 
67  Step 2: call getValueObjectConstructor(), implemented here.  This takes as
68   arguments the Record, the field names, and the prototype for domain objects.
69   It returns a constructor that can be used to create VOs (a VOC).  The VOC
70   itself takes two arguments: the buffer containing in-row data that has been
71   read, and an array of individual buffers for BLOB columns.
72 
73  Step 3: we want an instantiated VO both to have the properties defined in
74   the mapping and to have the behaviors of the user's Domain Object (DO).
75   So, after obtaining the VOC in JavaScript, we apply the user's prototype
76   to it, like this:
77     VOC.prototype = DOC.prototype;
78 
79  These steps are all currently are performed in NdbOperation.js
80 
81  Application:
82    * a row is read from the database into buffer op.buffers.row
83    * The operation's read valule is set to a newly constructed VO:
84      op.result.value = new VOC(op.buffers.row);
85    * The user's constructor is called on the new value:
86      DOC.call(op.result.value);
87 
88 
89 
90 *************************************************************/
91 
92 Envelope columnHandlerSetEnvelope("ColumnHandlerSet");
93 
94 // An Envelope that wraps Envelopes for passing them in mapData:
95 Envelope envelopeEnvelope("Envelope");
96 
97 /* Generic getter for all NdbRecordObjects
98 */
nroGetter(Local<String>,const AccessorInfo & info)99 void nroGetter(Local<String>, const AccessorInfo & info)
100 {
101   EscapableHandleScope scope(info.GetIsolate());
102   Envelope * env = static_cast<Envelope *>
103     (info.Holder()->GetAlignedPointerFromInternalField(0));
104   assert(env->isVO);
105   NdbRecordObject * nro =
106     static_cast<NdbRecordObject *>(info.Holder()->GetAlignedPointerFromInternalField(1));
107   int nField = info.Data()->Int32Value();
108   DEBUG_PRINT_DETAIL("_GET_ NdbRecordObject field %d", nField);
109   info.GetReturnValue().Set(nro->getField(nField));
110 }
111 
112 
113 /* Generic setter for all NdbRecordObjects
114 */
nroSetter(Local<String>,Local<Value> value,const SetterInfo & info)115 void nroSetter(Local<String>, Local<Value> value, const SetterInfo& info)
116 {
117   EscapableHandleScope scope(info.GetIsolate());
118   Envelope * env = static_cast<Envelope *>
119     (info.Holder()->GetAlignedPointerFromInternalField(0));
120   assert(env->isVO);
121   NdbRecordObject * nro =
122     static_cast<NdbRecordObject *>(info.Holder()->GetAlignedPointerFromInternalField(1));
123   int nField = info.Data()->Int32Value();
124   DEBUG_PRINT_DETAIL("+SET+ NdbRecordObject field %d", nField);
125   nro->setField(nField, value);
126 }
127 
nroGetFieldByNumber(const Arguments & args)128 void nroGetFieldByNumber(const Arguments &args) {
129   DEBUG_MARKER(UDEB_DEBUG);
130 
131   NdbRecordObject * nro =
132     static_cast<NdbRecordObject *>(args[0]->ToObject()->GetAlignedPointerFromInternalField(1));
133   int nField = args[1]->Int32Value();
134   args.GetReturnValue().Set(nro->getField(nField));
135 }
136 
137 /* Generic constructor wrapper.
138  * args[0]: row buffer
139  * args[1]: array of blob & text column values
140  * args.Data(): mapData holding the record, ColumnHandlers, and Envelope
141 */
nroConstructor(const Arguments & args)142 void nroConstructor(const Arguments &args) {
143   DEBUG_MARKER(UDEB_DEBUG);
144   EscapableHandleScope scope(args.GetIsolate());
145 
146   /* Unwrap record from mapData */
147   Local<Object> mapData = args.Data()->ToObject();
148   const Record * record =
149     unwrapPointer<const Record *>(mapData->Get(0)->ToObject());
150 
151   /* Unwrap Column Handlers from mapData */
152   ColumnHandlerSet * handlers =
153     unwrapPointer<ColumnHandlerSet *>(mapData->Get(1)->ToObject());
154 
155   /* Unwrap the Envelope */
156   Envelope * nroEnvelope =
157     unwrapPointer<Envelope *>(mapData->Get(2)->ToObject());
158 
159   /* Build NdbRecordObject */
160   NdbRecordObject * nro = new NdbRecordObject(record, handlers, args);
161 
162   /* Wrap for JavaScript */
163   Local<Value> jsRecordObject = nroEnvelope->wrap(nro);
164   nroEnvelope->freeFromGC(nro, jsRecordObject);
165 
166   /* Set Prototype */
167   Handle<Value> prototype = mapData->Get(3);
168   if(! prototype->IsNull())
169     jsRecordObject->ToObject()->SetPrototype(prototype);
170 
171   args.GetReturnValue().Set(scope.Escape(jsRecordObject));
172 }
173 
174 
175 /* arg0: Record constructed over the appropriate column list
176    arg1: Array of field names
177    arg2: DOC Prototype
178 
179    Returns: a constructor function that can be used to create native-backed
180    objects
181 */
getValueObjectConstructor(const Arguments & args)182 void getValueObjectConstructor(const Arguments &args) {
183   DEBUG_MARKER(UDEB_DEBUG);
184   EscapableHandleScope scope(args.GetIsolate());
185   Local<FunctionTemplate> ft = FunctionTemplate::New(args.GetIsolate());
186   ft->InstanceTemplate()->SetInternalFieldCount(2);
187 
188   /* Initialize the mapData */
189   Local<Object> mapData = Object::New(args.GetIsolate());
190 
191   /* Store the record in the mapData at 0 */
192   mapData->Set(0, args[0]);
193 
194   /* Build the ColumnHandlers and store them in the mapData at 1 */
195   const Record * record = unwrapPointer<const Record *>(args[0]->ToObject());
196   const uint32_t ncol = record->getNoOfColumns();
197   ColumnHandlerSet *columnHandlers = new ColumnHandlerSet(ncol);
198   for(unsigned int i = 0 ; i < ncol ; i++) {
199     const NdbDictionary::Column * col = record->getColumn(i);
200     uint32_t offset = record->getColumnOffset(i);
201     ColumnHandler * handler = columnHandlers->getHandler(i);
202     handler->init(args.GetIsolate(), col, offset);
203   }
204   Local<Value> jsHandlerSet = columnHandlerSetEnvelope.wrap(columnHandlers);
205   mapData->Set(1, jsHandlerSet);
206 
207   /* Create an Envelope and wrap it */
208   Envelope * nroEnvelope = new Envelope("NdbRecordObject");
209   nroEnvelope->isVO = true;
210   mapData->Set(2, envelopeEnvelope.wrap(nroEnvelope));
211 
212   /* Set the Prototype in the mapData */
213   mapData->Set(3, args[2]);
214 
215   /* Create accessors for the mapped fields in the instance template.
216      AccessorInfo.Data() for the accessor will hold the field number.
217   */
218   Local<Object> jsFields = args[1]->ToObject();
219   for(unsigned int i = 0 ; i < ncol; i++) {
220     Local<Value> fieldNumber = v8::Number::New(args.GetIsolate(), i);
221     Handle<String> fieldName = jsFields->Get(i)->ToString();
222     nroEnvelope->addAccessor(fieldName, nroGetter, nroSetter, fieldNumber);
223   }
224 
225   /* The generic constructor is the CallHandler */
226   ft->SetCallHandler(nroConstructor, mapData);
227   DEBUG_PRINT("Template fields: %d", ft->InstanceTemplate()->InternalFieldCount());
228 
229   args.GetReturnValue().Set(scope.Escape(ft->GetFunction()));
230 }
231 
232 
isValueObject(const Arguments & args)233 void isValueObject(const Arguments &args) {
234   bool answer = false;
235   Handle<Value> v = args[0];
236 
237   if(v->IsObject()) {
238     Handle<Object> o = v->ToObject();
239     if(o->InternalFieldCount() == 2) {
240       Envelope * n = (Envelope *) o->GetAlignedPointerFromInternalField(0);
241       answer = n->isVO;
242     }
243   }
244 
245   args.GetReturnValue().Set(answer);
246 }
247 
248 
getValueObjectWriteCount(const Arguments & args)249 void getValueObjectWriteCount(const Arguments &args) {
250   EscapableHandleScope scope(args.GetIsolate());
251   NdbRecordObject * nro = unwrapPointer<NdbRecordObject *>(args[0]->ToObject());
252   args.GetReturnValue().Set(nro->getWriteCount());
253 }
254 
255 
prepareForUpdate(const Arguments & args)256 void prepareForUpdate(const Arguments &args) {
257   EscapableHandleScope scope(args.GetIsolate());
258   NdbRecordObject * nro = unwrapPointer<NdbRecordObject *>(args[0]->ToObject());
259   args.GetReturnValue().Set(scope.Escape(nro->prepare()));
260 }
261 
262 
ValueObject_initOnLoad(Handle<Object> target)263 void ValueObject_initOnLoad(Handle<Object> target) {
264   DEFINE_JS_FUNCTION(target, "getValueObjectConstructor", getValueObjectConstructor);
265   DEFINE_JS_FUNCTION(target, "isValueObject", isValueObject);
266   DEFINE_JS_FUNCTION(target, "getValueObjectWriteCount", getValueObjectWriteCount);
267   DEFINE_JS_FUNCTION(target, "prepareForUpdate", prepareForUpdate);
268   DEFINE_JS_FUNCTION(target, "getValueObjectFieldByNumber", nroGetFieldByNumber);
269 }
270