1 /************ CMgoConn C++ Functions Source Code File (.CPP) ***********/
2 /*  Name: CMgoConn.CPP  Version 1.1                                    */
3 /*                                                                     */
4 /*  (C) Copyright to the author Olivier BERTRAND          2017 - 2021  */
5 /*                                                                     */
6 /*  This file contains the MongoDB C connection classes functions.     */
7 /***********************************************************************/
8 
9 /***********************************************************************/
10 /*  Include relevant MariaDB header file.                              */
11 /***********************************************************************/
12 #include <my_global.h>
13 
14 /***********************************************************************/
15 /*  Required objects includes.                                         */
16 /***********************************************************************/
17 #include "global.h"
18 #include "plgdbsem.h"
19 #include "colblk.h"
20 #include "xobject.h"
21 #include "xtable.h"
22 #include "filter.h"
23 #include "cmgoconn.h"
24 
25 bool CMgoConn::IsInit = false;
26 
27 bool IsArray(PSZ s);
28 bool MakeSelector(PGLOBAL g, PFIL fp, PSTRG s);
29 int  GetDefaultPrec(void);
30 
31 /* --------------------------- Class INCOL --------------------------- */
32 
33 /***********************************************************************/
34 /*  Add a column in the column list.                                   */
35 /***********************************************************************/
AddCol(PGLOBAL g,PCOL colp,char * jp)36 void INCOL::AddCol(PGLOBAL g, PCOL colp, char *jp)
37 {
38 	char *p;
39 	PKC   kp, kcp;
40 
41 	if ((p = strchr(jp, '.'))) {
42 		PINCOL icp;
43 
44 		*p++ = 0;
45 
46 		for (kp = Klist; kp; kp = kp->Next)
47 			if (kp->Incolp && !strcmp(jp, kp->Key))
48 				break;
49 
50 		if (!kp) {
51 			icp = new(g) INCOL();
52 			kcp = (PKC)PlugSubAlloc(g, NULL, sizeof(KEYCOL));
53 			kcp->Next = NULL;
54 			kcp->Incolp = icp;
55 			kcp->Colp = NULL;
56 			kcp->Key = PlugDup(g, jp);
57 			kcp->Array = IsArray(p);
58 
59 			if (Klist) {
60 				for (kp = Klist; kp->Next; kp = kp->Next);
61 
62 				kp->Next = kcp;
63 			} else
64 				Klist = kcp;
65 
66 		} else
67 			icp = kp->Incolp;
68 
69 		*(p - 1) = '.';
70 		icp->AddCol(g, colp, p);
71 	} else {
72 		kcp = (PKC)PlugSubAlloc(g, NULL, sizeof(KEYCOL));
73 
74 		kcp->Next = NULL;
75 		kcp->Incolp = NULL;
76 		kcp->Colp = colp;
77 		kcp->Key = jp;
78 		kcp->Array = IsArray(jp);
79 
80 		if (Klist) {
81 			for (kp = Klist; kp->Next; kp = kp->Next);
82 
83 			kp->Next = kcp;
84 		} else
85 			Klist = kcp;
86 
87 	} // endif jp
88 
89 }	// end of AddCol
90 
91 /***********************************************************************/
92 /*  Clear.                                                             */
93 /***********************************************************************/
Init(void)94 void INCOL::Init(void)
95 {
96 	bson_init(Child);
97 
98 	for (PKC kp = Klist; kp; kp = kp->Next)
99 		if (kp->Incolp)
100 			kp->Incolp->Init();
101 
102 } // end of init
103 
104 /***********************************************************************/
105 /*  Destroy.                                                           */
106 /***********************************************************************/
Destroy(void)107 void INCOL::Destroy(void)
108 {
109 	bson_destroy(Child);
110 
111 	for (PKC kp = Klist; kp; kp = kp->Next)
112 		if (kp->Incolp)
113 			kp->Incolp->Destroy();
114 
115 } // end of Destroy
116 
117 /* -------------------------- Class CMgoConn ------------------------- */
118 
119 /***********************************************************************/
120 /*  Implementation of the CMgoConn class.                              */
121 /***********************************************************************/
CMgoConn(PGLOBAL g,PCPARM pcg)122 CMgoConn::CMgoConn(PGLOBAL g, PCPARM pcg)
123 {
124 	Pcg = pcg;
125 	Uri = NULL;
126 //Pool = NULL;
127 	Client = NULL;
128 	Database = NULL;
129 	Collection = NULL;
130 	Cursor = NULL;
131 	Document = NULL;
132 	Query = NULL;
133 	Opts = NULL;
134 	Fpc = NULL;
135 	fp = NULL;
136 	m_Connected = false;
137 } // end of CMgoConn standard constructor
138 
139 /***********************************************************************/
140 /*  Required to initialize libmongoc's internals.											 */
141 /***********************************************************************/
mongo_init(bool init)142 void CMgoConn::mongo_init(bool init)
143 {
144 	if (init)
145 		mongoc_init();
146 	else if (IsInit)
147 		mongoc_cleanup();
148 
149 	IsInit = init;
150 }	// end of mongo_init
151 
152 /***********************************************************************/
153 /*  Connect to the MongoDB server and get the collection.              */
154 /***********************************************************************/
Connect(PGLOBAL g)155 bool CMgoConn::Connect(PGLOBAL g)
156 {
157 	if (!Pcg->Db_name || !Pcg->Coll_name) {
158 		// This would crash in mongoc_client_get_collection
159 		strcpy(g->Message, "Missing DB or collection name");
160 		return true;
161 	}	// endif name
162 
163 	if (!IsInit)
164 #if defined(_WIN32)
165 		__try {
166 		  mongo_init(true);
167 	  } __except (EXCEPTION_EXECUTE_HANDLER) {
168 		  strcpy(g->Message, "Cannot load MongoDB C driver");
169 		  return true;
170 	  }	// end try/except
171 #else   // !_WIN32
172 		mongo_init(true);
173 #endif  // !_WIN32
174 
175 	Uri = mongoc_uri_new_with_error(Pcg->Uristr, &Error);
176 
177 	if (!Uri) {
178 		sprintf(g->Message, "Failed to parse URI: \"%s\" Msg: %s",
179 			Pcg->Uristr, Error.message);
180 		return true;
181 	}	// endif Uri
182 
183 #if 0
184 	// Create a new client pool instance
185 	Pool = mongoc_client_pool_new(Uri);
186 	mongoc_client_pool_set_error_api(Pool, 2);
187 
188 	// Register the application name so we can track it in the profile logs
189 	// on the server. This can also be done from the URI.
190 	mongoc_client_pool_set_appname(Pool, "Connect");
191 
192 	// Create a new client instance
193 	Client = mongoc_client_pool_pop(Pool);
194 #else
195 	// Create a new client instance
196 	Client = mongoc_client_new_from_uri (Uri);
197 
198 	if (!Client) {
199 		sprintf(g->Message, "Failed to get Client");
200 		return true;
201 	}	// endif Client
202 
203 	// Register the application name so we can track it in the profile logs
204 	// on the server. This can also be done from the URI (see other examples).
205 	mongoc_client_set_appname (Client, "Connect");
206 
207 	// Get a handle on the database
208 	// Database = mongoc_client_get_database (Client, Pcg->Db_name);
209 #endif // 0
210 
211 	// Get a handle on the collection
212 	Collection = mongoc_client_get_collection(Client, Pcg->Db_name, Pcg->Coll_name);
213 
214 	if (!Collection) {
215 		sprintf(g->Message, "Failed to get Collection %s.%s",
216 			      Pcg->Db_name, Pcg->Coll_name);
217 		return true;
218 	}	// endif Collection
219 
220 	/*********************************************************************/
221 	/*  Link a Fblock. This make possible to automatically close it      */
222 	/*  in case of error (throw).                                        */
223 	/*********************************************************************/
224 	PDBUSER dbuserp = (PDBUSER)g->Activityp->Aptr;
225 
226 	fp = (PFBLOCK)PlugSubAlloc(g, NULL, sizeof(FBLOCK));
227 	fp->Type = TYPE_FB_MONGO;
228 	fp->Fname = NULL;
229 	fp->Next = dbuserp->Openlist;
230 	dbuserp->Openlist = fp;
231 	fp->Count = 1;
232 	fp->Length = 0;
233 	fp->Memory = NULL;
234 	fp->Mode = MODE_ANY;
235 	fp->File = this;
236 	fp->Handle = 0;
237 
238 	m_Connected = true;
239 	return false;
240 } // end of Connect
241 
242 /***********************************************************************/
243 /*  CollSize: returns the number of documents in the collection.       */
244 /***********************************************************************/
CollSize(PGLOBAL g)245 int CMgoConn::CollSize(PGLOBAL g)
246 {
247 	int         cnt;
248 	bson_t* query;
249 	const char* jf = NULL;
250 
251 	if (Pcg->Pipe)
252 		return 10;
253 	else if (Pcg->Filter)
254 		jf = Pcg->Filter;
255 
256 	if (jf) {
257 		query = bson_new_from_json((const uint8_t*)jf, -1, &Error);
258 
259 		if (!query) {
260 			htrc("Wrong filter: %s", Error.message);
261 			return 10;
262 		}	// endif Query
263 
264 	} else
265 		query = bson_new();
266 
267 #if defined(DEVELOPMENT)
268 	if (jf)
269 		cnt = (int)mongoc_collection_count_documents(Collection,
270 			query, NULL, NULL, NULL, &Error);
271 	else
272 		cnt = (int)mongoc_collection_estimated_document_count(
273 			Collection, NULL, NULL, NULL, &Error);
274 #else
275 	cnt = (int)mongoc_collection_count(Collection,
276 		MONGOC_QUERY_NONE, query, 0, 0, NULL, &Error);
277 #endif
278 
279 	if (cnt < 0) {
280 		htrc("Collection count: %s", Error.message);
281 		cnt = 2;
282 	} // endif Cardinal
283 
284 	bson_destroy(query);
285 	return cnt;
286 } // end of CollSize
287 
288 /***********************************************************************/
289 /*  Project: make the projection avoid path collision.                 */
290 /***********************************************************************/
Project(PGLOBAL g,PSTRG s)291 void CMgoConn::Project(PGLOBAL g, PSTRG s)
292 {
293 	bool   m, b = false;
294 	size_t n;
295 	PSZ    path;
296 	PCOL   cp;
297 	PTDB   tp = Pcg->Tdbp;
298 	PTHP   hp, php = NULL, * nphp = &php;
299 
300 	for (cp = tp->GetColumns(); cp; cp = cp->GetNext()) {
301 		path = cp->GetJpath(g, true);
302 
303 		// Resolve path collision
304 		for (hp = php; hp; hp = hp->Next) {
305 			if (strlen(path) < strlen(hp->Path)) {
306 				n = strlen(path);
307 				m = true;
308 			} else {
309 				n = strlen(hp->Path);
310 				m = false;
311 			} // endif path
312 
313 			if (!strncmp(path, hp->Path, n))
314 				break;
315 
316 		}	// endfor hp
317 
318 		if (!hp) {
319 			// New path
320 			hp = (PTHP)PlugSubAlloc(g, NULL, sizeof(PTH));
321 			hp->Path = path;
322 			hp->Name = cp->GetName();
323 			hp->Next = NULL;
324 			*nphp = hp;
325 			nphp = &hp->Next;
326 		} else if (m)  // Smaller path must replace longer one
327 			hp->Path = path;
328 
329 	} // endfor cp
330 
331 	for (hp = php; hp; hp = hp->Next) {
332 		if (b)
333 			s->Append(",\"");
334 		else
335 			b = true;
336 
337 		if (*hp->Path == '{') {
338 			// This is a Mongo defined column
339 			s->Append(hp->Name);
340 			s->Append("\":");
341 			s->Append(hp->Path);
342 		} else {
343 			s->Append(hp->Path);
344 			s->Append("\":1");
345 		}	// endif Path
346 
347 	} // endfor hp
348 
349 } // end of Project
350 
351 /***********************************************************************/
352 /*  MakeCursor: make the cursor used to retrieve documents.            */
353 /***********************************************************************/
MakeCursor(PGLOBAL g)354 bool CMgoConn::MakeCursor(PGLOBAL g)
355 {
356 	const char *p;
357 	bool  id, all = false;
358 	PCSZ  options = Pcg->Options;
359 	PTDB  tp = Pcg->Tdbp;
360 	PCOL  cp;
361 	PSTRG s = NULL;
362 	PFIL  filp = tp->GetFilter();
363 
364 	id = (tp->GetMode() == MODE_UPDATE || tp->GetMode() == MODE_DELETE);
365 
366 	if (options && !stricmp(options, "all")) {
367 		options = NULL;
368 		all = true;
369 	} else for (cp = tp->GetColumns(); cp && !all; cp = cp->GetNext())
370 		if (cp->GetFmt() && !strcmp(cp->GetFmt(), "*") && !options)
371 			all = true;
372 		else if (!id)
373 			id = !strcmp(cp->GetFmt() ? cp->GetFmt() : cp->GetName(), "_id");
374 
375 	if (Pcg->Pipe) {
376 		if (trace(1))
377 			htrc("Pipeline: %s\n", options);
378 
379 		p = strrchr(options, ']');
380 
381 		if (!p) {
382 			strcpy(g->Message, "Missing ] in pipeline");
383 			return true;
384 		} else
385 			*(char*)p = 0;
386 
387 		s = new(g) STRING(g, 1023, (PSZ)options);
388 
389 		if (filp) {
390 			s->Append(",{\"$match\":");
391 
392 			if (MakeSelector(g, filp, s)) {
393 				strcpy(g->Message, "Failed making selector");
394 				return true;
395 			} else
396 				s->Append('}');
397 
398 			tp->SetFilter(NULL);   // Not needed anymore
399 		} // endif To_Filter
400 
401 		if (tp->GetColumns() && !strstr(s->GetStr(), "$project")) {
402 			// Project list
403 			s->Append(",{\"$project\":{\"");
404 
405 			if (!id)
406 				s->Append("_id\":0,\"");
407 
408 			Project(g, s);
409 			s->Append("}}");
410 		} // endif all
411 
412 		s->Append("]}");
413 		s->Resize(s->GetLength() + 1);
414 		*(char*)p = ']';		 // Restore Colist for discovery
415 		p = s->GetStr();
416 
417 		if (trace(33))
418 			htrc("New Pipeline: %s\n", p);
419 
420 		Query = bson_new_from_json((const uint8_t *)p, -1, &Error);
421 
422 		if (!Query) {
423 			sprintf(g->Message, "Wrong pipeline: %s", Error.message);
424 			return true;
425 		}	// endif Query
426 
427 		Cursor = mongoc_collection_aggregate(Collection, MONGOC_QUERY_NONE,
428 			                                   Query, NULL, NULL);
429 
430 		if (mongoc_cursor_error(Cursor, &Error)) {
431 			sprintf(g->Message, "Mongo aggregate Failure: %s", Error.message);
432 			return true;
433 		} // endif error
434 
435 	} else {
436 		if (Pcg->Filter || filp) {
437 			if (trace(1)) {
438 				if (Pcg->Filter)
439 					htrc("Filter: %s\n", Pcg->Filter);
440 
441 				if (filp) {
442 					char buf[512];
443 
444 					filp->Prints(g, buf, 511);
445 					htrc("To_Filter: %s\n", buf);
446 				} // endif To_Filter
447 
448 			}	// endif trace
449 
450 			s = new(g) STRING(g, 1023, (PSZ)Pcg->Filter);
451 
452 			if (filp) {
453 				if (Pcg->Filter)
454 					s->Append(',');
455 
456 				if (MakeSelector(g, filp, s)) {
457 					strcpy(g->Message, "Failed making selector");
458 					return true;
459 				}	// endif Selector
460 
461 				tp->SetFilter(NULL);   // Not needed anymore
462 			} // endif To_Filter
463 
464 			if (trace(33))
465 				htrc("selector: %s\n", s->GetStr());
466 
467 			s->Resize(s->GetLength() + 1);
468 			Query = bson_new_from_json((const uint8_t *)s->GetStr(), -1, &Error);
469 
470 			if (!Query) {
471 				sprintf(g->Message, "Wrong filter: %s", Error.message);
472 				return true;
473 			}	// endif Query
474 
475 		} else
476 			Query = bson_new();
477 
478 		if (!all) {
479 			if (options && *options) {
480 				if (trace(1))
481 					htrc("options=%s\n", options);
482 
483 				p = options;
484 			} else if (tp->GetColumns()) {
485 				// Projection list
486 				if (s)
487 					s->Set("{\"projection\":{\"");
488 				else
489 					s = new(g) STRING(g, 511, "{\"projection\":{\"");
490 
491 				if (!id)
492 					s->Append("_id\":0,\"");
493 
494 				Project(g, s);
495 				s->Append("}}");
496 				s->Resize(s->GetLength() + 1);
497 				p = s->GetStr();
498 			} else {
499 				// count(*)	?
500 				p = "{\"projection\":{\"_id\":1}}";
501 			} // endif Options
502 
503 			Opts = bson_new_from_json((const uint8_t *)p, -1, &Error);
504 
505 			if (!Opts) {
506 				sprintf(g->Message, "Wrong options: %s", Error.message);
507 				return true;
508 			} // endif Opts
509 
510 		} // endif all
511 
512 		Cursor = mongoc_collection_find_with_opts(Collection, Query, Opts, NULL);
513 	} // endif Pipe
514 
515 	return false;
516 } // end of MakeCursor
517 
518 /***********************************************************************/
519 /*  Fetch next document.                                               */
520 /***********************************************************************/
ReadNext(PGLOBAL g)521 int CMgoConn::ReadNext(PGLOBAL g)
522 {
523 	int rc = RC_OK;
524 
525 	if (!Cursor && MakeCursor(g)) {
526 		rc = RC_FX;
527 	} else if (mongoc_cursor_next(Cursor, &Document)) {
528 		if (trace(512)) {
529 			bson_iter_t iter;
530 			ShowDocument(&iter, Document, "");
531 		} else if (trace(1))
532 			htrc("%s\n", GetDocument(g));
533 
534 	} else if (mongoc_cursor_error(Cursor, &Error)) {
535 		sprintf(g->Message, "Mongo Cursor Failure: %s", Error.message);
536 		rc = RC_FX;
537 	} else
538 		rc = RC_EF;
539 
540 	return rc;
541 } // end of Fetch
542 
543 /***********************************************************************/
544 /*  Get the Json string of the current document.                       */
545 /***********************************************************************/
GetDocument(PGLOBAL g)546 PSZ CMgoConn::GetDocument(PGLOBAL g)
547 {
548 	char *str = bson_as_json(Document, NULL);
549 	PSZ   doc = PlugDup(g, str);
550 
551 	bson_free(str);
552 	return doc;
553 } // end of GetDocument
554 
555 /***********************************************************************/
556 /*  Use to trace restaurants document contains.                        */
557 /***********************************************************************/
ShowDocument(bson_iter_t * iter,const bson_t * doc,const char * k)558 void CMgoConn::ShowDocument(bson_iter_t *iter, const bson_t *doc, const char *k)
559 {
560 	if (!doc || bson_iter_init(iter, doc)) {
561 		const char *key;
562 
563 		while (bson_iter_next(iter)) {
564 			key = bson_iter_key(iter);
565 			htrc("Found element key: \"%s\"\n", key);
566 
567 			switch (bson_iter_type(iter)) {
568 				case BSON_TYPE_UTF8:
569 					htrc("%s.%s=\"%s\"\n", k, key, bson_iter_utf8(iter, NULL));
570 					break;
571 				case BSON_TYPE_INT32:
572 					htrc("%s.%s=%d\n", k, key, bson_iter_int32(iter));
573 					break;
574 				case BSON_TYPE_INT64:
575 					htrc("%s.%s=%lld\n", k, key, bson_iter_int64(iter));
576 					break;
577 				case BSON_TYPE_DOUBLE:
578 					htrc("%s.%s=%g\n", k, key, bson_iter_double(iter));
579 					break;
580 				case BSON_TYPE_DATE_TIME:
581 					htrc("%s.%s=date(%lld)\n", k, key, bson_iter_date_time(iter));
582 					break;
583 				case BSON_TYPE_OID: {
584 					char str[25];
585 
586 					bson_oid_to_string(bson_iter_oid(iter), str);
587 					htrc("%s.%s=%s\n", k, key, str);
588 				} break;
589 				case BSON_TYPE_DECIMAL128: {
590 					char str[BSON_DECIMAL128_STRING];
591 					bson_decimal128_t dec;
592 
593 					bson_iter_decimal128(iter, &dec);
594 					bson_decimal128_to_string(&dec, str);
595 					htrc("%s.%s=%s\n", k, key, str);
596 				} break;
597 				case BSON_TYPE_DOCUMENT: {
598 					bson_iter_t child;
599 
600 					if (bson_iter_recurse(iter, &child))
601 						ShowDocument(&child, NULL, key);
602 
603 				} break;
604 				case BSON_TYPE_ARRAY: {
605 					bson_t* arr;
606 					bson_iter_t    itar;
607 					const uint8_t* data = NULL;
608 					uint32_t       len = 0;
609 
610 					bson_iter_array(iter, &len, &data);
611 					arr = bson_new_from_data(data, len);
612 					ShowDocument(&itar, arr, key);
613 				} break;
614 			}	// endswitch iter
615 
616 		}	// endwhile bson_iter_next
617 
618 	} // endif bson_iter_init
619 
620 } // end of ShowDocument
621 
622 /***********************************************************************/
623 /*  Group columns for inserting or updating.                           */
624 /***********************************************************************/
MakeColumnGroups(PGLOBAL g)625 void CMgoConn::MakeColumnGroups(PGLOBAL g)
626 {
627 	Fpc = new(g) INCOL();
628 
629 	for (PCOL colp = Pcg->Tdbp->GetColumns(); colp; colp = colp->GetNext())
630 		if (!colp->IsSpecial())
631 			Fpc->AddCol(g, colp, colp->GetJpath(g, false));
632 
633 } // end of MakeColumnGroups
634 
635 /***********************************************************************/
636 /*  DocWrite.                                                          */
637 /***********************************************************************/
DocWrite(PGLOBAL g,PINCOL icp)638 bool CMgoConn::DocWrite(PGLOBAL g, PINCOL icp)
639 {
640 	for (PKC kp = icp->Klist; kp; kp = kp->Next)
641 		if (kp->Incolp) {
642 			bool isdoc = !kp->Array;
643 
644 			if (isdoc)
645 				BSON_APPEND_DOCUMENT_BEGIN(icp->Child, kp->Key, kp->Incolp->Child);
646 			else
647 				BSON_APPEND_ARRAY_BEGIN(icp->Child, kp->Key, kp->Incolp->Child);
648 
649 			if (DocWrite(g, kp->Incolp))
650 				return true;
651 
652 			if (isdoc)
653 				bson_append_document_end(icp->Child, kp->Incolp->Child);
654 			else
655 				bson_append_array_end(icp->Child, kp->Incolp->Child);
656 
657 		} else if (AddValue(g, kp->Colp, icp->Child, kp->Key, false))
658 			return true;
659 
660 		return false;
661 } // end of DocWrite
662 
663 /***********************************************************************/
664 /*  WriteDB: Data Base write routine for CMGO access method.           */
665 /***********************************************************************/
Write(PGLOBAL g)666 int CMgoConn::Write(PGLOBAL g)
667 {
668 	int  rc = RC_OK;
669 	PTDB tp = Pcg->Tdbp;
670 
671 	if (tp->GetMode() == MODE_INSERT) {
672 		if (!Pcg->Line) {
673 			Fpc->Init();
674 
675 			if (DocWrite(g, Fpc))
676 				return RC_FX;
677 
678 			if (trace(2)) {
679 				char* str = bson_as_json(Fpc->Child, NULL);
680 				htrc("Inserting: %s\n", str);
681 				bson_free(str);
682 			} // endif trace
683 
684 			if (!mongoc_collection_insert(Collection, MONGOC_INSERT_NONE,
685 				                            Fpc->Child, NULL, &Error)) {
686 				sprintf(g->Message, "Mongo insert: %s", Error.message);
687 				rc = RC_FX;
688 			} // endif insert
689 
690 		} else {
691 			const uint8_t* val = (const uint8_t*)Pcg->Line;
692 			bson_t* doc = bson_new_from_json(val, -1, &Error);
693 
694 			if (doc && trace(2)) {
695 				char* str = bson_as_json(doc, NULL);
696 				htrc("Inserting: %s\n", str);
697 				bson_free(str);
698 			} // endif trace
699 
700 			if (!doc) {
701 				sprintf(g->Message, "bson_new_from_json: %s", Error.message);
702 				rc = RC_FX;
703 			}	else if (!mongoc_collection_insert(Collection,
704 				         MONGOC_INSERT_NONE, doc, NULL, &Error)) {
705 				sprintf(g->Message, "Mongo insert: %s", Error.message);
706 				bson_destroy(doc);
707 				rc = RC_FX;
708 			} // endif insert
709 
710 		} // endif Line
711 
712 	} else {
713 		bool        b = false;
714 		bson_iter_t iter;
715 		bson_t     *query = bson_new();
716 
717 		bson_iter_init(&iter, Document);
718 
719 		if (bson_iter_find(&iter, "_id"))
720 			switch (bson_iter_type(&iter)) {
721 				case BSON_TYPE_OID:
722 					b = BSON_APPEND_OID(query, "_id", bson_iter_oid(&iter));
723 					break;
724 				case BSON_TYPE_UTF8:
725 					b = BSON_APPEND_UTF8(query, "_id", bson_iter_utf8(&iter, NULL));
726 					break;
727 				case BSON_TYPE_INT32:
728 					b = BSON_APPEND_INT32(query, "_id", bson_iter_int32(&iter));
729 					break;
730 				case BSON_TYPE_INT64:
731 					b = BSON_APPEND_INT64(query, "_id", bson_iter_int64(&iter));
732 					break;
733 				case BSON_TYPE_DOUBLE:
734 					b = BSON_APPEND_DOUBLE(query, "_id", bson_iter_double(&iter));
735 					break;
736 				default:
737 					break;
738 			} // endswitch iter
739 
740 		if (b) {
741 			if (trace(2)) {
742 				char *str = bson_as_json(query, NULL);
743 				htrc("update query: %s\n", str);
744 				bson_free(str);
745 			}	// endif trace
746 
747 			if (tp->GetMode() == MODE_UPDATE) {
748 				bson_t  child;
749 				bson_t *update = bson_new();
750 
751 				BSON_APPEND_DOCUMENT_BEGIN(update, "$set", &child);
752 
753 				for (PCOL colp = tp->GetSetCols(); colp; colp = colp->GetNext())
754 					if (AddValue(g, colp, &child, colp->GetJpath(g, false), true))
755 						rc = RC_FX;
756 
757 				bson_append_document_end(update, &child);
758 
759 				if (rc == RC_OK)
760 					if (!mongoc_collection_update(Collection, MONGOC_UPDATE_NONE,
761 						query, update, NULL, &Error)) {
762 						sprintf(g->Message, "Mongo update: %s", Error.message);
763 						rc = RC_FX;
764 					} // endif update
765 
766 				bson_destroy(update);
767 			} else if (!mongoc_collection_remove(Collection,
768 				MONGOC_REMOVE_SINGLE_REMOVE, query, NULL, &Error)) {
769 				sprintf(g->Message, "Mongo delete: %s", Error.message);
770 				rc = RC_FX;
771 			} // endif remove
772 
773 		} else {
774 			strcpy(g->Message, "Mongo update: cannot find _id");
775 			rc = RC_FX;
776 		}	// endif b
777 
778 		bson_destroy(query);
779 	} // endif Mode
780 
781 	return rc;
782 } // end of Write
783 
784 /***********************************************************************/
785 /*  Remove all documents from the collection.                          */
786 /***********************************************************************/
DocDelete(PGLOBAL g)787 bool CMgoConn::DocDelete(PGLOBAL g)
788 {
789 	Query = bson_new();
790 
791 	if (!mongoc_collection_remove(Collection, MONGOC_REMOVE_NONE,
792 		                            Query, NULL, &Error)) {
793 		sprintf(g->Message, "Mongo remove all: %s", Error.message);
794 		return true;
795 	}	// endif remove
796 
797 	return false;
798 } // end of DocDelete
799 
800 /***********************************************************************/
801 /*  Rewind the collection.                                             */
802 /***********************************************************************/
Rewind(void)803 void CMgoConn::Rewind(void)
804 {
805 	mongoc_cursor_t *cursor = mongoc_cursor_clone(Cursor);
806 
807 	mongoc_cursor_destroy(Cursor);
808 	Cursor = cursor;
809 } // end of Rewind
810 
811 /***********************************************************************/
812 /*  Table close routine for MONGO tables.                              */
813 /***********************************************************************/
Close(void)814 void CMgoConn::Close(void)
815 {
816 	if (Query) bson_destroy(Query);
817 	if (Opts) bson_destroy(Opts);
818 	if (Cursor)	mongoc_cursor_destroy(Cursor);
819 	if (Collection) mongoc_collection_destroy(Collection);
820 //if (Client) mongoc_client_pool_push(Pool, Client);
821 //if (Pool) mongoc_client_pool_destroy(Pool);
822 	if (Client) mongoc_client_destroy(Client);
823 	if (Uri) mongoc_uri_destroy(Uri);
824 	if (Fpc) Fpc->Destroy();
825 	if (fp) fp->Count = 0;
826 } // end of Close
827 
828 /***********************************************************************/
829 /*  Mini: used to suppress blanks to json strings.                     */
830 /***********************************************************************/
Mini(PGLOBAL g,PCOL colp,const bson_t * bson,bool b)831 char *CMgoConn::Mini(PGLOBAL g, PCOL colp, const bson_t *bson, bool b)
832 {
833 	char  *s, *str = NULL;
834 	char  *Mbuf = (char*)PlugSubAlloc(g, NULL, (size_t)colp->GetLength() + 1);
835 	int    i, j = 0, k = 0, n = 0, m = GetDefaultPrec();
836 	bool   ok = true, dbl = false;
837 	double d;
838 	size_t len;
839 
840 	if (b)
841 		s = str = bson_array_as_json(bson, &len);
842 	else
843 		s = str = bson_as_json(bson, &len);
844 
845 	if (len > (size_t)colp->GetLength()) {
846 		sprintf(g->Message, "Value too long for column %s", colp->GetName());
847 		bson_free(str);
848 		throw (int)TYPE_AM_MGO;
849 	}	// endif len
850 
851 	for (i = 0; i < colp->GetLength() && s[i]; i++) {
852 		switch (s[i]) {
853 			case ' ':
854 				if (ok) continue;
855 				break;
856 			case '"':
857 				ok = !ok;
858 				break;
859 			case '.':
860 				if (j) dbl = true;
861 				break;
862 			default:
863 				if (ok) {
864 					if (isdigit(s[i])) {
865 						if (!j) j = k;
866 						if (dbl) n++;
867 					} else if (dbl && n > m) {
868 						Mbuf[k] = 0;
869 						d = atof(Mbuf + j);
870 						n = sprintf(Mbuf + j, "%.*f", m, d);
871 						k = j + n;
872 						j = n = 0;
873 					} else if (j)
874 						j = n = 0;
875 
876 				} // endif ok
877 
878 				break;
879 		} // endswitch s[i]
880 
881 		Mbuf[k++] = s[i];
882 	} // endfor i
883 
884 	bson_free(str);
885 
886 	Mbuf[k] = 0;
887 	return Mbuf;
888 } // end of Mini
889 
890 /***********************************************************************/
891 /*  Retrieve the column value from the document.                       */
892 /***********************************************************************/
GetColumnValue(PGLOBAL g,PCOL colp)893 void CMgoConn::GetColumnValue(PGLOBAL g, PCOL colp)
894 {
895 	char       *jpath = colp->GetJpath(g, false);
896 	bool        b = false;
897 	PVAL        value = colp->GetValue();
898 	bson_iter_t Iter;				    // Used to retrieve column value
899 	bson_iter_t Desc;				    // Descendant iter
900 
901 	if (*jpath == '{')
902 		jpath = colp->GetName();  // This is a Mongo defined column
903 
904 	if (!*jpath || !strcmp(jpath, "*")) {
905 		value->SetValue_psz(Mini(g, colp, Document, false));
906 	} else if (bson_iter_init(&Iter, Document) &&
907 		         bson_iter_find_descendant(&Iter, jpath, &Desc)) {
908 		switch (bson_iter_type(&Desc)) {
909 			case BSON_TYPE_UTF8:
910 				value->SetValue_psz((PSZ)bson_iter_utf8(&Desc, NULL));
911 				break;
912 			case BSON_TYPE_INT32:
913 				value->SetValue(bson_iter_int32(&Desc));
914 				break;
915 			case BSON_TYPE_INT64:
916 				value->SetValue(bson_iter_int64(&Desc));
917 				break;
918 			case BSON_TYPE_DOUBLE:
919 				value->SetValue(bson_iter_double(&Desc));
920 				break;
921 			case BSON_TYPE_DATE_TIME:
922 				value->SetValue(bson_iter_date_time(&Desc) / 1000);
923 				break;
924 			case BSON_TYPE_BOOL:
925 				b = bson_iter_bool(&Desc);
926 
927 				if (value->IsTypeNum())
928 					value->SetValue(b ? 1 : 0);
929 				else
930 					value->SetValue_psz(b ? "true" : "false");
931 
932 				break;
933 			case BSON_TYPE_OID: {
934 				char str[25];
935 
936 				bson_oid_to_string(bson_iter_oid(&Desc), str);
937 				value->SetValue_psz(str);
938 			}	break;
939 			case BSON_TYPE_ARRAY:
940 				b = true;
941 				// passthru
942 			case BSON_TYPE_DOCUMENT:
943 			{	 // All this because MongoDB can return the wrong type
944 				int            i = 0;
945 				const uint8_t *data = NULL;
946 				uint32_t       len = 0;
947 
948 				for (; i < 2; i++) {
949 					if (b) // Try array first
950 						bson_iter_array(&Desc, &len, &data);
951 					else
952 						bson_iter_document(&Desc, &len, &data);
953 
954 					if (!data) {
955 						len = 0;
956 						b = !b;
957 					} else
958 						break;
959 
960 				} // endfor i
961 
962 				if (data) {
963 					bson_t *doc = bson_new_from_data(data, len);
964 
965 					value->SetValue_psz(Mini(g, colp, doc, b));
966 					bson_destroy(doc);
967 				} else {
968 					// ... or we can also come here in case of NULL!
969 					value->Reset();
970 					value->SetNull(true);
971 				} // endif data
972 
973 			} break;
974 			case BSON_TYPE_NULL:
975 				// Apparently this does not work...
976 				value->Reset();
977 				value->SetNull(true);
978 				break;
979 			case BSON_TYPE_DECIMAL128: {
980 				char str[BSON_DECIMAL128_STRING];
981 				bson_decimal128_t dec;
982 
983 				bson_iter_decimal128(&Desc, &dec);
984 				bson_decimal128_to_string(&dec, str);
985 				value->SetValue_psz(str);
986 //			bson_free(str);
987 			} break;
988 			default:
989 				value->Reset();
990 				break;
991 		} // endswitch Desc
992 
993 	} else {
994 		// Field does not exist
995 		value->Reset();
996 		value->SetNull(true);
997 	} // endif Iter
998 
999 } // end of GetColumnValue
1000 
1001 /***********************************************************************/
1002 /*  AddValue: Add column value to the document to insert or update.    */
1003 /***********************************************************************/
AddValue(PGLOBAL g,PCOL colp,bson_t * doc,char * key,bool upd)1004 bool CMgoConn::AddValue(PGLOBAL g, PCOL colp, bson_t *doc, char *key, bool upd)
1005 {
1006 	bool rc = false;
1007 	PVAL value = colp->GetValue();
1008 
1009 	if (value->IsNull()) {
1010 //		if (upd)
1011 			rc = BSON_APPEND_NULL(doc, key);
1012 //		else
1013 //			return false;
1014 
1015 	} else switch (colp->GetResultType()) {
1016 		case TYPE_STRING:
1017 			if (colp->Stringify()) {
1018 				const uint8_t *val = (const uint8_t*)value->GetCharValue();
1019 				bson_t        *bsn = bson_new_from_json(val, -1, &Error);
1020 
1021 				if (!bsn) {
1022 					sprintf (g->Message, "AddValue: %s", Error.message);
1023 					return true;
1024 				} else if (*key) {
1025 					if (*val == '[')
1026 						rc = BSON_APPEND_ARRAY(doc, key, bsn);
1027 					else
1028 						rc = BSON_APPEND_DOCUMENT(doc, key, bsn);
1029 
1030 				} else {
1031 					bson_copy_to (bsn, doc);
1032 					rc = true;
1033 				} // endif's
1034 
1035 				bson_free(bsn);
1036 			}	else
1037 				rc = BSON_APPEND_UTF8(doc, key, value->GetCharValue());
1038 
1039 			break;
1040 		case TYPE_INT:
1041 		case TYPE_SHORT:
1042 			rc = BSON_APPEND_INT32(doc, key, value->GetIntValue());
1043 			break;
1044 		case TYPE_TINY:
1045 			rc = BSON_APPEND_BOOL(doc, key, value->GetIntValue());
1046 			break;
1047 		case TYPE_BIGINT:
1048 			rc = BSON_APPEND_INT64(doc, key, value->GetBigintValue());
1049 			break;
1050 		case TYPE_DOUBLE:
1051 			rc = BSON_APPEND_DOUBLE(doc, key, value->GetFloatValue());
1052 			break;
1053 		case TYPE_DECIM:
1054 		{bson_decimal128_t dec;
1055 
1056 		if (bson_decimal128_from_string(value->GetCharValue(), &dec))
1057 			rc = BSON_APPEND_DECIMAL128(doc, key, &dec);
1058 
1059 		} break;
1060 		case TYPE_DATE:
1061 			rc = BSON_APPEND_DATE_TIME(doc, key, value->GetBigintValue() * 1000);
1062 			break;
1063 		default:
1064 			sprintf(g->Message, "Type %d not supported yet", colp->GetResultType());
1065 			return true;
1066 	} // endswitch Buf_Type
1067 
1068 	if (!rc) {
1069 		strcpy(g->Message, "Adding value failed");
1070 		return true;
1071 	} else
1072 		return false;
1073 
1074 } // end of AddValue
1075 
1076 #if 0
1077 void *CMgoConn::mgo_alloc(size_t n)
1078 {
1079 	char *mst = (char*)PlgDBSubAlloc(G, NULL, n + sizeof(size_t));
1080 
1081 	if (mst) {
1082 		*(size_t*)mst = n;
1083 		return mst + sizeof(size_t);
1084 	} // endif mst
1085 
1086 	return NULL;
1087 } // end of mgo_alloc
1088 
1089 void *CMgoConn::mgo_calloc(size_t n, size_t sz)
1090 {
1091 	void *m = mgo_alloc(n * sz);
1092 
1093 	if (m)
1094 		memset(m, 0, n * sz);
1095 
1096 	return m;
1097 } // end of mgo_calloc
1098 
1099 void *CMgoConn::mgo_realloc(void *m, size_t n)
1100 {
1101 	if (!m)
1102 		return n ? mgo_alloc(n) : NULL;
1103 
1104 	size_t *osz = (size_t*)((char*)m - sizeof(size_t));
1105 
1106 	if (n > *osz) {
1107 		void *nwm = mgo_alloc(n);
1108 
1109 		if (nwm)
1110 			memcpy(nwm, m, *osz);
1111 
1112 		return nwm;
1113 	} else {
1114 		*osz = n;
1115 		return m;
1116 	}	// endif n
1117 
1118 } // end of mgo_realloc
1119 #endif // 0
1120 
1121