/*---------------------------------------------------------------------------------------------- Load data into the cache from the record set defined by hstmt, according to the specs in prgocs/cocs. Columns with m_icolID = 0 give properties of hvoBase. Load properties of at most crowMax objects; this may only be used if there is no vector property being loaded, since we could not be sure of having a complete record of the value of a vector without loading the next row. If crowMax is zero, load everything. Note: call from inside try/catch block; may throw exceptions. Note that prgocs[i] describes the column which ODBC indexes as [i+1]. ----------------------------------------------------------------------------------------------*/ void VwRsOdbcDa::Load(SQLHSTMT hstmt, OdbcColSpec * prgocs, int cocs, HVO hvoBase, int crowMax) { AssertArray(prgocs, cocs); Assert((uint)cocs <= (uint) 200); // limit because of size of rghvoBaseIds Assert(crowMax >= 0); ITsStrFactoryPtr qtsf; qtsf.CreateInstance(CLSID_TsStrFactory); ITsPropsFactoryPtr qtpf; qtpf.CreateInstance(CLSID_TsPropsFactory); // Block of variables for binary fields Vector<byte> vbData; // used to buffer data from binary fields const int kcbMaxData = 1000; // amount of binary data to read in one go byte rgbData[kcbMaxData]; // buffer for short binary data fields long cbData; // how many bytes in prgbData hold valid data byte * prgbData; // points to rgbData or vbData.Begin(), as appropriate // Similar block for Unicode text Vector<wchar> vchData; const int kcchMaxData = 1000; wchar rgchData[kcchMaxData]; long cchData; wchar * prgchData; Vector<HVO> vhvo; // accumulate objects for sequence property int nrows = 0; if (crowMax == 0) crowMax = INT_MAX; HVO rghvoBaseIds[200]; int icolVec = -1; // index of (one and only) column of type koctObjVec int hvoVecBase; // object that is base of vector property while (CheckSqlRc(SQLFetch(hstmt)) != SQL_NO_DATA) { // We have a record. for (int icol = 0; icol < cocs; icol++) { int nVal; HVO hvoVal; ITsStringPtr qtssVal; // TOxDO JohnT: fill this in... HVO hvoCurBase; // object whose property we will read. if (prgocs[icol].m_icolID == 0) hvoCurBase = hvoBase; else { // Must refer to a previous column; use <= because m_icolID is 1-based, so // if equal to i, it refers to the immediate previous column. Assert(prgocs[icol].m_icolID <= icol); hvoCurBase = rghvoBaseIds[prgocs[icol].m_icolID - 1]; } switch (prgocs[icol].m_oct) { default: Assert(false); ThrowHr(WarnHr(E_UNEXPECTED)); case koctInt: CheckSqlRc(SQLGetData(hstmt, (unsigned short)(icol + 1), SQL_C_SLONG, &nVal, 4, NULL)); CacheIntProp(hvoCurBase, prgocs[icol].m_tag, nVal); break; case koctUnicode: ReadUnicode(hstmt, icol + 1, rgchData, kcchMaxData, vchData, prgchData, cchData); CacheUnicodeProp(hvoCurBase, prgocs[icol].m_tag, prgchData, cchData); break; case koctString: case koctMlsAlt: case koctMltAlt: // Next column must give format; both are for the same property ReadUnicode(hstmt, icol + 1, rgchData, kcchMaxData, vchData, prgchData, cchData); if (koctMltAlt != prgocs[icol].m_oct) { Assert(icol < cocs - 1 && prgocs[icol + 1].m_oct == koctFmt); Assert(prgocs[icol].m_tag == prgocs[icol + 1].m_tag); // Leave the data in prgchData and cchData, to be processed next iteration // when we read the format. break; } // A MS alt without a FMT column, use the specified writing system both for the string // formatting and to indicate the alternative. CheckHr(qtsf->MakeStringRgch(prgchData, cchData, prgocs[icol].m_ws, &qtssVal)); CacheStringAlt(hvoCurBase, prgocs[icol].m_tag, prgocs[icol].m_ws, qtssVal); break; case koctFmt: // Previous column must be string or multistring; we have already checked same tag. Assert(icol > 0 && (prgocs[icol - 1].m_oct == koctString || prgocs[icol - 1].m_oct == koctMlsAlt)); ReadBinary(hstmt, icol + 1, rgbData, kcbMaxData, vbData, prgbData, cbData); int cbDataInt; cbDataInt = cbData; int cchDataInt; cchDataInt = cchData; if (cchDataInt == 0 && cbDataInt == 0) CheckHr(qtsf->MakeStringRgch(NULL, 0, prgocs[icol - 1].m_ws, &qtssVal)); else CheckHr(qtsf->DeserializeStringRgch(prgchData, &cchDataInt, prgbData, &cbDataInt, &qtssVal)); if (prgocs[icol - 1].m_oct == koctString) { CacheStringProp(hvoCurBase, prgocs[icol].m_tag, qtssVal); } else { CacheStringAlt(hvoCurBase, prgocs[icol].m_tag, prgocs[icol - 1].m_ws, qtssVal); } break; case koctObj: case koctBaseId: long nIndicator; CheckSqlRc(SQLGetData(hstmt, (unsigned short)(icol + 1), SQL_C_SLONG, &hvoVal, 4, &nIndicator)); // Treat null as zero. if (nIndicator == SQL_NULL_DATA) hvoVal = 0; if (prgocs[icol].m_oct == koctObj) CacheObjProp(hvoCurBase, prgocs[icol].m_tag, hvoVal); rghvoBaseIds[icol] = hvoVal; break; case koctObjVec: CheckSqlRc(SQLGetData(hstmt, (unsigned short)(icol + 1), SQL_C_SLONG, &hvoVal, 4, NULL)); rghvoBaseIds[icol] = hvoVal; // See if there has been a change in the base column, if so, record value and // start a new one. if (icolVec < 0) { // First iteration, ignore previous object icolVec = icol; hvoVecBase = hvoCurBase; } else { // Only one vector column allowed! Assert(icolVec == icol); if (hvoVecBase != hvoCurBase) { // Started a new vector! Record the old one CacheVecProp(hvoVecBase, prgocs[icolVec].m_tag, vhvo.Begin(), vhvo.Size()); // clear the list out and note new base object vhvo.Clear(); hvoVecBase = hvoCurBase; } } vhvo.Push(hvoVal); break; case koctTtp: ReadBinary(hstmt, icol + 1, rgbData, kcbMaxData, vbData, prgbData, cbData); if (cbData > 0) // otherwise field is null, cache nothing { cbDataInt = cbData; ITsTextPropsPtr qttp; qtpf->DeserializePropsRgb(prgbData, &cbDataInt, &qttp); CacheUnknown(hvoCurBase, prgocs[icol].m_tag, qttp); } break; } } // Stop if we have processed the requested number of rows. nrows++; if (nrows >= crowMax) break; } // If we are processing a vector, we need to fill in the last occurrence if (icolVec >= 0) { CacheVecProp(hvoVecBase, prgocs[icolVec].m_tag, vhvo.Begin(), vhvo.Size()); } }
/*---------------------------------------------------------------------------------------------- Crawl through the database and rename/delete the given styles. ----------------------------------------------------------------------------------------------*/ STDMETHODIMP FwDbMergeStyles::Process(DWORD hWnd) { BEGIN_COM_METHOD; Assert(m_vstuOldNames.Size() == m_vstuNewNames.Size()); if (!m_hvoRoot) return E_UNEXPECTED; if (!m_pclsidApp) return E_UNEXPECTED; if (LogFile()) { ULONG cbLog; StrAnsi staLog; staLog.Format("Changing style names in %S (%S)%n", DbName().Chars(), ServerName().Chars()); LogFile()->Write(staLog.Chars(), staLog.Length(), &cbLog); } m_qprog->DoModeless((HWND)hWnd); StrUni stuMsg(kstidChgStyleLabel); m_qprog->put_Title(stuMsg.Bstr()); ResetConnection(); BeginTrans(); CreateCommand(); SetPercentComplete(0); // We want to affect only a subset of the formatting and style information in the database, // based on which program is currently running, since different programs may be using // different sets of styles. This information is derived from m_hvoRoot and m_pclsidApp. // (Unfortunately, there's no way to derive one of these values from the other one.) The // current scheme works for Data Notebook, List Editor, TE, and LexText. Additional // programs may introduce even more complexities. // 1. Find all the owners of StStyle objects. This should match up to LangProject (Data // Notebook and List Editor), Scripture (TE), or LexDb (LexText). // 2. If an owner equals m_hvoRoot, then only those objects owned by m_hvoRoot have the // string and paragraph formatting fixed. Except that LexText also wants to fix all the // Text objects owned by the LangProject in addition to all the objects owned by the // LexDb, so those have to be added to the collection of objects. // 3. If none of the owners equals m_hvoRoot, we must be dealing with Data Notebook or List // Editor, which share a common set of styles owned by the LangProject itself. In // this case, we want all the objects owned by the LangProject except those owned by // another owner of styles (or the LangProject Text objects, since those use the // LexText styles even though they're not owned by the root object). (This isn't quite // right if either TE or LexText does not actually own any styles yet, but we won't worry // about this nicety.) // 4. After creating a temp table containing just the ids of those objects of interest, we // can then process all the relevant string formatting (via a string crawler) and // paragraph StyleRules (later in this method) // 5. Finally, we may need to fix the Style fields of the relevant UserViewField objects. // These are limited by the psclsidApp argument, since the UserView objects are specific // to specific applications. If pssclidApp indicates either Data Notebook or List // Editor, then UserViewField objects belonging to both of those programs are fixed. // Otherwise, only those UserViewField objects belonging to the specific program // identified by psclsidApp are fixed. ComBool fMoreRows; UINT cbSpaceTaken; ComBool fIsNull; int hobj = 0; int clid = 0; bool fFixOwned = false; int clidOwned = 0; int hvoLangProj = 0; Vector<int> vhvoOwners; Vector<int> vclidOwners; Vector<int> vhvoTexts; StrUni stuCmd; stuCmd.Format(L"SELECT DISTINCT c.Owner$, co.Class$ " L"FROM CmObject c " L"JOIN CmObject co on co.Id = c.Owner$ " L"WHERE c.Class$ = %d", kclidStStyle); CheckHr(GetOleDbCommand()->ExecCommand(stuCmd.Bstr(), knSqlStmtSelectWithOneRowset)); CheckHr(GetOleDbCommand()->GetRowset(0)); CheckHr(GetOleDbCommand()->NextRow(&fMoreRows)); while (fMoreRows) { CheckHr(GetOleDbCommand()->GetColValue(1, reinterpret_cast <BYTE *>(&hobj), isizeof(hobj), &cbSpaceTaken, &fIsNull, 0)); CheckHr(GetOleDbCommand()->GetColValue(2, reinterpret_cast <BYTE *>(&clid), isizeof(clid), &cbSpaceTaken, &fIsNull, 0)); if (hobj == m_hvoRoot) { fFixOwned = true; clidOwned = clid; } else if (clid == kclidLangProject) { hvoLangProj = hobj; } else { vhvoOwners.Push(hobj); vclidOwners.Push(clid); } CheckHr(GetOleDbCommand()->NextRow(&fMoreRows)); } Assert(hvoLangProj != 0); // This may not be defined in any of our header files. #ifndef kclidLexDb #define kclidLexDb 5005 #endif if (!fFixOwned || clidOwned == kclidLexDb) { // We need the set of LangProject_Texts objects to include or exclude. stuCmd.Format(L"SELECT Dst FROM LangProject_Texts WHERE Src = %d", hvoLangProj); CheckHr(GetOleDbCommand()->ExecCommand(stuCmd.Bstr(), knSqlStmtSelectWithOneRowset)); CheckHr(GetOleDbCommand()->GetRowset(0)); CheckHr(GetOleDbCommand()->NextRow(&fMoreRows)); while (fMoreRows) { CheckHr(GetOleDbCommand()->GetColValue(1, reinterpret_cast <BYTE *>(&hobj), isizeof(hobj), &cbSpaceTaken, &fIsNull, 0)); vhvoTexts.Push(hobj); CheckHr(GetOleDbCommand()->NextRow(&fMoreRows)); } } // Note that dbo.fnGetOwnedObjects$() returns the root object as the first row. stuCmd.Format(L"CREATE TABLE #OwnedObjIdsTbl%n" L"(%n" L" ObjId int not null%n" L")%n" L"CREATE CLUSTERED INDEX #OwnedObjIdsTblObjId ON #OwnedObjIdsTbl ([ObjId])%n"); CheckHr(GetOleDbCommand()->ExecCommand(stuCmd.Bstr(), knSqlStmtNoResults)); const OLECHAR * kpszDefTableFmt = L"INSERT INTO #OwnedObjIdsTbl%n" L"SELECT ObjId%n" L"FROM dbo.fnGetOwnedObjects$('%d', null, 0, 0, 1, null, 0)"; if (fFixOwned) { stuCmd.Format(kpszDefTableFmt, m_hvoRoot); stuCmd.FormatAppend(L";%n"); if (vhvoTexts.Size()) { stuCmd.FormatAppend( L"INSERT INTO #OwnedObjIdsTbl%n" L"SELECT ObjId%n" L"FROM dbo.fnGetOwnedObjects$('"); for (int ihvo = 0; ihvo < vhvoTexts.Size(); ++ihvo) if (ihvo == 0) stuCmd.FormatAppend(L"%d", vhvoTexts[ihvo]); else stuCmd.FormatAppend(L",%d", vhvoTexts[ihvo]); stuCmd.FormatAppend(L"', null, 0, 0, 1, null, 0);"); } } else { /* POSSIBLE SPEED ENHANCEMENT -------------------------- SELECT co.Id FROM CmObject co WHERE co.Owner$=1 AND co.OwnFlid$ NOT IN (6001006, 6001014, 6001040) gives a list of ids which own what we want to look through. This could then be handled by the following query, which runs in one-half to one-third of the time required by the current code. SELECT 1 UNION SELECT ObjId FROM dbo.fnGetOwnedObjects$('2,54728', null, 0, 0, 1, null, 0) whether the C++ code or the SQL code should put together the XML string is a good question. */ // REVIEW (SteveMiller): The temp tables below just slow things down. stuCmd.Clear(); if (vhvoOwners.Size() || vhvoTexts.Size()) { stuCmd.FormatAppend(L"CREATE TABLE #tblUnwanted ( ObjId int not null );%n"); stuCmd.FormatAppend(L"INSERT INTO #tblUnwanted%n"); stuCmd.FormatAppend(L"SELECT ObjId%n"); stuCmd.FormatAppend(L"FROM dbo.fnGetOwnedObjects$('"); for (int ihvo = 0; ihvo < vhvoOwners.Size(); ++ihvo) if (ihvo == 0) stuCmd.FormatAppend(L"%d", vhvoOwners[ihvo]); else stuCmd.FormatAppend(L",%d", vhvoOwners[ihvo]); for (int ihvo = 0; ihvo < vhvoTexts.Size(); ++ihvo) if (vhvoOwners.Size() < 0) // I don't think we can have an ownerless text, but hey... stuCmd.FormatAppend(L"%d", vhvoTexts[ihvo]); else stuCmd.FormatAppend(L",%d", vhvoTexts[ihvo]); stuCmd.FormatAppend(L"',null,0,0,1,null,0);%n"); } stuCmd.FormatAppend(kpszDefTableFmt, hvoLangProj); if (vhvoOwners.Size() || vhvoTexts.Size()) { stuCmd.FormatAppend(L"WHERE ObjId NOT IN (SELECT ObjId FROM #tblUnwanted);%n"); stuCmd.FormatAppend(L"DROP TABLE #tblUnwanted"); } stuCmd.FormatAppend(L";%n"); // terminate for Firebird } CheckHr(GetOleDbCommand()->ExecCommand(stuCmd.Bstr(), knSqlStmtNoResults)); // Do the standard stuff for monolingual and multilingual strings. DoAll(kstidChgStylePhaseOne, kstidChgStylePhaseTwo, false, // don't create a new transaction--this method handles it L"#OwnedObjIdsTbl"); // Fix all the StyleRules of instances of StPara. stuMsg.Load(kstidChgStylePhaseThree); m_qprog->put_Message(stuMsg.Bstr()); int nPercent = 0; SetPercentComplete(nPercent); Vector<int> vhobjFix; Vector<Vector<byte> > vvbFmtFix; vhobjFix.Clear(); vvbFmtFix.Clear(); Vector<byte> vbFmt; int cbFmt; ITsPropsFactoryPtr qtpf; qtpf.CreateInstance(CLSID_TsPropsFactory); int cRows; StrUni stuCmdCnt; stuCmdCnt.Format(L"SELECT COUNT(*) FROM StPara a%n" L"JOIN #OwnedObjIdsTbl b on b.ObjId = a.Id%n" L"WHERE a.StyleRules IS NOT NULL", m_hvoRoot); CheckHr(GetOleDbCommand()->ExecCommand(stuCmdCnt.Bstr(), knSqlStmtSelectWithOneRowset)); CheckHr(GetOleDbCommand()->GetRowset(0)); CheckHr(GetOleDbCommand()->NextRow(&fMoreRows)); CheckHr(GetOleDbCommand()->GetColValue(1, reinterpret_cast <BYTE *>(&cRows), isizeof(hobj), &cbSpaceTaken, &fIsNull, 0)); stuCmd.Format(L"SELECT a.Id, a.StyleRules FROM StPara a%n" L"JOIN #OwnedObjIdsTbl b on b.ObjId = a.Id%n" L"WHERE a.StyleRules IS NOT NULL", m_hvoRoot); CheckHr(GetOleDbCommand()->ExecCommand(stuCmd.Bstr(), knSqlStmtSelectWithOneRowset)); CheckHr(GetOleDbCommand()->GetRowset(0)); CheckHr(GetOleDbCommand()->NextRow(&fMoreRows)); int iRow = 0; ComVector<ITsTextProps> vqttp; vqttp.Resize(1); while (fMoreRows) { CheckHr(GetOleDbCommand()->GetColValue(1, reinterpret_cast <BYTE *>(&hobj), isizeof(hobj), &cbSpaceTaken, &fIsNull, 0)); CheckHr(GetOleDbCommand()->GetColValue(2, reinterpret_cast <BYTE *>(vbFmt.Begin()), vbFmt.Size(), &cbSpaceTaken, &fIsNull, 0)); cbFmt = cbSpaceTaken; if (cbFmt >= vbFmt.Size()) { vbFmt.Resize(cbFmt + 1); CheckHr(GetOleDbCommand()->GetColValue(2, reinterpret_cast <BYTE *>(vbFmt.Begin()), vbFmt.Size(), &cbSpaceTaken, &fIsNull, 0)); cbFmt = cbSpaceTaken; } vbFmt.Resize(cbFmt); ITsTextPropsPtr qttp; bool fModify = false; int cb = vbFmt.Size(); CheckHr(qtpf->DeserializePropsRgb(vbFmt.Begin(), &cb, (ITsTextProps **)&qttp)); // use a vector with exactly 1 item vqttp[0] = qttp; // Note: using "Normal" doesn't work all the time. For more complex scenarios where the // default style depends on the context of the deleted style, a replace should be done // instead of a delete. See FwStylesDlg.DeleteAndRenameStylesInDB() in FwStylesDlg.cs. fModify = ProcessFormatting(vqttp, g_pszwStyleNormal); if (fModify) { vhobjFix.Push(hobj); int cbNeeded; HRESULT hr; CheckHr(hr = vqttp[0]->SerializeRgb(vbFmt.Begin(), vbFmt.Size(), &cbNeeded)); if (hr == S_FALSE) { vbFmt.Resize(cbNeeded); hr = vqttp[0]->SerializeRgb(vbFmt.Begin(), vbFmt.Size(), &cbNeeded); } vbFmt.Resize(cbNeeded); vvbFmtFix.Push(vbFmt); } CheckHr(GetOleDbCommand()->NextRow(&fMoreRows)); iRow++; SetPercentComplete((iRow * 50) / cRows); } int ceFix = vhobjFix.Size(); SetPercentComplete(50); for (int ieFix = 0; ieFix < ceFix; ++ieFix) { stuCmd.Format(L"UPDATE StPara SET StyleRules=? WHERE [Id] = %d", vhobjFix[ieFix]); // Set the parameter and execute the command. CheckHr(GetOleDbCommand()->SetParameter(1, DBPARAMFLAGS_ISINPUT, NULL, DBTYPE_BYTES, reinterpret_cast<BYTE *>(vvbFmtFix[ieFix].Begin()), vvbFmtFix[ieFix].Size())); CheckHr(GetOleDbCommand()->ExecCommand(stuCmd.Bstr(), knSqlStmtNoResults)); SetPercentComplete(50 + ((ieFix * 50) / ceFix)); } SetPercentComplete(100); stuCmd.Assign(L"DROP TABLE #OwnedObjIdsTbl"); CheckHr(GetOleDbCommand()->ExecCommand(stuCmd.Bstr(), knSqlStmtNoResults)); // Fix style names in the view specs. // SQL gives us the power to just rename all items in one query. But that // won't correctly handle the situation of, for instance, renaming A to B, B to C, and // C to A. So we fix one item at a time and then update the database. static const GUID clsidNotebook = // {39886581-4DD5-11D4-8078-0000C0FB81B5} { 0x39886581, 0x4DD5, 0x11D4, { 0x80, 0x78, 0x00, 0x00, 0xC0, 0xFB, 0x81, 0xB5 } }; static GUID clsidListEditor = // {5EA62D01-7A78-11D4-8078-0000C0FB81B5} { 0x5EA62D01, 0x7A78, 0x11D4, { 0x80, 0x78, 0x00, 0x00, 0xC0, 0xFB, 0x81, 0xB5 } }; StrUni stuBaseCmd; stuBaseCmd.Format(L"%nFROM UserViewField a%n" L"JOIN UserViewRec_Fields uf on uf.Dst = a.Id%n" L"JOIN UserView_Records ur on ur.Dst = uf.Src%n" L"JOIN UserView uv on uv.Id = ur.Src%n" L"WHERE a.Style IS NOT NULL AND"); if (*m_pclsidApp == clsidNotebook || *m_pclsidApp == clsidListEditor) { Assert(!fFixOwned); stuBaseCmd.FormatAppend(L"%n\tuv.App in ('%g','%g')", &clsidNotebook, &clsidListEditor); } else { Assert(fFixOwned); stuBaseCmd.FormatAppend(L" uv.App = '%g'", m_pclsidApp); } stuMsg.Load(kstidChgStylePhaseFour); m_qprog->put_Message(stuMsg.Bstr()); SetPercentComplete(0); OLECHAR rgch[1024]; vhobjFix.Clear(); Vector<StrUni> vstuFix; stuCmdCnt.Format(L"SELECT COUNT(*)%s", stuBaseCmd.Chars()); CheckHr(GetOleDbCommand()->ExecCommand(stuCmdCnt.Bstr(), knSqlStmtSelectWithOneRowset)); CheckHr(GetOleDbCommand()->GetRowset(0)); CheckHr(GetOleDbCommand()->NextRow(&fMoreRows)); CheckHr(GetOleDbCommand()->GetColValue(1, reinterpret_cast <BYTE *>(&cRows), isizeof(hobj), &cbSpaceTaken, &fIsNull, 0)); stuCmd.Format(L"SELECT a.Id, a.Style%s", stuBaseCmd.Chars()); CheckHr(GetOleDbCommand()->ExecCommand(stuCmd.Bstr(), knSqlStmtSelectWithOneRowset)); CheckHr(GetOleDbCommand()->GetRowset(0)); CheckHr(GetOleDbCommand()->NextRow(&fMoreRows)); iRow = 0; while (fMoreRows) { CheckHr(GetOleDbCommand()->GetColValue(1, reinterpret_cast <BYTE *>(&hobj), isizeof(hobj), &cbSpaceTaken, &fIsNull, 0)); CheckHr(GetOleDbCommand()->GetColValue(2, reinterpret_cast <BYTE *>(*&rgch), isizeof(rgch), &cbSpaceTaken, &fIsNull, 0)); int cchw = cbSpaceTaken / isizeof(OLECHAR); StrUni stuOld(rgch, cchw); StrUni stuNew; if (Delete(stuOld)) { vstuFix.Push(L""); vhobjFix.Push(hobj); } else if (Rename(stuOld, stuNew)) { vstuFix.Push(stuNew); vhobjFix.Push(hobj); } CheckHr(GetOleDbCommand()->NextRow(&fMoreRows)); iRow++; SetPercentComplete((iRow * 50) / cRows); } SetPercentComplete(50); Assert(vhobjFix.Size() == vstuFix.Size()); ceFix = vstuFix.Size(); for (int ieFix = 0; ieFix < ceFix; ieFix++) { if (vstuFix[ieFix] == L"") { stuCmd.Format(L"UPDATE UserViewField SET Style = NULL where [Id] = '%d'", vhobjFix[ieFix]); } else { StrUtil::NormalizeStrUni(vstuFix[ieFix], UNORM_NFD); stuCmd.Format(L"UPDATE UserViewField SET Style = '%s' where [Id] = '%d'", vstuFix[ieFix].Chars(), vhobjFix[ieFix]); } CheckHr(GetOleDbCommand()->ExecCommand(stuCmd.Bstr(), knSqlStmtNoResults)); SetPercentComplete(50 + ((ieFix * 50) / ceFix)); } SetPercentComplete(100); CommitTrans(); Terminate(m_hvoRoot); if (m_qprog) m_qprog->DestroyHwnd(); END_COM_METHOD(g_fact, IID_IFwDbMergeStyles); }