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