1 /*
2  Copyright (c) 2013, 2021, Oracle and/or its affiliates. All rights
3  reserved.
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License, version 2.0,
7  as published by the Free Software Foundation.
8 
9  This program is also distributed with certain software (including
10  but not limited to OpenSSL) that is licensed under separate terms,
11  as designated in a particular file or component or in included license
12  documentation.  The authors of MySQL hereby grant you an additional
13  permission to link the program and your derivative works with the
14  separately licensed software that they have included with MySQL.
15 
16  This program is distributed in the hope that it will be useful,
17  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  GNU General Public License, version 2.0, for more details.
20 
21  You should have received a copy of the GNU General Public License
22  along with this program; if not, write to the Free Software
23  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
24  02110-1301  USA
25  */
26 
27 #include <node.h>
28 #include <node_buffer.h>
29 #include <NdbApi.hpp>
30 
31 #include "adapter_global.h"
32 #include "js_wrapper_macros.h"
33 #include "JsWrapper.h"
34 #include "NdbRecordObject.h"
35 
36 
37 /************************************************************
38           Value Object, a.k.a "VO" or "Native-Backed Object"
39 
40  A VO consists of:
41     * a Buffer holding data read from NDB
42     * an NdbRecord (wrapped by a Record) describing the layout of the buffer
43     * the NdbRecordObject
44       Holds both the buffer and the record
45       Maintains a list of columns to be written back, in READ-MODIFY-UPDATE
46       Manages NULL values itself
47       Delegates management of non-NULL values to its Column Proxies
48     * Mutable per-instance Column Proxies
49       Proxy the JavaScript value of a column
50       read it from the buffer if it has not yet been read
51       write it back to the buffer when requested
52     * Immutable per-class Column Handlers
53       Encode and decode values for column based on record layout
54 
55 
56  The ValueObject defines setters and getters for the mapped fields and
57  directs them to the NdbRecordObject
58 
59  Rough Call Flow
60  ---------------
61  A user supplies a mapping for a table.  The TableMetadata is fetched, and
62  used to resolve the mapping and create a DBTableHandler (dbt).  The dbt
63  can then be used to build a JavaScript constructor for VOs.
64 
65  Step 1: call getRecordForMapping(), implemented in DBDictionaryImpl.cpp.
66   This takes as arguments some parts of the DBTableHandler, and returns
67   a Record over the set of mapped columns.
68 
69  Step 2: call getValueObjectConstructor(), implemented here.  This takes as
70   arguments the Record, the field names, and any needed typeConverters.
71   It returns a constructor that can be used to create VOs (a VOC).  The VOC
72   itself takes one argument, the buffer containing data that has been read.
73 
74  Step 3: we want an instantiated VO both to have the properties defined in
75   the mapping and to have the behaviors of the user's Domain Object (DO).
76   So, after obtaining the VOC in JavaScript, we apply the user's prototype
77   to it, like this:
78     VOC.prototype = DOC.prototype;
79 
80  These steps are all currently are performed in NdbOperation.js
81 
82  Application:
83    * a row is read from the database into buffer op.buffers.row
84    * The operation's read valule is set to a newly constructed VO:
85      op.result.value = new VOC(op.buffers.row);
86    * The user's constructor is called on the new value:
87      DOC.call(op.result.value);
88 
89 
90 
91 *************************************************************/
92 
93 Envelope columnHandlerSetEnvelope("ColumnHandlerSet");
94 Envelope nroEnvelope("NdbRecordObject");
95 
96 
97 /* Generic getter for all NdbRecordObjects
98 */
nroGetter(Local<String>,const AccessorInfo & info)99 Handle<Value> nroGetter(Local<String>, const AccessorInfo & info)
100 {
101   NdbRecordObject * nro =
102     static_cast<NdbRecordObject *>(info.Holder()->GetPointerFromInternalField(1));
103   int nField = info.Data()->Int32Value();
104   return nro->getField(nField);
105 }
106 
107 
108 /* Generic setter for all NdbRecordObjects
109 */
nroSetter(Local<String>,Local<Value> value,const AccessorInfo & info)110 void nroSetter(Local<String>, Local<Value> value, const AccessorInfo& info)
111 {
112   NdbRecordObject * nro =
113     static_cast<NdbRecordObject *>(info.Holder()->GetPointerFromInternalField(1));
114   int nField = info.Data()->Int32Value();
115   nro->setField(nField, value);
116 }
117 
118 
119 /* Generic constructor wrapper.
120  * args[0]: row buffer
121  * args[1]: array of blob & text column values
122  * args.Data(): mapData holding the record and ColumnHandlers
123  * args.This(): VO built from the mapping-specific InstanceTemplate
124 */
nroConstructor(const Arguments & args)125 Handle<Value> nroConstructor(const Arguments &args) {
126   HandleScope scope;
127 
128   if(args.IsConstructCall()) {
129     /* Unwrap record from mapData */
130     Local<Object> mapData = args.Data()->ToObject();
131     const Record * record =
132       unwrapPointer<const Record *>(mapData->Get(0)->ToObject());
133 
134     /* Unwrap Column Handlers from mapData */
135     ColumnHandlerSet * handlers =
136       unwrapPointer<ColumnHandlerSet *>(mapData->Get(1)->ToObject());
137 
138     /* Build NdbRecordObject */
139     NdbRecordObject * nro = new NdbRecordObject(record, handlers, args[0], args[1]);
140 
141     /* Wrap for JavaScript */
142     wrapPointerInObject<NdbRecordObject *>(nro, nroEnvelope, args.This());
143     freeFromGC(nro, args.This());
144   }
145   else {
146     ThrowException(Exception::Error(String::New("must be a called as constructor")));
147   }
148   return args.This();
149 }
150 
151 
152 /* arg0: Record constructed over the appropriate column list
153    arg1: Array of field names
154    arg2: Array of typeConverters
155 
156    Returns: a constructor function that can be used to create native-backed
157    objects
158 */
getValueObjectConstructor(const Arguments & args)159 Handle<Value> getValueObjectConstructor(const Arguments &args) {
160   DEBUG_MARKER(UDEB_DEBUG);
161   HandleScope scope;
162   Local<FunctionTemplate> ft = FunctionTemplate::New();
163   Local<ObjectTemplate> inst = ft->InstanceTemplate();
164   inst->SetInternalFieldCount(2);
165 
166   /* Initialize the mapData */
167   Local<Object> mapData = Object::New();
168 
169   /* Store the record in the mapData at 0 */
170   mapData->Set(0, args[0]);
171 
172   /* Build the ColumnHandlers and store them in the mapData at 1 */
173   const Record * record = unwrapPointer<const Record *>(args[0]->ToObject());
174   const uint32_t ncol = record->getNoOfColumns();
175   ColumnHandlerSet *columnHandlers = new ColumnHandlerSet(ncol);
176   for(unsigned int i = 0 ; i < ncol ; i++) {
177     const NdbDictionary::Column * col = record->getColumn(i);
178     size_t offset = record->getColumnOffset(i);
179     ColumnHandler * handler = columnHandlers->getHandler(i);
180     handler->init(col, offset, args[2]->ToObject()->Get(i));
181   }
182   Local<Object> jsHandlerSet = columnHandlerSetEnvelope.newWrapper();
183   wrapPointerInObject<ColumnHandlerSet *>(columnHandlers,
184                                           columnHandlerSetEnvelope,
185                                           jsHandlerSet);
186   mapData->Set(1, jsHandlerSet);
187 
188   /* Create accessors for the mapped fields in the instance template.
189      AccessorInfo.Data() for the accessor will hold the field number.
190   */
191   Local<Object> jsFields = args[1]->ToObject();
192   for(unsigned int i = 0 ; i < ncol; i++) {
193     Handle<String> fieldName = jsFields->Get(i)->ToString();
194     inst->SetAccessor(fieldName, nroGetter, nroSetter, Number::New(i),
195                       DEFAULT, DontDelete);
196   }
197 
198   /* The generic constructor is the CallHandler */
199   ft->SetCallHandler(nroConstructor, Persistent<Object>::New(mapData));
200 
201   return scope.Close(ft->GetFunction());
202 }
203 
204 
isValueObject(const Arguments & args)205 Handle<Value> isValueObject(const Arguments &args) {
206   HandleScope scope;
207   bool answer = false;
208   Handle<Value> v = args[0];
209 
210   if(v->IsObject()) {
211     Local<Object> o = v->ToObject();
212     if(o->InternalFieldCount() == 2) {
213       Envelope * n = (Envelope *) o->GetPointerFromInternalField(0);
214       if(n == & nroEnvelope) {
215         answer = true;
216       }
217     }
218   }
219 
220   return scope.Close(Boolean::New(answer));
221 }
222 
223 
getValueObjectWriteCount(const Arguments & args)224 Handle<Value> getValueObjectWriteCount(const Arguments &args) {
225   HandleScope scope;
226   NdbRecordObject * nro = unwrapPointer<NdbRecordObject *>(args[0]->ToObject());
227   return scope.Close(Number::New(nro->getWriteCount()));
228 }
229 
230 
prepareForUpdate(const Arguments & args)231 Handle<Value> prepareForUpdate(const Arguments &args) {
232   HandleScope scope;
233   NdbRecordObject * nro = unwrapPointer<NdbRecordObject *>(args[0]->ToObject());
234   return scope.Close(nro->prepare());
235 }
236 
237 
ValueObject_initOnLoad(Handle<Object> target)238 void ValueObject_initOnLoad(Handle<Object> target) {
239   HandleScope scope;
240   DEFINE_JS_FUNCTION(target, "getValueObjectConstructor", getValueObjectConstructor);
241   DEFINE_JS_FUNCTION(target, "isValueObject", isValueObject);
242   DEFINE_JS_FUNCTION(target, "getValueObjectWriteCount", getValueObjectWriteCount);
243   DEFINE_JS_FUNCTION(target, "prepareForUpdate", prepareForUpdate);
244 }
245