Mozilla/mozilla/db/mork/src/morkWriter.cpp
2007-02-28 17:04:22 +00:00

2244 lines
60 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#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 )
{
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);
}
else
ev->NilPointerError();
}
// } ===== 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 )
{
register 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 ( inAtom->AliasYarn(&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
atom->AliasYarn(&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 ( atom->AliasYarn(&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
atom->AliasYarn(&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
atom->AliasYarn(&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