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
@@ -65,7 +65,10 @@ class dumpRecordset
65
65
  {
66
66
  row& rec = rs[i];
67
67
  for (size_t col = 0; col < fds.size(); ++col)
68
- m_widths[col] = std::max(_tcslen(rec[(short)col].c_str()), m_widths[col]);
68
+ {
69
+ if (!rec[(short)col].isNull())
70
+ m_widths[col] = std::max(_tcslen(rec[(short)col].c_str()), m_widths[col]);
71
+ }
69
72
  }
70
73
  }
71
74
 
@@ -199,12 +202,6 @@ class recordsetImple
199
202
  /* for registerMemoryBlock temp data */
200
203
  size_t m_joinRows;
201
204
 
202
- /*
203
- for optimazing join.
204
- If the first reading is using by unique key , set that field count.
205
- */
206
- short m_uniqueReadMaxField;
207
-
208
205
  public:
209
206
  typedef std::vector<row_ptr>::iterator iterator;
210
207
  typedef row_ptr item_type;
@@ -226,7 +223,7 @@ private:
226
223
  am->setParams(ptr, size, 0, true);
227
224
  m_memblock.push_back(boost::shared_ptr<autoMemory>(am, boost::bind(&autoMemory::release, _1)));
228
225
  unsigned char* p = am->ptr;
229
- // copy fileds
226
+ // copy fields
230
227
  if (addtype & mra_nextrows)
231
228
  {
232
229
  if (addtype == mra_nextrows)
@@ -241,13 +238,6 @@ private:
241
238
  m_mra->setCurFirstField((int)m_fds->size());
242
239
  if (tb)
243
240
  m_fds->addSelectedFields(tb);
244
- if (tb && (addtype == mra_nojoin))
245
- {
246
- const keydef& kd = tb->tableDef()->keyDefs[(int)tb->keyNum()];
247
- m_uniqueReadMaxField = (kd.segments[0].flags.bit0 == false)
248
- ? (short)m_fds->size()
249
- : 0;
250
- }
251
241
  }
252
242
 
253
243
  *(am->endFieldIndex) = (short)m_fds->size();
@@ -262,8 +252,7 @@ private:
262
252
  if (jmap)
263
253
  {
264
254
  // At Join that if some base records reference to a joined
265
- // record
266
- // that the joined record pointer is shared by some
255
+ // record that the joined record pointer is shared by some
267
256
  // base records.
268
257
  for (int i = 0; i < (int)rows; ++i)
269
258
  {
@@ -322,6 +311,16 @@ private:
322
311
  return -1;
323
312
  }
324
313
 
314
+ inline size_t getIndexOfEndPtr(short* p, int memBlockIndex) const
315
+ {
316
+ const boost::shared_ptr<autoMemory>& am = m_memblock[memBlockIndex];
317
+ if ((p >= am->endFieldIndex) && (p < am->endFieldIndex + JOINLIMIT_PER_RECORD))
318
+ return p - am->endFieldIndex;
319
+
320
+ assert(0);
321
+ return -1;
322
+ }
323
+
325
324
  // Duplicate row for hasManyJoin
326
325
  void duplicateRow(int row, int count)
327
326
  {
@@ -336,18 +335,73 @@ private:
336
335
  }
337
336
  }
338
337
 
338
+ void assignJoinFields(const recordsetImple& r, std::vector<int>& indexes)
339
+ {
340
+ int fieldCount = (int)m_fds->size();
341
+ m_fds->append(r.m_fds.get());
342
+
343
+ // append memblock
344
+ std::vector<short*> endIndex;
345
+ for (size_t i = 0;i < r.m_memblock.size(); ++i)
346
+ {
347
+ m_memblock.push_back(r.m_memblock[i]);
348
+ short* ei = r.m_memblock[i]->appendEndFieldIndex(*r.m_memblock[i]->endFieldIndex + fieldCount);
349
+ endIndex.push_back(ei);
350
+ }
351
+
352
+ if (r.size() == 0) return;
353
+
354
+ //counting (row * memblock)
355
+ int rblockSize = (int)dynamic_cast<memoryRecord*>(r.m_recordset[0])->memBlockSize();
356
+ int amsize = (int)(rblockSize * indexes.size());
357
+
358
+ autoMemory* amar = autoMemory::create(amsize);
359
+ amsize = 0;
360
+ autoMemory* nullRecordAm = 0;
361
+ for (int i = 0; i < (int)m_recordset.size(); ++i)
362
+ {
363
+ memoryRecord* row = dynamic_cast<memoryRecord*>(m_recordset[i]);
364
+ for (int j = 0; j < (int)rblockSize; ++j)
365
+ {
366
+ autoMemory* mb = amar + amsize;
367
+ if (indexes[i] != -1)
368
+ {
369
+ memoryRecord* srow = dynamic_cast<memoryRecord*>(r.m_recordset[indexes[i]]);
370
+ assert(srow->memBlockSize() == rblockSize);
371
+ const autoMemory& smb = srow->memBlock(j);
372
+ int index = r.getMemBlockIndex(smb.ptr);
373
+ row->setRecordData(mb, smb.ptr, 0, endIndex[index], false);
374
+ }else
375
+ {
376
+ // Alloc data buffer for not found record.
377
+ if (nullRecordAm == NULL)
378
+ {
379
+ nullRecordAm = autoMemory::create();
380
+ nullRecordAm->addref();
381
+ short endIndex = (short)m_fds->size();
382
+ nullRecordAm->setParams(0, r.m_fds->totalFieldLen(), &endIndex, true);
383
+ m_memblock.push_back(boost::shared_ptr<autoMemory>(nullRecordAm, boost::bind(&autoMemory::release, _1)));
384
+ }
385
+ row->setRecordData(mb, nullRecordAm->ptr, 0, nullRecordAm->endFieldIndex, false);
386
+ // Specify fieldnum
387
+ row->setInvalidMemblockLast();
388
+ }
389
+ ++amsize;
390
+ }
391
+ }
392
+ }
393
+
339
394
  public:
340
395
  inline recordsetImple()
341
396
  : m_fds(fielddefs::create(), boost::bind(&fielddefs::release, _1)),
342
- m_joinRows(0), m_uniqueReadMaxField(0)
397
+ m_joinRows(0)
343
398
  {
344
399
  m_mra.reset(new multiRecordAlocatorImple(this));
345
400
  }
346
401
 
347
402
  inline recordsetImple(const recordsetImple& r)
348
403
  : m_fds(r.m_fds),m_mra(r.m_mra), m_recordset(r.m_recordset),
349
- m_memblock(r.m_memblock), m_joinRows(r.m_joinRows),
350
- m_uniqueReadMaxField(r.m_uniqueReadMaxField)
404
+ m_memblock(r.m_memblock), m_joinRows(r.m_joinRows)
351
405
  {
352
406
  for (size_t i = 0; i < m_recordset.size(); ++i)
353
407
  m_recordset[i]->addref();
@@ -370,7 +424,6 @@ public:
370
424
  m_recordset = r.m_recordset;
371
425
  m_memblock = r.m_memblock;
372
426
  m_joinRows = r.m_joinRows;
373
- m_uniqueReadMaxField = r.m_uniqueReadMaxField;
374
427
  for (size_t i = 0; i < m_recordset.size(); ++i)
375
428
  m_recordset[i]->addref();
376
429
  }
@@ -384,7 +437,6 @@ public:
384
437
  {
385
438
  recordsetImple* p = new recordsetImple();
386
439
  p->m_joinRows = m_joinRows;
387
- p->m_uniqueReadMaxField = m_uniqueReadMaxField;
388
440
  p->m_fds.reset(m_fds->clone(), boost::bind(&fielddefs::release, _1));
389
441
 
390
442
  std::vector<size_t> offsets;
@@ -396,7 +448,7 @@ public:
396
448
  autoMemory* am = ama + i;
397
449
  am->addref();
398
450
  am->setParams(m_memblock[i]->ptr, m_memblock[i]->size, 0, true);
399
- *am->endFieldIndex = *m_memblock[i]->endFieldIndex;
451
+ am->assignEndFieldIndex(m_memblock[i]->endFieldIndex);
400
452
  p->m_memblock.push_back(boost::shared_ptr<autoMemory>(am, boost::bind(&autoMemory::release, _1)));
401
453
  offsets.push_back((am->ptr - m_memblock[i]->ptr));
402
454
  }
@@ -443,8 +495,10 @@ public:
443
495
  #pragma warn .8072
444
496
  autoMemory* a = amar + amindex;
445
497
  const boost::shared_ptr<autoMemory>& am = p->m_memblock[index];
446
- // isInvalidRecord will be reset.
447
498
  mr->setRecordData(a, ptr, mb.size, am->endFieldIndex, mb.owner);
499
+ // set endFieldIndex
500
+ size_t offset = getIndexOfEndPtr(mb.endFieldIndex, index);
501
+ a->endFieldIndex += offset;
448
502
  ++amindex;
449
503
  }
450
504
 
@@ -456,8 +510,6 @@ public:
456
510
  return p;
457
511
  }
458
512
 
459
- inline short uniqueReadMaxField() const { return m_uniqueReadMaxField; }
460
-
461
513
  inline void clearRecords()
462
514
  {
463
515
  if (m_recordset.size())
@@ -468,9 +520,8 @@ public:
468
520
  m_recordset[i]->release();
469
521
  }
470
522
  }
471
-
523
+
472
524
  m_recordset.clear();
473
- m_uniqueReadMaxField = 0;
474
525
  }
475
526
 
476
527
  inline const fielddefs* fieldDefs() const { return m_fds.get(); }
@@ -654,8 +705,6 @@ public:
654
705
 
655
706
  inline void appendField(const _TCHAR* name, int type, short len, uchar_td decimals=0)
656
707
  {
657
- assert(m_fds->size());
658
-
659
708
  fielddef fd;
660
709
  memset(&fd, 0, sizeof(fielddef));
661
710
  fd.len = len;
@@ -664,6 +713,13 @@ public:
664
713
  fd.decimals = decimals;
665
714
  fd.setName(name);
666
715
  fd.setCharsetIndex((*m_fds)[0].charsetIndex());
716
+ appendField(fd);
717
+ }
718
+
719
+ inline void appendField(const fielddef& fd)
720
+ {
721
+ assert(m_fds->size());
722
+
667
723
  if (blobLenBytes(fd))
668
724
  THROW_BZS_ERROR_WITH_MSG(_T("Can not append Blob or Text field."));
669
725
  m_fds->push_back(&fd);
@@ -697,6 +753,51 @@ public:
697
753
  m_recordset.reserve(size);
698
754
  }
699
755
 
756
+ void nestedLoopJoin(const recordsetImple& r, recordsetQuery& q, bool inner)
757
+ {
758
+
759
+ std::vector<int> indexes;
760
+ size_t i = 0;
761
+ indexes.reserve(size() * 3);
762
+ q.init(m_fds.get(), r.m_fds.get());
763
+ while (i < size())
764
+ {
765
+ size_t j = 0;
766
+ size_t found = 0;
767
+ q.setJoinRow(m_recordset[i]);
768
+ while (j < r.size())
769
+ {
770
+ if (q.matchJoin(r.m_recordset[j]))
771
+ {
772
+ indexes.push_back((int)j);
773
+ ++found;
774
+ }
775
+ if(q.matchStatus() == JOIN_NO_MORERECORD)
776
+ break;
777
+ ++j;
778
+ }
779
+ if (found == 0)
780
+ {
781
+ if (inner)
782
+ erase(i);
783
+ else
784
+ {
785
+ indexes.push_back(-1);
786
+ ++i;
787
+ }
788
+ }
789
+ else if (found == 1)
790
+ ++i;
791
+ else
792
+ {
793
+ duplicateRow((int)i, (int)(found - 1));
794
+ i += found;
795
+ }
796
+ }
797
+ assert(m_recordset.size() == indexes.size());
798
+ assignJoinFields(r, indexes);
799
+ }
800
+
700
801
  #ifdef _DEBUG
701
802
  void dump()
702
803
  {
@@ -229,6 +229,13 @@ public:
229
229
  }
230
230
  }
231
231
 
232
+ inline int currentRow()
233
+ {
234
+ if (m_filter->hasManyJoin())
235
+ return *((int*)(m_ptr + DATASIZE_BYTE));
236
+ return m_row;
237
+ }
238
+
232
239
  inline char* moveCurrentData(char* ptr, unsigned short& len, int& sqnum)
233
240
  {
234
241
  len = *((unsigned short*)ptr);
@@ -1011,8 +1018,8 @@ void table::doFind(ushort_td op, bool notIncCurrent)
1011
1018
  m_stat = m_impl->rc->seekMultiStat();
1012
1019
 
1013
1020
  /*If seek multi error, set keyvalue for keyValueDescription*/
1014
- if (m_stat != 0)
1015
- setSeekValueField(row);
1021
+ if (m_stat != 0 && m_impl->filterPtr->isSeeksMode())
1022
+ setSeekValueField(m_impl->rc->currentRow());
1016
1023
 
1017
1024
  // m_datalen = m_impl->rc->len();
1018
1025
  m_datalen = tableDef()->recordlen();
@@ -2110,6 +2117,8 @@ bool table::setSeekValueField(int row)
2110
2117
  keydef* kd = &tableDef()->keyDefs[(int)keyNum()];
2111
2118
  if (keyValues.size() % kd->segmentCount)
2112
2119
  return false;
2120
+ if (row >= keyValues.size())
2121
+ return false;
2113
2122
  // Check uniqe key
2114
2123
  if (kd->segments[0].flags.bit0)
2115
2124
  return false;
@@ -446,6 +446,7 @@ public:
446
446
  {
447
447
  m_matched = true;
448
448
  // check is this logic range of max ?
449
+ // (v == 0) it was max value of this node.
449
450
  // if max then set judge node to next logic
450
451
  if ((m_fd->opr != 0) && m_judge && (v == 0) && m_next->m_judgeType)
451
452
  m_next->m_judge = true;
@@ -669,8 +669,8 @@ struct handshale_t
669
669
  If you change this version then you need change The ($TargetName) project options too.
670
670
  */
671
671
  #define C_INTERFACE_VER_MAJOR "3"//##1 Build marker! Don't remove
672
- #define C_INTERFACE_VER_MINOR "6"//##2 Build marker! Don't remove
673
- #define C_INTERFACE_VER_RELEASE "1"//##3 Build marker! Don't remove
672
+ #define C_INTERFACE_VER_MINOR "7"//##2 Build marker! Don't remove
673
+ #define C_INTERFACE_VER_RELEASE "0"//##3 Build marker! Don't remove
674
674
 
675
675
  /* dnamic load library name.
676
676
  The default extention of Mac is ".boudle", Therefore ".so" is popular. */
@@ -733,8 +733,8 @@ struct handshale_t
733
733
  */
734
734
 
735
735
  #define CPP_INTERFACE_VER_MAJOR "3"//##4 Build marker! Don't remove
736
- #define CPP_INTERFACE_VER_MINOR "6"//##5 Build marker! Don't remove
737
- #define CPP_INTERFACE_VER_RELEASE "1"//##6 Build marker! Don't remove
736
+ #define CPP_INTERFACE_VER_MINOR "7"//##5 Build marker! Don't remove
737
+ #define CPP_INTERFACE_VER_RELEASE "0"//##6 Build marker! Don't remove
738
738
 
739
739
  /* use autolink tdclcpp */
740
740
  #if (__BCPLUSPLUS__ || _MSC_VER)
@@ -770,7 +770,7 @@ struct handshale_t
770
770
 
771
771
 
772
772
  #define TRANSACTD_VER_MAJOR 3//##7 Build marker! Don't remove
773
- #define TRANSACTD_VER_MINOR 6//##8 Build marker! Don't remove
773
+ #define TRANSACTD_VER_MINOR 7//##8 Build marker! Don't remove
774
774
  #define TRANSACTD_VER_RELEASE 0//##9 Build marker! Don't remove
775
775
 
776
776
  #endif // BZS_DB_PROTOCOL_TDAP_TDAPCAPI_H
@@ -331,6 +331,7 @@ var FMT_RIGHT = 2;
331
331
  var MAGNIFICATION = 100;
332
332
  var resultCode = 0;
333
333
  var q;
334
+ var gq;
334
335
 
335
336
  WScript.quit(main());
336
337
  /*--------------------------------------------------------------------------------*/
@@ -906,6 +907,25 @@ function tesAlias(db)
906
907
  tb.close();
907
908
  }
908
909
 
910
+ function testRecordsetJoin(atu, ate)
911
+ {
912
+ initQuery();
913
+ q.where("id" , ">=", 1).and("id", "<=", 10).select("id", "name");
914
+ rs = atu.index(0).keyValue(1).read(q);
915
+ checkEqual(rs.size, 10, "RecordsetJoin 1");
916
+ q.reset().where("id" , ">=", 1).and("id", "<=", 5);
917
+ rse = ate.index(0).keyValue(1).read(q);
918
+ checkEqual(rse.size, 5, "RecordsetJoin 2");
919
+ var rq = createRecordsetQuery();
920
+ var rs1 = rs.clone();
921
+ rq.when("id", "=", "id");
922
+ rs1.join(rse, rq);
923
+ checkEqual(rs1.size, 5, "RecordsetJoin 3");
924
+ rs.outerjoin(rse, rq);
925
+ checkEqual(rs.size, 10, "RecordsetJoin 4");
926
+ }
927
+
928
+
909
929
  /*--------------------------------------------------------------------------------*/
910
930
  function test(atu, ate, db)
911
931
  {
@@ -1218,6 +1238,8 @@ function test(atu, ate, db)
1218
1238
 
1219
1239
  //Alias
1220
1240
  tesAlias(db);
1241
+
1242
+ testRecordsetJoin(atu, ate);
1221
1243
  //WScript.Echo(" -- End Test -- ");
1222
1244
  }
1223
1245
  /*--------------------------------------------------------------------------------*/
@@ -24,6 +24,7 @@ use BizStation\Transactd\Transactd;
24
24
  use BizStation\Transactd\PooledDbManager;
25
25
  use BizStation\Transactd\ConnectParams;
26
26
  use BizStation\Transactd\Tabledef;
27
+ use BizStation\Transactd\Fielddef;
27
28
  use BizStation\Transactd\Database;
28
29
  use BizStation\Transactd\BtrVersions;
29
30
  use BizStation\Transactd\Nstable;
@@ -1429,4 +1430,38 @@ class TransactdTest extends PHPUnit_Framework_TestCase
1429
1430
  $this->assertEquals($tb->deleteByObject($usr, false/*inKey*/), false);
1430
1431
  $this->assertEquals($tb->deleteByObject($usr), true); //default value true
1431
1432
  }
1433
+
1434
+ public function testRecordsetJoin()
1435
+ {
1436
+ $db = new Database();
1437
+ $db->open(URI, Transactd::TYPE_SCHEMA_BDF, Transactd::TD_OPEN_NORMAL);
1438
+ $this->assertEquals($db->stat(), 0);
1439
+ $at = new ActiveTable($db, "user", Transactd::TD_OPEN_READONLY);
1440
+ $ate = new ActiveTable($db, "extention", Transactd::TD_OPEN_READONLY);
1441
+ $q = new Query;
1442
+ $q->where('id','>=', 1)->and_('id','<=',10);
1443
+ $rs = $at->index(0)->keyValue(1)->read($q);
1444
+ $this->assertEquals($rs->size(), 10);
1445
+ $q->reset()->where('id','>=', 1)->and_('id','<=',5);
1446
+ $rse = $ate->index(0)->keyValue(1)->read($q);
1447
+ $this->assertEquals($rse->size(), 5);
1448
+ $rs1 = clone($rs);
1449
+ $rq = new RecordsetQuery;
1450
+ $rq->when('id', '=', 'id');
1451
+ $rs1->join($rse, $rq);
1452
+ $this->assertEquals($rs1->size(), 5);
1453
+ $rs->outerJoin($rse, $rq);
1454
+ $this->assertEquals($rs->size(), 10);
1455
+
1456
+ // Recordset::appendField
1457
+ $n = $rs->fieldDefs()->size();
1458
+ $fd = new Fielddef;
1459
+ $fd->type = Transactd::ft_integer;
1460
+ $fd->len = 4;
1461
+ $fd->name = 'abc';
1462
+ $rs->appendField($fd);
1463
+ $this->assertEquals($n + 1, $rs->fieldDefs()->size());
1464
+ $rs->appendField($fd->name, $fd->type, $fd->len);
1465
+ $this->assertEquals($n + 2, $rs->fieldDefs()->size());
1466
+ }
1432
1467
  }
@@ -2114,4 +2114,44 @@ describe Transactd, 'V3Features' do
2114
2114
  tb.close
2115
2115
  db.close
2116
2116
  end
2117
+
2118
+ it 'recordset join' do
2119
+ db = Transactd::Database.new()
2120
+ db.open(URL, Transactd::TYPE_SCHEMA_BDF, Transactd::TD_OPEN_NORMAL)
2121
+ at = Transactd::ActiveTable.new(db, "user")
2122
+ ate = Transactd::ActiveTable.new(db, "extention")
2123
+
2124
+ q = Transactd::Query.new()
2125
+ q.where("id", ">=", 1).and_("id", "<=", 10)
2126
+ rs = at.index(0).keyValue(1).read(q)
2127
+ expect(rs.size).to eq 10
2128
+
2129
+ q.reset().where("id", ">=", 1).and_("id", "<=", 5)
2130
+ rse = ate.index(0).keyValue(1).read(q)
2131
+ expect(rse.size).to eq 5
2132
+
2133
+ rs1 = rs.clone();
2134
+ rq = Transactd::RecordsetQuery.new()
2135
+ rq.when('id', '=', 'id')
2136
+
2137
+ #Join
2138
+ rs1.join(rse, rq)
2139
+ expect(rs1.size).to eq 5
2140
+
2141
+ #outerJoin
2142
+ rs.outerJoin(rse, rq)
2143
+ expect(rs.size).to eq 10
2144
+
2145
+ #appendField
2146
+ n = rs.fieldDefs().size
2147
+ fd = Transactd::Fielddef.new()
2148
+ fd.name = 'abc'
2149
+ fd.len = 2
2150
+ fd.type = Transactd::Ft_integer
2151
+ rs.appendField(fd)
2152
+ expect(rs.fieldDefs().size).to eq n+1
2153
+ expect(rs.fieldDefs()[n].name).to eq 'abc'
2154
+ db.close
2155
+ end
2156
+
2117
2157
  end