transactd 3.6.1 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/bin/common/{tdclc_32_3_6.dll → tdclc_32_3_7.dll} +0 -0
  3. data/bin/common/{tdclc_64_3_6.dll → tdclc_64_3_7.dll} +0 -0
  4. data/build/swig/ruby/tdclrb_wrap.cpp +125 -17
  5. data/build/tdclc/tdclc.cbproj +1 -1
  6. data/build/tdclc/tdclc.rc +4 -4
  7. data/build/tdclcpp/tdclcpp.rc +4 -4
  8. data/build/tdclcpp/tdclcpp_bc.cbproj +1 -1
  9. data/build/tdclrb/tdclrb.rc +4 -4
  10. data/source/bzs/db/protocol/tdap/client/field.cpp +24 -0
  11. data/source/bzs/db/protocol/tdap/client/field.h +2 -0
  12. data/source/bzs/db/protocol/tdap/client/groupQuery.cpp +243 -59
  13. data/source/bzs/db/protocol/tdap/client/groupQuery.h +8 -0
  14. data/source/bzs/db/protocol/tdap/client/memRecord.cpp +19 -6
  15. data/source/bzs/db/protocol/tdap/client/memRecord.h +10 -1
  16. data/source/bzs/db/protocol/tdap/client/recordset.cpp +17 -0
  17. data/source/bzs/db/protocol/tdap/client/recordset.h +3 -0
  18. data/source/bzs/db/protocol/tdap/client/recordsetImple.h +131 -30
  19. data/source/bzs/db/protocol/tdap/client/table.cpp +11 -2
  20. data/source/bzs/db/protocol/tdap/mysql/recordsetReader.h +1 -0
  21. data/source/bzs/db/protocol/tdap/tdapcapi.h +5 -5
  22. data/source/bzs/test/tdclatl/test_v3.js +22 -0
  23. data/source/bzs/test/tdclphp/transactd_v3_Test.php +35 -0
  24. data/source/bzs/test/tdclrb/transactd_v3_spec.rb +40 -0
  25. data/source/bzs/test/trdclengn/testField.h +320 -0
  26. data/source/bzs/test/trdclengn/test_tdclcpp_v3.cpp +9 -0
  27. data/source/global/tdclatl/Recordset.cpp +44 -2
  28. data/source/global/tdclatl/Recordset.h +6 -2
  29. data/source/global/tdclatl/tdclatl.idl +5 -1
  30. metadata +4 -4
@@ -226,32 +226,111 @@ bool fieldValues::isNull(int index) const
226
226
  }
227
227
 
228
228
 
229
+ struct recordCompItem
230
+ {
231
+ judgeFunc isMatchFunc;
232
+ comp1Func compFunc;
233
+ short index;
234
+ short indexTmp; // For join match. converted field index
235
+ short cmpIndex; // For CMPLOGICAL_FIELD only
236
+ ushort_td cmpLen;
237
+ uchar_td compType;
238
+ char combine;
239
+ struct
240
+ {
241
+ bool nullable : 1;
242
+ bool nulllog : 1; // condition has isNull or isNotNull
243
+ };
244
+ };
245
+
246
+ inline char getCombine(const std::vector<std::_tstring>& tokns, int index)
247
+ {
248
+ if (index + 3 < (int)tokns.size())
249
+ {
250
+ std::_tstring s = tokns[index + 3];
251
+ if (s == _T("or"))
252
+ return eCor;
253
+ else if (s == _T("and"))
254
+ return eCand;
255
+ }
256
+ return eCend;
257
+ }
258
+
259
+ inline uchar_td getCompType(const _TCHAR* v, bool compField, bool part)
260
+ {
261
+
262
+ uchar_td compType = getFilterLogicTypeCode(v);
263
+ if (compField)
264
+ compType |= CMPLOGICAL_FIELD;
265
+ if (!part)
266
+ compType |= CMPLOGICAL_VAR_COMP_ALL;
267
+ return compType;
268
+ }
269
+
270
+ inline bool getNullLog(uchar_td compType)
271
+ {
272
+ uchar_td log = (compType & 0xf);
273
+ return ((log == eIsNull) || (log == eIsNotNull));
274
+ }
275
+
276
+ inline comp1Func getCompFunc2(const fielddef& fdd, uchar_td compType)
277
+ {
278
+ uchar_td type = fdd.type;
279
+ if (fdd.isLegacyTimeFormat())
280
+ {
281
+ if (type == ft_mytime) type = ft_mytime_num_cmp;
282
+ if (type == ft_mydatetime) type = ft_mydatetime_num_cmp;
283
+ if (type == ft_mytimestamp) type = ft_mytimestamp_num_cmp;
284
+ }
285
+ return getCompFunc(type, fdd.len, compType,
286
+ fdd.varLenBytes() + fdd.blobLenBytes());
287
+ }
288
+
289
+ inline short getIndex(const fielddefs* fdinfo, const std::_tstring& v)
290
+ {
291
+ short index = fdinfo->indexByName(v);
292
+ if (index == -1)
293
+ THROW_BZS_ERROR_WITH_MSG(_T("recordsetQuery:Invalid field name ")
294
+ + std::_tstring(v));
295
+ return index;
296
+ }
297
+
298
+ inline bool isSingedInteger(uchar_td type)
299
+ {
300
+ return ((type == ft_integer) || (type == ft_autoinc));
301
+ }
302
+
303
+ inline bool isUnsingedInteger(uchar_td type)
304
+ {
305
+ return ((type == ft_logical) || (type == ft_uinteger) || (type == ft_set) ||
306
+ (type == ft_bit) || (type == ft_enum) || (type == ft_autoIncUnsigned) ||
307
+ (type == ft_myyear));
308
+ }
309
+
310
+ bool canDirectComp(const fielddef& l, const fielddef& r)
311
+ {
312
+ if (l.type == r.type && l.charsetIndex() == r.charsetIndex())
313
+ return true;
314
+ if (isSingedInteger(l.type) == isSingedInteger(r.type)) return true;
315
+ if (isUnsingedInteger(l.type) == isUnsingedInteger(r.type)) return true;
316
+ return false;
317
+ }
318
+
319
+
229
320
  // ---------------------------------------------------------------------------
230
321
  // struct recordsetQueryImple
231
322
  // ---------------------------------------------------------------------------
232
323
  struct recordsetQueryImple
233
324
  {
234
- row_ptr row;
235
- struct compItem
236
- {
237
- judgeFunc isMatchFunc;
238
- comp1Func compFunc;
239
- short index;
240
- short cmpIndex; // For CMPLOGICAL_FIELD only
241
- uchar_td compType;
242
- char combine;
243
- struct
244
- {
245
- bool nullable : 1;
246
- bool nulllog : 1;
247
- };
248
- };
249
- std::vector<compItem> compItems;
250
- fielddefs compFields;
325
+ row_ptr row; // For matchBy condition values or join target of converted fields
326
+ row_ptr joinRow; // For join target row
327
+ std::vector<recordCompItem> compItems; // condition parms
328
+ fielddefs compFields; // condition fields
251
329
  short endIndex;
330
+ short matchHint;
252
331
  bool mysqlnull;
253
332
 
254
- recordsetQueryImple() : row(NULL),mysqlnull(false) {}
333
+ recordsetQueryImple() : row(NULL), joinRow(NULL), mysqlnull(false) {}
255
334
  recordsetQueryImple(const recordsetQueryImple& r)
256
335
  : row(r.row), compItems(r.compItems), compFields(r.compFields), mysqlnull(r.mysqlnull)
257
336
  {
@@ -304,31 +383,39 @@ recordsetQuery::~recordsetQuery()
304
383
  delete m_imple;
305
384
  }
306
385
 
386
+ void recordsetQuery::createTempRecord()
387
+ {
388
+ m_imple->compFields.calcFieldPos(0 /*startIndex*/, true);
389
+ m_imple->row = memoryRecord::create(m_imple->compFields);
390
+ m_imple->row->addref();
391
+ m_imple->row->setRecordData(autoMemory::create(), 0, 0, &m_imple->endIndex, true);
392
+ }
393
+ /*
394
+ init for matchBy
395
+ */
307
396
  void recordsetQuery::init(const fielddefs* fdinfo)
308
397
  {
309
398
  m_imple->mysqlnull = fdinfo->mysqlnullEnable();
310
399
  const std::vector<std::_tstring>& tokns = getWheres();
311
- m_imple->compFields.clear();
400
+ m_imple->row = NULL;
312
401
  m_imple->compItems.clear();
402
+ m_imple->compFields.clear();
313
403
  for (int i = 0; i < (int)tokns.size(); i += 4)
314
404
  {
315
- recordsetQueryImple::compItem itm;
316
- itm.index = fdinfo->indexByName(tokns[i].c_str());
317
- if (itm.index == -1)
318
- THROW_BZS_ERROR_WITH_MSG(_T("recordsetQuery:Invalid field name ") + tokns[i]);
319
- itm.nullable = (*fdinfo)[itm.index].isNullable();
405
+ recordCompItem itm;
406
+ itm.indexTmp = -1;
407
+ itm.index = getIndex(fdinfo, tokns[i]);
408
+ itm.nullable = (*fdinfo)[itm.index].isNullable() & m_imple->mysqlnull;
409
+ itm.cmpLen = (*fdinfo)[itm.index].len;
320
410
  m_imple->compItems.push_back(itm);
321
411
  m_imple->compFields.push_back(&((*fdinfo)[itm.index]));
322
412
  }
323
- m_imple->compFields.calcFieldPos(0 /*startIndex*/, true);
324
- m_imple->row = memoryRecord::create(m_imple->compFields);
325
- m_imple->row->addref();
326
- m_imple->row->setRecordData(autoMemory::create(), 0, 0, &m_imple->endIndex, true);
413
+ createTempRecord();
327
414
 
328
415
  int index = 0;
329
416
  for (int i = 0; i < (int)tokns.size(); i += 4)
330
417
  {
331
- recordsetQueryImple::compItem& itm = m_imple->compItems[index];
418
+ recordCompItem& itm = m_imple->compItems[index];
332
419
  field fd = (*m_imple->row)[index];
333
420
 
334
421
  std::_tstring value = tokns[i + 2];
@@ -337,66 +424,163 @@ void recordsetQuery::init(const fielddefs* fdinfo)
337
424
  {
338
425
  value.erase(value.begin());
339
426
  value.erase(value.end() - 1);
340
- itm.cmpIndex = fdinfo->indexByName(value);
341
- if (itm.cmpIndex == -1)
342
- THROW_BZS_ERROR_WITH_MSG(_T("recordsetQuery:Invalid field name ") + value);
427
+ itm.cmpIndex = getIndex(fdinfo, value);
343
428
  }
344
429
  else
345
430
  fd = value.c_str();
346
431
  bool part = fd.isCompPartAndMakeValue();
347
- itm.compType = getFilterLogicTypeCode(tokns[i + 1].c_str());
348
- if (compField)
349
- itm.compType |= CMPLOGICAL_FIELD;
350
- if (!part)
351
- itm.compType |= CMPLOGICAL_VAR_COMP_ALL;
352
- eCompType log = (eCompType)(itm.compType & 0xf);
353
- itm.nulllog = ((log == eIsNull) || (log == eIsNotNull));
432
+ itm.compType = getCompType(tokns[i + 1].c_str(), compField, part);
433
+ itm.nulllog = (m_imple->mysqlnull && getNullLog(itm.compType));
354
434
  fielddef& fdd = const_cast<fielddef&>(m_imple->compFields[index]);
355
435
  fdd.len = m_imple->compFields[index].compDataLen((const uchar_td*)fd.ptr(), part);
356
- uchar_td type = fdd.type;
357
- if (fdd.isLegacyTimeFormat())
358
- {
359
- if (type == ft_mytime) type = ft_mytime_num_cmp;
360
- if (type == ft_mydatetime) type = ft_mydatetime_num_cmp;
361
- if (type == ft_mytimestamp) type = ft_mytimestamp_num_cmp;
436
+ itm.compFunc = getCompFunc2(fdd, itm.compType);
437
+ itm.isMatchFunc = getJudgeFunc((eCompType)itm.compType);
438
+ itm.combine = getCombine(tokns, i);
439
+ itm.cmpLen = fdd.len;
440
+ if (itm.compType & CMPLOGICAL_FIELD)
441
+ { // No field type convert
442
+ const fielddef& fdr = m_imple->compFields[itm.cmpIndex];
443
+ itm.cmpLen = std::min(fdd.len, fdr.len);
362
444
  }
445
+ ++index;
446
+ }
447
+ }
363
448
 
364
- itm.compFunc = getCompFunc(type, fdd.len, itm.compType,
365
- fdd.varLenBytes() + fdd.blobLenBytes());
449
+ /*
450
+ init for join
451
+ */
452
+ void recordsetQuery::init(const fielddefs* fdinfo, const fielddefs* rfdinfo)
453
+ {
454
+ m_imple->mysqlnull = fdinfo->mysqlnullEnable();
455
+ const std::vector<std::_tstring>& tokns = getWheres();
456
+ m_imple->row = NULL;
457
+ m_imple->compItems.clear();
458
+ m_imple->compFields.clear();
459
+ for (int i = 0; i < (int)tokns.size(); i += 4)
460
+ {
461
+ recordCompItem itm;
462
+ itm.indexTmp = -1;
463
+ itm.index = getIndex(fdinfo, tokns[i]);
464
+ itm.cmpIndex = getIndex(rfdinfo, tokns[i+2]);
465
+ itm.nullable = true;//(*fdinfo)[itm.index].isNullable() | (*rfdinfo)[itm.cmpIndex].isNullable();
466
+ itm.compType = getCompType(tokns[i + 1].c_str(), true, false);
467
+ itm.nulllog = getNullLog(itm.compType);
468
+ if (itm.nulllog)
469
+ THROW_BZS_ERROR_WITH_MSG(_T("recordsetQuery:Join can not use null compare. "));
470
+
366
471
  itm.isMatchFunc = getJudgeFunc((eCompType)itm.compType);
472
+ itm.combine = getCombine(tokns, i);
473
+
474
+ // different field type
475
+ const fielddef& fdd = (*fdinfo)[itm.index];
476
+ const fielddef& fddr = (*rfdinfo)[itm.cmpIndex];
477
+ if (fdd.isBlob() || fddr.isBlob())
478
+ THROW_BZS_ERROR_WITH_MSG(_T("recordsetQuery: BLOB or TEXT can not use for join key field. "));
479
+ if (!canDirectComp(fdd, fddr))
480
+ {
481
+ m_imple->compFields.push_back(&fddr);
482
+ short index = (short)m_imple->compFields.size() - 1;
483
+ itm.indexTmp = index;
484
+ if (fdd.isNullable())
485
+ {
486
+ fielddef& fd = const_cast<fielddef&>(m_imple->compFields[index]);
487
+ fd.setNullable(true, fdd.isDefaultNull());
488
+ }
489
+ itm.cmpLen = fddr.len;
490
+ itm.compFunc = getCompFunc2(fddr, itm.compType);
491
+
492
+ }else
493
+ { // same type
494
+ const fielddef& fdest = (fdd.len < fddr.len) ? fdd : fddr;
495
+ itm.cmpLen = fdest.len;
496
+ itm.compFunc = getCompFunc2(fdest, itm.compType);
497
+ }
498
+ m_imple->compItems.push_back(itm);
499
+ }
500
+ if (m_imple->compFields.size())
501
+ createTempRecord();
502
+ }
503
+
504
+ void recordsetQuery::setJoinRow(const row_ptr row)
505
+ {
506
+ if (m_imple->row)
507
+ {
508
+ // Convert data type
509
+ for (size_t i = 0; i < m_imple->compItems.size(); ++i)
510
+ {
511
+ recordCompItem& itm = m_imple->compItems[i];
512
+ if (itm.indexTmp != -1)
513
+ {
514
+ field f = (*m_imple->row)[itm.indexTmp];
515
+ const field& fd = (*row)[itm.index];
516
+ if (fd.isNull())
517
+ f.setNull(true);
518
+ else
519
+ f = fd.c_str();
520
+ }
521
+ }
522
+ }
523
+ else
524
+ m_imple->joinRow = row;
525
+ }
367
526
 
368
- if (i + 3 < (int)tokns.size())
527
+ #define HINT_LEFT_NULL 1
528
+ /*
529
+ match for join
530
+ */
531
+ bool recordsetQuery::matchJoin(const row_ptr rrow) const
532
+ {
533
+ m_imple->matchHint = 0;
534
+ for (int i = 0; i < (int)m_imple->compItems.size(); ++i)
535
+ {
536
+ recordCompItem& itm = m_imple->compItems[i];
537
+ bool ret;
538
+ int nullJudge = 2;
539
+ const field& f = itm.indexTmp == -1 ? (*m_imple->joinRow)[itm.index] : (*m_imple->row)[itm.indexTmp];
540
+ const field& fr = (*rrow)[itm.cmpIndex];
541
+ if (itm.nullable)
542
+ nullJudge = f.nullCompMatch(fr, 0);
543
+ if (nullJudge < 2)
369
544
  {
370
- std::_tstring s = tokns[i + 3];
371
- if (s == _T("or"))
372
- itm.combine = eCor;
373
- else if (s == _T("and"))
374
- itm.combine = eCand;
545
+ ret = (nullJudge == 0);
546
+ m_imple->matchHint |= (nullJudge == -1) ? HINT_LEFT_NULL : 0;
375
547
  }
376
548
  else
377
- itm.combine = eCend;
378
- ++index;
549
+ {
550
+ nullJudge = itm.compFunc((const char*)f.ptr(), (const char*)fr.ptr(), itm.cmpLen);
551
+ ret = itm.isMatchFunc(nullJudge);
552
+ }
553
+ if (isEndComp(itm.combine, ret)) return ret;
379
554
  }
555
+ return true;
556
+ }
557
+
558
+ int recordsetQuery::matchStatus() const
559
+ {
560
+ if (m_imple->matchHint & HINT_LEFT_NULL && m_imple->compItems.size() == 1)
561
+ return JOIN_NO_MORERECORD;
562
+
563
+ return 0;
380
564
  }
381
565
 
382
566
  bool recordsetQuery::match(const row_ptr row) const
383
567
  {
384
568
  for (int i = 0; i < (int)m_imple->compItems.size(); ++i)
385
569
  {
386
- recordsetQueryImple::compItem& itm = m_imple->compItems[i];
570
+ recordCompItem& itm = m_imple->compItems[i];
387
571
  bool ret;
388
572
  int nullJudge = 2;
389
573
  const field& f = (*row)[itm.index];
390
- if (m_imple->mysqlnull && (itm.nullable || itm.nulllog))
574
+ if (itm.nullable || itm.nulllog)
391
575
  nullJudge = f.nullComp((eCompType)(itm.compType & 0xf));
392
576
  if (nullJudge < 2)
393
577
  ret = (nullJudge == 0) ? true : false;
394
578
  else
395
579
  {
396
580
  if (itm.compType & CMPLOGICAL_FIELD)
397
- ret = itm.isMatchFunc(itm.compFunc((const char*)f.ptr(), (const char*)(*row)[itm.cmpIndex].ptr(), f.len()));
581
+ ret = itm.isMatchFunc(itm.compFunc((const char*)f.ptr(), (const char*)(*row)[itm.cmpIndex].ptr(), itm.cmpLen));
398
582
  else
399
- ret = itm.isMatchFunc(itm.compFunc((const char*)f.ptr(), (const char*)((*m_imple->row)[i].ptr()), (*m_imple->row)[i].len()));
583
+ ret = itm.isMatchFunc(itm.compFunc((const char*)f.ptr(), (const char*)((*m_imple->row)[i].ptr()), itm.cmpLen));
400
584
  }
401
585
  if (isEndComp(itm.combine, ret)) return ret;
402
586
  }
@@ -31,6 +31,8 @@ namespace tdap
31
31
  namespace client
32
32
  {
33
33
 
34
+ #define JOIN_NO_MORERECORD 1
35
+
34
36
  class DLLLIB fieldNames
35
37
  {
36
38
 
@@ -108,8 +110,14 @@ class DLLLIB recordsetQuery : protected query
108
110
  friend class recordsetImple;
109
111
 
110
112
  struct recordsetQueryImple* m_imple;
113
+
114
+ void createTempRecord();
111
115
  void init(const fielddefs* fdinfo);
116
+ void init(const fielddefs* fdinfo, const fielddefs* rfdinfo);
112
117
  bool match(const row_ptr row) const;
118
+ bool matchJoin(const row_ptr rrow) const;
119
+ void setJoinRow(const row_ptr row);
120
+ int matchStatus() const;
113
121
 
114
122
  public:
115
123
  recordsetQuery();
@@ -36,7 +36,7 @@ namespace client
36
36
  {
37
37
 
38
38
  autoMemory::autoMemory() : refarymem(), ptr(0), endFieldIndex(NULL), size(0),
39
- owner(false)
39
+ usedIndex(0), owner(false)
40
40
  {
41
41
  }
42
42
 
@@ -45,7 +45,7 @@ void autoMemory::setParams(unsigned char* p, size_t s, short* endIndex, bool own
45
45
  if (owner)
46
46
  {
47
47
  delete[] ptr;
48
- delete endFieldIndex;
48
+ delete[] endFieldIndex;
49
49
  }
50
50
 
51
51
  ptr = p;
@@ -59,11 +59,24 @@ void autoMemory::setParams(unsigned char* p, size_t s, short* endIndex, bool own
59
59
  memcpy(ptr, p, size);
60
60
  else
61
61
  memset(ptr, 0, size + 1);
62
- endFieldIndex = new short;
62
+ endFieldIndex = new short[JOINLIMIT_PER_RECORD];
63
+ memset(endFieldIndex, 0, sizeof(short) * JOINLIMIT_PER_RECORD);
63
64
  if (endIndex != NULL)
64
- *endFieldIndex = *endIndex;
65
+ endFieldIndex[usedIndex] = *endIndex;
65
66
  }
66
-
67
+ }
68
+
69
+ short* autoMemory::appendEndFieldIndex(short value)
70
+ {
71
+ assert(owner);
72
+ endFieldIndex[++usedIndex] = value;
73
+ return endFieldIndex + (usedIndex);
74
+ }
75
+
76
+ void autoMemory::assignEndFieldIndex(short* p)
77
+ {
78
+ assert(owner);
79
+ memcpy(endFieldIndex, p, sizeof(short) * JOINLIMIT_PER_RECORD);
67
80
  }
68
81
 
69
82
  autoMemory::~autoMemory()
@@ -71,7 +84,7 @@ autoMemory::~autoMemory()
71
84
  if (owner)
72
85
  {
73
86
  delete[] ptr;
74
- delete endFieldIndex;
87
+ delete[] endFieldIndex;
75
88
  }
76
89
  }
77
90
 
@@ -44,9 +44,12 @@ public:
44
44
  void setParams(unsigned char* p, size_t s, short* endIndex, bool own);
45
45
 
46
46
  autoMemory& operator=(const bzs::db::protocol::tdap::client::autoMemory& p);
47
+ short* appendEndFieldIndex(short value);
48
+ void assignEndFieldIndex(short* p);
47
49
  unsigned char* ptr;
48
50
  short* endFieldIndex;
49
- unsigned int size ;
51
+ unsigned int size;
52
+ unsigned char usedIndex;
50
53
  bool owner ;
51
54
  static autoMemory* create(int n);
52
55
  static autoMemory* create();
@@ -134,6 +137,12 @@ class DLLLIB memoryRecord : public fieldsBase
134
137
  }
135
138
  }
136
139
 
140
+ inline void setInvalidMemblockLast()
141
+ {
142
+ int num = memBlockSize() -1;
143
+ m_InvalidFlags |= ((2L << num) | 1L);
144
+ }
145
+
137
146
  void releaseMemory();
138
147
 
139
148
  protected:
@@ -180,6 +180,18 @@ recordset& recordset::reverse()
180
180
  return *this;
181
181
  }
182
182
 
183
+ recordset& recordset::join(const recordset& rs, recordsetQuery& rq)
184
+ {
185
+ m_imple->nestedLoopJoin(*rs.m_imple, rq, true);
186
+ return *this;
187
+ }
188
+
189
+ recordset& recordset::outerJoin(const recordset& rs, recordsetQuery& rq)
190
+ {
191
+ m_imple->nestedLoopJoin(*rs.m_imple, rq, false);
192
+ return *this;
193
+ }
194
+
183
195
  void recordset::reserve(size_t size)
184
196
  {
185
197
  m_imple->reserve(size);
@@ -190,6 +202,11 @@ void recordset::appendField(const _TCHAR* name, int type, short len)
190
202
  m_imple->appendField(name, type, len);
191
203
  }
192
204
 
205
+ void recordset::appendField(const fielddef& fd)
206
+ {
207
+ m_imple->appendField(fd);
208
+ }
209
+
193
210
  recordset& recordset::operator+=(const recordset& r)
194
211
  {
195
212
  if (r.size() == 0)
@@ -71,8 +71,11 @@ public:
71
71
  const _TCHAR* name7 = NULL, const _TCHAR* name8 = NULL);
72
72
  recordset& orderBy(const sortFields& orders);
73
73
  recordset& reverse();
74
+ recordset& join(const recordset& rs, recordsetQuery& rq);
75
+ recordset& outerJoin(const recordset& rs, recordsetQuery& rq);
74
76
  void reserve(size_t size);
75
77
  void appendField(const _TCHAR* name, int type, short len);
78
+ void appendField(const fielddef& fd);
76
79
  recordset& operator+=(const recordset& r);
77
80
  void release();
78
81
  static recordset* create();