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