summaryrefslogtreecommitdiff
path: root/db/mork/src/morkStore.h
blob: f0471ff15c1d3c73e618ededae855ee4e3a2d959 (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
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
/* -*- 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 _MORKSTORE_
#define _MORKSTORE_ 1

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

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

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

#ifndef _MORKPOOL_
#include "morkPool.h"
#endif

#ifndef _MORKZONE_
#include "morkZone.h"
#endif

#ifndef _MORKATOM_
#include "morkAtom.h"
#endif

#ifndef _MORKROWSPACE_
#include "morkRowSpace.h"
#endif

#ifndef _MORKATOMSPACE_
#include "morkAtomSpace.h"
#endif

//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789

#define morkDerived_kPort  /*i*/ 0x7054 /* ascii 'pT' */

#define morkDerived_kStore  /*i*/ 0x7354 /* ascii 'sT' */

/*| kGroundColumnSpace: we use the 'column space' as the default scope
**| for grounding column name IDs, and this is also the default scope for
**| all other explicitly tokenized strings.
|*/
#define morkStore_kGroundColumnSpace 'c' /* for mStore_GroundColumnSpace*/
#define morkStore_kColumnSpaceScope ((mork_scope) 'c') /*kGroundColumnSpace*/
#define morkStore_kValueSpaceScope ((mork_scope) 'v')
#define morkStore_kStreamBufSize (8 * 1024) /* okay buffer size */

#define morkStore_kReservedColumnCount 0x20 /* for well-known columns */

#define morkStore_kNoneToken ((mork_token) 'n')
#define morkStore_kFormColumn ((mork_column) 'f')
#define morkStore_kAtomScopeColumn ((mork_column) 'a')
#define morkStore_kRowScopeColumn ((mork_column) 'r')
#define morkStore_kMetaScope ((mork_scope) 'm')
#define morkStore_kKindColumn ((mork_column) 'k')
#define morkStore_kStatusColumn ((mork_column) 's')

/*| morkStore: 
|*/
class morkStore :  public morkObject, public nsIMdbStore {

public: // state is public because the entire Mork system is private

  NS_DECL_ISUPPORTS_INHERITED

  morkEnv*        mPort_Env;      // non-refcounted env which created port
  morkFactory*    mPort_Factory;  // weak ref to suite factory
  nsIMdbHeap*     mPort_Heap;     // heap in which this port allocs objects
  
// { ===== begin morkNode interface =====
public: // morkNode virtual methods
  
  void ClosePort(morkEnv* ev); // called by CloseMorkNode();

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

public: // other port methods

  // { ----- begin attribute methods -----
//  NS_IMETHOD IsFrozenMdbObject(nsIMdbEnv* ev, mdb_bool* outIsReadonly);
  // same as nsIMdbPort::GetIsPortReadonly() when this object is inside a port.
  // } ----- end attribute methods -----

  // { ----- begin factory methods -----
//  NS_IMETHOD GetMdbFactory(nsIMdbEnv* ev, nsIMdbFactory** acqFactory); 
  // } ----- end factory methods -----

  // { ----- begin ref counting for well-behaved cyclic graphs -----
  NS_IMETHOD GetWeakRefCount(nsIMdbEnv* ev, // weak refs
    mdb_count* outCount) override;
  NS_IMETHOD GetStrongRefCount(nsIMdbEnv* ev, // strong refs
    mdb_count* outCount) override;

  NS_IMETHOD AddWeakRef(nsIMdbEnv* ev) override;
#ifndef _MSC_VER
  // The first declaration of AddStrongRef is to suppress -Werror,-Woverloaded-virtual.
  NS_IMETHOD_(mork_uses) AddStrongRef(morkEnv* ev) override;
#endif
  NS_IMETHOD_(mork_uses) AddStrongRef(nsIMdbEnv* ev) override;

  NS_IMETHOD CutWeakRef(nsIMdbEnv* ev) override;
#ifndef _MSC_VER
  // The first declaration of CutStrongRef is to suppress -Werror,-Woverloaded-virtual.
  NS_IMETHOD_(mork_uses) CutStrongRef(morkEnv* ev) override;
#endif
  NS_IMETHOD CutStrongRef(nsIMdbEnv* ev) override;

  NS_IMETHOD CloseMdbObject(nsIMdbEnv* ev) override; // called at strong refs zero
  NS_IMETHOD IsOpenMdbObject(nsIMdbEnv* ev, mdb_bool* outOpen) override;
  // } ----- end ref counting -----
  
// } ===== end nsIMdbObject methods =====

// { ===== begin nsIMdbPort methods =====

  // { ----- begin attribute methods -----
  NS_IMETHOD GetIsPortReadonly(nsIMdbEnv* ev, mdb_bool* outBool) override;
  NS_IMETHOD GetIsStore(nsIMdbEnv* ev, mdb_bool* outBool) override;
  NS_IMETHOD GetIsStoreAndDirty(nsIMdbEnv* ev, mdb_bool* outBool) override;

  NS_IMETHOD GetUsagePolicy(nsIMdbEnv* ev,
    mdbUsagePolicy* ioUsagePolicy) override;

  NS_IMETHOD SetUsagePolicy(nsIMdbEnv* ev,
    const mdbUsagePolicy* inUsagePolicy) override;
  // } ----- end attribute methods -----

  // { ----- begin memory policy methods -----  
  NS_IMETHOD IdleMemoryPurge( // do memory management already scheduled
    nsIMdbEnv* ev, // context
    mdb_size* outEstimatedBytesFreed) override; // approximate bytes actually freed

  NS_IMETHOD SessionMemoryPurge( // request specific footprint decrease
    nsIMdbEnv* ev, // context
    mdb_size inDesiredBytesFreed, // approximate number of bytes wanted
    mdb_size* outEstimatedBytesFreed) override; // approximate bytes actually freed

  NS_IMETHOD PanicMemoryPurge( // desperately free all possible memory
    nsIMdbEnv* ev, // context
    mdb_size* outEstimatedBytesFreed) override; // approximate bytes actually freed
  // } ----- end memory policy methods -----

  // { ----- begin filepath methods -----
  NS_IMETHOD GetPortFilePath(
    nsIMdbEnv* ev, // context
    mdbYarn* outFilePath, // name of file holding port content
    mdbYarn* outFormatVersion) override; // file format description

  NS_IMETHOD GetPortFile(
    nsIMdbEnv* ev, // context
    nsIMdbFile** acqFile) override; // acquire file used by port or store
  // } ----- end filepath methods -----

  // { ----- begin export methods -----
  NS_IMETHOD BestExportFormat( // determine preferred export format
    nsIMdbEnv* ev, // context
    mdbYarn* outFormatVersion) override; // file format description

  NS_IMETHOD
  CanExportToFormat( // can export content in given specific format?
    nsIMdbEnv* ev, // context
    const char* inFormatVersion, // file format description
    mdb_bool* outCanExport) override; // whether ExportSource() might succeed

  NS_IMETHOD ExportToFormat( // export content in given specific format
    nsIMdbEnv* ev, // context
    // const char* inFilePath, // the file to receive exported content
    nsIMdbFile* ioFile, // destination abstract file interface
    const char* inFormatVersion, // file format description
    nsIMdbThumb** acqThumb) override; // acquire thumb for incremental export
  // Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
  // then the export will be finished.

  // } ----- end export methods -----

  // { ----- begin token methods -----
  NS_IMETHOD TokenToString( // return a string name for an integer token
    nsIMdbEnv* ev, // context
    mdb_token inToken, // token for inTokenName inside this port
    mdbYarn* outTokenName) override; // the type of table to access
  
  NS_IMETHOD StringToToken( // return an integer token for scope name
    nsIMdbEnv* ev, // context
    const char* inTokenName, // Latin1 string to tokenize if possible
    mdb_token* outToken) override; // token for inTokenName inside this port
    
  // String token zero is never used and never supported. If the port
  // is a mutable store, then StringToToken() to create a new
  // association of inTokenName with a new integer token if possible.
  // But a readonly port will return zero for an unknown scope name.

  NS_IMETHOD QueryToken( // like StringToToken(), but without adding
    nsIMdbEnv* ev, // context
    const char* inTokenName, // Latin1 string to tokenize if possible
    mdb_token* outToken) override; // token for inTokenName inside this port
  
  // QueryToken() will return a string token if one already exists,
  // but unlike StringToToken(), will not assign a new token if not
  // already in use.

  // } ----- end token methods -----

  // { ----- begin row methods -----  
  NS_IMETHOD HasRow( // contains a row with the specified oid?
    nsIMdbEnv* ev, // context
    const mdbOid* inOid,  // hypothetical row oid
    mdb_bool* outHasRow) override; // whether GetRow() might succeed

  NS_IMETHOD GetRowRefCount( // get number of tables that contain a row 
    nsIMdbEnv* ev, // context
    const mdbOid* inOid,  // hypothetical row oid
    mdb_count* outRefCount) override; // number of tables containing inRowKey 
    
  NS_IMETHOD GetRow( // access one row with specific oid
    nsIMdbEnv* ev, // context
    const mdbOid* inOid,  // hypothetical row oid
    nsIMdbRow** acqRow) override; // acquire specific row (or null)

  NS_IMETHOD FindRow(nsIMdbEnv* ev, // search for row with matching cell
    mdb_scope inRowScope,   // row scope for row ids
    mdb_column inColumn,   // the column to search (and maintain an index)
    const mdbYarn* inTargetCellValue, // cell value for which to search
    mdbOid* outRowOid, // out row oid on match (or {0,-1} for no match)
    nsIMdbRow** acqRow) override; // acquire matching row (or nil for no match)
                         // can be null if you only want the oid
  // FindRow() searches for one row that has a cell in column inColumn with
  // a contained value with the same form (i.e. charset) and is byte-wise
  // identical to the blob described by yarn inTargetCellValue.  Both content
  // and form of the yarn must be an exact match to find a matching row.
  //
  // (In other words, both a yarn's blob bytes and form are significant.  The
  // form is not expected to vary in columns used for identity anyway.  This
  // is intended to make the cost of FindRow() cheaper for MDB implementors,
  // since any cell value atomization performed internally must necessarily
  // make yarn form significant in order to avoid data loss in atomization.)
  //
  // FindRow() can lazily create an index on attribute inColumn for all rows
  // with that attribute in row space scope inRowScope, so that subsequent
  // calls to FindRow() will perform faster.  Such an index might or might
  // not be persistent (but this seems desirable if it is cheap to do so).
  // Note that lazy index creation in readonly DBs is not very feasible.
  //
  // This FindRow() interface assumes that attribute inColumn is effectively
  // an alternative means of unique identification for a row in a rowspace,
  // so correct behavior is only guaranteed when no duplicates for this col
  // appear in the given set of rows.  (If more than one row has the same cell
  // value in this column, no more than one will be found; and cutting one of
  // two duplicate rows can cause the index to assume no other such row lives
  // in the row space, so future calls return nil for negative search results
  // even though some duplicate row might still live within the rowspace.)
  //
  // In other words, the FindRow() implementation is allowed to assume simple
  // hash tables mapping unqiue column keys to associated row values will be
  // sufficient, where any duplication is not recorded because only one copy
  // of a given key need be remembered.  Implementors are not required to sort
  // all rows by the specified column.
  // } ----- end row methods -----

  // { ----- begin table methods -----  
  NS_IMETHOD HasTable( // supports a table with the specified oid?
    nsIMdbEnv* ev, // context
    const mdbOid* inOid,  // hypothetical table oid
    mdb_bool* outHasTable) override; // whether GetTable() might succeed
    
  NS_IMETHOD GetTable( // access one table with specific oid
    nsIMdbEnv* ev, // context
    const mdbOid* inOid,  // hypothetical table oid
    nsIMdbTable** acqTable) override; // acquire specific table (or null)
  
  NS_IMETHOD HasTableKind( // supports a table of the specified type?
    nsIMdbEnv* ev, // context
    mdb_scope inRowScope, // rid scope for row ids
    mdb_kind inTableKind, // the type of table to access
    mdb_count* outTableCount, // current number of such tables
    mdb_bool* outSupportsTable) override; // whether GetTableKind() might succeed
        
  NS_IMETHOD GetTableKind( // access one (random) table of specific type
    nsIMdbEnv* ev, // context
    mdb_scope inRowScope,      // row scope for row ids
    mdb_kind inTableKind,      // the type of table to access
    mdb_count* outTableCount, // current number of such tables
    mdb_bool* outMustBeUnique, // whether port can hold only one of these
    nsIMdbTable** acqTable) override; // acquire scoped collection of rows
    
  NS_IMETHOD
  GetPortTableCursor( // get cursor for all tables of specific type
    nsIMdbEnv* ev, // context
    mdb_scope inRowScope, // row scope for row ids
    mdb_kind inTableKind, // the type of table to access
    nsIMdbPortTableCursor** acqCursor) override; // all such tables in the port
  // } ----- end table methods -----


  // { ----- begin commit methods -----

  NS_IMETHOD ShouldCompress( // store wastes at least inPercentWaste?
    nsIMdbEnv* ev, // context
    mdb_percent inPercentWaste, // 0..100 percent file size waste threshold
    mdb_percent* outActualWaste, // 0..100 percent of file actually wasted
    mdb_bool* outShould) override; // true when about inPercentWaste% is wasted
  // ShouldCompress() returns true if the store can determine that the file
  // will shrink by an estimated percentage of inPercentWaste% (or more) if
  // CompressCommit() is called, because that percentage of the file seems
  // to be recoverable free space.  The granularity is only in terms of 
  // percentage points, and any value over 100 is considered equal to 100.
  //
  // If a store only has an approximate idea how much space might be saved
  // during a compress, then a best guess should be made.  For example, the
  // Mork implementation might keep track of how much file space began with
  // text content before the first updating transaction, and then consider
  // all content following the start of the first transaction as potentially
  // wasted space if it is all updates and not just new content.  (This is
  // a safe assumption in the sense that behavior will stabilize on a low
  // estimate of wastage after a commit removes all transaction updates.)
  //
  // Some db formats might attempt to keep a very accurate reckoning of free
  // space size, so a very accurate determination can be made.  But other db
  // formats might have difficulty determining size of free space, and might
  // require some lengthy calculation to answer.  This is the reason for
  // passing in the percentage threshold of interest, so that such lengthy
  // computations can terminate early as soon as at least inPercentWaste is
  // found, so that the entire file need not be groveled when unnecessary.
  // However, we hope implementations will always favor fast but imprecise
  // heuristic answers instead of extremely slow but very precise answers.
  //
  // If the outActualWaste parameter is non-nil, it will be used to return
  // the actual estimated space wasted as a percentage of file size.  (This
  // parameter is provided so callers need not call repeatedly with altered
  // inPercentWaste values to isolate the actual wastage figure.)  Note the
  // actual wastage figure returned can exactly equal inPercentWaste even
  // when this grossly underestimates the real figure involved, if the db
  // finds it very expensive to determine the extent of wastage after it is
  // known to at least exceed inPercentWaste.  Note we expect that whenever
  // outShould returns true, that outActualWaste returns >= inPercentWaste.
  //
  // The effect of different inPercentWaste values is not very uniform over
  // the permitted range.  For example, 50 represents 50% wastage, or a file
  // that is about double what it should be ideally.  But 99 represents 99%
  // wastage, or a file that is about ninety-nine times as big as it should
  // be ideally.  In the smaller direction, 25 represents 25% wastage, or
  // a file that is only 33% larger than it should be ideally.
  //
  // Callers can determine what policy they want to use for considering when
  // a file holds too much wasted space, and express this as a percentage
  // of total file size to pass as in the inPercentWaste parameter.  A zero
  // likely returns always trivially true, and 100 always trivially false.
  // The great majority of callers are expected to use values from 25 to 75,
  // since most plausible thresholds for compressing might fall between the
  // extremes of 133% of ideal size and 400% of ideal size.  (Presumably the
  // larger a file gets, the more important the percentage waste involved, so
  // a sliding scale for compress thresholds might use smaller numbers for
  // much bigger file sizes.)
  
  // } ----- end commit methods -----

// } ===== end nsIMdbPort methods =====

// { ===== begin nsIMdbStore methods =====

  // { ----- begin table methods -----
  NS_IMETHOD NewTable( // make one new table of specific type
    nsIMdbEnv* ev, // context
    mdb_scope inRowScope,    // row scope for row ids
    mdb_kind inTableKind,    // the type of table to access
    mdb_bool inMustBeUnique, // whether store can hold only one of these
    const mdbOid* inOptionalMetaRowOid, // can be nil to avoid specifying
    nsIMdbTable** acqTable) override; // acquire scoped collection of rows
    
  NS_IMETHOD NewTableWithOid( // make one new table of specific type
    nsIMdbEnv* ev, // context
    const mdbOid* inOid,   // caller assigned oid
    mdb_kind inTableKind,    // the type of table to access
    mdb_bool inMustBeUnique, // whether store can hold only one of these
    const mdbOid* inOptionalMetaRowOid, // can be nil to avoid specifying 
    nsIMdbTable** acqTable) override; // acquire scoped collection of rows
  // } ----- end table methods -----

  // { ----- begin row scope methods -----
  NS_IMETHOD RowScopeHasAssignedIds(nsIMdbEnv* ev,
    mdb_scope inRowScope,   // row scope for row ids
    mdb_bool* outCallerAssigned, // nonzero if caller assigned specified
    mdb_bool* outStoreAssigned) override; // nonzero if store db assigned specified

  NS_IMETHOD SetCallerAssignedIds(nsIMdbEnv* ev,
    mdb_scope inRowScope,   // row scope for row ids
    mdb_bool* outCallerAssigned, // nonzero if caller assigned specified
    mdb_bool* outStoreAssigned) override; // nonzero if store db assigned specified

  NS_IMETHOD SetStoreAssignedIds(nsIMdbEnv* ev,
    mdb_scope inRowScope,   // row scope for row ids
    mdb_bool* outCallerAssigned, // nonzero if caller assigned specified
    mdb_bool* outStoreAssigned) override; // nonzero if store db assigned specified
  // } ----- end row scope methods -----

  // { ----- begin row methods -----
  NS_IMETHOD NewRowWithOid(nsIMdbEnv* ev, // new row w/ caller assigned oid
    const mdbOid* inOid,   // caller assigned oid
    nsIMdbRow** acqRow) override; // create new row

  NS_IMETHOD NewRow(nsIMdbEnv* ev, // new row with db assigned oid
    mdb_scope inRowScope,   // row scope for row ids
    nsIMdbRow** acqRow) override; // create new row
  // Note this row must be added to some table or cell child before the
  // store is closed in order to make this row persist across sesssions.

  // } ----- end row methods -----

  // { ----- begin inport/export methods -----
  NS_IMETHOD ImportContent( // import content from port
    nsIMdbEnv* ev, // context
    mdb_scope inRowScope, // scope for rows (or zero for all?)
    nsIMdbPort* ioPort, // the port with content to add to store
    nsIMdbThumb** acqThumb) override; // acquire thumb for incremental import
  // Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
  // then the import will be finished.

  NS_IMETHOD ImportFile( // import content from port
    nsIMdbEnv* ev, // context
    nsIMdbFile* ioFile, // the file with content to add to store
    nsIMdbThumb** acqThumb) override; // acquire thumb for incremental import
  // Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
  // then the import will be finished.
  // } ----- end inport/export methods -----

  // { ----- begin hinting methods -----
  NS_IMETHOD
  ShareAtomColumnsHint( // advise re shared column content atomizing
    nsIMdbEnv* ev, // context
    mdb_scope inScopeHint, // zero, or suggested shared namespace
    const mdbColumnSet* inColumnSet) override; // cols desired tokenized together

  NS_IMETHOD
  AvoidAtomColumnsHint( // advise column with poor atomizing prospects
    nsIMdbEnv* ev, // context
    const mdbColumnSet* inColumnSet) override; // cols with poor atomizing prospects
  // } ----- end hinting methods -----

  // { ----- begin commit methods -----
  NS_IMETHOD LargeCommit( // save important changes if at all possible
    nsIMdbEnv* ev, // context
    nsIMdbThumb** acqThumb) override; // acquire thumb for incremental commit
  // Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
  // then the commit will be finished.  Note the store is effectively write
  // locked until commit is finished or canceled through the thumb instance.
  // Until the commit is done, the store will report it has readonly status.

  NS_IMETHOD SessionCommit( // save all changes if large commits delayed
    nsIMdbEnv* ev, // context
    nsIMdbThumb** acqThumb) override; // acquire thumb for incremental commit
  // Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
  // then the commit will be finished.  Note the store is effectively write
  // locked until commit is finished or canceled through the thumb instance.
  // Until the commit is done, the store will report it has readonly status.

  NS_IMETHOD
  CompressCommit( // commit and make db physically smaller if possible
    nsIMdbEnv* ev, // context
    nsIMdbThumb** acqThumb) override; // acquire thumb for incremental commit
  // Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
  // then the commit will be finished.  Note the store is effectively write
  // locked until commit is finished or canceled through the thumb instance.
  // Until the commit is done, the store will report it has readonly status.
  
  // } ----- end commit methods -----

// } ===== end nsIMdbStore methods =====

public: // typesafe refcounting inlines calling inherited morkNode methods
  static void SlotWeakPort(morkPort* me,
    morkEnv* ev, morkPort** ioSlot)
  { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
  
  static void SlotStrongPort(morkPort* me,
    morkEnv* ev, morkPort** ioSlot)
  { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
// public: // slots inherited from morkPort (meant to inform only)
  // nsIMdbHeap*     mNode_Heap;
  // mork_able    mNode_Mutable; // can this node be modified?
  // mork_load    mNode_Load;    // is this node clean or dirty?
  // 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_uses    mNode_Uses;    // refcount for strong refs
  // mork_refs    mNode_Refs;    // refcount for strong refs + weak refs
 
  // morkEnv*        mPort_Env;      // non-refcounted env which created port
  // morkFactory*    mPort_Factory;  // weak ref to suite factory
  // nsIMdbHeap*     mPort_Heap;     // heap in which this port allocs objects

public: // state is public because the entire Mork system is private

// mStore_OidAtomSpace might be unnecessary; I don't remember why I wanted it.
  morkAtomSpace*   mStore_OidAtomSpace;   // ground atom space for oids
  morkAtomSpace*   mStore_GroundAtomSpace; // ground atom space for scopes
  morkAtomSpace*   mStore_GroundColumnSpace; // ground column space for scopes

  nsIMdbFile*      mStore_File; // the file containing Mork text
  morkStream*      mStore_InStream; // stream using file used by the builder
  morkBuilder*     mStore_Builder; // to parse Mork text and build structures 

  morkStream*      mStore_OutStream; // stream using file used by the writer
  
  morkRowSpaceMap  mStore_RowSpaces;  // maps mork_scope -> morkSpace
  morkAtomSpaceMap mStore_AtomSpaces; // maps mork_scope -> morkSpace
  
  morkZone         mStore_Zone;
  
  morkPool         mStore_Pool;

  // we alloc a max size book atom to reuse space for atom map key searches:
  // morkMaxBookAtom  mStore_BookAtom; // staging area for atom map searches
  
  morkFarBookAtom  mStore_FarBookAtom; // staging area for atom map searches
  
  // GroupIdentity should be one more than largest seen in a parsed db file:
  mork_gid         mStore_CommitGroupIdentity; // transaction ID number
  
  // group positions are used to help compute PercentOfStoreWasted():
  mork_pos         mStore_FirstCommitGroupPos; // start of first group
  mork_pos         mStore_SecondCommitGroupPos; // start of second group
  // If the first commit group is very near the start of the file (say less
  // than 512 bytes), then we might assume the file started nearly empty and
  // that most of the first group is not wasted.  In that case, the pos of
  // the second commit group might make a better estimate of the start of
  // transaction space that might represent wasted file space.  That's why
  // we support fields for both first and second commit group positions.
  //
  // We assume that a zero in either group pos means that the slot has not
  // yet been given a valid value, since the file will always start with a
  // tag, and a commit group cannot actually start at position zero.
  //
  // Either or both the first or second commit group positions might be
  // supplied by either morkWriter (while committing) or morkBuilder (while
  // parsing), since either reading or writing the file might encounter the
  // first transaction groups which came into existence either in the past
  // or in the very recent present.
  
  mork_bool        mStore_CanAutoAssignAtomIdentity;
  mork_bool        mStore_CanDirty; // changes imply the store becomes dirty?
  mork_u1          mStore_CanWriteIncremental; // compress not required?
  mork_u1          mStore_Pad; // for u4 alignment
  
  // mStore_CanDirty should be FALSE when parsing a file while building the
  // content going into the store, because such data structure modifications
  // are actuallly in sync with the file.  So content read from a file must
  // be clean with respect to the file.  After a file is finished parsing,
  // the mStore_CanDirty slot should become TRUE, so that any additional
  // changes at runtime cause structures to be marked dirty with respect to
  // the file which must later be updated with changes during a commit.
  //
  // It might also make sense to set mStore_CanDirty to FALSE while a commit
  // is in progress, lest some internal transformations make more content
  // appear dirty when it should not.  So anyone modifying content during a
  // commit should think about the intended significance regarding dirty.

public: // more specific dirty methods for store:
  void SetStoreDirty() { this->SetNodeDirty(); }
  void SetStoreClean() { this->SetNodeClean(); }
  
  mork_bool IsStoreClean() const { return this->IsNodeClean(); }
  mork_bool IsStoreDirty() const { return this->IsNodeDirty(); }
 
public: // setting dirty based on CanDirty:
 
  void MaybeDirtyStore()
  { if ( mStore_CanDirty ) this->SetStoreDirty(); }
  
public: // space waste analysis

  mork_percent PercentOfStoreWasted(morkEnv* ev);
 
public: // setting store and all subspaces canDirty:
 
  void SetStoreAndAllSpacesCanDirty(morkEnv* ev, mork_bool inCanDirty);

public: // building an atom inside mStore_FarBookAtom from a char* string

  morkFarBookAtom* StageAliasAsFarBookAtom(morkEnv* ev,
    const morkMid* inMid, morkAtomSpace* ioSpace, mork_cscode inForm);

  morkFarBookAtom* StageYarnAsFarBookAtom(morkEnv* ev,
    const mdbYarn* inYarn, morkAtomSpace* ioSpace);

  morkFarBookAtom* StageStringAsFarBookAtom(morkEnv* ev,
    const char* inString, mork_cscode inForm, morkAtomSpace* ioSpace);

public: // determining whether incremental writing is a good use of time:

  mork_bool DoPreferLargeOverCompressCommit(morkEnv* ev);
  // true when mStore_CanWriteIncremental && store has file large enough 

public: // lazy creation of members and nested row or atom spaces

  morkAtomSpace*   LazyGetOidAtomSpace(morkEnv* ev);
  morkAtomSpace*   LazyGetGroundAtomSpace(morkEnv* ev);
  morkAtomSpace*   LazyGetGroundColumnSpace(morkEnv* ev);

  morkStream*      LazyGetInStream(morkEnv* ev);
  morkBuilder*     LazyGetBuilder(morkEnv* ev); 
  void             ForgetBuilder(morkEnv* ev); 

  morkStream*      LazyGetOutStream(morkEnv* ev);
  
  morkRowSpace*    LazyGetRowSpace(morkEnv* ev, mdb_scope inRowScope);
  morkAtomSpace*   LazyGetAtomSpace(morkEnv* ev, mdb_scope inAtomScope);
 
// { ===== begin morkNode interface =====
public: // morkNode virtual methods
  virtual void CloseMorkNode(morkEnv* ev) override; // CloseStore() only if open
  
public: // morkStore construction & destruction
  morkStore(morkEnv* ev, const morkUsage& inUsage,
     nsIMdbHeap* ioNodeHeap, // the heap (if any) for this node instance
     morkFactory* inFactory, // the factory for this
     nsIMdbHeap* ioPortHeap  // the heap to hold all content in the port
     );
  void CloseStore(morkEnv* ev); // called by CloseMorkNode();

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

public: // dynamic type identification
  morkEnv* CanUseStore(nsIMdbEnv* mev, mork_bool inMutable,
                       nsresult* outErr) const;
   mork_bool IsStore() const
  { return IsNode() && mNode_Derived == morkDerived_kStore; }
// } ===== end morkNode methods =====

public: // typing
  static void NonStoreTypeError(morkEnv* ev);
  static void NilStoreFileError(morkEnv* ev);
  static void CannotAutoAssignAtomIdentityError(morkEnv* ev);
  
public: //  store utilties
  
  morkAtom* YarnToAtom(morkEnv* ev, const mdbYarn* inYarn, bool createIfMissing = true);
  morkAtom* AddAlias(morkEnv* ev, const morkMid& inMid,
    mork_cscode inForm);

public: // other store methods

  void RenumberAllCollectableContent(morkEnv* ev);

  nsIMdbStore* AcquireStoreHandle(morkEnv* ev); // mObject_Handle

  morkPool* StorePool() { return &mStore_Pool; }

  mork_bool OpenStoreFile(morkEnv* ev, // return value equals ev->Good()
    mork_bool inFrozen,
    // const char* inFilePath,
    nsIMdbFile* ioFile, // db abstract file interface
    const mdbOpenPolicy* inOpenPolicy);

  mork_bool CreateStoreFile(morkEnv* ev, // return value equals ev->Good()
    // const char* inFilePath,
    nsIMdbFile* ioFile, // db abstract file interface
    const mdbOpenPolicy* inOpenPolicy);
    
  morkAtom* CopyAtom(morkEnv* ev, const morkAtom* inAtom);
  // copy inAtom (from some other store) over to this store
    
  mork_token CopyToken(morkEnv* ev, mdb_token inToken, morkStore* inStore);
  // copy inToken from inStore over to this store
    
  mork_token BufToToken(morkEnv* ev, const morkBuf* inBuf);
  mork_token StringToToken(morkEnv* ev, const char* inTokenName);
  mork_token QueryToken(morkEnv* ev, const char* inTokenName);
  void TokenToString(morkEnv* ev, mdb_token inToken, mdbYarn* outTokenName);
  
  mork_bool MidToOid(morkEnv* ev, const morkMid& inMid,
     mdbOid* outOid);
  mork_bool OidToYarn(morkEnv* ev, const mdbOid& inOid, mdbYarn* outYarn);
  mork_bool MidToYarn(morkEnv* ev, const morkMid& inMid,
     mdbYarn* outYarn);

  morkBookAtom* MidToAtom(morkEnv* ev, const morkMid& inMid);
  morkRow* MidToRow(morkEnv* ev, const morkMid& inMid);
  morkTable* MidToTable(morkEnv* ev, const morkMid& inMid);
  
  morkRow* OidToRow(morkEnv* ev, const mdbOid* inOid);
  // OidToRow() finds old row with oid, or makes new one if not found.

  morkTable* OidToTable(morkEnv* ev, const mdbOid* inOid,
    const mdbOid* inOptionalMetaRowOid);
  // OidToTable() finds old table with oid, or makes new one if not found.
  
  static void SmallTokenToOneByteYarn(morkEnv* ev, mdb_token inToken,
    mdbYarn* outYarn);
  
  mork_bool HasTableKind(morkEnv* ev, mdb_scope inRowScope, 
    mdb_kind inTableKind, mdb_count* outTableCount);
  
  morkTable* GetTableKind(morkEnv* ev, mdb_scope inRowScope, 
    mdb_kind inTableKind, mdb_count* outTableCount,
    mdb_bool* outMustBeUnique);

  morkRow* FindRow(morkEnv* ev, mdb_scope inScope, mdb_column inColumn,
    const mdbYarn* inTargetCellValue);
  
  morkRow* GetRow(morkEnv* ev, const mdbOid* inOid);
  morkTable* GetTable(morkEnv* ev, const mdbOid* inOid);
    
  morkTable* NewTable(morkEnv* ev, mdb_scope inRowScope,
    mdb_kind inTableKind, mdb_bool inMustBeUnique,
    const mdbOid* inOptionalMetaRowOid);

  morkPortTableCursor* GetPortTableCursor(morkEnv* ev, mdb_scope inRowScope,
    mdb_kind inTableKind) ;

  morkRow* NewRowWithOid(morkEnv* ev, const mdbOid* inOid);
  morkRow* NewRow(morkEnv* ev, mdb_scope inRowScope);

  morkThumb* MakeCompressCommitThumb(morkEnv* ev, mork_bool inDoCollect);

public: // commit related methods

  mork_bool MarkAllStoreContentDirty(morkEnv* ev);
  // MarkAllStoreContentDirty() visits every object in the store and marks 
  // them dirty, including every table, row, cell, and atom.  The return
  // equals ev->Good(), to show whether any error happened.  This method is
  // intended for use in the beginning of a "compress commit" which writes
  // all store content, whether dirty or not.  We dirty everything first so
  // that later iterations over content can mark things clean as they are
  // written, and organize the process of serialization so that objects are
  // written only at need (because of being dirty).

public: // typesafe refcounting inlines calling inherited morkNode methods
  static void SlotWeakStore(morkStore* me,
    morkEnv* ev, morkStore** ioSlot)
  { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
  
  static void SlotStrongStore(morkStore* me,
    morkEnv* ev, morkStore** ioSlot)
  { 
    morkStore* store = *ioSlot;
    if ( me != store )
    {
      if ( store )
      {
        // what if this nulls out the ev and causes asserts?
        // can we move this after the CutStrongRef()?
        *ioSlot = 0;
        store->Release();
      }
      if ( me && me->AddRef() )
        *ioSlot = me;
    }
  }
};

//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789

#endif /* _MORKSTORE_ */