summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/db/mork/morkWriter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mailnews/db/mork/morkWriter.cpp')
-rw-r--r--comm/mailnews/db/mork/morkWriter.cpp1936
1 files changed, 1936 insertions, 0 deletions
diff --git a/comm/mailnews/db/mork/morkWriter.cpp b/comm/mailnews/db/mork/morkWriter.cpp
new file mode 100644
index 0000000000..dc1bb1a1ed
--- /dev/null
+++ b/comm/mailnews/db/mork/morkWriter.cpp
@@ -0,0 +1,1936 @@
+/* -*- 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 _MDB_
+# include "mdb.h"
+#endif
+
+#ifndef _MORK_
+# include "mork.h"
+#endif
+
+#ifndef _MORKBLOB_
+# include "morkBlob.h"
+#endif
+
+#ifndef _MORKNODE_
+# include "morkNode.h"
+#endif
+
+#ifndef _MORKENV_
+# include "morkEnv.h"
+#endif
+
+#ifndef _MORKARRAY_
+# include "morkWriter.h"
+#endif
+
+// #ifndef _MORKFILE_
+// #include "morkFile.h"
+// #endif
+
+#ifndef _MORKSTREAM_
+# include "morkStream.h"
+#endif
+
+#ifndef _MORKSTORE_
+# include "morkStore.h"
+#endif
+
+#ifndef _MORKATOMSPACE_
+# include "morkAtomSpace.h"
+#endif
+
+#ifndef _MORKROWSPACE_
+# include "morkRowSpace.h"
+#endif
+
+#ifndef _MORKROWMAP_
+# include "morkRowMap.h"
+#endif
+
+#ifndef _MORKATOMMAP_
+# include "morkAtomMap.h"
+#endif
+
+#ifndef _MORKROW_
+# include "morkRow.h"
+#endif
+
+#ifndef _MORKTABLE_
+# include "morkTable.h"
+#endif
+
+#ifndef _MORKCELL_
+# include "morkCell.h"
+#endif
+
+#ifndef _MORKATOM_
+# include "morkAtom.h"
+#endif
+
+#ifndef _MORKCH_
+# include "morkCh.h"
+#endif
+
+// 456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void morkWriter::CloseMorkNode(
+ morkEnv* ev) // CloseTable() only if open
+{
+ if (this->IsOpenNode()) {
+ this->MarkClosing();
+ this->CloseWriter(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkWriter::~morkWriter() // assert CloseTable() executed earlier
+{
+ MORK_ASSERT(this->IsShutNode());
+ MORK_ASSERT(mWriter_Store == 0);
+}
+
+/*public non-poly*/
+morkWriter::morkWriter(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, morkStore* ioStore,
+ nsIMdbFile* ioFile, nsIMdbHeap* ioSlotHeap)
+ : morkNode(ev, inUsage, ioHeap),
+ mWriter_Store(0),
+ mWriter_File(0),
+ mWriter_Bud(0),
+ mWriter_Stream(0),
+ mWriter_SlotHeap(0)
+
+ ,
+ mWriter_CommitGroupIdentity(0) // see mStore_CommitGroupIdentity
+ ,
+ mWriter_GroupBufFill(0)
+
+ ,
+ mWriter_TotalCount(morkWriter_kCountNumberOfPhases),
+ mWriter_DoneCount(0)
+
+ ,
+ mWriter_LineSize(0),
+ mWriter_MaxIndent(morkWriter_kMaxIndent),
+ mWriter_MaxLine(morkWriter_kMaxLine)
+
+ ,
+ mWriter_TableForm(0),
+ mWriter_TableAtomScope('v'),
+ mWriter_TableRowScope(0),
+ mWriter_TableKind(0)
+
+ ,
+ mWriter_RowForm(0),
+ mWriter_RowAtomScope(0),
+ mWriter_RowScope(0)
+
+ ,
+ mWriter_DictForm(0),
+ mWriter_DictAtomScope('v')
+
+ ,
+ mWriter_NeedDirtyAll(morkBool_kFalse),
+ mWriter_Incremental(morkBool_kTrue) // opposite of mWriter_NeedDirtyAll
+ ,
+ mWriter_DidStartDict(morkBool_kFalse),
+ mWriter_DidEndDict(morkBool_kTrue)
+
+ ,
+ mWriter_SuppressDirtyRowNewline(morkBool_kFalse),
+ mWriter_DidStartGroup(morkBool_kFalse),
+ mWriter_DidEndGroup(morkBool_kTrue),
+ mWriter_Phase(morkWriter_kPhaseNothingDone)
+
+ ,
+ mWriter_BeVerbose(ev->mEnv_BeVerbose)
+
+ ,
+ mWriter_TableRowArrayPos(0)
+
+ // empty constructors for map iterators:
+ ,
+ mWriter_StoreAtomSpacesIter(),
+ mWriter_AtomSpaceAtomAidsIter()
+
+ ,
+ mWriter_StoreRowSpacesIter(),
+ mWriter_RowSpaceTablesIter(),
+ mWriter_RowSpaceRowsIter() {
+ mWriter_GroupBuf[0] = 0;
+
+ mWriter_SafeNameBuf[0] = 0;
+ mWriter_SafeNameBuf[morkWriter_kMaxColumnNameSize * 2] = 0;
+ mWriter_ColNameBuf[0] = 0;
+ mWriter_ColNameBuf[morkWriter_kMaxColumnNameSize] = 0;
+
+ mdbYarn* y = &mWriter_ColYarn;
+ y->mYarn_Buf = mWriter_ColNameBuf; // where to put col bytes
+ y->mYarn_Fill = 0; // set later by writer
+ y->mYarn_Size = morkWriter_kMaxColumnNameSize; // our buf size
+ y->mYarn_More = 0; // set later by writer
+ y->mYarn_Form = 0; // set later by writer
+ y->mYarn_Grow = 0; // do not allow buffer growth
+
+ y = &mWriter_SafeYarn;
+ y->mYarn_Buf = mWriter_SafeNameBuf; // where to put col bytes
+ y->mYarn_Fill = 0; // set later by writer
+ y->mYarn_Size = morkWriter_kMaxColumnNameSize * 2; // our buf size
+ y->mYarn_More = 0; // set later by writer
+ y->mYarn_Form = 0; // set later by writer
+ y->mYarn_Grow = 0; // do not allow buffer growth
+
+ if (ev->Good()) {
+ if (ioSlotHeap && ioFile && ioStore) {
+ morkStore::SlotWeakStore(ioStore, ev, &mWriter_Store);
+ nsIMdbFile_SlotStrongFile(ioFile, ev, &mWriter_File);
+ nsIMdbHeap_SlotStrongHeap(ioSlotHeap, ev, &mWriter_SlotHeap);
+ if (ev->Good()) {
+ mNode_Derived = morkDerived_kWriter;
+ }
+ } else
+ ev->NilPointerError();
+ }
+}
+
+void morkWriter::MakeWriterStream(morkEnv* ev) // give writer a suitable stream
+{
+ mWriter_Incremental = !mWriter_NeedDirtyAll; // opposites
+
+ if (!mWriter_Stream && ev->Good()) {
+ if (mWriter_File) {
+ morkStream* stream = 0;
+ mork_bool frozen = morkBool_kFalse; // need to modify
+ nsIMdbHeap* heap = mWriter_SlotHeap;
+
+ if (mWriter_Incremental) {
+ stream =
+ new (*heap, ev) morkStream(ev, morkUsage::kHeap, heap, mWriter_File,
+ morkWriter_kStreamBufSize, frozen);
+ } else // compress commit
+ {
+ nsIMdbFile* bud = 0;
+ mWriter_File->AcquireBud(ev->AsMdbEnv(), heap, &bud);
+ if (bud) {
+ if (ev->Good()) {
+ mWriter_Bud = bud;
+ stream =
+ new (*heap, ev) morkStream(ev, morkUsage::kHeap, heap, bud,
+ morkWriter_kStreamBufSize, frozen);
+ } else
+ bud->Release();
+ }
+ }
+
+ if (stream) {
+ if (ev->Good())
+ mWriter_Stream = stream;
+ else
+ stream->CutStrongRef(ev->AsMdbEnv());
+ }
+ } else
+ this->NilWriterFileError(ev);
+ }
+}
+
+/*public non-poly*/ void morkWriter::CloseWriter(
+ morkEnv* ev) // called by CloseMorkNode();
+{
+ if (this->IsNode()) {
+ morkStore::SlotWeakStore((morkStore*)0, ev, &mWriter_Store);
+ nsIMdbFile_SlotStrongFile((nsIMdbFile*)0, ev, &mWriter_File);
+ nsIMdbFile_SlotStrongFile((nsIMdbFile*)0, ev, &mWriter_Bud);
+ morkStream::SlotStrongStream((morkStream*)0, ev, &mWriter_Stream);
+ nsIMdbHeap_SlotStrongHeap((nsIMdbHeap*)0, ev, &mWriter_SlotHeap);
+ this->MarkShut();
+ } else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+/*static*/ void morkWriter::NonWriterTypeError(morkEnv* ev) {
+ ev->NewError("non morkWriter");
+}
+
+/*static*/ void morkWriter::NilWriterStoreError(morkEnv* ev) {
+ ev->NewError("nil mWriter_Store");
+}
+
+/*static*/ void morkWriter::NilWriterBudError(morkEnv* ev) {
+ ev->NewError("nil mWriter_Bud");
+}
+
+/*static*/ void morkWriter::NilWriterFileError(morkEnv* ev) {
+ ev->NewError("nil mWriter_File");
+}
+
+/*static*/ void morkWriter::NilWriterStreamError(morkEnv* ev) {
+ ev->NewError("nil mWriter_Stream");
+}
+
+/*static*/ void morkWriter::UnsupportedPhaseError(morkEnv* ev) {
+ ev->NewError("unsupported mWriter_Phase");
+}
+
+mork_bool morkWriter::WriteMore(
+ morkEnv* ev) // call until IsWritingDone() is true
+{
+ if (this->IsOpenNode()) {
+ if (this->IsWriter()) {
+ if (!mWriter_Stream) this->MakeWriterStream(ev);
+
+ if (mWriter_Stream) {
+ if (ev->Bad()) {
+ ev->NewWarning("writing stops on error");
+ mWriter_Phase = morkWriter_kPhaseWritingDone;
+ }
+ switch (mWriter_Phase) {
+ case morkWriter_kPhaseNothingDone:
+ OnNothingDone(ev);
+ break;
+
+ case morkWriter_kPhaseDirtyAllDone:
+ OnDirtyAllDone(ev);
+ break;
+
+ case morkWriter_kPhasePutHeaderDone:
+ OnPutHeaderDone(ev);
+ break;
+
+ case morkWriter_kPhaseRenumberAllDone:
+ OnRenumberAllDone(ev);
+ break;
+
+ case morkWriter_kPhaseStoreAtomSpaces:
+ OnStoreAtomSpaces(ev);
+ break;
+
+ case morkWriter_kPhaseAtomSpaceAtomAids:
+ OnAtomSpaceAtomAids(ev);
+ break;
+
+ case morkWriter_kPhaseStoreRowSpacesTables:
+ OnStoreRowSpacesTables(ev);
+ break;
+
+ case morkWriter_kPhaseRowSpaceTables:
+ OnRowSpaceTables(ev);
+ break;
+
+ case morkWriter_kPhaseTableRowArray:
+ OnTableRowArray(ev);
+ break;
+
+ case morkWriter_kPhaseStoreRowSpacesRows:
+ OnStoreRowSpacesRows(ev);
+ break;
+
+ case morkWriter_kPhaseRowSpaceRows:
+ OnRowSpaceRows(ev);
+ break;
+
+ case morkWriter_kPhaseContentDone:
+ OnContentDone(ev);
+ break;
+
+ case morkWriter_kPhaseWritingDone:
+ OnWritingDone(ev);
+ break;
+
+ default:
+ this->UnsupportedPhaseError(ev);
+ }
+ } else
+ this->NilWriterStreamError(ev);
+ } else
+ this->NonWriterTypeError(ev);
+ } else
+ this->NonOpenNodeError(ev);
+
+ return ev->Good();
+}
+
+static const char morkWriter_kHexDigits[] = "0123456789ABCDEF";
+
+mork_size morkWriter::WriteYarn(morkEnv* ev, const mdbYarn* inYarn)
+// return number of atom bytes written on the current line (which
+// implies that escaped line breaks will make the size value smaller
+// than the entire yarn's size, since only part goes on a last line).
+{
+ mork_size outSize = 0;
+ mork_size lineSize = mWriter_LineSize;
+ morkStream* stream = mWriter_Stream;
+
+ const mork_u1* b = (const mork_u1*)inYarn->mYarn_Buf;
+ if (b) {
+ int c;
+ mork_fill fill = inYarn->mYarn_Fill;
+
+ const mork_u1* end = b + fill;
+ while (b < end && ev->Good()) {
+ if (lineSize + outSize >= mWriter_MaxLine) // continue line?
+ {
+ stream->PutByteThenNewline(ev, '\\');
+ mWriter_LineSize = lineSize = outSize = 0;
+ }
+
+ c = *b++; // next byte to print
+ if (morkCh_IsValue(c)) {
+ stream->Putc(ev, c);
+ ++outSize; // c
+ } else if (c == ')' || c == '$' || c == '\\') {
+ stream->Putc(ev, '\\');
+ stream->Putc(ev, c);
+ outSize += 2; // '\' c
+ } else {
+ outSize += 3; // '$' hex hex
+ stream->Putc(ev, '$');
+ stream->Putc(ev, morkWriter_kHexDigits[(c >> 4) & 0x0F]);
+ stream->Putc(ev, morkWriter_kHexDigits[c & 0x0F]);
+ }
+ }
+ }
+ mWriter_LineSize += outSize;
+
+ return outSize;
+}
+
+mork_size morkWriter::WriteAtom(morkEnv* ev, const morkAtom* inAtom)
+// return number of atom bytes written on the current line (which
+// implies that escaped line breaks will make the size value smaller
+// than the entire atom's size, since only part goes on a last line).
+{
+ mork_size outSize = 0;
+ mdbYarn yarn; // to ref content inside atom
+
+ if (morkAtom::AliasYarn(inAtom, &yarn)) {
+ if (mWriter_DidStartDict && yarn.mYarn_Form != mWriter_DictForm)
+ this->ChangeDictForm(ev, yarn.mYarn_Form);
+
+ outSize = this->WriteYarn(ev, &yarn);
+ // mWriter_LineSize += stream->Write(ev, inYarn->mYarn_Buf, outSize);
+ } else
+ inAtom->BadAtomKindError(ev);
+
+ return outSize;
+}
+
+void morkWriter::WriteAtomSpaceAsDict(morkEnv* ev, morkAtomSpace* ioSpace) {
+ morkStream* stream = mWriter_Stream;
+ nsIMdbEnv* mdbev = ev->AsMdbEnv();
+ mork_scope scope = ioSpace->SpaceScope();
+ if (scope < 0x80) {
+ if (mWriter_LineSize) stream->PutLineBreak(ev);
+ stream->PutString(ev, "< <(a=");
+ stream->Putc(ev, (int)scope);
+ ++mWriter_LineSize;
+ stream->PutString(ev, ")> // (f=iso-8859-1)");
+ mWriter_LineSize = stream->PutIndent(ev, morkWriter_kDictAliasDepth);
+ } else
+ ioSpace->NonAsciiSpaceScopeName(ev);
+
+ if (ev->Good()) {
+ mdbYarn yarn; // to ref content inside atom
+ char buf[64]; // buffer for staging the dict alias hex ID
+ char* idBuf = buf + 1; // where the id always starts
+ buf[0] = '('; // we always start with open paren
+ morkBookAtom* atom = 0;
+ morkAtomAidMapIter* ai = &mWriter_AtomSpaceAtomAidsIter;
+ ai->InitAtomAidMapIter(ev, &ioSpace->mAtomSpace_AtomAids);
+ mork_change* c = 0;
+
+ for (c = ai->FirstAtom(ev, &atom); c && ev->Good();
+ c = ai->NextAtom(ev, &atom)) {
+ if (atom) {
+ if (atom->IsAtomDirty()) {
+ atom->SetAtomClean(); // neutralize change
+
+ morkAtom::AliasYarn(atom, &yarn);
+ mork_size size = ev->TokenAsHex(idBuf, atom->mBookAtom_Id);
+
+ if (yarn.mYarn_Form != mWriter_DictForm)
+ this->ChangeDictForm(ev, yarn.mYarn_Form);
+
+ mork_size pending =
+ yarn.mYarn_Fill + size + morkWriter_kYarnEscapeSlop + 4;
+ this->IndentOverMaxLine(ev, pending, morkWriter_kDictAliasDepth);
+ mork_size bytesWritten;
+ stream->Write(mdbev, buf, size + 1, &bytesWritten); // + '('
+ mWriter_LineSize += bytesWritten;
+
+ pending -= (size + 1);
+ this->IndentOverMaxLine(ev, pending, morkWriter_kDictAliasValueDepth);
+ stream->Putc(ev, '='); // start alias
+ ++mWriter_LineSize;
+
+ this->WriteYarn(ev, &yarn);
+ stream->Putc(ev, ')'); // end alias
+ ++mWriter_LineSize;
+
+ ++mWriter_DoneCount;
+ }
+ } else
+ ev->NilPointerError();
+ }
+ ai->CloseMapIter(ev);
+ }
+
+ if (ev->Good()) {
+ ioSpace->SetAtomSpaceClean();
+ // this->IndentAsNeeded(ev, 0);
+ // stream->PutByteThenNewline(ev, '>'); // end dict
+
+ stream->Putc(ev, '>'); // end dict
+ ++mWriter_LineSize;
+ }
+}
+
+/*
+(I'm putting the text of this message in file morkWriter.cpp.)
+
+I'm making a change which should cause rows and tables to go away
+when a Mork db is compress committed, when the rows and tables
+are no longer needed. Because this is subtle, I'm describing it
+here in case misbehavior is ever observed. Otherwise you'll have
+almost no hope of fixing a related bug.
+
+This is done entirely in morkWriter.cpp: morkWriter::DirtyAll(),
+which currently marks all rows and tables dirty so they will be
+written in a later phase of the commit. My change is to merely
+selectively not mark certain rows and tables dirty, when they seem
+to be superfluous.
+
+A row is no longer needed when the mRow_GcUses slot hits zero, and
+this is used by the following inline morkRow method:
+
+ mork_bool IsRowUsed() const { return mRow_GcUses != 0; }
+
+Naturally disaster ensues if mRow_GcUses is ever smaller than right.
+
+Similarly, we should drop tables when mTable_GcUses hits zero, but
+only when a table contains no row members. We consider tables to
+self reference (and prevent collection) when they contain content.
+Again, disaster ensues if mTable_GcUses is ever smaller than right.
+
+ mork_count GetRowCount() const
+ { return mTable_RowArray.mArray_Fill; }
+
+ mork_bool IsTableUsed() const
+ { return (mTable_GcUses != 0 || this->GetRowCount() != 0); }
+
+Now let's question why the design involves filtering what gets set
+to dirty. Why not apply a filter in the later phase when we write
+content? Because I'm afraid of missing some subtle interaction in
+updating table and row relationships. It seems safer to write a row
+or table when it starts out dirty, before morkWriter::DirtyAll() is
+called. So this design calls for writing out rows and tables when
+they are still clearly used, and additionally, <i>when we have just
+been actively writing to them right before this commit</i>.
+
+Presumably if they are truly useless, they will no longer be dirtied
+in later sessions and will get collected during the next compress
+commit. So we wait to collect them until they become all dead, and
+not just mostly dead. (At which time you can feel free to go through
+their pockets looking for loose change.)
+*/
+
+mork_bool morkWriter::DirtyAll(morkEnv* ev)
+// DirtyAll() visits every store sub-object 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). Note the method can
+// stop early when any error happens, since this will abort any commit.
+{
+ morkStore* store = mWriter_Store;
+ if (store) {
+ store->SetStoreDirty();
+ mork_change* c = 0;
+
+ if (ev->Good()) {
+ morkAtomSpaceMapIter* asi = &mWriter_StoreAtomSpacesIter;
+ asi->InitAtomSpaceMapIter(ev, &store->mStore_AtomSpaces);
+
+ mork_scope* key = 0; // ignore keys in map
+ morkAtomSpace* space = 0; // old val node in the map
+
+ for (c = asi->FirstAtomSpace(ev, key, &space); c && ev->Good();
+ c = asi->NextAtomSpace(ev, key, &space)) {
+ if (space) {
+ if (space->IsAtomSpace()) {
+ space->SetAtomSpaceDirty();
+ morkBookAtom* atom = 0;
+ morkAtomAidMapIter* ai = &mWriter_AtomSpaceAtomAidsIter;
+ ai->InitAtomAidMapIter(ev, &space->mAtomSpace_AtomAids);
+
+ for (c = ai->FirstAtom(ev, &atom); c && ev->Good();
+ c = ai->NextAtom(ev, &atom)) {
+ if (atom) {
+ atom->SetAtomDirty();
+ ++mWriter_TotalCount;
+ } else
+ ev->NilPointerError();
+ }
+
+ ai->CloseMapIter(ev);
+ } else
+ space->NonAtomSpaceTypeError(ev);
+ } else
+ ev->NilPointerError();
+ }
+ }
+
+ if (ev->Good()) {
+ morkRowSpaceMapIter* rsi = &mWriter_StoreRowSpacesIter;
+ rsi->InitRowSpaceMapIter(ev, &store->mStore_RowSpaces);
+
+ mork_scope* key = 0; // ignore keys in map
+ morkRowSpace* space = 0; // old val node in the map
+
+ for (c = rsi->FirstRowSpace(ev, key, &space); c && ev->Good();
+ c = rsi->NextRowSpace(ev, key, &space)) {
+ if (space) {
+ if (space->IsRowSpace()) {
+ space->SetRowSpaceDirty();
+ if (ev->Good()) {
+#ifdef MORK_ENABLE_PROBE_MAPS
+ morkRowProbeMapIter* ri = &mWriter_RowSpaceRowsIter;
+#else /*MORK_ENABLE_PROBE_MAPS*/
+ morkRowMapIter* ri = &mWriter_RowSpaceRowsIter;
+#endif /*MORK_ENABLE_PROBE_MAPS*/
+ ri->InitRowMapIter(ev, &space->mRowSpace_Rows);
+
+ morkRow* row = 0; // old key row in the map
+
+ for (c = ri->FirstRow(ev, &row); c && ev->Good();
+ c = ri->NextRow(ev, &row)) {
+ if (row && row->IsRow()) // need to dirty row?
+ {
+ if (row->IsRowUsed() || row->IsRowDirty()) {
+ row->DirtyAllRowContent(ev);
+ ++mWriter_TotalCount;
+ }
+ } else
+ row->NonRowTypeWarning(ev);
+ }
+ ri->CloseMapIter(ev);
+ }
+
+ if (ev->Good()) {
+ morkTableMapIter* ti = &mWriter_RowSpaceTablesIter;
+ ti->InitTableMapIter(ev, &space->mRowSpace_Tables);
+
+#ifdef MORK_BEAD_OVER_NODE_MAPS
+ morkTable* table = ti->FirstTable(ev);
+
+ for (; table && ev->Good(); table = ti->NextTable(ev))
+#else /*MORK_BEAD_OVER_NODE_MAPS*/
+ mork_tid* tableKey = 0; // ignore keys in table map
+ morkTable* table = 0; // old key row in the map
+
+ for (c = ti->FirstTable(ev, tableKey, &table); c && ev->Good();
+ c = ti->NextTable(ev, tableKey, &table))
+#endif /*MORK_BEAD_OVER_NODE_MAPS*/
+ {
+ if (table && table->IsTable()) // need to dirty table?
+ {
+ if (table->IsTableUsed() || table->IsTableDirty()) {
+ // table->DirtyAllTableContent(ev);
+ // only necessary to mark table itself dirty:
+ table->SetTableDirty();
+ table->SetTableRewrite();
+ ++mWriter_TotalCount;
+ }
+ } else
+ table->NonTableTypeWarning(ev);
+ }
+ ti->CloseMapIter(ev);
+ }
+ } else
+ space->NonRowSpaceTypeError(ev);
+ } else
+ ev->NilPointerError();
+ }
+ }
+ } else
+ this->NilWriterStoreError(ev);
+
+ return ev->Good();
+}
+
+mork_bool morkWriter::OnNothingDone(morkEnv* ev) {
+ mWriter_Incremental = !mWriter_NeedDirtyAll; // opposites
+
+ if (!mWriter_Store->IsStoreDirty() && !mWriter_NeedDirtyAll) {
+ mWriter_Phase = morkWriter_kPhaseWritingDone;
+ return morkBool_kTrue;
+ }
+
+ // morkStream* stream = mWriter_Stream;
+ if (mWriter_NeedDirtyAll) this->DirtyAll(ev);
+
+ if (ev->Good())
+ mWriter_Phase = morkWriter_kPhaseDirtyAllDone;
+ else
+ mWriter_Phase = morkWriter_kPhaseWritingDone; // stop on error
+
+ return ev->Good();
+}
+
+mork_bool morkWriter::StartGroup(morkEnv* ev) {
+ nsIMdbEnv* mdbev = ev->AsMdbEnv();
+ morkStream* stream = mWriter_Stream;
+ mWriter_DidStartGroup = morkBool_kTrue;
+ mWriter_DidEndGroup = morkBool_kFalse;
+
+ char buf[4 + morkWriter_kGroupBufSize + 2]; // "@$${" + groupid + "{@"
+ char* p = buf;
+ *p++ = '@';
+ *p++ = '$';
+ *p++ = '$';
+ *p++ = '{';
+
+ mork_token groupID = mWriter_CommitGroupIdentity;
+ mork_fill idFill = ev->TokenAsHex(p, groupID);
+ mWriter_GroupBufFill = 0;
+ // ev->TokenAsHex(mWriter_GroupBuf, groupID);
+ if (idFill < morkWriter_kGroupBufSize) {
+ // TokenAsHex appends a '\0', but it's not included in idFill count.
+ MORK_MEMCPY(mWriter_GroupBuf, p, idFill + 1);
+ mWriter_GroupBufFill = idFill;
+ } else {
+ *mWriter_GroupBuf = '\0';
+ }
+
+ p += idFill;
+ *p++ = '{';
+ *p++ = '@';
+
+ stream->PutLineBreak(ev);
+
+ morkStore* store = mWriter_Store;
+ if (store) // might need to capture commit group position?
+ {
+ mork_pos groupPos;
+ stream->Tell(mdbev, &groupPos);
+ if (!store->mStore_FirstCommitGroupPos)
+ store->mStore_FirstCommitGroupPos = groupPos;
+ else if (!store->mStore_SecondCommitGroupPos)
+ store->mStore_SecondCommitGroupPos = groupPos;
+ }
+
+ mork_size bytesWritten;
+ stream->Write(mdbev, buf, 4 + idFill + 2,
+ &bytesWritten); // '@$${' + idFill + '{@'
+ stream->PutLineBreak(ev);
+ mWriter_LineSize = 0;
+
+ return ev->Good();
+}
+
+mork_bool morkWriter::CommitGroup(morkEnv* ev) {
+ if (mWriter_DidStartGroup) {
+ nsIMdbEnv* mdbev = ev->AsMdbEnv();
+ mork_size bytesWritten;
+ morkStream* stream = mWriter_Stream;
+
+ if (mWriter_LineSize) stream->PutLineBreak(ev);
+
+ stream->Putc(ev, '@');
+ stream->Putc(ev, '$');
+ stream->Putc(ev, '$');
+ stream->Putc(ev, '}');
+
+ mork_fill bufFill = mWriter_GroupBufFill;
+ if (bufFill) stream->Write(mdbev, mWriter_GroupBuf, bufFill, &bytesWritten);
+
+ stream->Putc(ev, '}');
+ stream->Putc(ev, '@');
+ stream->PutLineBreak(ev);
+
+ mWriter_LineSize = 0;
+ }
+
+ mWriter_DidStartGroup = morkBool_kFalse;
+ mWriter_DidEndGroup = morkBool_kTrue;
+
+ return ev->Good();
+}
+
+mork_bool morkWriter::AbortGroup(morkEnv* ev) {
+ if (mWriter_DidStartGroup) {
+ morkStream* stream = mWriter_Stream;
+ stream->PutLineBreak(ev);
+ stream->PutStringThenNewline(ev, "@$$}~~}@");
+ mWriter_LineSize = 0;
+ }
+
+ mWriter_DidStartGroup = morkBool_kFalse;
+ mWriter_DidEndGroup = morkBool_kTrue;
+
+ return ev->Good();
+}
+
+mork_bool morkWriter::OnDirtyAllDone(morkEnv* ev) {
+ if (ev->Good()) {
+ nsIMdbEnv* mdbev = ev->AsMdbEnv();
+ morkStream* stream = mWriter_Stream;
+ mork_pos resultPos;
+ if (mWriter_NeedDirtyAll) // compress commit
+ {
+ stream->Seek(mdbev, 0, &resultPos); // beginning of stream
+ stream->PutStringThenNewline(ev, morkWriter_kFileHeader);
+ mWriter_LineSize = 0;
+ } else // else mWriter_Incremental
+ {
+ mork_pos eos = stream->Length(ev); // length is end of stream
+ if (ev->Good()) {
+ stream->Seek(mdbev, eos, &resultPos); // goto end of stream
+ if (eos < 128) // maybe need file header?
+ {
+ stream->PutStringThenNewline(ev, morkWriter_kFileHeader);
+ mWriter_LineSize = 0;
+ }
+ this->StartGroup(ev); // begin incremental transaction
+ }
+ }
+ }
+
+ if (ev->Good())
+ mWriter_Phase = morkWriter_kPhasePutHeaderDone;
+ else
+ mWriter_Phase = morkWriter_kPhaseWritingDone; // stop on error
+
+ return ev->Good();
+}
+
+mork_bool morkWriter::OnPutHeaderDone(morkEnv* ev) {
+ morkStream* stream = mWriter_Stream;
+ if (mWriter_LineSize) stream->PutLineBreak(ev);
+
+ // if ( mWriter_NeedDirtyAll )
+ // stream->PutStringThenNewline(ev, "// OnPutHeaderDone()");
+ mWriter_LineSize = 0;
+
+ if (mWriter_NeedDirtyAll) // compress commit
+ {
+ morkStore* store = mWriter_Store;
+ if (store)
+ store->RenumberAllCollectableContent(ev);
+ else
+ this->NilWriterStoreError(ev);
+ }
+
+ if (ev->Good())
+ mWriter_Phase = morkWriter_kPhaseRenumberAllDone;
+ else
+ mWriter_Phase = morkWriter_kPhaseWritingDone; // stop on error
+
+ return ev->Good();
+}
+
+mork_bool morkWriter::OnRenumberAllDone(morkEnv* ev) {
+ morkStream* stream = mWriter_Stream;
+ if (mWriter_LineSize) stream->PutLineBreak(ev);
+
+ // if ( mWriter_NeedDirtyAll )
+ // stream->PutStringThenNewline(ev, "// OnRenumberAllDone()");
+ mWriter_LineSize = 0;
+
+ if (mWriter_NeedDirtyAll) // compress commit
+ {
+ }
+
+ if (ev->Good())
+ mWriter_Phase = morkWriter_kPhaseStoreAtomSpaces;
+ else
+ mWriter_Phase = morkWriter_kPhaseWritingDone; // stop on error
+
+ return ev->Good();
+}
+
+mork_bool morkWriter::OnStoreAtomSpaces(morkEnv* ev) {
+ morkStream* stream = mWriter_Stream;
+ if (mWriter_LineSize) stream->PutLineBreak(ev);
+
+ // if ( mWriter_NeedDirtyAll )
+ // stream->PutStringThenNewline(ev, "// OnStoreAtomSpaces()");
+ mWriter_LineSize = 0;
+
+ if (mWriter_NeedDirtyAll) // compress commit
+ {
+ }
+
+ if (ev->Good()) {
+ morkStore* store = mWriter_Store;
+ if (store) {
+ morkAtomSpace* space = store->LazyGetGroundColumnSpace(ev);
+ if (space && space->IsAtomSpaceDirty()) {
+ // stream->PutStringThenNewline(ev, "// ground column space dict:");
+
+ if (mWriter_LineSize) {
+ stream->PutLineBreak(ev);
+ mWriter_LineSize = 0;
+ }
+ this->WriteAtomSpaceAsDict(ev, space);
+ space->SetAtomSpaceClean();
+ }
+ } else
+ this->NilWriterStoreError(ev);
+ }
+
+ if (ev->Good())
+ mWriter_Phase = morkWriter_kPhaseStoreRowSpacesTables;
+ else
+ mWriter_Phase = morkWriter_kPhaseWritingDone; // stop on error
+
+ return ev->Good();
+}
+
+mork_bool morkWriter::OnAtomSpaceAtomAids(morkEnv* ev) {
+ morkStream* stream = mWriter_Stream;
+ if (mWriter_LineSize) stream->PutLineBreak(ev);
+
+ // if ( mWriter_NeedDirtyAll )
+ // stream->PutStringThenNewline(ev, "// OnAtomSpaceAtomAids()");
+ mWriter_LineSize = 0;
+
+ if (mWriter_NeedDirtyAll) // compress commit
+ {
+ }
+
+ if (ev->Good())
+ mWriter_Phase = morkWriter_kPhaseStoreRowSpacesTables;
+ else
+ mWriter_Phase = morkWriter_kPhaseWritingDone; // stop on error
+
+ return ev->Good();
+}
+
+void morkWriter::WriteAllStoreTables(morkEnv* ev) {
+ morkStore* store = mWriter_Store;
+ if (store && ev->Good()) {
+ morkRowSpaceMapIter* rsi = &mWriter_StoreRowSpacesIter;
+ rsi->InitRowSpaceMapIter(ev, &store->mStore_RowSpaces);
+
+ mork_scope* key = 0; // ignore keys in map
+ morkRowSpace* space = 0; // old val node in the map
+ mork_change* c = 0;
+
+ for (c = rsi->FirstRowSpace(ev, key, &space); c && ev->Good();
+ c = rsi->NextRowSpace(ev, key, &space)) {
+ if (space) {
+ if (space->IsRowSpace()) {
+ space->SetRowSpaceClean();
+ if (ev->Good()) {
+ morkTableMapIter* ti = &mWriter_RowSpaceTablesIter;
+ ti->InitTableMapIter(ev, &space->mRowSpace_Tables);
+
+#ifdef MORK_BEAD_OVER_NODE_MAPS
+ morkTable* table = ti->FirstTable(ev);
+
+ for (; table && ev->Good(); table = ti->NextTable(ev))
+#else /*MORK_BEAD_OVER_NODE_MAPS*/
+ mork_tid* key2 = 0; // ignore keys in table map
+ morkTable* table = 0; // old key row in the map
+
+ for (c = ti->FirstTable(ev, key2, &table); c && ev->Good();
+ c = ti->NextTable(ev, key2, &table))
+#endif /*MORK_BEAD_OVER_NODE_MAPS*/
+ {
+ if (table && table->IsTable()) {
+ if (table->IsTableDirty()) {
+ mWriter_BeVerbose =
+ (ev->mEnv_BeVerbose || table->IsTableVerbose());
+
+ if (this->PutTableDict(ev, table)) this->PutTable(ev, table);
+
+ table->SetTableClean(ev);
+ mWriter_BeVerbose = ev->mEnv_BeVerbose;
+ }
+ } else
+ table->NonTableTypeWarning(ev);
+ }
+ ti->CloseMapIter(ev);
+ }
+ if (ev->Good()) {
+ mWriter_TableRowScope = 0; // ensure no table context now
+
+#ifdef MORK_ENABLE_PROBE_MAPS
+ morkRowProbeMapIter* ri = &mWriter_RowSpaceRowsIter;
+#else /*MORK_ENABLE_PROBE_MAPS*/
+ morkRowMapIter* ri = &mWriter_RowSpaceRowsIter;
+#endif /*MORK_ENABLE_PROBE_MAPS*/
+ ri->InitRowMapIter(ev, &space->mRowSpace_Rows);
+
+ morkRow* row = 0; // old row in the map
+
+ for (c = ri->FirstRow(ev, &row); c && ev->Good();
+ c = ri->NextRow(ev, &row)) {
+ if (row && row->IsRow()) {
+ // later we should also check that table use count is nonzero:
+ if (row->IsRowDirty()) // && row->IsRowUsed() ??
+ {
+ mWriter_BeVerbose = ev->mEnv_BeVerbose;
+ if (this->PutRowDict(ev, row)) {
+ if (ev->Good() && mWriter_DidStartDict) {
+ this->EndDict(ev);
+ if (mWriter_LineSize < 32 && ev->Good())
+ mWriter_SuppressDirtyRowNewline = morkBool_kTrue;
+ }
+
+ if (ev->Good()) this->PutRow(ev, row);
+ }
+ mWriter_BeVerbose = ev->mEnv_BeVerbose;
+ }
+ } else
+ row->NonRowTypeWarning(ev);
+ }
+ ri->CloseMapIter(ev);
+ }
+ } else
+ space->NonRowSpaceTypeError(ev);
+ } else
+ ev->NilPointerError();
+ }
+ }
+}
+
+mork_bool morkWriter::OnStoreRowSpacesTables(morkEnv* ev) {
+ morkStream* stream = mWriter_Stream;
+ if (mWriter_LineSize) stream->PutLineBreak(ev);
+
+ // if ( mWriter_NeedDirtyAll )
+ // stream->PutStringThenNewline(ev, "// OnStoreRowSpacesTables()");
+ mWriter_LineSize = 0;
+
+ if (mWriter_NeedDirtyAll) // compress commit
+ {
+ }
+
+ // later we'll break this up, but today we'll write all in one shot:
+ this->WriteAllStoreTables(ev);
+
+ if (ev->Good())
+ mWriter_Phase = morkWriter_kPhaseStoreRowSpacesRows;
+ else
+ mWriter_Phase = morkWriter_kPhaseWritingDone; // stop on error
+
+ return ev->Good();
+}
+
+mork_bool morkWriter::OnRowSpaceTables(morkEnv* ev) {
+ morkStream* stream = mWriter_Stream;
+ if (mWriter_LineSize) stream->PutLineBreak(ev);
+
+ // if ( mWriter_NeedDirtyAll )
+ // stream->PutStringThenNewline(ev, "// OnRowSpaceTables()");
+ mWriter_LineSize = 0;
+
+ if (mWriter_NeedDirtyAll) // compress commit
+ {
+ }
+
+ if (ev->Good())
+ mWriter_Phase = morkWriter_kPhaseStoreRowSpacesRows;
+ else
+ mWriter_Phase = morkWriter_kPhaseWritingDone; // stop on error
+
+ return ev->Good();
+}
+
+mork_bool morkWriter::OnTableRowArray(morkEnv* ev) {
+ morkStream* stream = mWriter_Stream;
+ if (mWriter_LineSize) stream->PutLineBreak(ev);
+
+ // if ( mWriter_NeedDirtyAll )
+ // stream->PutStringThenNewline(ev, "// OnTableRowArray()");
+ mWriter_LineSize = 0;
+
+ if (mWriter_NeedDirtyAll) // compress commit
+ {
+ }
+
+ if (ev->Good())
+ mWriter_Phase = morkWriter_kPhaseStoreRowSpacesRows;
+ else
+ mWriter_Phase = morkWriter_kPhaseWritingDone; // stop on error
+
+ return ev->Good();
+}
+
+mork_bool morkWriter::OnStoreRowSpacesRows(morkEnv* ev) {
+ morkStream* stream = mWriter_Stream;
+ if (mWriter_LineSize) stream->PutLineBreak(ev);
+
+ // if ( mWriter_NeedDirtyAll )
+ // stream->PutStringThenNewline(ev, "// OnStoreRowSpacesRows()");
+ mWriter_LineSize = 0;
+
+ if (mWriter_NeedDirtyAll) // compress commit
+ {
+ }
+
+ if (ev->Good())
+ mWriter_Phase = morkWriter_kPhaseContentDone;
+ else
+ mWriter_Phase = morkWriter_kPhaseWritingDone; // stop on error
+
+ return ev->Good();
+}
+
+mork_bool morkWriter::OnRowSpaceRows(morkEnv* ev) {
+ morkStream* stream = mWriter_Stream;
+ if (mWriter_LineSize) stream->PutLineBreak(ev);
+
+ // if ( mWriter_NeedDirtyAll )
+ // stream->PutStringThenNewline(ev, "// OnRowSpaceRows()");
+ mWriter_LineSize = 0;
+
+ if (mWriter_NeedDirtyAll) // compress commit
+ {
+ }
+
+ if (ev->Good())
+ mWriter_Phase = morkWriter_kPhaseContentDone;
+ else
+ mWriter_Phase = morkWriter_kPhaseWritingDone; // stop on error
+
+ return ev->Good();
+}
+
+mork_bool morkWriter::OnContentDone(morkEnv* ev) {
+ morkStream* stream = mWriter_Stream;
+ if (mWriter_LineSize) stream->PutLineBreak(ev);
+
+ // if ( mWriter_NeedDirtyAll )
+ // stream->PutStringThenNewline(ev, "// OnContentDone()");
+ mWriter_LineSize = 0;
+
+ if (mWriter_Incremental) {
+ if (ev->Good())
+ this->CommitGroup(ev);
+ else
+ this->AbortGroup(ev);
+ } else if (mWriter_Store && ev->Good()) {
+ // after rewriting everything, there are no transaction groups:
+ mWriter_Store->mStore_FirstCommitGroupPos = 0;
+ mWriter_Store->mStore_SecondCommitGroupPos = 0;
+ }
+
+ stream->Flush(ev->AsMdbEnv());
+ nsIMdbFile* bud = mWriter_Bud;
+ if (bud) {
+ bud->Flush(ev->AsMdbEnv());
+ bud->BecomeTrunk(ev->AsMdbEnv());
+ nsIMdbFile_SlotStrongFile((nsIMdbFile*)0, ev, &mWriter_Bud);
+ } else if (!mWriter_Incremental) // should have a bud?
+ this->NilWriterBudError(ev);
+
+ mWriter_Phase = morkWriter_kPhaseWritingDone; // stop always
+ mWriter_DoneCount = mWriter_TotalCount;
+
+ return ev->Good();
+}
+
+mork_bool morkWriter::OnWritingDone(morkEnv* ev) {
+ mWriter_DoneCount = mWriter_TotalCount;
+ ev->NewWarning("writing is done");
+ return ev->Good();
+}
+
+mork_bool morkWriter::PutTableChange(morkEnv* ev,
+ const morkTableChange* inChange) {
+ nsIMdbEnv* mdbev = ev->AsMdbEnv();
+ if (inChange->IsAddRowTableChange()) {
+ this->PutRow(ev, inChange->mTableChange_Row); // row alone means add
+ } else if (inChange->IsCutRowTableChange()) {
+ mWriter_Stream->Putc(ev, '-'); // prefix '-' indicates cut row
+ ++mWriter_LineSize;
+ this->PutRow(ev, inChange->mTableChange_Row);
+ } else if (inChange->IsMoveRowTableChange()) {
+ this->PutRow(ev, inChange->mTableChange_Row);
+ char buf[64];
+ char* p = buf;
+ *p++ = '!'; // for moves, position is indicated by prefix '!'
+ mork_size posSize = ev->TokenAsHex(p, inChange->mTableChange_Pos);
+ p += posSize;
+ *p++ = ' ';
+ mork_size bytesWritten;
+ mWriter_Stream->Write(mdbev, buf, posSize + 2, &bytesWritten);
+ mWriter_LineSize += bytesWritten;
+ } else
+ inChange->UnknownChangeError(ev);
+
+ return ev->Good();
+}
+
+mork_bool morkWriter::PutTable(morkEnv* ev, morkTable* ioTable) {
+ if (ev->Good()) this->StartTable(ev, ioTable);
+
+ if (ev->Good()) {
+ if (ioTable->IsTableRewrite() || mWriter_NeedDirtyAll) {
+ morkArray* array = &ioTable->mTable_RowArray; // vector of rows
+ mork_fill fill = array->mArray_Fill; // count of rows
+ morkRow** rows = (morkRow**)array->mArray_Slots;
+ if (rows && fill) {
+ morkRow** end = rows + fill;
+ while (rows < end && ev->Good()) {
+ morkRow* r = *rows++; // next row to consider
+ this->PutRow(ev, r);
+ }
+ }
+ } else // incremental write only table changes
+ {
+ morkList* list = &ioTable->mTable_ChangeList;
+ morkNext* next = list->GetListHead();
+ while (next && ev->Good()) {
+ this->PutTableChange(ev, (morkTableChange*)next);
+ next = next->GetNextLink();
+ }
+ }
+ }
+
+ if (ev->Good()) this->EndTable(ev);
+
+ ioTable->SetTableClean(ev); // note this also cleans change list
+ mWriter_TableRowScope = 0;
+
+ ++mWriter_DoneCount;
+ return ev->Good();
+}
+
+mork_bool morkWriter::PutTableDict(morkEnv* ev, morkTable* ioTable) {
+ morkRowSpace* space = ioTable->mTable_RowSpace;
+ mWriter_TableRowScope = space->SpaceScope();
+ mWriter_TableForm = 0; // (f=iso-8859-1)
+ mWriter_TableAtomScope = 'v'; // (a=v)
+ mWriter_TableKind = ioTable->mTable_Kind;
+
+ mWriter_RowForm = mWriter_TableForm;
+ mWriter_RowAtomScope = mWriter_TableAtomScope;
+ mWriter_RowScope = mWriter_TableRowScope;
+
+ mWriter_DictForm = mWriter_TableForm;
+ mWriter_DictAtomScope = mWriter_TableAtomScope;
+
+ // if ( ev->Good() )
+ // this->StartDict(ev); // delay as long as possible
+
+ if (ev->Good()) {
+ morkRow* r = ioTable->mTable_MetaRow;
+ if (r) {
+ if (r->IsRow())
+ this->PutRowDict(ev, r);
+ else
+ r->NonRowTypeError(ev);
+ }
+ morkArray* array = &ioTable->mTable_RowArray; // vector of rows
+ mork_fill fill = array->mArray_Fill; // count of rows
+ morkRow** rows = (morkRow**)array->mArray_Slots;
+ if (rows && fill) {
+ morkRow** end = rows + fill;
+ while (rows < end && ev->Good()) {
+ r = *rows++; // next row to consider
+ if (r && r->IsRow())
+ this->PutRowDict(ev, r);
+ else
+ r->NonRowTypeError(ev);
+ }
+ }
+ // we may have a change for a row which is no longer in the
+ // table, but contains a cell with something not in the dictionary.
+ // So, loop through the rows in the change log, writing out any
+ // dirty dictionary elements.
+ morkList* list = &ioTable->mTable_ChangeList;
+ morkNext* next = list->GetListHead();
+ while (next && ev->Good()) {
+ r = ((morkTableChange*)next)->mTableChange_Row;
+ if (r && r->IsRow()) this->PutRowDict(ev, r);
+ next = next->GetNextLink();
+ }
+ }
+ if (ev->Good()) this->EndDict(ev);
+
+ return ev->Good();
+}
+
+void morkWriter::WriteTokenToTokenMetaCell(morkEnv* ev, mork_token inCol,
+ mork_token inValue) {
+ morkStream* stream = mWriter_Stream;
+ mork_bool isKindCol = (morkStore_kKindColumn == inCol);
+ mork_u1 valSep = (mork_u1)((isKindCol) ? '^' : '=');
+
+ char buf[128]; // buffer for staging the two hex IDs
+ char* p = buf;
+
+ mork_size bytesWritten;
+ if (inCol < 0x80) {
+ stream->Putc(ev, '(');
+ stream->Putc(ev, (char)inCol);
+ stream->Putc(ev, valSep);
+ } else {
+ *p++ = '('; // we always start with open paren
+
+ *p++ = '^'; // indicates col is hex ID
+ mork_size colSize = ev->TokenAsHex(p, inCol);
+ p += colSize;
+ *p++ = (char)valSep;
+ stream->Write(ev->AsMdbEnv(), buf, colSize + 3, &bytesWritten);
+
+ mWriter_LineSize += bytesWritten;
+ }
+
+ if (isKindCol) {
+ p = buf;
+ mork_size valSize = ev->TokenAsHex(p, inValue);
+ p += valSize;
+ *p++ = ':';
+ *p++ = 'c';
+ *p++ = ')';
+ stream->Write(ev->AsMdbEnv(), buf, valSize + 3, &bytesWritten);
+ mWriter_LineSize += bytesWritten;
+ } else {
+ this->IndentAsNeeded(ev, morkWriter_kTableMetaCellValueDepth);
+ mdbYarn* yarn = &mWriter_ColYarn;
+ // mork_u1* yarnBuf = (mork_u1*) yarn->mYarn_Buf;
+ mWriter_Store->TokenToString(ev, inValue, yarn);
+ this->WriteYarn(ev, yarn);
+ stream->Putc(ev, ')');
+ ++mWriter_LineSize;
+ }
+
+ // mork_fill fill = yarn->mYarn_Fill;
+ // yarnBuf[ fill ] = ')'; // append terminator
+ // mWriter_LineSize += stream->Write(ev, yarnBuf, fill + 1); // +1 for ')'
+}
+
+void morkWriter::WriteStringToTokenDictCell(morkEnv* ev, const char* inCol,
+ mork_token inValue)
+// Note inCol should begin with '(' and end with '=', with col in between.
+{
+ morkStream* stream = mWriter_Stream;
+ mWriter_LineSize += stream->PutString(ev, inCol);
+
+ this->IndentAsNeeded(ev, morkWriter_kDictMetaCellValueDepth);
+ mdbYarn* yarn = &mWriter_ColYarn;
+ // mork_u1* yarnBuf = (mork_u1*) yarn->mYarn_Buf;
+ mWriter_Store->TokenToString(ev, inValue, yarn);
+ this->WriteYarn(ev, yarn);
+ stream->Putc(ev, ')');
+ ++mWriter_LineSize;
+
+ // mork_fill fill = yarn->mYarn_Fill;
+ // yarnBuf[ fill ] = ')'; // append terminator
+ // mWriter_LineSize += stream->Write(ev, yarnBuf, fill + 1); // +1 for ')'
+}
+
+void morkWriter::ChangeDictAtomScope(morkEnv* ev, mork_scope inScope) {
+ if (inScope != mWriter_DictAtomScope) {
+ ev->NewWarning("unexpected atom scope change");
+
+ morkStream* stream = mWriter_Stream;
+ if (mWriter_LineSize) stream->PutLineBreak(ev);
+ mWriter_LineSize = 0;
+
+ char buf[128]; // buffer for staging the two hex IDs
+ char* p = buf;
+ *p++ = '<'; // we always start with open paren
+ *p++ = '('; // we always start with open paren
+ *p++ = (char)morkStore_kAtomScopeColumn;
+
+ mork_size scopeSize = 1; // default to one byte
+ if (inScope >= 0x80) {
+ *p++ = '^'; // indicates col is hex ID
+ scopeSize = ev->TokenAsHex(p, inScope);
+ p += scopeSize;
+ } else {
+ *p++ = '='; // indicates col is imm byte
+ *p++ = (char)(mork_u1)inScope;
+ }
+
+ *p++ = ')';
+ *p++ = '>';
+ *p = 0;
+
+ mork_size pending = scopeSize + 6;
+ this->IndentOverMaxLine(ev, pending, morkWriter_kDictAliasDepth);
+ mork_size bytesWritten;
+
+ stream->Write(ev->AsMdbEnv(), buf, pending, &bytesWritten);
+ mWriter_LineSize += bytesWritten;
+
+ mWriter_DictAtomScope = inScope;
+ }
+}
+
+void morkWriter::ChangeRowForm(morkEnv* ev, mork_cscode inNewForm) {
+ if (inNewForm != mWriter_RowForm) {
+ morkStream* stream = mWriter_Stream;
+ if (mWriter_LineSize) stream->PutLineBreak(ev);
+ mWriter_LineSize = 0;
+
+ char buf[128]; // buffer for staging the two hex IDs
+ char* p = buf;
+ *p++ = '['; // we always start with open bracket
+ *p++ = '('; // we always start with open paren
+ *p++ = (char)morkStore_kFormColumn;
+
+ mork_size formSize = 1; // default to one byte
+ if (!morkCh_IsValue(inNewForm)) {
+ *p++ = '^'; // indicates col is hex ID
+ formSize = ev->TokenAsHex(p, inNewForm);
+ p += formSize;
+ } else {
+ *p++ = '='; // indicates col is imm byte
+ *p++ = (char)(mork_u1)inNewForm;
+ }
+
+ *p++ = ')';
+ *p++ = ']';
+ *p = 0;
+
+ mork_size pending = formSize + 6;
+ this->IndentOverMaxLine(ev, pending, morkWriter_kRowCellDepth);
+ mork_size bytesWritten;
+ stream->Write(ev->AsMdbEnv(), buf, pending, &bytesWritten);
+ mWriter_LineSize += bytesWritten;
+
+ mWriter_RowForm = inNewForm;
+ }
+}
+
+void morkWriter::ChangeDictForm(morkEnv* ev, mork_cscode inNewForm) {
+ if (inNewForm != mWriter_DictForm) {
+ morkStream* stream = mWriter_Stream;
+ if (mWriter_LineSize) stream->PutLineBreak(ev);
+ mWriter_LineSize = 0;
+
+ char buf[128]; // buffer for staging the two hex IDs
+ char* p = buf;
+ *p++ = '<'; // we always start with open angle
+ *p++ = '('; // we always start with open paren
+ *p++ = (char)morkStore_kFormColumn;
+
+ mork_size formSize = 1; // default to one byte
+ if (!morkCh_IsValue(inNewForm)) {
+ *p++ = '^'; // indicates col is hex ID
+ formSize = ev->TokenAsHex(p, inNewForm);
+ p += formSize;
+ } else {
+ *p++ = '='; // indicates col is imm byte
+ *p++ = (char)(mork_u1)inNewForm;
+ }
+
+ *p++ = ')';
+ *p++ = '>';
+ *p = 0;
+
+ mork_size pending = formSize + 6;
+ this->IndentOverMaxLine(ev, pending, morkWriter_kDictAliasDepth);
+
+ mork_size bytesWritten;
+ stream->Write(ev->AsMdbEnv(), buf, pending, &bytesWritten);
+ mWriter_LineSize += bytesWritten;
+
+ mWriter_DictForm = inNewForm;
+ }
+}
+
+void morkWriter::StartDict(morkEnv* ev) {
+ morkStream* stream = mWriter_Stream;
+ if (mWriter_DidStartDict) {
+ stream->Putc(ev, '>'); // end dict
+ ++mWriter_LineSize;
+ }
+ mWriter_DidStartDict = morkBool_kTrue;
+ mWriter_DidEndDict = morkBool_kFalse;
+
+ if (mWriter_LineSize) stream->PutLineBreak(ev);
+ mWriter_LineSize = 0;
+
+ if (mWriter_TableRowScope) // blank line before table's dict?
+ stream->PutLineBreak(ev);
+
+ if (mWriter_DictForm || mWriter_DictAtomScope != 'v') {
+ stream->Putc(ev, '<');
+ stream->Putc(ev, ' ');
+ stream->Putc(ev, '<');
+ mWriter_LineSize = 3;
+ if (mWriter_DictForm)
+ this->WriteStringToTokenDictCell(ev, "(f=", mWriter_DictForm);
+ if (mWriter_DictAtomScope != 'v')
+ this->WriteStringToTokenDictCell(ev, "(a=", mWriter_DictAtomScope);
+
+ stream->Putc(ev, '>');
+ ++mWriter_LineSize;
+
+ mWriter_LineSize = stream->PutIndent(ev, morkWriter_kDictAliasDepth);
+ } else {
+ stream->Putc(ev, '<');
+ // stream->Putc(ev, ' ');
+ ++mWriter_LineSize;
+ }
+}
+
+void morkWriter::EndDict(morkEnv* ev) {
+ morkStream* stream = mWriter_Stream;
+ if (mWriter_DidStartDict) {
+ stream->Putc(ev, '>'); // end dict
+ ++mWriter_LineSize;
+ }
+ mWriter_DidStartDict = morkBool_kFalse;
+ mWriter_DidEndDict = morkBool_kTrue;
+}
+
+void morkWriter::StartTable(morkEnv* ev, morkTable* ioTable) {
+ mdbOid toid; // to receive table oid
+ ioTable->GetTableOid(ev, &toid);
+
+ if (ev->Good()) {
+ morkStream* stream = mWriter_Stream;
+ if (mWriter_LineSize) stream->PutLineBreak(ev);
+ mWriter_LineSize = 0;
+ // stream->PutLineBreak(ev);
+
+ char buf[64 + 16]; // buffer for staging hex
+ char* p = buf;
+ *p++ = '{'; // punct 1
+ mork_size punctSize =
+ (mWriter_BeVerbose) ? 10 : 3; // counting "{ {/*r=*/ "
+
+ if (ioTable->IsTableRewrite() && mWriter_Incremental) {
+ *p++ = '-';
+ ++punctSize; // counting '-' // punct ++
+ ++mWriter_LineSize;
+ }
+ mork_size oidSize = ev->OidAsHex(p, toid);
+ p += oidSize;
+ *p++ = ' '; // punct 2
+ *p++ = '{'; // punct 3
+ if (mWriter_BeVerbose) {
+ *p++ = '/'; // punct=4
+ *p++ = '*'; // punct=5
+ *p++ = 'r'; // punct=6
+ *p++ = '='; // punct=7
+
+ mork_token tableUses = (mork_token)ioTable->mTable_GcUses;
+ mork_size usesSize = ev->TokenAsHex(p, tableUses);
+ punctSize += usesSize;
+ p += usesSize;
+
+ *p++ = '*'; // punct=8
+ *p++ = '/'; // punct=9
+ *p++ = ' '; // punct=10
+ }
+ mork_size bytesWritten;
+
+ stream->Write(ev->AsMdbEnv(), buf, oidSize + punctSize, &bytesWritten);
+ mWriter_LineSize += bytesWritten;
+
+ mork_kind tk = mWriter_TableKind;
+ if (tk) {
+ this->IndentAsNeeded(ev, morkWriter_kTableMetaCellDepth);
+ this->WriteTokenToTokenMetaCell(ev, morkStore_kKindColumn, tk);
+ }
+
+ stream->Putc(ev, '('); // start 's' col cell
+ stream->Putc(ev, 's'); // column
+ stream->Putc(ev, '='); // column
+ mWriter_LineSize += 3;
+
+ int prio = (int)ioTable->mTable_Priority;
+ if (prio > 9) // need to force down to max decimal digit?
+ prio = 9;
+ prio += '0'; // add base digit zero
+ stream->Putc(ev, prio); // priority: (s=0
+ ++mWriter_LineSize;
+
+ if (ioTable->IsTableUnique()) {
+ stream->Putc(ev, 'u'); // (s=0u
+ ++mWriter_LineSize;
+ }
+ if (ioTable->IsTableVerbose()) {
+ stream->Putc(ev, 'v'); // (s=0uv
+ ++mWriter_LineSize;
+ }
+
+ // stream->Putc(ev, ':'); // (s=0uv:
+ // stream->Putc(ev, 'c'); // (s=0uv:c
+ stream->Putc(ev, ')'); // end 's' col cell (s=0uv:c)
+ mWriter_LineSize += 1; // maybe 3 if we add ':' and 'c'
+
+ morkRow* r = ioTable->mTable_MetaRow;
+ if (r) {
+ if (r->IsRow()) {
+ mWriter_SuppressDirtyRowNewline = morkBool_kTrue;
+ this->PutRow(ev, r);
+ } else
+ r->NonRowTypeError(ev);
+ }
+
+ stream->Putc(ev, '}'); // end meta
+ ++mWriter_LineSize;
+
+ if (mWriter_LineSize < mWriter_MaxIndent) {
+ stream->Putc(ev, ' '); // nice white space
+ ++mWriter_LineSize;
+ }
+ }
+}
+
+void morkWriter::EndTable(morkEnv* ev) {
+ morkStream* stream = mWriter_Stream;
+ stream->Putc(ev, '}'); // end table
+ ++mWriter_LineSize;
+
+ mWriter_TableAtomScope = 'v'; // (a=v)
+}
+
+mork_bool morkWriter::PutRowDict(morkEnv* ev, morkRow* ioRow) {
+ mWriter_RowForm = mWriter_TableForm;
+
+ morkCell* cells = ioRow->mRow_Cells;
+ if (cells) {
+ morkStream* stream = mWriter_Stream;
+ mdbYarn yarn; // to ref content inside atom
+ char buf[64]; // buffer for staging the dict alias hex ID
+ char* idBuf = buf + 1; // where the id always starts
+ buf[0] = '('; // we always start with open paren
+
+ morkCell* end = cells + ioRow->mRow_Length;
+ --cells; // prepare for preincrement:
+ while (++cells < end && ev->Good()) {
+ morkAtom* atom = cells->GetAtom();
+ if (atom && atom->IsAtomDirty()) {
+ if (atom->IsBook()) // is it possible to write atom ID?
+ {
+ if (!this->DidStartDict()) {
+ this->StartDict(ev);
+ if (ev->Bad()) break;
+ }
+ atom->SetAtomClean(); // neutralize change
+
+ this->IndentAsNeeded(ev, morkWriter_kDictAliasDepth);
+ morkBookAtom* ba = (morkBookAtom*)atom;
+ mork_size size = ev->TokenAsHex(idBuf, ba->mBookAtom_Id);
+ mork_size bytesWritten;
+ stream->Write(ev->AsMdbEnv(), buf, size + 1, &bytesWritten); // '('
+ mWriter_LineSize += bytesWritten;
+
+ if (morkAtom::AliasYarn(atom, &yarn)) {
+ mork_scope atomScope = atom->GetBookAtomSpaceScope(ev);
+ if (atomScope && atomScope != mWriter_DictAtomScope)
+ this->ChangeDictAtomScope(ev, atomScope);
+
+ if (mWriter_DidStartDict && yarn.mYarn_Form != mWriter_DictForm)
+ this->ChangeDictForm(ev, yarn.mYarn_Form);
+
+ mork_size pending =
+ yarn.mYarn_Fill + morkWriter_kYarnEscapeSlop + 1;
+ this->IndentOverMaxLine(ev, pending,
+ morkWriter_kDictAliasValueDepth);
+
+ stream->Putc(ev, '='); // start value
+ ++mWriter_LineSize;
+
+ this->WriteYarn(ev, &yarn);
+
+ stream->Putc(ev, ')'); // end value
+ ++mWriter_LineSize;
+ } else
+ atom->BadAtomKindError(ev);
+
+ ++mWriter_DoneCount;
+ }
+ }
+ }
+ }
+ return ev->Good();
+}
+
+mork_bool morkWriter::IsYarnAllValue(const mdbYarn* inYarn) {
+ mork_fill fill = inYarn->mYarn_Fill;
+ const mork_u1* buf = (const mork_u1*)inYarn->mYarn_Buf;
+ const mork_u1* end = buf + fill;
+ --buf; // prepare for preincrement
+ while (++buf < end) {
+ mork_ch c = *buf;
+ if (!morkCh_IsValue(c)) return morkBool_kFalse;
+ }
+ return morkBool_kTrue;
+}
+
+mork_bool morkWriter::PutVerboseCell(morkEnv* ev, morkCell* ioCell,
+ mork_bool inWithVal) {
+ morkStream* stream = mWriter_Stream;
+ morkStore* store = mWriter_Store;
+
+ mdbYarn* colYarn = &mWriter_ColYarn;
+
+ morkAtom* atom = (inWithVal) ? ioCell->GetAtom() : (morkAtom*)0;
+
+ mork_column col = ioCell->GetColumn();
+ store->TokenToString(ev, col, colYarn);
+
+ mdbYarn yarn; // to ref content inside atom
+ morkAtom::AliasYarn(atom, &yarn); // works even when atom==nil
+
+ if (yarn.mYarn_Form != mWriter_RowForm)
+ this->ChangeRowForm(ev, yarn.mYarn_Form);
+
+ mork_size pending =
+ yarn.mYarn_Fill + colYarn->mYarn_Fill + morkWriter_kYarnEscapeSlop + 3;
+ this->IndentOverMaxLine(ev, pending, morkWriter_kRowCellDepth);
+
+ stream->Putc(ev, '('); // start cell
+ ++mWriter_LineSize;
+
+ this->WriteYarn(ev, colYarn); // column
+
+ pending = yarn.mYarn_Fill + morkWriter_kYarnEscapeSlop;
+ this->IndentOverMaxLine(ev, pending, morkWriter_kRowCellValueDepth);
+ stream->Putc(ev, '=');
+ ++mWriter_LineSize;
+
+ this->WriteYarn(ev, &yarn); // value
+
+ stream->Putc(ev, ')'); // end cell
+ ++mWriter_LineSize;
+
+ return ev->Good();
+}
+
+mork_bool morkWriter::PutVerboseRowCells(morkEnv* ev, morkRow* ioRow) {
+ morkCell* cells = ioRow->mRow_Cells;
+ if (cells) {
+ morkCell* end = cells + ioRow->mRow_Length;
+ --cells; // prepare for preincrement:
+ while (++cells < end && ev->Good()) {
+ // note we prefer to avoid writing cells here with no value:
+ if (cells->GetAtom()) // does cell have any value?
+ this->PutVerboseCell(ev, cells, /*inWithVal*/ morkBool_kTrue);
+ }
+ }
+ return ev->Good();
+}
+
+mork_bool morkWriter::PutCell(morkEnv* ev, morkCell* ioCell,
+ mork_bool inWithVal) {
+ morkStream* stream = mWriter_Stream;
+ char buf[128]; // buffer for staging hex ids
+ char* idBuf = buf + 2; // where the id always starts
+ buf[0] = '('; // we always start with open paren
+ buf[1] = '^'; // column is always a hex ID
+
+ mork_size colSize = 0; // the size of col hex ID
+ mork_size bytesWritten;
+
+ morkAtom* atom = (inWithVal) ? ioCell->GetAtom() : (morkAtom*)0;
+
+ mork_column col = ioCell->GetColumn();
+ char* p = idBuf;
+ colSize = ev->TokenAsHex(p, col);
+ p += colSize;
+
+ mdbYarn yarn; // to ref content inside atom
+ morkAtom::AliasYarn(atom, &yarn); // works even when atom==nil
+
+ if (yarn.mYarn_Form != mWriter_RowForm)
+ this->ChangeRowForm(ev, yarn.mYarn_Form);
+
+ if (atom && atom->IsBook()) // is it possible to write atom ID?
+ {
+ this->IndentAsNeeded(ev, morkWriter_kRowCellDepth);
+ *p++ = '^';
+ morkBookAtom* ba = (morkBookAtom*)atom;
+
+ mork_size valSize = ev->TokenAsHex(p, ba->mBookAtom_Id);
+ mork_fill yarnFill = yarn.mYarn_Fill;
+ mork_bool putImmYarn = (yarnFill <= valSize);
+ if (putImmYarn) putImmYarn = this->IsYarnAllValue(&yarn);
+
+ if (putImmYarn) // value no bigger than id?
+ {
+ p[-1] = '='; // go back and clobber '^' with '=' instead
+ if (yarnFill) {
+ MORK_MEMCPY(p, yarn.mYarn_Buf, yarnFill);
+ p += yarnFill;
+ }
+ *p++ = ')';
+ mork_size distance = (mork_size)(p - buf);
+ stream->Write(ev->AsMdbEnv(), buf, distance, &bytesWritten);
+ mWriter_LineSize += bytesWritten;
+ } else {
+ p += valSize;
+ *p = ')';
+ stream->Write(ev->AsMdbEnv(), buf, colSize + valSize + 4, &bytesWritten);
+ mWriter_LineSize += bytesWritten;
+ }
+
+ if (atom->IsAtomDirty()) {
+ atom->SetAtomClean();
+ ++mWriter_DoneCount;
+ }
+ } else // must write an anonymous atom
+ {
+ mork_size pending =
+ yarn.mYarn_Fill + colSize + morkWriter_kYarnEscapeSlop + 2;
+ this->IndentOverMaxLine(ev, pending, morkWriter_kRowCellDepth);
+
+ mork_size bytesWritten;
+ stream->Write(ev->AsMdbEnv(), buf, colSize + 2, &bytesWritten);
+ mWriter_LineSize += bytesWritten;
+
+ pending -= (colSize + 2);
+ this->IndentOverMaxLine(ev, pending, morkWriter_kRowCellDepth);
+ stream->Putc(ev, '=');
+ ++mWriter_LineSize;
+
+ this->WriteYarn(ev, &yarn);
+ stream->Putc(ev, ')'); // end cell
+ ++mWriter_LineSize;
+ }
+ return ev->Good();
+}
+
+mork_bool morkWriter::PutRowCells(morkEnv* ev, morkRow* ioRow) {
+ morkCell* cells = ioRow->mRow_Cells;
+ if (cells) {
+ morkCell* end = cells + ioRow->mRow_Length;
+ --cells; // prepare for preincrement:
+ while (++cells < end && ev->Good()) {
+ // note we prefer to avoid writing cells here with no value:
+ if (cells->GetAtom()) // does cell have any value?
+ this->PutCell(ev, cells, /*inWithVal*/ morkBool_kTrue);
+ }
+ }
+ return ev->Good();
+}
+
+mork_bool morkWriter::PutRow(morkEnv* ev, morkRow* ioRow) {
+ if (ioRow && ioRow->IsRow()) {
+ mWriter_RowForm = mWriter_TableForm;
+
+ mork_size bytesWritten;
+ morkStream* stream = mWriter_Stream;
+ char buf[128 + 16]; // buffer for staging hex
+ char* p = buf;
+ mdbOid* roid = &ioRow->mRow_Oid;
+ mork_size ridSize = 0;
+
+ mork_scope tableScope = mWriter_TableRowScope;
+
+ if (ioRow->IsRowDirty()) {
+ if (mWriter_SuppressDirtyRowNewline || !mWriter_LineSize)
+ mWriter_SuppressDirtyRowNewline = morkBool_kFalse;
+ else {
+ if (tableScope) // in a table?
+ mWriter_LineSize = stream->PutIndent(ev, morkWriter_kRowDepth);
+ else
+ mWriter_LineSize = stream->PutIndent(ev, 0); // no indent
+ }
+
+ // mork_rid rid = roid->mOid_Id;
+ *p++ = '['; // start row punct=1
+ mork_size punctSize =
+ (mWriter_BeVerbose) ? 9 : 1; // counting "[ /*r=*/ "
+
+ mork_bool rowRewrite = ioRow->IsRowRewrite();
+
+ if (rowRewrite && mWriter_Incremental) {
+ *p++ = '-';
+ ++punctSize; // counting '-'
+ ++mWriter_LineSize;
+ }
+
+ if (tableScope && roid->mOid_Scope == tableScope)
+ ridSize = ev->TokenAsHex(p, roid->mOid_Id);
+ else
+ ridSize = ev->OidAsHex(p, *roid);
+
+ p += ridSize;
+
+ if (mWriter_BeVerbose) {
+ *p++ = ' '; // punct=2
+ *p++ = '/'; // punct=3
+ *p++ = '*'; // punct=4
+ *p++ = 'r'; // punct=5
+ *p++ = '='; // punct=6
+
+ mork_size usesSize = ev->TokenAsHex(p, (mork_token)ioRow->mRow_GcUses);
+ punctSize += usesSize;
+ p += usesSize;
+
+ *p++ = '*'; // punct=7
+ *p++ = '/'; // punct=8
+ *p++ = ' '; // punct=9
+ }
+ stream->Write(ev->AsMdbEnv(), buf, ridSize + punctSize, &bytesWritten);
+ mWriter_LineSize += bytesWritten;
+
+ // special case situation where row puts exactly one column:
+ if (!rowRewrite && mWriter_Incremental && ioRow->HasRowDelta()) {
+ mork_column col = ioRow->GetDeltaColumn();
+ morkCell dummy(col, morkChange_kNil, (morkAtom*)0);
+ morkCell* cell = 0;
+
+ mork_bool withVal = (ioRow->GetDeltaChange() != morkChange_kCut);
+
+ if (withVal) {
+ mork_pos cellPos = 0; // dummy pos
+ cell = ioRow->GetCell(ev, col, &cellPos);
+ }
+ if (!cell) cell = &dummy;
+
+ if (mWriter_BeVerbose)
+ this->PutVerboseCell(ev, cell, withVal);
+ else
+ this->PutCell(ev, cell, withVal);
+ } else // put entire row?
+ {
+ if (mWriter_BeVerbose)
+ this->PutVerboseRowCells(ev, ioRow); // write all, verbosely
+ else
+ this->PutRowCells(ev, ioRow); // write all, hex notation
+ }
+
+ stream->Putc(ev, ']'); // end row
+ ++mWriter_LineSize;
+ } else {
+ this->IndentAsNeeded(ev, morkWriter_kRowDepth);
+
+ if (tableScope && roid->mOid_Scope == tableScope)
+ ridSize = ev->TokenAsHex(p, roid->mOid_Id);
+ else
+ ridSize = ev->OidAsHex(p, *roid);
+
+ stream->Write(ev->AsMdbEnv(), buf, ridSize, &bytesWritten);
+ mWriter_LineSize += bytesWritten;
+ stream->Putc(ev, ' ');
+ ++mWriter_LineSize;
+ }
+
+ ++mWriter_DoneCount;
+
+ ioRow->SetRowClean(); // try to do this at the very last
+ } else
+ ioRow->NonRowTypeWarning(ev);
+
+ return ev->Good();
+}
+
+// 456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789