summaryrefslogtreecommitdiff
path: root/db/mork/src/morkWriter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'db/mork/src/morkWriter.cpp')
-rw-r--r--db/mork/src/morkWriter.cpp2206
1 files changed, 2206 insertions, 0 deletions
diff --git a/db/mork/src/morkWriter.cpp b/db/mork/src/morkWriter.cpp
new file mode 100644
index 0000000000..98ca5457f0
--- /dev/null
+++ b/db/mork/src/morkWriter.cpp
@@ -0,0 +1,2206 @@
+/* -*- 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
+
+//3456789_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[ 64 ];
+ 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 )
+ {
+ MORK_MEMCPY(mWriter_GroupBuf, p, idFill + 1);
+ mWriter_GroupBufFill = idFill;
+ }
+ else
+ *mWriter_GroupBuf = 0;
+
+ p += idFill;
+ *p++ = '{';
+ *p++ = '@';
+ *p = 0;
+
+ 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, idFill + 6, &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();
+}
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+