summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/db/mork/morkTable.h
blob: c0ca5ddd84ba56618f370c32cf55401588096e56 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-  */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef _MORKTABLE_
#define _MORKTABLE_ 1

#ifndef _MORK_
#  include "mork.h"
#endif

#ifndef _MORKNODE_
#  include "morkNode.h"
#endif

#ifndef _MORKDEQUE_
#  include "morkDeque.h"
#endif

#ifndef _MORKOBJECT_
#  include "morkObject.h"
#endif

#ifndef _MORKARRAY_
#  include "morkArray.h"
#endif

#ifndef _MORKROWMAP_
#  include "morkRowMap.h"
#endif

#ifndef _MORKNODEMAP_
#  include "morkNodeMap.h"
#endif

#ifndef _MORKPROBEMAP_
#  include "morkProbeMap.h"
#endif

#ifndef _MORKBEAD_
#  include "morkBead.h"
#endif

// 456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789

class nsIMdbTable;
#define morkDerived_kTable /*i*/ 0x5462 /* ascii 'Tb' */

/*| kStartRowArraySize: starting physical size of array for mTable_RowArray.
**| We want this number very small, so that a table containing exactly one
**| row member will not pay too significantly in space overhead.  But we want
**| a number bigger than one, so there is some space for growth.
|*/
#define morkTable_kStartRowArraySize 3 /* modest starting size for array */

/*| kMakeRowMapThreshold: this is the number of rows in a table which causes
**| a hash table (mTable_RowMap) to be lazily created for faster member row
**| identification, during such operations as cuts and adds.  This number must
**| be small enough that linear searches are not bad for member counts less
**| than this; but this number must also be large enough that creating a hash
**| table does not increase the per-row space overhead by a big percentage.
**| For speed, numbers on the order of ten to twenty are all fine; for space,
**| I believe a number as small as ten will have too much space overhead.
|*/
#define morkTable_kMakeRowMapThreshold 17 /* when to build mTable_RowMap */

#define morkTable_kStartRowMapSlotCount 13
#define morkTable_kMaxTableGcUses 0x0FF /* max for 8-bit unsigned int */

#define morkTable_kUniqueBit ((mork_u1)(1 << 0))
#define morkTable_kVerboseBit ((mork_u1)(1 << 1))
#define morkTable_kNotedBit ((mork_u1)(1 << 2))   /* space has change notes */
#define morkTable_kRewriteBit ((mork_u1)(1 << 3)) /* must rewrite all rows */
#define morkTable_kNewMetaBit ((mork_u1)(1 << 4)) /* new table meta row */

class morkTable : public morkObject, public morkLink, public nsIMdbTable {
  // NOTE the morkLink base is for morkRowSpace::mRowSpace_TablesByPriority

  // public: // slots inherited from morkObject (meant to inform only)
  // nsIMdbHeap*    mNode_Heap;

  // mork_base      mNode_Base;     // must equal morkBase_kNode
  // mork_derived   mNode_Derived;  // depends on specific node subclass

  // mork_access    mNode_Access;   // kOpen, kClosing, kShut, or kDead
  // mork_usage     mNode_Usage;    // kHeap, kStack, kMember, kGlobal, kNone
  // mork_able      mNode_Mutable;  // can this node be modified?
  // mork_load      mNode_Load;     // is this node clean or dirty?

  // mork_uses      mNode_Uses;     // refcount for strong refs
  // mork_refs      mNode_Refs;     // refcount for strong refs + weak refs

  // mork_color   mBead_Color;   // ID for this bead
  // morkHandle*  mObject_Handle;  // weak ref to handle for this object

 public:  // bead color setter & getter replace obsolete member mTable_Id:
  NS_DECL_ISUPPORTS_INHERITED
  mork_tid TableId() const { return mBead_Color; }
  void SetTableId(mork_tid inTid) { mBead_Color = inTid; }

  // we override these so we use xpcom ref-counting semantics.
#ifndef _MSC_VER
  // The first declaration of AddStrongRef is to suppress
  // -Werror,-Woverloaded-virtual.
  virtual mork_refs AddStrongRef(nsIMdbEnv* ev) override;
#endif
  virtual mork_refs AddStrongRef(morkEnv* ev) override;
#ifndef _MSC_VER
  // The first declaration of CutStrongRef is to suppress
  // -Werror,-Woverloaded-virtual.
  virtual nsresult CutStrongRef(nsIMdbEnv* ev) override;
#endif
  virtual mork_refs CutStrongRef(morkEnv* ev) override;

 public:
  // { ===== begin nsIMdbCollection methods =====
  // { ----- begin attribute methods -----
  NS_IMETHOD GetSeed(nsIMdbEnv* ev,
                     mdb_seed* outSeed) override;  // member change count
  NS_IMETHOD GetCount(nsIMdbEnv* ev,
                      mdb_count* outCount) override;  // member count

  NS_IMETHOD GetPort(nsIMdbEnv* ev,
                     nsIMdbPort** acqPort) override;  // collection container
  // } ----- end attribute methods -----

  // { ----- begin cursor methods -----
  NS_IMETHOD GetCursor(     // make a cursor starting iter at inMemberPos
      nsIMdbEnv* ev,        // context
      mdb_pos inMemberPos,  // zero-based ordinal pos of member in collection
      nsIMdbCursor** acqCursor) override;  // acquire new cursor instance
  // } ----- end cursor methods -----

  // { ----- begin ID methods -----
  NS_IMETHOD GetOid(nsIMdbEnv* ev,
                    mdbOid* outOid) override;  // read object identity
  NS_IMETHOD BecomeContent(nsIMdbEnv* ev,
                           const mdbOid* inOid) override;  // exchange content
  // } ----- end ID methods -----

  // { ----- begin activity dropping methods -----
  NS_IMETHOD DropActivity(  // tell collection usage no longer expected
      nsIMdbEnv* ev) override;
  // } ----- end activity dropping methods -----

  // } ===== end nsIMdbCollection methods =====
  NS_IMETHOD SetTablePriority(nsIMdbEnv* ev, mdb_priority inPrio) override;
  NS_IMETHOD GetTablePriority(nsIMdbEnv* ev, mdb_priority* outPrio) override;

  NS_IMETHOD GetTableBeVerbose(nsIMdbEnv* ev, mdb_bool* outBeVerbose) override;
  NS_IMETHOD SetTableBeVerbose(nsIMdbEnv* ev, mdb_bool inBeVerbose) override;

  NS_IMETHOD GetTableIsUnique(nsIMdbEnv* ev, mdb_bool* outIsUnique) override;

  NS_IMETHOD GetTableKind(nsIMdbEnv* ev, mdb_kind* outTableKind) override;
  NS_IMETHOD GetRowScope(nsIMdbEnv* ev, mdb_scope* outRowScope) override;

  NS_IMETHOD GetMetaRow(
      nsIMdbEnv* ev,                       // context
      const mdbOid* inOptionalMetaRowOid,  // can be nil to avoid specifying
      mdbOid* outOid,  // output meta row oid, can be nil to suppress output
      nsIMdbRow** acqRow)
      override;  // acquire table's unique singleton meta row
  // The purpose of a meta row is to support the persistent recording of
  // meta info about a table as cells put into the distinguished meta row.
  // Each table has exactly one meta row, which is not considered a member
  // of the collection of rows inside the table.  The only way to tell
  // whether a row is a meta row is by the fact that it is returned by this
  // GetMetaRow() method from some table. Otherwise nothing distinguishes
  // a meta row from any other row.  A meta row can be used anyplace that
  // any other row can be used, and can even be put into other tables (or
  // the same table) as a table member, if this is useful for some reason.
  // The first attempt to access a table's meta row using GetMetaRow() will
  // cause the meta row to be created if it did not already exist.  When the
  // meta row is created, it will have the row oid that was previously
  // requested for this table's meta row; or if no oid was ever explicitly
  // specified for this meta row, then a unique oid will be generated in
  // the row scope named "m" (so obviously MDB clients should not
  // manually allocate any row IDs from that special meta scope namespace).
  // The meta row oid can be specified either when the table is created, or
  // else the first time that GetMetaRow() is called, by passing a non-nil
  // pointer to an oid for parameter inOptionalMetaRowOid.  The meta row's
  // actual oid is returned in outOid (if this is a non-nil pointer), and
  // it will be different from inOptionalMetaRowOid when the meta row was
  // already given a different oid earlier.
  // } ----- end meta attribute methods -----

  // { ----- begin cursor methods -----
  NS_IMETHOD
  GetTableRowCursor(     // make a cursor, starting iteration at inRowPos
      nsIMdbEnv* ev,     // context
      mdb_pos inRowPos,  // zero-based ordinal position of row in table
      nsIMdbTableRowCursor** acqCursor)
      override;  // acquire new cursor instance
  // } ----- end row position methods -----

  // { ----- begin row position methods -----
  NS_IMETHOD PosToOid(           // get row member for a table position
      nsIMdbEnv* ev,             // context
      mdb_pos inRowPos,          // zero-based ordinal position of row in table
      mdbOid* outOid) override;  // row oid at the specified position

  NS_IMETHOD OidToPos(            // test for the table position of a row member
      nsIMdbEnv* ev,              // context
      const mdbOid* inOid,        // row to find in table
      mdb_pos* outPos) override;  // zero-based ordinal position of row in table

  NS_IMETHOD PosToRow(   // test for the table position of a row member
      nsIMdbEnv* ev,     // context
      mdb_pos inRowPos,  // zero-based ordinal position of row in table
      nsIMdbRow** acqRow) override;  // acquire row at table position inRowPos

  NS_IMETHOD RowToPos(            // test for the table position of a row member
      nsIMdbEnv* ev,              // context
      nsIMdbRow* ioRow,           // row to find in table
      mdb_pos* outPos) override;  // zero-based ordinal position of row in table
  // } ----- end row position methods -----

  // { ----- begin oid set methods -----
  NS_IMETHOD AddOid(  // make sure the row with inOid is a table member
      nsIMdbEnv* ev,  // context
      const mdbOid* inOid) override;  // row to ensure membership in table

  NS_IMETHOD HasOid(        // test for the table position of a row member
      nsIMdbEnv* ev,        // context
      const mdbOid* inOid,  // row to find in table
      mdb_bool* outHasOid) override;  // whether inOid is a member row

  NS_IMETHOD CutOid(  // make sure the row with inOid is not a member
      nsIMdbEnv* ev,  // context
      const mdbOid* inOid) override;  // row to remove from table
  // } ----- end oid set methods -----

  // { ----- begin row set methods -----
  NS_IMETHOD NewRow(  // create a new row instance in table
      nsIMdbEnv* ev,  // context
      mdbOid*
          ioOid,  // please use minus one (unbound) rowId for db-assigned IDs
      nsIMdbRow** acqRow) override;  // create new row

  NS_IMETHOD AddRow(  // make sure the row with inOid is a table member
      nsIMdbEnv* ev,  // context
      nsIMdbRow* ioRow) override;  // row to ensure membership in table

  NS_IMETHOD HasRow(     // test for the table position of a row member
      nsIMdbEnv* ev,     // context
      nsIMdbRow* ioRow,  // row to find in table
      mdb_bool* outHasRow) override;  // whether row is a table member

  NS_IMETHOD CutRow(  // make sure the row with inOid is not a member
      nsIMdbEnv* ev,  // context
      nsIMdbRow* ioRow) override;  // row to remove from table

  NS_IMETHOD CutAllRows(        // remove all rows from the table
      nsIMdbEnv* ev) override;  // context
  // } ----- end row set methods -----

  // { ----- begin hinting methods -----
  NS_IMETHOD SearchColumnsHint(  // advise re future expected search cols
      nsIMdbEnv* ev,             // context
      const mdbColumnSet* inColumnSet)
      override;  // columns likely to be searched

  NS_IMETHOD SortColumnsHint(  // advise re future expected sort columns
      nsIMdbEnv* ev,           // context
      const mdbColumnSet* inColumnSet)
      override;  // columns for likely sort requests

  NS_IMETHOD StartBatchChangeHint(    // advise before many adds and cuts
      nsIMdbEnv* ev,                  // context
      const void* inLabel) override;  // intend unique address to match end call
  // If batch starts nest by virtue of nesting calls in the stack, then
  // the address of a local variable makes a good batch start label that
  // can be used at batch end time, and such addresses remain unique.

  NS_IMETHOD EndBatchChangeHint(      // advise before many adds and cuts
      nsIMdbEnv* ev,                  // context
      const void* inLabel) override;  // label matching start label
  // Suppose a table is maintaining one or many sort orders for a table,
  // so that every row added to the table must be inserted in each sort,
  // and every row cut must be removed from each sort.  If a db client
  // intends to make many such changes before needing any information
  // about the order or positions of rows inside a table, then a client
  // might tell the table to start batch changes in order to disable
  // sorting of rows for the interim.  Presumably a table will then do
  // a full sort of all rows at need when the batch changes end, or when
  // a surprise request occurs for row position during batch changes.
  // } ----- end hinting methods -----

  // { ----- begin searching methods -----
  NS_IMETHOD FindRowMatches(  // search variable number of sorted cols
      nsIMdbEnv* ev,          // context
      const mdbYarn*
          inPrefix,  // content to find as prefix in row's column cell
      nsIMdbTableRowCursor** acqCursor) override;  // set of matching rows

  NS_IMETHOD GetSearchColumns(  // query columns used by FindRowMatches()
      nsIMdbEnv* ev,            // context
      mdb_count* outCount,      // context
      mdbColumnSet* outColSet)
      override;  // caller supplied space to put columns
  // GetSearchColumns() returns the columns actually searched when the
  // FindRowMatches() method is called.  No more than mColumnSet_Count
  // slots of mColumnSet_Columns will be written, since mColumnSet_Count
  // indicates how many slots are present in the column array.  The
  // actual number of search column used by the table is returned in
  // the outCount parameter; if this number exceeds mColumnSet_Count,
  // then a caller needs a bigger array to read the entire column set.
  // The minimum of mColumnSet_Count and outCount is the number slots
  // in mColumnSet_Columns that were actually written by this method.
  //
  // Callers are expected to change this set of columns by calls to
  // nsIMdbTable::SearchColumnsHint() or SetSearchSorting(), or both.
  // } ----- end searching methods -----

  // { ----- begin sorting methods -----
  // sorting: note all rows are assumed sorted by row ID as a secondary
  // sort following the primary column sort, when table rows are sorted.

  NS_IMETHOD
  CanSortColumn(            // query which column is currently used for sorting
      nsIMdbEnv* ev,        // context
      mdb_column inColumn,  // column to query sorting potential
      mdb_bool* outCanSort) override;  // whether the column can be sorted

  NS_IMETHOD GetSorting(    // view same table in particular sorting
      nsIMdbEnv* ev,        // context
      mdb_column inColumn,  // requested new column for sorting table
      nsIMdbSorting** acqSorting) override;  // acquire sorting for column

  NS_IMETHOD SetSearchSorting(  // use this sorting in FindRowMatches()
      nsIMdbEnv* ev,            // context
      mdb_column inColumn,      // often same as nsIMdbSorting::GetSortColumn()
      nsIMdbSorting* ioSorting) override;  // requested sorting for some column
  // SetSearchSorting() attempts to inform the table that ioSorting
  // should be used during calls to FindRowMatches() for searching
  // the column which is actually sorted by ioSorting.  This method
  // is most useful in conjunction with nsIMdbSorting::SetCompare(),
  // because otherwise a caller would not be able to override the
  // comparison ordering method used during searches.  Note that some
  // database implementations might be unable to use an arbitrarily
  // specified sort order, either due to schema or runtime interface
  // constraints, in which case ioSorting might not actually be used.
  // Presumably ioSorting is an instance that was returned from some
  // earlier call to nsIMdbTable::GetSorting().  A caller can also
  // use nsIMdbTable::SearchColumnsHint() to specify desired change
  // in which columns are sorted and searched by FindRowMatches().
  //
  // A caller can pass a nil pointer for ioSorting to request that
  // column inColumn no longer be used at all by FindRowMatches().
  // But when ioSorting is non-nil, then inColumn should match the
  // column actually sorted by ioSorting; when these do not agree,
  // implementations are instructed to give precedence to the column
  // specified by ioSorting (so this means callers might just pass
  // zero for inColumn when ioSorting is also provided, since then
  // inColumn is both redundant and ignored).
  // } ----- end sorting methods -----

  // { ----- begin moving methods -----
  // moving a row does nothing unless a table is currently unsorted

  NS_IMETHOD MoveOid(         // change position of row in unsorted table
      nsIMdbEnv* ev,          // context
      const mdbOid* inOid,    // row oid to find in table
      mdb_pos inHintFromPos,  // suggested hint regarding start position
      mdb_pos inToPos,        // desired new position for row inRowId
      mdb_pos* outActualPos) override;  // actual new position of row in table

  NS_IMETHOD MoveRow(         // change position of row in unsorted table
      nsIMdbEnv* ev,          // context
      nsIMdbRow* ioRow,       // row oid to find in table
      mdb_pos inHintFromPos,  // suggested hint regarding start position
      mdb_pos inToPos,        // desired new position for row inRowId
      mdb_pos* outActualPos) override;  // actual new position of row in table
  // } ----- end moving methods -----

  // { ----- begin index methods -----
  NS_IMETHOD AddIndex(      // create a sorting index for column if possible
      nsIMdbEnv* ev,        // context
      mdb_column inColumn,  // the column to sort by index
      nsIMdbThumb** acqThumb)
      override;  // acquire thumb for incremental index building
  // Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
  // then the index addition will be finished.

  NS_IMETHOD CutIndex(      // stop supporting a specific column index
      nsIMdbEnv* ev,        // context
      mdb_column inColumn,  // the column with index to be removed
      nsIMdbThumb** acqThumb)
      override;  // acquire thumb for incremental index destroy
  // Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
  // then the index removal will be finished.

  NS_IMETHOD HasIndex(      // query for current presence of a column index
      nsIMdbEnv* ev,        // context
      mdb_column inColumn,  // the column to investigate
      mdb_bool* outHasIndex)
      override;  // whether column has index for this column

  NS_IMETHOD EnableIndexOnSort(       // create an index for col on first sort
      nsIMdbEnv* ev,                  // context
      mdb_column inColumn) override;  // the column to index if ever sorted

  NS_IMETHOD QueryIndexOnSort(  // check whether index on sort is enabled
      nsIMdbEnv* ev,            // context
      mdb_column inColumn,      // the column to investigate
      mdb_bool* outIndexOnSort)
      override;  // whether column has index-on-sort enabled

  NS_IMETHOD DisableIndexOnSort(      // prevent future index creation on sort
      nsIMdbEnv* ev,                  // context
      mdb_column inColumn) override;  // the column to index if ever sorted
  // } ----- end index methods -----

  morkStore* mTable_Store;  // non-refcnted ptr to port

  // mTable_RowSpace->SpaceScope() is row scope
  morkRowSpace* mTable_RowSpace;  // non-refcnted ptr to containing space

  morkRow* mTable_MetaRow;   // table's actual meta row
  mdbOid mTable_MetaRowOid;  // oid for meta row

  morkRowMap* mTable_RowMap;  // (strong ref) hash table of all members
  morkArray mTable_RowArray;  // array of morkRow pointers

  morkList mTable_ChangeList;   // list of table changes
  mork_u2 mTable_ChangesCount;  // length of changes list
  mork_u2 mTable_ChangesMax;    // max list length before rewrite

  // mork_tid        mTable_Id;
  mork_kind mTable_Kind;

  mork_u1 mTable_Flags;           // bit flags
  mork_priority mTable_Priority;  // 0..9, any other value equals 9
  mork_u1 mTable_GcUses;          // persistent references from cells
  mork_u1 mTable_Pad;             // for u4 alignment

 public:  // flags bit twiddling
  void SetTableUnique() { mTable_Flags |= morkTable_kUniqueBit; }
  void SetTableVerbose() { mTable_Flags |= morkTable_kVerboseBit; }
  void SetTableNoted() { mTable_Flags |= morkTable_kNotedBit; }
  void SetTableRewrite() { mTable_Flags |= morkTable_kRewriteBit; }
  void SetTableNewMeta() { mTable_Flags |= morkTable_kNewMetaBit; }

  void ClearTableUnique() { mTable_Flags &= (mork_u1)~morkTable_kUniqueBit; }
  void ClearTableVerbose() { mTable_Flags &= (mork_u1)~morkTable_kVerboseBit; }
  void ClearTableNoted() { mTable_Flags &= (mork_u1)~morkTable_kNotedBit; }
  void ClearTableRewrite() { mTable_Flags &= (mork_u1)~morkTable_kRewriteBit; }
  void ClearTableNewMeta() { mTable_Flags &= (mork_u1)~morkTable_kNewMetaBit; }

  mork_bool IsTableUnique() const {
    return (mTable_Flags & morkTable_kUniqueBit) != 0;
  }

  mork_bool IsTableVerbose() const {
    return (mTable_Flags & morkTable_kVerboseBit) != 0;
  }

  mork_bool IsTableNoted() const {
    return (mTable_Flags & morkTable_kNotedBit) != 0;
  }

  mork_bool IsTableRewrite() const {
    return (mTable_Flags & morkTable_kRewriteBit) != 0;
  }

  mork_bool IsTableNewMeta() const {
    return (mTable_Flags & morkTable_kNewMetaBit) != 0;
  }

 public
     :  // table dirty handling more complex than morkNode::SetNodeDirty() etc.
  void SetTableDirty() { this->SetNodeDirty(); }
  void SetTableClean(morkEnv* ev);

  mork_bool IsTableClean() const { return this->IsNodeClean(); }
  mork_bool IsTableDirty() const { return this->IsNodeDirty(); }

 public:  // morkNode memory management operators
  void* operator new(size_t inSize, nsIMdbHeap& ioHeap,
                     morkEnv* ev) noexcept(true) {
    return morkNode::MakeNew(inSize, ioHeap, ev);
  }

  // { ===== begin morkNode interface =====
 public:                                             // morkNode virtual methods
  virtual void CloseMorkNode(morkEnv* ev) override;  // CloseTable() if open

 public:  // morkTable construction & destruction
  morkTable(
      morkEnv* ev, const morkUsage& inUsage, nsIMdbHeap* ioNodeHeap,
      morkStore* ioStore, nsIMdbHeap* ioSlotHeap, morkRowSpace* ioRowSpace,
      const mdbOid* inOptionalMetaRowOid,  // can be nil to avoid specifying
      mork_tid inTableId, mork_kind inKind, mork_bool inMustBeUnique);
  void CloseTable(morkEnv* ev);  // called by CloseMorkNode();

 private:  // copying is not allowed
  morkTable(const morkTable& other);
  morkTable& operator=(const morkTable& other);
  virtual ~morkTable();  // assert that close executed earlier

 public:  // dynamic type identification
  mork_bool IsTable() const {
    return IsNode() && mNode_Derived == morkDerived_kTable;
  }
  // } ===== end morkNode methods =====

 public:  // errors
  static void NonTableTypeError(morkEnv* ev);
  static void NonTableTypeWarning(morkEnv* ev);
  static void NilRowSpaceError(morkEnv* ev);

 public:  // warnings
  static void TableGcUsesUnderflowWarning(morkEnv* ev);

 public:  // noting table changes
  mork_bool HasChangeOverflow() const {
    return mTable_ChangesCount >= mTable_ChangesMax;
  }

  void NoteTableSetAll(morkEnv* ev);
  void NoteTableMoveRow(morkEnv* ev, morkRow* ioRow, mork_pos inPos);

  void note_row_change(morkEnv* ev, mork_change inChange, morkRow* ioRow);
  void note_row_move(morkEnv* ev, morkRow* ioRow, mork_pos inNewPos);

  void NoteTableAddRow(morkEnv* ev, morkRow* ioRow) {
    this->note_row_change(ev, morkChange_kAdd, ioRow);
  }

  void NoteTableCutRow(morkEnv* ev, morkRow* ioRow) {
    this->note_row_change(ev, morkChange_kCut, ioRow);
  }

 protected:  // internal row map methods
  morkRow* find_member_row(morkEnv* ev, morkRow* ioRow);
  void build_row_map(morkEnv* ev);

 public:  // other table methods
  mork_bool MaybeDirtySpaceStoreAndTable();

  morkRow* GetMetaRow(morkEnv* ev, const mdbOid* inOptionalMetaRowOid);

  mork_u2 AddTableGcUse(morkEnv* ev);
  mork_u2 CutTableGcUse(morkEnv* ev);

  // void DirtyAllTableContent(morkEnv* ev);

  mork_seed TableSeed() const { return mTable_RowArray.mArray_Seed; }

  morkRow* SafeRowAt(morkEnv* ev, mork_pos inPos) {
    return (morkRow*)mTable_RowArray.SafeAt(ev, inPos);
  }

  nsIMdbTable* AcquireTableHandle(morkEnv* ev);  // mObject_Handle

  mork_count GetRowCount() const { return mTable_RowArray.mArray_Fill; }

  mork_bool IsTableUsed() const {
    return (mTable_GcUses != 0 || this->GetRowCount() != 0);
  }

  void GetTableOid(morkEnv* ev, mdbOid* outOid);
  mork_pos ArrayHasOid(morkEnv* ev, const mdbOid* inOid);
  mork_bool MapHasOid(morkEnv* ev, const mdbOid* inOid);
  mork_bool AddRow(morkEnv* ev, morkRow* ioRow);  // returns ev->Good()
  mork_bool CutRow(morkEnv* ev, morkRow* ioRow);  // returns ev->Good()
  mork_bool CutAllRows(morkEnv* ev);              // returns ev->Good()

  mork_pos MoveRow(
      morkEnv* ev, morkRow* ioRow,  // change row position
      mork_pos inHintFromPos,       // suggested hint regarding start position
      mork_pos inToPos);            // desired new position for row ioRow
  // MoveRow() returns the actual position of ioRow afterwards; this
  // position is -1 if and only if ioRow was not found as a member.

  morkTableRowCursor* NewTableRowCursor(morkEnv* ev, mork_pos inRowPos);

 public:  // typesafe refcounting inlines calling inherited morkNode methods
  static void SlotWeakTable(morkTable* me, morkEnv* ev, morkTable** ioSlot) {
    morkNode::SlotWeakNode((morkNode*)me, ev, (morkNode**)ioSlot);
  }

  static void SlotStrongTable(morkTable* me, morkEnv* ev, morkTable** ioSlot) {
    morkNode::SlotStrongNode((morkNode*)me, ev, (morkNode**)ioSlot);
  }
};

// 456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789

// use negative values for kCut and kAdd, to keep non-neg move pos distinct:
#define morkTableChange_kCut ((mork_pos)-1)  /* shows row was cut */
#define morkTableChange_kAdd ((mork_pos)-2)  /* shows row was added */
#define morkTableChange_kNone ((mork_pos)-3) /* unknown change */

class morkTableChange : public morkNext {
 public:  // state is public because the entire Mork system is private
  morkRow* mTableChange_Row;  // the row in the change

  mork_pos mTableChange_Pos;  // kAdd, kCut, or non-neg for row move

 public:
  morkTableChange(morkEnv* ev, mork_change inChange, morkRow* ioRow);
  // use this constructor for inChange == morkChange_kAdd or morkChange_kCut

  morkTableChange(morkEnv* ev, morkRow* ioRow, mork_pos inPos);
  // use this constructor when the row is moved

 public:
  void UnknownChangeError(
      morkEnv* ev) const;  // morkChange_kAdd or morkChange_kCut
  void NegativeMovePosError(
      morkEnv* ev) const;  // move must be non-neg position

 public:
  mork_bool IsAddRowTableChange() const {
    return (mTableChange_Pos == morkTableChange_kAdd);
  }

  mork_bool IsCutRowTableChange() const {
    return (mTableChange_Pos == morkTableChange_kCut);
  }

  mork_bool IsMoveRowTableChange() const { return (mTableChange_Pos >= 0); }

 public:
  mork_pos GetMovePos() const { return mTableChange_Pos; }
  // GetMovePos() assumes that IsMoveRowTableChange() is true.
};

// 456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789

#define morkDerived_kTableMap /*i*/ 0x744D /* ascii 'tM' */

/*| morkTableMap: maps mork_token -> morkTable
|*/
#ifdef MORK_BEAD_OVER_NODE_MAPS
class morkTableMap : public morkBeadMap {
#else  /*MORK_BEAD_OVER_NODE_MAPS*/
class morkTableMap : public morkNodeMap {  // for mapping tokens to tables
#endif /*MORK_BEAD_OVER_NODE_MAPS*/

 public:
  virtual ~morkTableMap();
  morkTableMap(morkEnv* ev, const morkUsage& inUsage, nsIMdbHeap* ioHeap,
               nsIMdbHeap* ioSlotHeap);

 public:  // other map methods
#ifdef MORK_BEAD_OVER_NODE_MAPS
  mork_bool AddTable(morkEnv* ev, morkTable* ioTable) {
    return this->AddBead(ev, ioTable);
  }
  // the AddTable() boolean return equals ev->Good().

  mork_bool CutTable(morkEnv* ev, mork_tid inTid) {
    return this->CutBead(ev, inTid);
  }
  // The CutTable() boolean return indicates whether removal happened.

  morkTable* GetTable(morkEnv* ev, mork_tid inTid) {
    return (morkTable*)this->GetBead(ev, inTid);
  }
  // Note the returned table does NOT have an increase in refcount for this.

  mork_num CutAllTables(morkEnv* ev) { return this->CutAllBeads(ev); }
  // CutAllTables() releases all the referenced table values.

#else  /*MORK_BEAD_OVER_NODE_MAPS*/
  mork_bool AddTable(morkEnv* ev, morkTable* ioTable) {
    return this->AddNode(ev, ioTable->TableId(), ioTable);
  }
  // the AddTable() boolean return equals ev->Good().

  mork_bool CutTable(morkEnv* ev, mork_tid inTid) {
    return this->CutNode(ev, inTid);
  }
  // The CutTable() boolean return indicates whether removal happened.

  morkTable* GetTable(morkEnv* ev, mork_tid inTid) {
    return (morkTable*)this->GetNode(ev, inTid);
  }
  // Note the returned table does NOT have an increase in refcount for this.

  mork_num CutAllTables(morkEnv* ev) { return this->CutAllNodes(ev); }
  // CutAllTables() releases all the referenced table values.
#endif /*MORK_BEAD_OVER_NODE_MAPS*/
};

#ifdef MORK_BEAD_OVER_NODE_MAPS
class morkTableMapIter : public morkBeadMapIter {
#else  /*MORK_BEAD_OVER_NODE_MAPS*/
class morkTableMapIter : public morkMapIter {  // typesafe wrapper class
#endif /*MORK_BEAD_OVER_NODE_MAPS*/

 public:
#ifdef MORK_BEAD_OVER_NODE_MAPS
  morkTableMapIter(morkEnv* ev, morkTableMap* ioMap)
      : morkBeadMapIter(ev, ioMap) {}

  morkTableMapIter() : morkBeadMapIter() {}
  void InitTableMapIter(morkEnv* ev, morkTableMap* ioMap) {
    this->InitBeadMapIter(ev, ioMap);
  }

  morkTable* FirstTable(morkEnv* ev) { return (morkTable*)this->FirstBead(ev); }

  morkTable* NextTable(morkEnv* ev) { return (morkTable*)this->NextBead(ev); }

  morkTable* HereTable(morkEnv* ev) { return (morkTable*)this->HereBead(ev); }

#else  /*MORK_BEAD_OVER_NODE_MAPS*/
  morkTableMapIter(morkEnv* ev, morkTableMap* ioMap) : morkMapIter(ev, ioMap) {}

  morkTableMapIter() : morkMapIter() {}
  void InitTableMapIter(morkEnv* ev, morkTableMap* ioMap) {
    this->InitMapIter(ev, ioMap);
  }

  mork_change* FirstTable(morkEnv* ev, mork_tid* outTid, morkTable** outTable) {
    return this->First(ev, outTid, outTable);
  }

  mork_change* NextTable(morkEnv* ev, mork_tid* outTid, morkTable** outTable) {
    return this->Next(ev, outTid, outTable);
  }

  mork_change* HereTable(morkEnv* ev, mork_tid* outTid, morkTable** outTable) {
    return this->Here(ev, outTid, outTable);
  }

  // cutting while iterating hash map might dirty the parent table:
  mork_change* CutHereTable(morkEnv* ev, mork_tid* outTid,
                            morkTable** outTable) {
    return this->CutHere(ev, outTid, outTable);
  }
#endif /*MORK_BEAD_OVER_NODE_MAPS*/
};

// 456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789

#endif /* _MORKTABLE_ */