Bug Summary

File:libsynthesis/src/DB_interfaces/odbc_db/odbcapids.cpp
Warning:line 2253, column 5
Value stored to 'linktext' is never read

Annotated Source Code

1/**
2 * @File odbcapids.cpp
3 *
4 * @Author Lukas Zeller (luz@plan44.ch)
5 *
6 * @brief TODBCApiDS
7 * ODBC based datastore API implementation
8 * @Note Currently also contains what will later become TCustomImplDS
9 *
10 * Copyright (c) 2001-2011 by Synthesis AG + plan44.ch
11 *
12 * @Date 2005-09-16 : luz : created from odbcdbdatastore
13 */
14
15#include "prefix_file.h"
16
17#ifdef SQL_SUPPORT1
18
19// includes
20#include "sysync.h"
21#include "odbcapids.h"
22#include "odbcapiagent.h"
23
24
25// sanity check for current implementation - either SQLite or ODBC must be enabled
26#if !defined(ODBCAPI_SUPPORT) && !defined(SQLITE_SUPPORT1)
27 #error "ODBC or SQLite API must be enabled"
28#endif
29
30
31namespace sysync {
32
33// special ID mode names
34const char * const SpecialIDModeNames[numSpecialIDModes] = {
35 "none",
36 "unixmsrnd6"
37};
38
39
40
41#ifdef SCRIPT_SUPPORT1
42
43class TODBCDSfuncs {
44public:
45
46 // ODBC datastore specific script functions
47 // ========================================
48
49 // SETSQLFILTER(string filter)
50 static void func_SetSQLFilter(TItemField *&aTermP, TScriptContext *aFuncContextP)
51 {
52 aFuncContextP->getLocalVar(0)->getAsString(
53 static_cast<TODBCApiDS *>(aFuncContextP->getCallerContext())->fSQLFilter
54 );
55 }; // func_SetSQLFilter
56
57
58 // ADDBYUPDATING(string idtoupdate)
59 static void func_AddByUpdating(TItemField *&aTermP, TScriptContext *aFuncContextP)
60 {
61 TODBCApiDS *dsP = static_cast<TODBCApiDS *>(aFuncContextP->getCallerContext());
62 // only if inserting, convert to updating an existing record
63 if (dsP->fInserting) {
64 string localid;
65 aFuncContextP->getLocalVar(0)->getAsString(localid);
66 aFuncContextP->fParentContextP->fTargetItemP->setLocalID(localid.c_str());
67 // switch off inserting from here on, such that UPDATE SQL will be used
68 dsP->fInserting = false;
69 }
70 }; // func_AddByUpdating
71
72
73 #ifdef SQLITE_SUPPORT1
74 // integer SQLITELASTID()
75 // get ROWID created by last insert
76 static void func_SQLGetLastID(TItemField *&aTermP, TScriptContext *aFuncContextP)
77 {
78 TODBCApiDS *dsP = static_cast<TODBCApiDS *>(aFuncContextP->getCallerContext());
79 if (dsP->fUseSQLite && dsP->fSQLiteP) {
80 aTermP->setAsInteger(
81 sqlite3_last_insert_rowid(dsP->fSQLiteP)
82 );
83 }
84 else {
85 aTermP->unAssign();
86 }
87 }; // func_SQLGetLastID
88 #endif // SQLITE_SUPPORT
89
90}; // TODBCDSfuncs
91
92
93const uInt8 param_OneStr[] = { VAL(fty_string)( (uInt8)fty_string) };
94
95// builtin function table for datastore level
96const TBuiltInFuncDef ODBCDSFuncDefs[] = {
97 { "SETSQLFILTER", TODBCDSfuncs::func_SetSQLFilter, fty_none, 1, param_OneStr },
98 { "ADDBYUPDATING", TODBCDSfuncs::func_AddByUpdating, fty_none, 1, param_OneStr },
99 #ifdef SQLITE_SUPPORT1
100 { "SQLITELASTID", TODBCDSfuncs::func_SQLGetLastID, fty_integer, 0, NULL__null },
101 #endif
102};
103
104
105// chain to generic local datastore funcs
106static void *ODBCDSChainFunc2(void *&aCtx)
107{
108 // context pointer for datastore-level funcs is the datastore
109 // -> no change needed
110 // next table is customimplds's (and then the localdatastore's from there)
111 return (void *)&CustomDSFuncTable2;
112} // ODBCDSChainFunc
113
114const TFuncTable ODBCDSFuncTable2 = {
115 sizeof(ODBCDSFuncDefs) / sizeof(TBuiltInFuncDef), // size of table
116 ODBCDSFuncDefs, // table pointer
117 ODBCDSChainFunc2 // chain generic DB functions
118};
119
120
121
122#endif // SCRIPT_SUPPORT
123
124
125
126// Config
127// ======
128
129TOdbcDSConfig::TOdbcDSConfig(const char* aName, TConfigElement *aParentElement) :
130 inherited(aName,aParentElement)
131{
132 // nop so far
133 clear();
134 // except ensure we have a decent rand()
135 srand((uInt32)time(NULL__null));
136} // TOdbcDSConfig::TOdbcDSConfig
137
138
139TOdbcDSConfig::~TOdbcDSConfig()
140{
141 // nop so far
142} // TOdbcDSConfig::~TOdbcDSConfig
143
144
145// init defaults
146void TOdbcDSConfig::clear(void)
147{
148 // init defaults
149 // - commit modes
150 fCommitItems=false; // commit item changes all at end of write by default
151 // - retries
152 fInsertRetries=0; // none
153 fUpdateRetries=0; // none
154 // - folder key
155 #ifdef HAS_SQL_ADMIN
156 fFolderKeySQL.erase();
157 // - Sync target table
158 fGetSyncTargetSQL.erase();
159 fNewSyncTargetSQL.erase();
160 fUpdateSyncTargetSQL.erase();
161 fDeleteSyncTargetSQL.erase();
162 fSyncTimestamp=true;
163 #ifdef OLD_1_0_5_CONFIG_COMPATIBLE
164 // Compatibility with old config files
165 // - layout of map table
166 fMapTableName.erase();
167 fRemoteIDMapField.erase();
168 fLocalIDMapField.erase();
169 fStringLocalID=true;
170 fTargetKeyMapField.erase();
171 fStringTargetkey=true;
172 // - layout of data table
173 fLocalTableName.erase();
174 // - name of folderkey subselection field in data table, empty if none
175 fFolderKeyField.erase();
176 fStringFolderkey=true;
177 // - name of field which contains Local ID (GUID)
178 fLocalIDField.erase();
179 // - mod date/time
180 fModifiedDateField.erase();
181 fModifiedTimeField.erase();
182 #endif // OLD_1_0_5_CONFIG_COMPATIBLE
183 // Map table access, new version
184 // - SQL to fetch all map entries of a target
185 fMapFetchAllSQL.erase();
186 // - SQL to insert new map entry by localid
187 fMapInsertSQL.erase();
188 // - SQL to update existing map entry by localid
189 fMapUpdateSQL.erase();
190 // - SQL to delete a map entry by localid
191 fMapDeleteSQL.erase();
192 #endif // HAS_SQL_ADMIN
193 // Data table access, new version
194 // - SQL to fetch local ID and timestamp from data table
195 fLocalIDAndTimestampFetchSQL.erase();
196 fModifiedTimestamp=true;
197 // - SQL to fetch actual data fields from data table. SQL result must
198 // contain mapped fields in the same order as they appear in <fieldmap>
199 fDataFetchSQL.erase();
200 // - SQL to insert new data in a record, including modification date
201 fDataInsertSQL.erase();
202 // - SQL to update actual data in a record, including modification date
203 fDataUpdateSQL.erase();
204 // - SQL to delete a record
205 fDataDeleteSQL.erase();
206 // - SQL statement(s) to zap entire sync set
207 fDataZapSQL.erase();
208 // Data table options
209 // - try to translate filters to SQL if possible
210 fFilterOnDBLevel=false; // don't try
211 // - quoting mode for string literals
212 fQuotingMode=qm_duplsingle; // default to what was hard-coded before it became configurable in 2.1.1.5
213 // - Obtaining ID for new records
214 fDetermineNewIDOnce=false;
215 fObtainNewIDAfterInsert=false;
216 fObtainNewLocalIDSql.erase();
217 #ifdef SCRIPT_SUPPORT1
218 fLocalIDScript.erase();
219 #endif
220 fMinNextID=1000000;
221 fSpecialIDMode=sidm_none;
222 fInsertReturnsID=false;
223 fInsertIDAsOutParam=false;
224 fIgnoreAffectedCount=false;
225 fLastModDBFieldType=dbft_timestamp; // default to what we always had before 3.1 engine for ODBC
226 // SQLite support
227 #ifdef SQLITE_SUPPORT1
228 fSQLiteFileName.erase();
229 fSQLiteBusyTimeout=15; // 15 secs
230 #endif
231 // clear inherited
232 inherited::clear();
233} // TOdbcDSConfig::clear
234
235
236// config element parsing
237bool TOdbcDSConfig::localStartElement(const char *aElementName, const char **aAttributes, sInt32 aLine)
238{
239 // commit modes
240 if (strucmp(aElementName,"commititems")==0)
241 expectBool(fCommitItems);
242 // retries
243 else if (strucmp(aElementName,"insertretries")==0)
244 expectInt16(fInsertRetries);
245 else if (strucmp(aElementName,"updateretries")==0)
246 expectInt16(fUpdateRetries);
247 #ifdef HAS_SQL_ADMIN
248 // - folder key
249 else if (strucmp(aElementName,"folderkeysql")==0)
250 expectString(fFolderKeySQL);
251 // - Sync target table handling
252 else if (strucmp(aElementName,"synctargetgetsql")==0)
253 expectString(fGetSyncTargetSQL);
254 else if (strucmp(aElementName,"synctargetnewsql")==0)
255 expectString(fNewSyncTargetSQL);
256 else if (strucmp(aElementName,"synctargetupdatesql")==0)
257 expectString(fUpdateSyncTargetSQL);
258 else if (strucmp(aElementName,"synctargetdeletesql")==0)
259 expectString(fDeleteSyncTargetSQL);
260 else if (strucmp(aElementName,"synctimestamp")==0)
261 expectBool(fSyncTimestamp);
262 #ifdef OLD_1_0_5_CONFIG_COMPATIBLE
263 // Compatibility with old config files
264 // - layout of map table
265 else if (strucmp(aElementName,"maptablename")==0)
266 expectString(fMapTableName);
267 else if (strucmp(aElementName,"remoteidmapfield")==0)
268 expectString(fRemoteIDMapField);
269 else if (strucmp(aElementName,"localidmapfield")==0)
270 expectString(fLocalIDMapField);
271 else if (strucmp(aElementName,"stringlocalid")==0)
272 expectBool(fStringLocalID);
273 else if (strucmp(aElementName,"targetkeymapfield")==0)
274 expectString(fTargetKeyMapField);
275 else if (strucmp(aElementName,"stringtargetkey")==0)
276 expectBool(fStringTargetkey);
277 // - layout of data table
278 else if (strucmp(aElementName,"datatablename")==0) {
279 ReportError(false,"Warning: old-style config - use full SQL statements with %%xx replacement sequences instead");
280 expectString(fLocalTableName);
281 }
282 else if (strucmp(aElementName,"folderkeyfield")==0)
283 expectString(fFolderKeyField);
284 else if (strucmp(aElementName,"stringfolderkey")==0)
285 expectBool(fStringFolderkey);
286 else if (strucmp(aElementName,"localidfield")==0)
287 expectString(fLocalIDField);
288 else if (strucmp(aElementName,"moddatefield")==0)
289 expectString(fModifiedDateField);
290 else if (strucmp(aElementName,"modtimefield")==0)
291 expectString(fModifiedTimeField);
292 // Note: was never documented so will be phased out right now
293 // else if (strucmp(aElementName,"genselectcond")==0)
294 // expectString(fGeneralSelectCondition);
295 #endif // OLD_1_0_5_CONFIG_COMPATIBLE
296 // Map table access, new version
297 else if (strucmp(aElementName,"selectmapallsql")==0)
298 expectString(fMapFetchAllSQL);
299 else if (strucmp(aElementName,"insertmapsql")==0)
300 expectString(fMapInsertSQL);
301 else if (strucmp(aElementName,"updatemapsql")==0)
302 expectString(fMapUpdateSQL);
303 else if (strucmp(aElementName,"deletemapsql")==0)
304 expectString(fMapDeleteSQL);
305 #endif // HAS_SQL_ADMIN
306 // Data table access, new version
307 else if (strucmp(aElementName,"quotingmode")==0)
308 expectEnum(sizeof(fQuotingMode),&fQuotingMode,quotingModeNames,numQuotingModes);
309 else if (strucmp(aElementName,"dbcanfilter")==0)
310 expectBool(fFilterOnDBLevel);
311 else if (strucmp(aElementName,"selectidandmodifiedsql")==0)
312 expectString(fLocalIDAndTimestampFetchSQL);
313 else if (strucmp(aElementName,"modtimestamp")==0)
314 expectBool(fModifiedTimestamp);
315 else if (strucmp(aElementName,"selectdatasql")==0)
316 expectString(fDataFetchSQL);
317 else if (strucmp(aElementName,"insertdatasql")==0)
318 expectString(fDataInsertSQL);
319 else if (strucmp(aElementName,"updatedatasql")==0)
320 expectString(fDataUpdateSQL);
321 else if (strucmp(aElementName,"deletedatasql")==0)
322 expectString(fDataDeleteSQL);
323 else if (strucmp(aElementName,"zapdatasql")==0)
324 expectString(fDataZapSQL);
325 // - Obtaining ID for new records
326 else if (strucmp(aElementName,"determineidonce")==0)
327 expectBool(fDetermineNewIDOnce);
328 else if (strucmp(aElementName,"obtainidafterinsert")==0)
329 expectBool(fObtainNewIDAfterInsert);
330 else if (strucmp(aElementName,"obtainlocalidsql")==0)
331 expectString(fObtainNewLocalIDSql);
332 else if (strucmp(aElementName,"minnextid")==0)
333 expectInt32(fMinNextID);
334 #ifdef SCRIPT_SUPPORT1
335 else if (strucmp(aElementName,"localidscript")==0)
336 expectScript(fLocalIDScript, aLine, getDSFuncTableP());
337 #endif
338 else if (strucmp(aElementName,"specialidmode")==0)
339 expectEnum(sizeof(fSpecialIDMode),&fSpecialIDMode,SpecialIDModeNames,numSpecialIDModes);
340 else if (strucmp(aElementName,"insertreturnsid")==0)
341 expectBool(fInsertReturnsID);
342 else if (strucmp(aElementName,"idasoutparam")==0) // %%% possibly obsolete, replaced by %pkos and %pkoi
343 expectBool(fInsertIDAsOutParam);
344 else if (strucmp(aElementName,"ignoreaffectedcount")==0)
345 expectBool(fIgnoreAffectedCount);
346 else if (strucmp(aElementName,"lastmodfieldtype")==0)
347 expectEnum(sizeof(fLastModDBFieldType),&fLastModDBFieldType,DBFieldTypeNames,numDBfieldTypes);
348 // - SQLite support
349 #ifdef SQLITE_SUPPORT1
350 else if (strucmp(aElementName,"sqlitefile")==0)
351 expectMacroString(fSQLiteFileName);
352 else if (strucmp(aElementName,"sqlitebusytimeout")==0)
353 expectUInt32(fSQLiteBusyTimeout);
354 #endif
355 // - field mappings
356 else if (strucmp(aElementName,"fieldmap")==0) {
357 // check reference argument
358 const char* ref = getAttr(aAttributes,"fieldlist");
359 if (!ref) {
360 ReportError(true,"fieldmap missing 'fieldlist' attribute");
361 }
362 else {
363 // look for field list
364 TMultiFieldDatatypesConfig *mfcfgP =
365 dynamic_cast<TMultiFieldDatatypesConfig *>(getSyncAppBase()->getRootConfig()->fDatatypesConfigP);
366 if (!mfcfgP) throw TConfigParseException("no multifield config");
367 TFieldListConfig *cfgP = mfcfgP->getFieldList(ref);
368 if (!cfgP)
369 return fail("fieldlist '%s' not defined for fieldmap",ref);
370 // - store field list reference in map
371 fFieldMappings.fFieldListP=cfgP;
372 // - let element handle parsing
373 expectChildParsing(fFieldMappings);
374 }
375 }
376 // - none known here
377 else
378 return inherited::localStartElement(aElementName,aAttributes,aLine);
379 // ok
380 return true;
381} // TOdbcDSConfig::localStartElement
382
383
384// resolve
385void TOdbcDSConfig::localResolve(bool aLastPass)
386{
387 if (aLastPass) {
388 #ifndef ODBC_UNICODE
389 // make sure we don't try to use UTF-16
390 if (fDataCharSet==chs_utf16)
391 SYSYNC_THROW(TConfigParseException("UTF-16 wide character set can only be used on ODBC-Unicode enabled platforms"))throw TConfigParseException("UTF-16 wide character set can only be used on ODBC-Unicode enabled platforms"
)
;
392 #endif
393 #ifdef SQLITE_SUPPORT1
394 // %%% NOP at this time
395 #endif
396 #ifdef OLD_1_0_5_CONFIG_COMPATIBLE
397 // generate new settings from old ones
398 if (fLocalIDAndTimestampFetchSQL.empty()) {
399 // SELECT localid,modifieddate(,modifiedtime)...
400 fLocalIDAndTimestampFetchSQL="SELECT ";
401 fLocalIDAndTimestampFetchSQL+=fLocalIDField;
402 fLocalIDAndTimestampFetchSQL+=", ";
403 fLocalIDAndTimestampFetchSQL += fModifiedDateField;
404 if (!fModifiedTimestamp && !fModifiedTimeField.empty()) {
405 fLocalIDAndTimestampFetchSQL += ", ";
406 fLocalIDAndTimestampFetchSQL += fModifiedTimeField;
407 }
408 // ...FROM datatable
409 fLocalIDAndTimestampFetchSQL+=" FROM ";
410 fLocalIDAndTimestampFetchSQL+=fLocalTableName;
411 // ...WHERE folderkey=x...
412 fLocalIDAndTimestampFetchSQL+=" WHERE ";
413 fLocalIDAndTimestampFetchSQL+=fFolderKeyField;
414 fLocalIDAndTimestampFetchSQL+='=';
415 quoteStringAppend("%f",fLocalIDAndTimestampFetchSQL,fStringFolderkey ? qm_backslash : qm_none);
416 // ...AND filterclause
417 fLocalIDAndTimestampFetchSQL+="%AF";
418 PDEBUGPRINTFX(DBG_EXOTIC,(" <selectidandmodifiedsql>%s</selectidandmodifiedsql>",fLocalIDAndTimestampFetchSQL.c_str())){ if (((0x80000000) & getDbgMask()) == (0x80000000)) getDbgLogger
()->setNextMask(0x80000000).DebugPrintfLastMask (" <selectidandmodifiedsql>%s</selectidandmodifiedsql>"
,fLocalIDAndTimestampFetchSQL.c_str()); }
;
419 }
420 if (fDataFetchSQL.empty()) {
421 // SELECT %N FROM datatable WHERE localid=%k AND folderkey=%f
422 fDataFetchSQL="SELECT %N FROM ";
423 fDataFetchSQL+=fLocalTableName;
424 fDataFetchSQL+=" WHERE ";
425 fDataFetchSQL+=fLocalIDField;
426 fDataFetchSQL+='=';
427 quoteStringAppend("%k",fDataFetchSQL,fStringLocalID ? qm_backslash : qm_none);
428 fDataFetchSQL+=" AND ";
429 fDataFetchSQL+=fFolderKeyField;
430 fDataFetchSQL+='=';
431 quoteStringAppend("%f",fDataFetchSQL,fStringFolderkey ? qm_backslash : qm_none);
432 PDEBUGPRINTFX(DBG_EXOTIC,(" <selectdatasql>%s</selectdatasql>",fDataFetchSQL.c_str())){ if (((0x80000000) & getDbgMask()) == (0x80000000)) getDbgLogger
()->setNextMask(0x80000000).DebugPrintfLastMask (" <selectdatasql>%s</selectdatasql>"
,fDataFetchSQL.c_str()); }
;
433 }
434 if (fDataInsertSQL.empty()) {
435 // INSERT INTO datatable (keyfield,modtimestamp,folderkey,%N) VALUES (%k,%M,%f,%v)
436 fDataInsertSQL="INSERT INTO ";
437 fDataInsertSQL+=fLocalTableName;
438 fDataInsertSQL+=" (";
439 if (fDetermineNewIDOnce || !fObtainNewIDAfterInsert) {
440 // insert key only if previously generated separately
441 fDataInsertSQL+=fLocalIDField;
442 fDataInsertSQL+=", ";
443 }
444 fDataInsertSQL += fModifiedDateField;
445 if (!fModifiedTimestamp && !fModifiedTimeField.empty()) {
446 fDataInsertSQL += ", ";
447 fDataInsertSQL += fModifiedTimeField;
448 }
449 if (!fFolderKeyField.empty()) {
450 fDataInsertSQL += ", ";
451 fDataInsertSQL+=fFolderKeyField;
452 }
453 fDataInsertSQL+=", %N) VALUES (";
454 if (fDetermineNewIDOnce || !fObtainNewIDAfterInsert) {
455 // insert key only if previously generated separately
456 quoteStringAppend("%k",fDataInsertSQL,fStringLocalID ? qm_backslash : qm_none);
457 fDataInsertSQL += ", ";
458 }
459 if (!fModifiedTimestamp) {
460 fDataInsertSQL += "%dM";
461 if (!fModifiedTimeField.empty()) fDataInsertSQL += ", %tM";
462 }
463 else {
464 fDataInsertSQL += "%M";
465 }
466 if (!fFolderKeyField.empty()) {
467 fDataInsertSQL += ", %f";
468 }
469 fDataInsertSQL+=", %v)";
470 PDEBUGPRINTFX(DBG_EXOTIC,(" <insertdatasql>%s</insertdatasql>",fDataInsertSQL.c_str())){ if (((0x80000000) & getDbgMask()) == (0x80000000)) getDbgLogger
()->setNextMask(0x80000000).DebugPrintfLastMask (" <insertdatasql>%s</insertdatasql>"
,fDataInsertSQL.c_str()); }
;
471 }
472 if (fDataUpdateSQL.empty()) {
473 // UPDATE datatable SET modtimestamp=%M, %V WHERE localid=%k
474 fDataUpdateSQL="UPDATE ";
475 fDataUpdateSQL+=fLocalTableName;
476 fDataUpdateSQL+=" SET ";
477 if (!fModifiedTimestamp) {
478 fDataUpdateSQL += fModifiedDateField;
479 fDataUpdateSQL += "=%dM";
480 if (!fModifiedTimeField.empty()) {
481 fDataUpdateSQL += ", ";
482 fDataUpdateSQL += fModifiedTimeField;
483 fDataUpdateSQL += "=%tM";
484 }
485 }
486 else {
487 fDataUpdateSQL += fModifiedDateField;
488 fDataUpdateSQL += "=%M";
489 }
490 fDataUpdateSQL += ", %V WHERE ";
491 fDataUpdateSQL+=fLocalIDField;
492 fDataUpdateSQL += "=";
493 quoteStringAppend("%k",fDataUpdateSQL,fStringLocalID ? qm_backslash : qm_none);
494 PDEBUGPRINTFX(DBG_EXOTIC,(" <updatedatasql>%s</updatedatasql>",fDataUpdateSQL.c_str())){ if (((0x80000000) & getDbgMask()) == (0x80000000)) getDbgLogger
()->setNextMask(0x80000000).DebugPrintfLastMask (" <updatedatasql>%s</updatedatasql>"
,fDataUpdateSQL.c_str()); }
;
495 }
496 if (fDataDeleteSQL.empty()) {
497 // DELETE FROM datatable WHERE localid=%k
498 fDataDeleteSQL="DELETE FROM ";
499 fDataDeleteSQL+=fLocalTableName;
500 fDataDeleteSQL+=" WHERE ";
501 fDataDeleteSQL+=fLocalIDField;
502 fDataDeleteSQL+="=";
503 quoteStringAppend("%k",fDataDeleteSQL,fStringLocalID ? qm_backslash : qm_none);
504 PDEBUGPRINTFX(DBG_EXOTIC,(" <deletedatasql>%s</deletedatasql>",fDataDeleteSQL.c_str())){ if (((0x80000000) & getDbgMask()) == (0x80000000)) getDbgLogger
()->setNextMask(0x80000000).DebugPrintfLastMask (" <deletedatasql>%s</deletedatasql>"
,fDataDeleteSQL.c_str()); }
;
505 }
506 if (fDataZapSQL.empty() && !fLocalTableName.empty()) {
507 // DELETE FROM datatable
508 fDataZapSQL="DELETE FROM ";
509 fDataZapSQL+=fLocalTableName;
510 // ...WHERE folderkey=x...
511 fDataZapSQL+=" WHERE ";
512 fDataZapSQL+=fFolderKeyField;
513 fDataZapSQL+='=';
514 quoteStringAppend("%f",fDataZapSQL,fStringFolderkey ? qm_backslash : qm_none);
515 // ...AND filterclause
516 fDataZapSQL+="%AF";
517 PDEBUGPRINTFX(DBG_EXOTIC,(" <zapdatasql>%s</zapdatasql>",fDataZapSQL.c_str())){ if (((0x80000000) & getDbgMask()) == (0x80000000)) getDbgLogger
()->setNextMask(0x80000000).DebugPrintfLastMask (" <zapdatasql>%s</zapdatasql>"
,fDataZapSQL.c_str()); }
;
518 }
519 // Map table
520 if (fMapFetchAllSQL.empty()) {
521 // SELECT localid,remoteid FROM maptable WHERE targetkey=%t
522 fMapFetchAllSQL="SELECT ";
523 fMapFetchAllSQL+=fLocalIDMapField;
524 fMapFetchAllSQL+=", ";
525 fMapFetchAllSQL+=fRemoteIDMapField;
526 fMapFetchAllSQL+=" FROM ";
527 fMapFetchAllSQL+=fMapTableName;
528 fMapFetchAllSQL+=" WHERE ";
529 fMapFetchAllSQL+=fTargetKeyMapField;
530 fMapFetchAllSQL+="=";
531 quoteStringAppend("%t",fMapFetchAllSQL,fStringTargetkey ? qm_backslash : qm_none);
532 PDEBUGPRINTFX(DBG_EXOTIC,(" <selectmapallsql>%s</selectmapallsql>",fMapFetchAllSQL.c_str())){ if (((0x80000000) & getDbgMask()) == (0x80000000)) getDbgLogger
()->setNextMask(0x80000000).DebugPrintfLastMask (" <selectmapallsql>%s</selectmapallsql>"
,fMapFetchAllSQL.c_str()); }
;
533 }
534 if (fMapInsertSQL.empty()) {
535 // INSERT INTO maptable (targetkey,localid,remoteid) VALUES (%t,%k,%r)
536 fMapInsertSQL="INSERT INTO ";
537 fMapInsertSQL+=fMapTableName;
538 fMapInsertSQL+=" (";
539 fMapInsertSQL+=fTargetKeyMapField;
540 fMapInsertSQL+=", ";
541 fMapInsertSQL+=fLocalIDMapField;
542 fMapInsertSQL+=", ";
543 fMapInsertSQL+=fRemoteIDMapField;
544 fMapInsertSQL+=") VALUES (";
545 quoteStringAppend("%t",fMapInsertSQL,fStringTargetkey ? qm_backslash : qm_none);
546 fMapInsertSQL+=", ";
547 quoteStringAppend("%k",fMapInsertSQL,fStringLocalID ? qm_backslash : qm_none);
548 fMapInsertSQL+=", '%r')";
549 PDEBUGPRINTFX(DBG_EXOTIC,(" <insertmapsql>%s</insertmapsql>",fMapInsertSQL.c_str())){ if (((0x80000000) & getDbgMask()) == (0x80000000)) getDbgLogger
()->setNextMask(0x80000000).DebugPrintfLastMask (" <insertmapsql>%s</insertmapsql>"
,fMapInsertSQL.c_str()); }
;
550 }
551 if (fMapUpdateSQL.empty()) {
552 // UPDATE maptable SET remoteid=%r WHERE targetkey=%t AND localid=%k
553 fMapUpdateSQL="UPDATE ";
554 fMapUpdateSQL+=fMapTableName;
555 fMapUpdateSQL+=" SET ";
556 fMapUpdateSQL+=fRemoteIDMapField;
557 fMapUpdateSQL+="='%r' WHERE "; // is always a string
558 fMapUpdateSQL+=fTargetKeyMapField;
559 fMapUpdateSQL+="=";
560 quoteStringAppend("%t",fMapUpdateSQL,fStringTargetkey ? qm_backslash : qm_none);
561 fMapUpdateSQL+=" AND ";
562 fMapUpdateSQL+=fLocalIDMapField;
563 fMapUpdateSQL+="=";
564 quoteStringAppend("%k",fMapUpdateSQL,fStringLocalID ? qm_backslash : qm_none);
565 PDEBUGPRINTFX(DBG_EXOTIC,(" <updatemapsql>%s</updatemapsql>",fMapUpdateSQL.c_str())){ if (((0x80000000) & getDbgMask()) == (0x80000000)) getDbgLogger
()->setNextMask(0x80000000).DebugPrintfLastMask (" <updatemapsql>%s</updatemapsql>"
,fMapUpdateSQL.c_str()); }
;
566 }
567 if (fMapDeleteSQL.empty()) {
568 // DELETE FROM maptable WHERE targetkey=%t AND localid=%k
569 fMapDeleteSQL="DELETE FROM ";
570 fMapDeleteSQL+=fMapTableName;
571 fMapDeleteSQL+=" WHERE ";
572 fMapDeleteSQL+=fTargetKeyMapField;
573 fMapDeleteSQL+="=";
574 quoteStringAppend("%t",fMapDeleteSQL,fStringTargetkey ? qm_backslash : qm_none);
575 // up to here, this is a DELETE all for one target, so we
576 // can use this to extend fDeleteSyncTargetSQL
577 fDeleteSyncTargetSQL+=" %GO ";
578 fDeleteSyncTargetSQL+=fMapDeleteSQL;
579 // now add clause for single item
580 fMapDeleteSQL+=" AND ";
581 fMapDeleteSQL+=fLocalIDMapField;
582 fMapDeleteSQL+="=";
583 quoteStringAppend("%k",fMapDeleteSQL,fStringLocalID ? qm_backslash : qm_none);
584 PDEBUGPRINTFX(DBG_EXOTIC,(" <deletemapsql>%s</deletemapsql>",fMapDeleteSQL.c_str())){ if (((0x80000000) & getDbgMask()) == (0x80000000)) getDbgLogger
()->setNextMask(0x80000000).DebugPrintfLastMask (" <deletemapsql>%s</deletemapsql>"
,fMapDeleteSQL.c_str()); }
;
585 }
586 #endif
587 }
588 // resolve inherited
589 inherited::localResolve(aLastPass);
590} // TOdbcDSConfig::localResolve
591
592
593#ifdef SCRIPT_SUPPORT1
594void TOdbcDSConfig::apiResolveScripts(void)
595{
596 // resolve scripts which are API specific (in SAME ORDER AS IN apiRebuildScriptContexts()!)
597 TScriptContext::resolveScript(getSyncAppBase(),fLocalIDScript,fResolveContextP,NULL__null);
598}
599#endif
600
601
602// - create appropriate datastore from config, calls addTypeSupport as well
603TLocalEngineDS *TOdbcDSConfig::newLocalDataStore(TSyncSession *aSessionP)
604{
605 // Synccap defaults to normal set supported by the engine by default
606 TLocalEngineDS *ldsP;
607 if (IS_CLIENT(!getSyncAppBase()->isServer())) {
608 ldsP = new TODBCApiDS(this,aSessionP,getName(),aSessionP->getSyncCapMask() & ~(isOneWayFromRemoteSupported() ? 0 : SCAP_MASK_ONEWAY_SERVER0x0020));
609 }
610 else {
611 ldsP = new TODBCApiDS(this,aSessionP,getName(),aSessionP->getSyncCapMask() & ~(isOneWayFromRemoteSupported() ? 0 : SCAP_MASK_ONEWAY_CLIENT0x0008));
612 }
613 // do common stuff
614 addTypes(ldsP,aSessionP);
615 // return
616 return ldsP;
617} // TOdbcDSConfig::newLocalDataStore
618
619
620
621// Field Map item
622// ==============
623
624TODBCFieldMapItem::TODBCFieldMapItem(const char *aElementName, TConfigElement *aParentElement) :
625 inherited(aElementName,aParentElement)
626{
627 #ifdef STREAMFIELD_SUPPORT1
628 fReadBlobSQL.erase();
629 fKeyFieldName.erase();
630 #endif
631} // TODBCFieldMapItem::TODBCFieldMapItem
632
633
634void TODBCFieldMapItem::checkAttrs(const char **aAttributes)
635{
636 #ifdef STREAMFIELD_SUPPORT1
637 AssignString(fReadBlobSQL,getAttr(aAttributes,"readblobsql"));
638 AssignString(fKeyFieldName,getAttr(aAttributes,"keyfield"));
639 #endif
640 inherited::checkAttrs(aAttributes);
641} // TODBCFieldMapItem::checkAttrs
642
643
644
645#ifdef ARRAYDBTABLES_SUPPORT1
646
647// array container
648// ===============
649
650TODBCFieldMapArrayItem::TODBCFieldMapArrayItem(TCustomDSConfig *aCustomDSConfigP, TConfigElement *aParentElement) :
651 inherited(aCustomDSConfigP,aParentElement)
652{
653 clear();
654} // TODBCFieldMapArrayItem::TODBCFieldMapArrayItem
655
656
657TODBCFieldMapArrayItem::~TODBCFieldMapArrayItem()
658{
659 // nop so far
660 clear();
661} // TODBCFieldMapArrayItem::~TODBCFieldMapArrayItem
662
663
664// init defaults
665void TODBCFieldMapArrayItem::clear(void)
666{
667 // init defaults
668 // - clear values
669 fSelectArraySQL.erase();
670 fInsertElementSQL.erase();
671 fDeleteArraySQL.erase();
672 fAlwaysCleanArray=false;
673 // clear inherited
674 inherited::clear();
675} // TODBCFieldMapArrayItem::clear
676
677
678// config element parsing
679bool TODBCFieldMapArrayItem::localStartElement(const char *aElementName, const char **aAttributes, sInt32 aLine)
680{
681 if (strucmp(aElementName,"selectarraysql")==0)
682 expectString(fSelectArraySQL);
683 else if (strucmp(aElementName,"deletearraysql")==0)
684 expectString(fDeleteArraySQL);
685 else if (strucmp(aElementName,"insertelementsql")==0)
686 expectString(fInsertElementSQL);
687 else if (strucmp(aElementName,"alwaysclean")==0)
688 expectBool(fAlwaysCleanArray);
689 // - none known here
690 else
691 return inherited::localStartElement(aElementName,aAttributes,aLine);
692 // ok
693 return true;
694} // TODBCFieldMapArrayItem::localStartElement
695
696
697// resolve
698void TODBCFieldMapArrayItem::localResolve(bool aLastPass)
699{
700 if (aLastPass) {
701 // check for required settings
702 // %%% tbd
703 }
704 // resolve inherited
705 inherited::localResolve(aLastPass);
706} // TODBCFieldMapArrayItem::localResolve
707
708#endif // ARRAYDBTABLES_SUPPORT
709
710
711/*
712 * Implementation of TODBCApiDS
713 */
714
715
716// constructor
717TODBCApiDS::TODBCApiDS(
718 TOdbcDSConfig *aConfigP,
719 sysync::TSyncSession *aSessionP,
720 const char *aName,
721 uInt32 aCommonSyncCapMask
722) :
723 inherited(aConfigP,aSessionP, aName, aCommonSyncCapMask)
724 #ifdef ODBCAPI_SUPPORT
725 ,fODBCConnectionHandle(SQL_NULL_HANDLE0) // no connection yet
726 ,fODBCReadStatement(SQL_NULL_HANDLE0) // no active statements either
727 ,fODBCWriteStatement(SQL_NULL_HANDLE0)
728 #ifdef SCRIPT_SUPPORT1
729 ,fODBCScriptStatement(SQL_NULL_HANDLE0)
730 #endif
731 #endif // ODBCAPI_SUPPORT
732 #ifdef SQLITE_SUPPORT1
733 ,fSQLiteP(NULL__null)
734 ,fSQLiteStmtP(NULL__null)
735 ,fStepRc(SQLITE_OK0)
736 #endif
737{
738 // save pointer to config record
739 fConfigP=aConfigP;
740 // make a local copy of the typed agent pointer
741 fAgentP=static_cast<TODBCApiAgent *>(fSessionP);
742 // make a local copy of the typed agent config pointer
743 fAgentConfigP = dynamic_cast<TOdbcAgentConfig *>(
744 aSessionP->getRootConfig()->fAgentConfigP
745 );
746 if (!fAgentConfigP) throw TSyncException(DEBUGTEXT("TODBCApiDS finds no AgentConfig","odds7")"TODBCApiDS finds no AgentConfig");
747 // init other stuff
748 fODBCAdminData=false; // not known yet if we'll get admin data (target, map) from ODBC or not
749 // - SQLite support
750 #ifdef SQLITE_SUPPORT1
751 fUseSQLite = !fConfigP->fSQLiteFileName.empty(); // if we have a SQLite file name, use it instead of ODBC
752 #endif
753 // clear rest
754 InternalResetDataStore();
755} // TODBCApiDS::TODBCApiDS
756
757
758TODBCApiDS::~TODBCApiDS()
759{
760 InternalResetDataStore();
761} // TODBCApiDS::~TODBCApiDS
762
763
764/// @brief called while agent is still fully ok, so we must clean up such that later call of destructor does NOT access agent any more
765void TODBCApiDS::announceAgentDestruction(void)
766{
767 // reset myself
768 InternalResetDataStore();
769 // make sure we don't access the agent any more
770 engTerminateDatastore();
771 fAgentP = NULL__null;
772 // call inherited
773 inherited::announceAgentDestruction();
774} // TODBCApiDS::announceAgentDestruction
775
776
777/// @brief called to reset datastore
778/// @note must be safe to be called multiple times and even after announceAgentDestruction()
779void TODBCApiDS::InternalResetDataStore(void)
780{
781 #ifdef OBJECT_FILTERING1
782 fFilterExpressionTested=false; // not tested yet
783 fFilterWorksOnDBLevel=true; // but assume it works
784 #endif
785 #ifdef SCRIPT_SUPPORT1
786 // no SQL filter in place yet
787 fSQLFilter.erase();
788 #endif
789 #ifdef SQLITE_SUPPORT1
790 // clean up SQLite
791 if (fSQLiteP) {
792 // stop it in case something is still running
793 sqlite3_interrupt(fSQLiteP);
794 // if we have a statement, finalize it
795 if (fSQLiteStmtP) {
796 // discard it
797 sqlite3_finalize(fSQLiteStmtP);
798 fSQLiteStmtP=NULL__null;
799 }
800 int sqrc = sqlite3_close(fSQLiteP);
801 if (sqrc!=SQLITE_OK0) {
802 PDEBUGPRINTFX(DBG_ERROR,("Error closing SQLite data file: sqlite3_close() returns %d",sqrc)){ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ("Error closing SQLite data file: sqlite3_close() returns %d"
,sqrc); }
;
803 }
804 else {
805 PDEBUGPRINTFX(DBG_DBAPI,("Closed SQLite data file")){ if (((0x02000000) & getDbgMask()) == (0x02000000)) getDbgLogger
()->setNextMask(0x02000000).DebugPrintfLastMask ("Closed SQLite data file"
); }
;
806 }
807 fSQLiteP=NULL__null;
808 }
809 #endif
810 // Clear map table and sync set lists
811 if (fAgentP) {
812 #ifdef ODBCAPI_SUPPORT
813 // no active statements any more
814 if (fODBCReadStatement) {
815 SafeSQLFreeHandle(SQL_HANDLE_STMT,fODBCReadStatement);
816 fODBCReadStatement=SQL_NULL_HANDLE0;
817 }
818 if (fODBCWriteStatement) {
819 SafeSQLFreeHandle(SQL_HANDLE_STMT,fODBCWriteStatement);
820 fODBCWriteStatement=SQL_NULL_HANDLE0;
821 }
822 // make sure connection is closed (and open transaction rolled back)
823 fAgentP->closeODBCConnection(fODBCConnectionHandle);
824 #endif // ODBCAPI_SUPPORT
825 // clear parameter maps
826 resetSQLParameterMaps();
827 }
828} // TODBCApiDS::InternalResetDataStore
829
830
831#ifdef SCRIPT_SUPPORT1
832// called to rebuild script context for API level scripts in the datastore context
833// Note: rebuild order must be SAME AS resolve order in apiResolveScripts()
834void TODBCApiDS::apiRebuildScriptContexts(void)
835{
836 // local ID generation script
837 TScriptContext::rebuildContext(fSessionP->getSyncAppBase(),fConfigP->fLocalIDScript,fScriptContextP,fSessionP);
838} // TODBCApiDS::apiRebuildScriptContexts
839#endif
840
841
842#ifdef ODBCAPI_SUPPORT
843
844// - get (DB level) ODBC handle
845SQLHDBC TODBCApiDS::getODBCConnectionHandle(void)
846{
847 if (fODBCConnectionHandle==SQL_NULL_HANDLE0 && fAgentP) {
848 // get a handle
849 PDEBUGPRINTFX(DBG_DBAPI+DBG_EXOTIC,("Datastore %s does not own a DB connection yet -> pulling connection from session level",getName())){ if (((0x02000000 +0x80000000) & getDbgMask()) == (0x02000000
+0x80000000)) getDbgLogger()->setNextMask(0x02000000 +0x80000000
).DebugPrintfLastMask ("Datastore %s does not own a DB connection yet -> pulling connection from session level"
,getName()); }
;
850 fODBCConnectionHandle = fAgentP->pullODBCConnectionHandle();
851 }
852 PDEBUGPRINTFX(DBG_DBAPI+DBG_EXOTIC,("Datastore %s: using connection handle 0x%lX",getName(),(uIntArch)fODBCConnectionHandle)){ if (((0x02000000 +0x80000000) & getDbgMask()) == (0x02000000
+0x80000000)) getDbgLogger()->setNextMask(0x02000000 +0x80000000
).DebugPrintfLastMask ("Datastore %s: using connection handle 0x%lX"
,getName(),(uIntArch)fODBCConnectionHandle); }
;
853 return fODBCConnectionHandle;
854} // TODBCApiDS::getODBCConnectionHandle
855
856
857// - check for connection-level error
858void TODBCApiDS::checkConnectionError(SQLRETURNlong aResult)
859{
860 if (!fAgentP) return;
861 if (fODBCConnectionHandle!=SQL_NULL_HANDLE0) {
862 // check on local connection
863 fAgentP->checkODBCError(aResult,SQL_HANDLE_DBC,fODBCConnectionHandle);
864 }
865 // check on session level
866 fAgentP->checkConnectionError(aResult);
867} // TODBCApiDS::checkConnectionError
868
869#endif // ODBCAPI_SUPPORT
870
871
872// helper for mapping field names
873bool TODBCApiDS::addFieldNameList(string &aSQL, bool aForWrite, bool aForUpdate, bool aAssignedOnly, TMultiFieldItem *aItemP, TFieldMapList &fml, uInt16 aSetNo, char aFirstSep)
874{
875 TFieldMapList::iterator pos;
876 TODBCFieldMapItem *fmiP;
877
878 bool doit;
879 bool allfields=true; // all fields listed (no array fields)
880
881 for (pos=fml.begin(); pos!=fml.end(); pos++) {
882 fmiP = (TODBCFieldMapItem *)*pos;
883 if (!fmiP->isArray()) {
884 doit=
885 aSetNo==fmiP->setNo &&
886 (aForWrite
887 ? (fmiP->writable && ((aForUpdate && fmiP->for_update) || (!aForUpdate && fmiP->for_insert)))
888 : fmiP->readable
889 #ifdef STREAMFIELD_SUPPORT1
890 && (fmiP->fReadBlobSQL.empty() || !fmiP->fKeyFieldName.empty()) // only read fields here that don't use a proxy or read a key FOR the proxy
891 #endif
892
893 );
894 if (doit && aAssignedOnly && aForWrite && aItemP) {
895 // check base field is assigned
896 // This should cover all cases (remember that MimeDirItem assigns empty values
897 // to all "available" fields before parsing data)
898 // - array fields are "assigned" if they have an element assigned or
899 // been called assignEmpty()
900 // - field blocks addressed by repeating properties will either be assigned all
901 // or not at all, so we can safely check base field ID only
902 // - negative FIDs except VARIDX_UNDEFINED address local vars, which
903 // should be assigned only if they may also be written
904 TItemField *fieldP=getMappedFieldOrVar(*aItemP,fmiP->fid,0,true); // existing only
905 if (!fieldP || !(fieldP->isAssigned())) doit=false; // avoid writing unassigned or non-existing (array) fields
906 }
907 if (doit) {
908 // add comma
909 if (aFirstSep) { aSQL+=aFirstSep; aSQL+=' '; }
910 aFirstSep=','; // further names are always comma separated
911 #ifdef STREAMFIELD_SUPPORT1
912 if (fmiP->readable && !aForWrite && !fmiP->fKeyFieldName.empty()) {
913 // the actual field contents will be read via proxy, but we read the record key here instead
914 aSQL+=fmiP->fKeyFieldName;
915 }
916 else
917 #endif
918 {
919 // just add field name
920 aSQL+=fmiP->fElementName;
921 }
922 }
923 }
924 else
925 allfields=false; // at least one array field
926 }
927 return allfields;
928} // TODBCApiDS::addFieldNameList
929
930
931// helper for mapping field values
932// - returns aNoData=true if there is no field (e.g. array index too high) for any of the values
933// - returns false if not all fields could be listed (i.e. there are array fields)
934bool TODBCApiDS::addFieldNameValueList(string &aSQL, bool aAssignedOnly, bool &aNoData, bool &aAllEmpty, TMultiFieldItem &aItem ,bool aForUpdate, TFieldMapList &fml, uInt16 aSetNo, sInt16 aRepOffset, char aFirstSep)
935{
936 TFieldMapList::iterator pos;
937 TODBCFieldMapItem *fmiP;
938 bool allfields=true;
939 bool doit;
940
941 aNoData = true; // assume no data
942 aAllEmpty = true; // assume empty fields only
943 for (pos=fml.begin(); pos!=fml.end(); pos++) {
944 fmiP = (TODBCFieldMapItem *)*pos;
945 if (fmiP->isArray()) {
946 allfields=false; // at least one array field
947 }
948 else if (aSetNo==fmiP->setNo && fmiP->writable && ((aForUpdate && fmiP->for_update) || (!aForUpdate && fmiP->for_insert)) ) {
949 doit=true;
950 if (aAssignedOnly) {
951 // check base field is assigned (see notes in addFieldNameList())
952 TItemField *fieldP=getMappedFieldOrVar(aItem,fmiP->fid,0,true); // get existing fields only (for arrays)
953 if (!fieldP || !(fieldP->isAssigned())) doit=false; // avoid writing unassigned fields
954 }
955 if (doit) {
956 // add comma
957 if (aFirstSep) { aSQL+=aFirstSep; aSQL+=' '; }
958 aFirstSep=','; // further names are always comma separated
959 // add field name and equal sign if for update
960 if (aForUpdate) {
961 // add field name
962 aSQL+=fmiP->fElementName;
963 aSQL+='=';
964 }
965 // check how to add field value
966 if (fmiP->as_param || fmiP->dbfieldtype==dbft_blob) {
967 // either explicitly marked as param or BLOB: pass value as in-param
968 addSQLParameterMap(true,false,param_field,fmiP,&aItem,aRepOffset); // in-param
969 // add parameter placeholder into SQL
970 aSQL+='?';
971 }
972 else {
973 // add field value as literal
974 // - get base field
975 sInt16 fid=fmiP->fid;
976 if (fid==VARIDX_UNDEFINED-128) return false; // field does not exist
977 #ifndef SCRIPT_SUPPORT1
978 // check index before using it (should not be required, as map indices are resolved
979 if (!aItem.getItemType()->isFieldIndexValid(fid)) return false; // field does not exist
980 #endif
981 if (appendFieldsLiteral(aItem,fid,aRepOffset,*fmiP,aSQL)) aAllEmpty=false;
982 }
983 }
984 }
985 }
986 aNoData=false; // data for all non-array fields found
987 return allfields;
988} // TODBCApiDS::addFieldNameValueList
989
990
991// helper for filling ODBC results into mapped fields
992// - if mapped field is an array, aRepOffset will be used as array index
993// - if mapped field is not an array, aRepOffset will be added to the base fid
994void TODBCApiDS::fillFieldsFromSQLResult(SQLHSTMTlong aStatement, sInt16 &aColIndex, TMultiFieldItem &aItem, TFieldMapList &fml, uInt16 aSetNo, sInt16 aRepOffset)
995{
996 sInt16 fid;
997 TFieldMapList::iterator pos;
998 TODBCFieldMapItem *fmiP;
999 bool notnull=false; // default to empty
1000
1001 // get data for all mapped fields
1002 for (pos=fml.begin(); pos!=fml.end(); pos++) {
1003 fmiP = (TODBCFieldMapItem *)*pos;
1004 try {
1005 if (
1006 !fmiP->isArray() &&
1007 aSetNo==fmiP->setNo &&
1008 fmiP->readable
1009 ) {
1010 // was specified in SELECT, so we can read it
1011 if ((fid=fmiP->fid)!=VARIDX_UNDEFINED-128) {
1012 TItemField *fieldP;
1013 TDBFieldType dbfty = fmiP->dbfieldtype;
1014 // single field mapping
1015 fieldP=getMappedFieldOrVar(aItem,fid,aRepOffset);
1016 if (fieldP) {
1017 #ifdef STREAMFIELD_SUPPORT1
1018 if (!fmiP->fReadBlobSQL.empty()) {
1019 // retrieve key for this field if map specifies a "keyfield"
1020 string key;
1021 #ifdef SQLITE_SUPPORT1
1022 // - always for data access
1023 if (fUseSQLite) {
1024 if (!fmiP->fKeyFieldName.empty()) {
1025 key = (const char *)sqlite3_column_text(fSQLiteStmtP,aColIndex-1);
1026 // read something, next column
1027 aColIndex++;
1028 }
1029 }
1030 else
1031 #endif
1032 {
1033 #ifdef ODBCAPI_SUPPORT
1034 if (!fmiP->fKeyFieldName.empty()) {
1035 fAgentP->getColumnValueAsString(aStatement,aColIndex,key,chs_ascii);
1036 // read something, next column
1037 aColIndex++;
1038 }
1039 #endif // ODBCAPI_SUPPORT
1040 }
1041 // use proxy for this field
1042 if (!fieldP->isBasedOn(fty_string)) {
1043 throw TSyncException("<readblobsql> allowed for string or BLOB fields only!");
1044 }
1045 // create and install a proxy for this field
1046 PDEBUGPRINTFX(DBG_DBAPI+DBG_EXOTIC,({ if (((0x02000000 +0x80000000) & getDbgMask()) == (0x02000000
+0x80000000)) getDbgLogger()->setNextMask(0x02000000 +0x80000000
).DebugPrintfLastMask ( "Installed proxy for '%s' to retrieve data later when needed. MasterKey='%s', 'DetailKey='%s'"
, fmiP->getName(), aItem.getLocalID(), key.c_str() ); }
1047 "Installed proxy for '%s' to retrieve data later when needed. MasterKey='%s', 'DetailKey='%s'",{ if (((0x02000000 +0x80000000) & getDbgMask()) == (0x02000000
+0x80000000)) getDbgLogger()->setNextMask(0x02000000 +0x80000000
).DebugPrintfLastMask ( "Installed proxy for '%s' to retrieve data later when needed. MasterKey='%s', 'DetailKey='%s'"
, fmiP->getName(), aItem.getLocalID(), key.c_str() ); }
1048 fmiP->getName(),{ if (((0x02000000 +0x80000000) & getDbgMask()) == (0x02000000
+0x80000000)) getDbgLogger()->setNextMask(0x02000000 +0x80000000
).DebugPrintfLastMask ( "Installed proxy for '%s' to retrieve data later when needed. MasterKey='%s', 'DetailKey='%s'"
, fmiP->getName(), aItem.getLocalID(), key.c_str() ); }
1049 aItem.getLocalID(),{ if (((0x02000000 +0x80000000) & getDbgMask()) == (0x02000000
+0x80000000)) getDbgLogger()->setNextMask(0x02000000 +0x80000000
).DebugPrintfLastMask ( "Installed proxy for '%s' to retrieve data later when needed. MasterKey='%s', 'DetailKey='%s'"
, fmiP->getName(), aItem.getLocalID(), key.c_str() ); }
1050 key.c_str(){ if (((0x02000000 +0x80000000) & getDbgMask()) == (0x02000000
+0x80000000)) getDbgLogger()->setNextMask(0x02000000 +0x80000000
).DebugPrintfLastMask ( "Installed proxy for '%s' to retrieve data later when needed. MasterKey='%s', 'DetailKey='%s'"
, fmiP->getName(), aItem.getLocalID(), key.c_str() ); }
1051 )){ if (((0x02000000 +0x80000000) & getDbgMask()) == (0x02000000
+0x80000000)) getDbgLogger()->setNextMask(0x02000000 +0x80000000
).DebugPrintfLastMask ( "Installed proxy for '%s' to retrieve data later when needed. MasterKey='%s', 'DetailKey='%s'"
, fmiP->getName(), aItem.getLocalID(), key.c_str() ); }
;
1052 TODBCFieldProxy *odbcProxyP = new TODBCFieldProxy(this,fmiP,aItem.getLocalID(),key.c_str());
1053 // attach it to the string or blob field
1054 static_cast<TStringField *>(fieldP)->setBlobProxy(odbcProxyP);
1055 }
1056 else
1057 #endif // STREAMFIELD_SUPPORT
1058 {
1059 // Note: this will also select fields that may not
1060 // be available for the current remote party, but these
1061 // will be suppressed by the TMultiFieldItemType descendant.
1062 // As different items COULD theoretically use different
1063 // formats, filtering here already would be complicated.
1064 // so we live with reading probably more fields than really
1065 // needed.
1066 #ifdef SQLITE_SUPPORT1
1067 // - always for data access
1068 if (fUseSQLite) {
1069 // SQLite
1070 notnull = fAgentP->getSQLiteColValueAsField(
1071 fSQLiteStmtP, // local sqlite statement
1072 aColIndex-1, // SQLITE colindex starts at 0, not 1 like in ODBC
1073 dbfty,
1074 fieldP,
1075 fConfigP->fDataCharSet,
1076 fmiP->floating_ts ? TCTX_UNKNOWN((timecontext_t) ((tctx_tz_unknown) | TCTX_SYMBOLIC_TZ)) : fConfigP->fDataTimeZone,
1077 fConfigP->fUserZoneOutput
1078 );
1079 }
1080 else
1081 #endif
1082 {
1083 #ifdef ODBCAPI_SUPPORT
1084 // ODBC
1085 notnull=fAgentP->getColumnValueAsField(
1086 aStatement,
1087 aColIndex,
1088 dbfty,
1089 fieldP,
1090 fConfigP->fDataCharSet, // real charset, including UTF16
1091 fmiP->floating_ts ? TCTX_UNKNOWN((timecontext_t) ((tctx_tz_unknown) | TCTX_SYMBOLIC_TZ)) : fConfigP->fDataTimeZone,
1092 fConfigP->fUserZoneOutput
1093 );
1094 #endif // ODBCAPI_SUPPORT
1095 }
1096 // next column
1097 aColIndex++;
1098 }
1099 } // if field available
1100 else {
1101 // this can happen with array maps mapping to field blocks with a too high fMaxRepeat
1102 // it should not happen with array fields.
1103 throw TSyncException(DEBUGTEXT("FATAL: Field in map not found in item","odds6")"FATAL: Field in map not found in item");
1104 }
1105 } // if fid specified
1106 else {
1107 // no field mapped, skip column
1108 aColIndex++;
1109 }
1110 } // if readable, not array and correct set number (and thus SELECTed) field
1111 }
1112 catch (exception &e) {
1113 PDEBUGPRINTFX(DBG_ERROR,({ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ( "fillFieldsFromSQLResult field='%s', colindex=%hd, fid=%hd%s, setno=%hd, dbfty=%s,failed: %s"
, fmiP->fElementName.c_str(), aColIndex, fmiP->fid, fmiP
->isArray() ? " (array)" : "", fmiP->setNo, DBFieldTypeNames
[fmiP->dbfieldtype], e.what() ); }
1114 "fillFieldsFromSQLResult field='%s', colindex=%hd, fid=%hd%s, setno=%hd, dbfty=%s,failed: %s",{ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ( "fillFieldsFromSQLResult field='%s', colindex=%hd, fid=%hd%s, setno=%hd, dbfty=%s,failed: %s"
, fmiP->fElementName.c_str(), aColIndex, fmiP->fid, fmiP
->isArray() ? " (array)" : "", fmiP->setNo, DBFieldTypeNames
[fmiP->dbfieldtype], e.what() ); }
1115 fmiP->fElementName.c_str(),{ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ( "fillFieldsFromSQLResult field='%s', colindex=%hd, fid=%hd%s, setno=%hd, dbfty=%s,failed: %s"
, fmiP->fElementName.c_str(), aColIndex, fmiP->fid, fmiP
->isArray() ? " (array)" : "", fmiP->setNo, DBFieldTypeNames
[fmiP->dbfieldtype], e.what() ); }
1116 aColIndex,{ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ( "fillFieldsFromSQLResult field='%s', colindex=%hd, fid=%hd%s, setno=%hd, dbfty=%s,failed: %s"
, fmiP->fElementName.c_str(), aColIndex, fmiP->fid, fmiP
->isArray() ? " (array)" : "", fmiP->setNo, DBFieldTypeNames
[fmiP->dbfieldtype], e.what() ); }
1117 fmiP->fid,{ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ( "fillFieldsFromSQLResult field='%s', colindex=%hd, fid=%hd%s, setno=%hd, dbfty=%s,failed: %s"
, fmiP->fElementName.c_str(), aColIndex, fmiP->fid, fmiP
->isArray() ? " (array)" : "", fmiP->setNo, DBFieldTypeNames
[fmiP->dbfieldtype], e.what() ); }
1118 fmiP->isArray() ? " (array)" : "",{ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ( "fillFieldsFromSQLResult field='%s', colindex=%hd, fid=%hd%s, setno=%hd, dbfty=%s,failed: %s"
, fmiP->fElementName.c_str(), aColIndex, fmiP->fid, fmiP
->isArray() ? " (array)" : "", fmiP->setNo, DBFieldTypeNames
[fmiP->dbfieldtype], e.what() ); }
1119 fmiP->setNo,{ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ( "fillFieldsFromSQLResult field='%s', colindex=%hd, fid=%hd%s, setno=%hd, dbfty=%s,failed: %s"
, fmiP->fElementName.c_str(), aColIndex, fmiP->fid, fmiP
->isArray() ? " (array)" : "", fmiP->setNo, DBFieldTypeNames
[fmiP->dbfieldtype], e.what() ); }
1120 DBFieldTypeNames[fmiP->dbfieldtype],{ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ( "fillFieldsFromSQLResult field='%s', colindex=%hd, fid=%hd%s, setno=%hd, dbfty=%s,failed: %s"
, fmiP->fElementName.c_str(), aColIndex, fmiP->fid, fmiP
->isArray() ? " (array)" : "", fmiP->setNo, DBFieldTypeNames
[fmiP->dbfieldtype], e.what() ); }
1121 e.what(){ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ( "fillFieldsFromSQLResult field='%s', colindex=%hd, fid=%hd%s, setno=%hd, dbfty=%s,failed: %s"
, fmiP->fElementName.c_str(), aColIndex, fmiP->fid, fmiP
->isArray() ? " (array)" : "", fmiP->setNo, DBFieldTypeNames
[fmiP->dbfieldtype], e.what() ); }
1122 )){ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ( "fillFieldsFromSQLResult field='%s', colindex=%hd, fid=%hd%s, setno=%hd, dbfty=%s,failed: %s"
, fmiP->fElementName.c_str(), aColIndex, fmiP->fid, fmiP
->isArray() ? " (array)" : "", fmiP->setNo, DBFieldTypeNames
[fmiP->dbfieldtype], e.what() ); }
;
1123 throw;
1124 }
1125 } // field loop
1126} // TODBCApiDS::fillFieldsFromSQLResult
1127
1128
1129
1130#ifdef ARRAYDBTABLES_SUPPORT1
1131
1132// read an array field
1133// NOTE: non-array fields are already read when this is called
1134void TODBCApiDS::readArray(SQLHSTMTlong aStatement, TMultiFieldItem &aItem, TFieldMapArrayItem *aMapItemP)
1135{
1136 string sql;
1137 sInt16 colindex,i;
1138 uInt16 setno;
1139
1140 TODBCFieldMapArrayItem *fmaiP = dynamic_cast<TODBCFieldMapArrayItem *>(aMapItemP);
1141 if (!fmaiP) return; // do nothing
1142 #ifdef SCRIPT_SUPPORT1
1143 // process init script
1144 fArrIdx=0; // start at array index=0
1145 fParentKey=aItem.getLocalID();
1146 fWriting=false;
1147 fInserting=false;
1148 fDeleting=false;
1149 fAgentP->fScriptContextDatastore=this;
1150 if (!TScriptContext::executeTest(
1151 true, // read array if script returns nothing or no script present
1152 fScriptContextP, // context
1153 fmaiP->fInitScript, // the script
1154 fConfigP->getDSFuncTableP(),fAgentP, // funcdefs/context
1155 &aItem,true // target item, writeable
1156 )) {
1157 // prevented array reading by script returning false
1158 return; // do nothing for this array
1159 }
1160 #else
1161 fArrIdx=0; // start at array index=0
1162 #endif // SCRIPT_SUPPORT
1163 // process SQL statement(s)
1164 i=0;
1165 while (getNextSQLStatement(fmaiP->fSelectArraySQL,i,sql,setno)) {
1166 // Apply substitutions
1167 resetSQLParameterMaps();
1168 DoDataSubstitutions(sql,fmaiP->fArrayFieldMapList,setno,false,false,&aItem,fArrIdx*fmaiP->fRepeatInc);
1169 prepareSQLStatement(aStatement, sql.c_str(),true,"reading array");
1170 // - prepare parameters
1171 bindSQLParameters(aStatement,true);
1172 // Execute
1173 try {
1174 execSQLStatement(aStatement,sql,true,NULL__null,true);
1175 }
1176 catch (exception &e) {
1177 PDEBUGPRINTFX(DBG_DATA+DBG_DBAPI,({ if (((0x00000080 +0x02000000) & getDbgMask()) == (0x00000080
+0x02000000)) getDbgLogger()->setNextMask(0x00000080 +0x02000000
).DebugPrintfLastMask ( "readarray:execSQLStatement sql='%s' failed: %s"
, sql.c_str(), e.what() ); }
1178 "readarray:execSQLStatement sql='%s' failed: %s",{ if (((0x00000080 +0x02000000) & getDbgMask()) == (0x00000080
+0x02000000)) getDbgLogger()->setNextMask(0x00000080 +0x02000000
).DebugPrintfLastMask ( "readarray:execSQLStatement sql='%s' failed: %s"
, sql.c_str(), e.what() ); }
1179 sql.c_str(),{ if (((0x00000080 +0x02000000) & getDbgMask()) == (0x00000080
+0x02000000)) getDbgLogger()->setNextMask(0x00000080 +0x02000000
).DebugPrintfLastMask ( "readarray:execSQLStatement sql='%s' failed: %s"
, sql.c_str(), e.what() ); }
1180 e.what(){ if (((0x00000080 +0x02000000) & getDbgMask()) == (0x00000080
+0x02000000)) getDbgLogger()->setNextMask(0x00000080 +0x02000000
).DebugPrintfLastMask ( "readarray:execSQLStatement sql='%s' failed: %s"
, sql.c_str(), e.what() ); }
1181 )){ if (((0x00000080 +0x02000000) & getDbgMask()) == (0x00000080
+0x02000000)) getDbgLogger()->setNextMask(0x00000080 +0x02000000
).DebugPrintfLastMask ( "readarray:execSQLStatement sql='%s' failed: %s"
, sql.c_str(), e.what() ); }
;
1182 throw;
1183 }
1184 // get out params
1185 saveAndCleanupSQLParameters(aStatement,true);
1186 // Fetch
1187 // - fetch result(s) now, at most fMaxRepeat
1188 while (fmaiP->fMaxRepeat==0 || fArrIdx<fmaiP->fMaxRepeat) {
1189 colindex=1; // ODBC style, fillFieldsFromSQLResult will adjust for SQLite if needed
1190 if (!fetchNextRow(aStatement,true)) break; // all available rows fetched
1191 // now fill data into array's mapped fields (array fields or field blocks)
1192 fillFieldsFromSQLResult(aStatement,colindex,aItem,fmaiP->fArrayFieldMapList,setno,fArrIdx*fmaiP->fRepeatInc);
1193 #ifdef SCRIPT_SUPPORT1
1194 // process afterread script
1195 fAgentP->fScriptContextDatastore=this;
1196 if (!TScriptContext::execute(fScriptContextP,fmaiP->fAfterReadScript,fConfigP->getDSFuncTableP(),fAgentP,&aItem,true))
1197 throw TSyncException("<afterreadscript> failed");
1198 #endif
1199 // increment array index/count
1200 fArrIdx++;
1201 }
1202 // no more records
1203 finalizeSQLStatement(aStatement,true);
1204 // save size of array in "sizefrom" field (if it's not an array)
1205 TItemField *fldP = getMappedBaseFieldOrVar(aItem,fmaiP->fid);
1206 if (fldP && !fldP->isArray())
1207 fldP->setAsInteger(fArrIdx);
1208 // make pass filter if there are no items in the array
1209 #ifdef OBJECT_FILTERING1
1210 if (fArrIdx==0) aItem.makePassFilter(fmaiP->fNoItemsFilter.c_str()); // item is made pass filter, if possible
1211 #endif
1212 #ifdef SCRIPT_SUPPORT1
1213 // process finish script, can perform more elaborated stuff that makePassFilter can
1214 fAgentP->fScriptContextDatastore=this;
1215 if (!TScriptContext::execute(fScriptContextP,fmaiP->fFinishScript,fConfigP->getDSFuncTableP(),fAgentP,&aItem,true))
1216 throw TSyncException("<finishscript> failed");
1217 #endif
1218 }
1219} // TODBCApiDS::readArray
1220
1221
1222// write or delete an array field
1223// NOTE: non-array fields are already written when this is called
1224void TODBCApiDS::writeArray(bool aDelete, bool aInsert, SQLHSTMTlong aStatement, TMultiFieldItem &aItem, TFieldMapArrayItem *aMapItemP)
1225{
1226 string sql;
1227 sInt16 i;
1228 uInt16 setno;
1229 bool done, allempty;
1230
1231 TODBCFieldMapArrayItem *fmaiP = dynamic_cast<TODBCFieldMapArrayItem *>(aMapItemP);
1232 PDEBUGPRINTFX(DBG_DATA+DBG_DBAPI+DBG_EXOTIC,("Writing Array, fmaiP=0x%lX",(long)fmaiP)){ if (((0x00000080 +0x02000000 +0x80000000) & getDbgMask(
)) == (0x00000080 +0x02000000 +0x80000000)) getDbgLogger()->
setNextMask(0x00000080 +0x02000000 +0x80000000).DebugPrintfLastMask
("Writing Array, fmaiP=0x%lX",(long)fmaiP); }
;
1233 if (!fmaiP) return; // do nothing
1234 PDEBUGPRINTFX(DBG_DATA+DBG_DBAPI+DBG_EXOTIC,("Writing Array")){ if (((0x00000080 +0x02000000 +0x80000000) & getDbgMask(
)) == (0x00000080 +0x02000000 +0x80000000)) getDbgLogger()->
setNextMask(0x00000080 +0x02000000 +0x80000000).DebugPrintfLastMask
("Writing Array"); }
;
1235 // Check initscript first. If it returns false, we do not insert anything
1236 bool doit;
1237 #ifdef SCRIPT_SUPPORT1
1238 // process init script
1239 fArrIdx=0; // start at array index=0
1240 fParentKey=aItem.getLocalID();
1241 fWriting=true;
1242 fInserting=aInsert;
1243 fDeleting=aDelete;
1244 fAgentP->fScriptContextDatastore=this;
1245 doit = TScriptContext::executeTest(
1246 true, // write array if script returns nothing or no script present
1247 fScriptContextP, // context
1248 fmaiP->fInitScript, // the script
1249 &ODBCDSFuncTable1,fAgentP, // funcdefs/context
1250 &aItem,true // target item, writeable
1251 );
1252 #else
1253 doit=true; // no script, always do it
1254 fArrIdx=0; // start at array index=0
1255 #endif // SCRIPT_SUPPORT
1256 PDEBUGPRINTFX(DBG_DATA+DBG_DBAPI+DBG_EXOTIC,("Writing Array: doit=%d, aDelete=%d, aInsert=%d",(int) doit,(int) aDelete,(int) aInsert)){ if (((0x00000080 +0x02000000 +0x80000000) & getDbgMask(
)) == (0x00000080 +0x02000000 +0x80000000)) getDbgLogger()->
setNextMask(0x00000080 +0x02000000 +0x80000000).DebugPrintfLastMask
("Writing Array: doit=%d, aDelete=%d, aInsert=%d",(int) doit
,(int) aDelete,(int) aInsert); }
;
1257 // check if we need delete first
1258 if (doit && aDelete) {
1259 i=0;
1260 while (getNextSQLStatement(fmaiP->fDeleteArraySQL,i,sql,setno)) {
1261 // apply data substitutions
1262 resetSQLParameterMaps();
1263 DoDataSubstitutions(sql,fmaiP->fArrayFieldMapList,setno,true,false,&aItem,0);
1264 // - prepare parameters
1265 prepareSQLStatement(aStatement, sql.c_str(),true,"array erase");
1266 bindSQLParameters(aStatement,true);
1267 // issue
1268 execSQLStatement(aStatement,sql,true,NULL__null,true);
1269 // get out params
1270 saveAndCleanupSQLParameters(aStatement,true);
1271 // done
1272 finalizeSQLStatement(aStatement,true);
1273 }
1274 }
1275 // check if we need to insert the array elements
1276 doit=doit && aInsert;
1277 #ifdef OBJECT_FILTERING1
1278 if (doit) {
1279 // test filter
1280 doit =
1281 fmaiP->fNoItemsFilter.empty() || // no filter, always generate
1282 !aItem.testFilter(fmaiP->fNoItemsFilter.c_str()); // if item passes filter, array should not be written
1283 }
1284 #endif
1285 if (doit) {
1286 // do array write
1287 sInt32 numElements=0; // unlimited to begin with
1288 // - see if we have a sizefrom field
1289 TItemField *fldP = getMappedBaseFieldOrVar(aItem,fmaiP->fid);
1290 if (fldP) {
1291 if (fldP->isArray())
1292 numElements=fldP->arraySize(); // number of detail records to write is size of this array
1293 else
1294 numElements=fldP->getAsInteger(); // number of detail records to write are specified in integer variable
1295 }
1296 else {
1297 // entire array (=up to INSERT statement that would contain only empty fields)
1298 // Note: we cannot use the size of a single array field, as there might be
1299 // more than one with different sizes. End of array is when
1300 // DoDataSubstitutions() returns allempty==true
1301 numElements=REP_ARRAY32767; // this is a big number
1302 }
1303 // - limit to maxrepeat
1304 if (fmaiP->fMaxRepeat!=0 && numElements>fmaiP->fMaxRepeat)
1305 numElements=fmaiP->fMaxRepeat;
1306 // - now write
1307 #ifdef SCRIPT_SUPPORT1
1308 fDeleting=false; // no longer deleting records
1309 #endif
1310 while (fArrIdx<numElements) {
1311 #ifdef SCRIPT_SUPPORT1
1312 // process beforewrite script, end of array if it returns false
1313 fAgentP->fScriptContextDatastore=this;
1314 if (!TScriptContext::executeTest(true,fScriptContextP,fmaiP->fBeforeWriteScript,fConfigP->getDSFuncTableP(),fAgentP,&aItem,true))
1315 goto endarray;
1316 #endif
1317 // perform insert statement(s)
1318 i=0;
1319 bool firststatement=true;
1320 while (getNextSQLStatement(fmaiP->fInsertElementSQL,i,sql,setno)) {
1321 // do substitutions and find out if empty
1322 resetSQLParameterMaps();
1323 DoDataSubstitutions(sql,fmaiP->fArrayFieldMapList,setno,true,false,&aItem,fArrIdx*fmaiP->fRepeatInc,&allempty,&done);
1324 // stop here if FIRST STATEMENT in (unlimited) array insert is all empty
1325 if (firststatement && fmaiP->fMaxRepeat==0 && allempty)
1326 goto endarray;
1327 // store if not empty or empty storage allowed
1328 if (fmaiP->fStoreEmpty || !allempty) {
1329 // - prepare parameters
1330 prepareSQLStatement(aStatement, sql.c_str(),true,"array element insert");
1331 bindSQLParameters(aStatement,true);
1332 // issue
1333 execSQLStatement(aStatement,sql,false,NULL__null,true);
1334 // get out params
1335 saveAndCleanupSQLParameters(aStatement,true);
1336 // no more records
1337 finalizeSQLStatement(aStatement,true);
1338 }
1339 firststatement=false;
1340 }
1341 #ifdef SCRIPT_SUPPORT1
1342 // process afterwrite script, end of array if it returns false
1343 fAgentP->fScriptContextDatastore=this;
1344 if (!TScriptContext::executeTest(true,fScriptContextP,fmaiP->fAfterWriteScript,fConfigP->getDSFuncTableP(),fAgentP,&aItem,true))
1345 goto endarray;
1346 #endif
1347 // next
1348 fArrIdx++;
1349 } // while
1350 endarray:
1351 #ifdef SCRIPT_SUPPORT1
1352 // process finish script
1353 fAgentP->fScriptContextDatastore=this;
1354 if (!TScriptContext::execute(fScriptContextP,fmaiP->fFinishScript,fConfigP->getDSFuncTableP(),fAgentP,&aItem,true))
1355 throw TSyncException("<finishscript> failed");
1356 #endif
1357 ;
1358 }
1359} // TODBCApiDS::writeArray
1360
1361
1362// read array fields.
1363// - if aStatement is passed NULL, routine will allocate/destroy its
1364// own statement for reading the array fields
1365void TODBCApiDS::readArrayFields(SQLHSTMTlong aStatement, TMultiFieldItem &aItem, TFieldMapList &fml)
1366{
1367 TFieldMapList::iterator pos;
1368
1369 // get data for all array table fields
1370 for (pos=fml.begin(); pos!=fml.end(); pos++) {
1371 if ((*pos)->isArray()) {
1372 readArray(aStatement,aItem,(TFieldMapArrayItem *)*pos);
1373 }
1374 }
1375} // TODBCApiDS::readArrayFields
1376
1377
1378void TODBCApiDS::writeArrayFields(SQLHSTMTlong aStatement, TMultiFieldItem &aItem, TFieldMapList &fml, bool aInsert)
1379{
1380 TFieldMapList::iterator pos;
1381
1382 // write all array table fields
1383 for (pos=fml.begin(); pos!=fml.end(); pos++) {
1384 if ((*pos)->isArray()) {
1385 // delete first, then insert
1386 writeArray(!aInsert || static_cast<TODBCFieldMapArrayItem *>(*pos)->fAlwaysCleanArray,true,aStatement,aItem,(TFieldMapArrayItem *)*pos);
1387 }
1388 }
1389} // TODBCApiDS::writeArrayFields
1390
1391
1392void TODBCApiDS::deleteArrayFields(SQLHSTMTlong aStatement, TMultiFieldItem &aItem, TFieldMapList &fml)
1393{
1394 TFieldMapList::iterator pos;
1395
1396 // delete all array table fields
1397 for (pos=fml.begin(); pos!=fml.end(); pos++) {
1398 if ((*pos)->isArray()) {
1399 // only delete, no insert
1400 writeArray(true,false,aStatement,aItem,(TFieldMapArrayItem *)*pos);
1401 }
1402 }
1403} // TODBCApiDS::deleteArrayFields
1404
1405#endif // ARRAYDBTABLES_SUPPORT
1406
1407
1408
1409// append field value(s) as single literal to SQL text
1410// - returns true if field(s) were not empty
1411// - even non-existing or empty field will append at least NULL or '' to SQL
1412bool TODBCApiDS::appendFieldsLiteral(TMultiFieldItem &aItem, sInt16 aFid, sInt16 aRepOffset,TODBCFieldMapItem &aFieldMapping, string &aSQL)
1413{
1414 string val;
1415 bool notempty=false;
1416
1417 TItemField *fieldP;
1418
1419 // get mapped item field or local script variable
1420 fieldP=getMappedFieldOrVar(aItem,aFid,aRepOffset,true); // existing (array fields) only
1421 // now process single field
1422 if (!fieldP) goto novalue; // no data -> empty
1423 notempty=appendFieldValueLiteral(*fieldP, aFieldMapping.dbfieldtype, aFieldMapping.maxsize, aFieldMapping.floating_ts, aSQL);
1424 return notempty;
1425novalue:
1426 // no value was produced
1427 aSQL+="NULL";
1428 return false;
1429} // TODBCApiDS::appendFieldsLiteral
1430
1431
1432// append field value as literal to SQL text
1433// - returns true if field(s) were not empty
1434// - even non-existing or empty field will append at least NULL or '' to SQL
1435bool TODBCApiDS::appendFieldValueLiteral(TItemField &aField,TDBFieldType aDBFieldType, uInt32 aMaxSize, bool aIsFloating, string &aSQL)
1436{
1437 return
1438 fAgentP->appendFieldValueLiteral(
1439 aField, aDBFieldType, aMaxSize, aSQL,
1440 fConfigP->sqlPrepCharSet(),
1441 fConfigP->fDataLineEndMode,
1442 fConfigP->fQuotingMode,
1443 aIsFloating ? TCTX_UNKNOWN((timecontext_t) ((tctx_tz_unknown) | TCTX_SYMBOLIC_TZ)) : fConfigP->fDataTimeZone,
1444 fRecordSize
1445 );
1446} // TODBCApiDS::appendFieldValueLiteral
1447
1448
1449// issue single (or multiple) data update statements
1450// NOTE: returns false if no rows affected. If no information about rows affected
1451// is found, returns false if all statements return SQL_NODATA.
1452// If config fIgnoreAffectedCount is set, always returns true (unless we have an error).
1453bool TODBCApiDS::IssueDataWriteSQL(
1454 SQLHSTMTlong aStatement,
1455 const string &aSQL,
1456 const char *aComment,
1457 bool aForUpdate,
1458 TFieldMapList &aFieldMapList, // field map list for %N,%V and %v
1459 TMultiFieldItem *aItemP // item to read values and localid from
1460)
1461{
1462 uInt16 setno=0; // default to 0
1463 string sql;
1464 bool d,hasdata=false;
1465 SQLLENlong affectedRows,totalAffected=-1;
1466
1467 sInt16 i=0;
1468
1469 // add field values
1470 fRecordSize=0; // reset count, appendFieldValueLiteral will update size
1471 while (getNextSQLStatement(aSQL,i,sql,setno)) {
1472 // - do substitutions
1473 resetSQLParameterMaps();
1474 PDEBUGPRINTFX(DBG_DBAPI+DBG_EXOTIC,("SQL before substitutions: %s",aSQL.c_str())){ if (((0x02000000 +0x80000000) & getDbgMask()) == (0x02000000
+0x80000000)) getDbgLogger()->setNextMask(0x02000000 +0x80000000
).DebugPrintfLastMask ("SQL before substitutions: %s",aSQL.c_str
()); }
;
1475 DoDataSubstitutions(
1476 sql, // string to apply substitutions to
1477 aFieldMapList, // field map list for %N,%V and %v
1478 setno, // set number
1479 true, // for write
1480 aForUpdate, // for update?
1481 aItemP // item to read values and localid from
1482 );
1483 // - prepare parameters
1484 prepareSQLStatement(aStatement, sql.c_str(),true,aComment);
1485 bindSQLParameters(aStatement,true);
1486 // - execute it
1487 d = execSQLStatement(aStatement,sql,true,NULL__null,true);
1488 // - get number of affected rows
1489 #ifdef SQLITE_SUPPORT1
1490 if (fUseSQLite) {
1491 affectedRows = sqlite3_changes(fSQLiteP);
1492 }
1493 else
1494 #endif
1495 {
1496 #ifdef ODBCAPI_SUPPORT
1497 SQLRETURNlong res = SQLRowCount(aStatement,&affectedRows);
1498 if (res!=SQL_SUCCESS0 && res!=SQL_SUCCESS_WITH_INFO)
1499 affectedRows = -1; // unknown
1500 #else
1501 affectedRows=0; // no API, no rows affected
1502 #endif
1503 }
1504 // - count affected rows
1505 if (totalAffected<0)
1506 totalAffected = affectedRows;
1507 else if (affectedRows>0)
1508 totalAffected += affectedRows;
1509 PDEBUGPRINTFX(DBG_DBAPI+DBG_EXOTIC,("IssueDataWriteSQL: Statement reports %ld affected rows - totalAffected=%ld",affectedRows,totalAffected)){ if (((0x02000000 +0x80000000) & getDbgMask()) == (0x02000000
+0x80000000)) getDbgLogger()->setNextMask(0x02000000 +0x80000000
).DebugPrintfLastMask ("IssueDataWriteSQL: Statement reports %ld affected rows - totalAffected=%ld"
,affectedRows,totalAffected); }
;
1510 // get out params
1511 saveAndCleanupSQLParameters(aStatement,true);
1512 // done with statement
1513 finalizeSQLStatement(aStatement,true);
1514 hasdata = hasdata || d;
1515 }
1516 if (fConfigP->fIgnoreAffectedCount)
1517 return true; // assume always successful
1518 else if (totalAffected<0)
1519 return hasdata; // no reliable info about affected rows - use hasdata instead
1520 else
1521 return totalAffected>0; // assume modified something if any row was affected
1522} // TODBCApiDS::IssueDataWriteSQL
1523
1524
1525#ifdef HAS_SQL_ADMIN
1526
1527// issue single (or multiple) map access statements
1528// NOTE: if all return SQL_NODATA, function will return false.
1529bool TODBCApiDS::IssueMapSQL(
1530 SQLHSTMTlong aStatement,
1531 const string &aSQL,
1532 const char *aComment,
1533 TMapEntryType aEntryType,
1534 const char *aLocalID,
1535 const char *aRemoteID,
1536 uInt32 aMapFlags
1537)
1538{
1539 uInt16 setno=0; // default to 0
1540 string sql;
1541 bool d,hasdata=false;
1542
1543 sInt16 i=0;
1544 while (getNextSQLStatement(aSQL,i,sql,setno)) {
1545 // - do substitutions
1546 DoMapSubstitutions(sql,aEntryType,aLocalID,aRemoteID,aMapFlags);
1547 // - execute it
1548 d = execSQLStatement(aStatement,sql,true,aComment,false);
1549 hasdata = hasdata || d;
1550 }
1551 return hasdata;
1552} // TODBCApiDS::IssueMapSQL
1553
1554#endif // HAS_SQL_ADMIN
1555
1556
1557// execute SQL statement as-is, without any substitutions
1558bool TODBCApiDS::execSQLStatement(SQLHSTMTlong aStatement, string &aSQL, bool aNoDataAllowed, const char *aComment, bool aForData)
1559{
1560 #ifdef ODBCAPI_SUPPORT
1561 SQLRETURNlong res;
1562 #endif
1563
1564 // show what statement will be executed
1565 #ifdef SYDEBUG2
1566 if (aComment && PDEBUGTEST(DBG_DBAPI)(((0x02000000) & getDbgMask()) == (0x02000000))) {
1567 PDEBUGPRINTFX(DBG_DBAPI,("SQL for %s:",aComment)){ if (((0x02000000) & getDbgMask()) == (0x02000000)) getDbgLogger
()->setNextMask(0x02000000).DebugPrintfLastMask ("SQL for %s:"
,aComment); }
;
1568 PDEBUGPUTSX(DBG_DBAPI,aSQL.c_str()){ if (((0x02000000) & getDbgMask()) == (0x02000000)) getDbgLogger
()->DebugPuts( 0x02000000,aSQL.c_str()); }
;
1569 }
1570 #endif
1571 // avoid executing empty statement
1572 if (aSQL.empty()) return true; // "ok", nothing to execute
1573 #ifdef SQLITE_SUPPORT1
1574 if (fUseSQLite && aForData) {
1575 // execute (possibly already prepared) statement in SQLite
1576 if (!fSQLiteStmtP) {
1577 // not yet prepared, do it now
1578 fAgentP->prepareSQLiteStatement(aSQL.c_str(),fSQLiteP,fSQLiteStmtP);
1579 }
1580 // do first step
1581 fStepRc = sqlite3_step(fSQLiteStmtP);
1582 // clean up right now if we don't have data
1583 if (fStepRc!=SQLITE_ROW100) {
1584 fStepRc=sqlite3_finalize(fSQLiteStmtP);
1585 fSQLiteStmtP=NULL__null;
1586 fAgentP->checkSQLiteError(fStepRc,fSQLiteP);
1587 }
1588 // check if we MUST have data here
1589 if (!aNoDataAllowed) {
1590 if (fStepRc!=SQLITE_ROW100) {
1591 // we're not happy because we have no data
1592 return false;
1593 }
1594 }
1595 return true; // ok
1596 }
1597 #endif
1598 {
1599 #ifdef ODBCAPI_SUPPORT
1600 // execute statement
1601 TP_DEFIDX(li);
1602 TP_SWITCH(li,fSessionP->fTPInfo,TP_database);
1603 #ifdef ODBC_UNICODE
1604 if (fConfigP->fDataCharSet==chs_utf16) {
1605 // aSQL is UTF-8 here
1606 // make UCS2 string to pass to wide char API
1607 string wSQL;
1608 appendUTF8ToUTF16ByteString(aSQL.c_str(), wSQL, ODBC_BIGENDIAN);
1609 wSQL += (char)0; // together with the implicit 8-bit NUL terminator, this makes a 16-bit NUL terminator
1610 // execute it with the "W" version
1611 res = SafeSQLExecDirectW(
1612 aStatement,
1613 (SQLWCHAR *)wSQL.c_str(),
1614 wSQL.size()/2 // actual number of Unicode chars
1615 );
1616 }
1617 else
1618 #endif // ODBC_UNICODE
1619 {
1620 res = SafeSQLExecDirect(
1621 aStatement,
1622 (SQLCHAR *)aSQL.c_str(),
1623 aSQL.size()
1624 );
1625 }
1626 TP_START(fSessionP->fTPInfo,li);
1627 // check
1628 if (aNoDataAllowed)
1629 return fAgentP->checkStatementHasData(res,aStatement);
1630 else {
1631 fAgentP->checkStatementError(res,aStatement);
1632 return true;
1633 }
1634 #endif // ODBCAPI_SUPPORT
1635 }
1636 // should never happen
1637 return false;
1638} // TODBCApiDS::execSQLStatement
1639
1640
1641// get next statment from statement list
1642// - may begin with %GO(setno)
1643bool TODBCApiDS::getNextSQLStatement(const string &aSQL, sInt16 &aStartAt, string &aOneSQL, uInt16 &aSetNo)
1644{
1645 aSetNo=0; // default to set 0
1646 string::size_type e;
1647 bool foundone=false;
1648 sInt32 n=aSQL.size();
1649
1650 // check for %GO
1651 while (aStartAt<n) {
1652 // avoid "compare" here as it is not consistent in Linux g++ bastring.h and MSL
1653 if (strnncmp(aSQL.c_str()+aStartAt,"%GO",3)==0) {
1654 aStartAt+=3;
1655 // check for statement number spec
1656 const char *p = aSQL.c_str()+aStartAt;
1657 const char *q=p;
1658 if (*p=='(') {
1659 p++;
1660 // get set number
1661 if (sscanf(p,"%hd",&aSetNo)!=1) aSetNo=0;
1662 // search closing paranthesis
1663 while (*p) { if (*p++==')') break; }
1664 }
1665 // skip set no specs
1666 aStartAt+=p-q;
1667 }
1668 // - skip spaces
1669 while (aStartAt<sInt32(aSQL.size())) {
1670 if (!isspace(aSQL[aStartAt])) break;
1671 aStartAt++;
1672 }
1673 // aStart now points to beginning of next statement
1674 // - determine end of next statement
1675 e=aSQL.find("%GO",aStartAt);
1676 if (e==string::npos) {
1677 // last statement
1678 e=aSQL.size(); // set to end of string
1679 }
1680 if (e>string::size_type(aStartAt)) {
1681 // Not-empty statement
1682 aOneSQL.assign(aSQL,aStartAt,e-aStartAt);
1683 // Next statement starts here
1684 aStartAt=e;
1685 // statement found
1686 foundone=true;
1687 break;
1688 }
1689 } // while more in input and empty statement
1690 return foundone;
1691} // TODBCApiDS::getNextSQLStatement
1692
1693
1694// inform logic of coming state change
1695localstatus TODBCApiDS::dsBeforeStateChange(TLocalEngineDSState aOldState,TLocalEngineDSState aNewState)
1696{
1697 // let inherited do its stuff as well
1698 return inherited::dsBeforeStateChange(aOldState,aNewState);
1699} // TODBCApiDS::dsBeforeStateChange
1700
1701
1702
1703// inform logic of happened state change
1704localstatus TODBCApiDS::dsAfterStateChange(TLocalEngineDSState aOldState,TLocalEngineDSState aNewState)
1705{
1706 // let inherited do its stuff as well
1707 return inherited::dsAfterStateChange(aOldState,aNewState);
1708} // TODBCApiDS::dsAfterStateChange
1709
1710
1711#ifdef ODBCAPI_SUPPORT
1712
1713// Routine mapping
1714#define lineartimeToLiteralAppend lineartimeToODBCLiteralAppend
1715
1716#endif // ODBCAPI_SUPPORT
1717
1718
1719#ifdef HAS_SQL_ADMIN
1720
1721// log datastore sync result
1722// - Called at end of sync with this datastore
1723void TODBCApiDS::dsLogSyncResult(void)
1724{
1725 uInt16 setno=0; // default to 0
1726 string sql;
1727 sInt16 i=0;
1728
1729 // if we have a SQL statement and logging of this session is enabled, log
1730 if (fSessionP->logEnabled() && !fAgentConfigP->fWriteLogSQL.empty()) {
1731 // execute SQL statement for logging
1732 try {
1733 SQLHSTMTlong statement=fAgentP->newStatementHandle(getODBCConnectionHandle());
1734 try {
1735 while (getNextSQLStatement(fAgentConfigP->fWriteLogSQL,i,sql,setno)) {
1736 // - do substitutions
1737 DoLogSubstitutions(sql,false);
1738 execSQLStatement(statement,sql,true,"Log Entry Write",false);
1739 finalizeSQLStatement(statement,false);
1740 }
1741 // release the statement handle
1742 SafeSQLFreeHandle(SQL_HANDLE_STMT,statement);
1743 // commit the transaction (this is called AFTER the data transactions probably have been
1744 // rolled back in EndDataWrite
1745 SafeSQLEndTran(SQL_HANDLE_DBC,getODBCConnectionHandle(),SQL_COMMIT);
1746 }
1747 catch (exception &e) {
1748 // release the statement handle
1749 SafeSQLFreeHandle(SQL_HANDLE_STMT,statement);
1750 throw;
1751 }
1752 }
1753 catch (exception &e) {
1754 // release the statement handle
1755 PDEBUGPRINTFX(DBG_ERROR,("Failed to issue Log SQL statement: %s",e.what())){ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ("Failed to issue Log SQL statement: %s"
,e.what()); }
;
1756 }
1757 }
1758 // let ancestor process
1759 TStdLogicDS::dsLogSyncResult();
1760} // TODBCApiDS::dsLogSyncResult
1761
1762
1763// Do logfile SQL/plaintext substitutions
1764void TODBCApiDS::DoLogSubstitutions(string &aLog,bool aPlaintext)
1765{
1766 string s;
1767 size_t i;
1768
1769 // logging
1770 if (!aPlaintext) {
1771 // only for SQL text
1772 // Note: we need to do free form strings here to ensure correct DB string escaping
1773 // %rD Datastore remote path
1774 StringSubst(aLog,"%rD",getRemoteDBPath(),3,chs_ascii,lem_cstr,fConfigP->fQuotingMode);
1775 // %nR Remote name: [Manufacturer ]Model")
1776 StringSubst(aLog,"%nR",fSessionP->getRemoteDescName(),3,chs_ascii,lem_cstr,fConfigP->fQuotingMode);
1777 // %vR Remote Device Version Info ("Type (HWV, FWV, SWV) Oem")
1778 StringSubst(aLog,"%vR",fSessionP->getRemoteInfoString(),3,chs_ascii,lem_cstr,fConfigP->fQuotingMode);
1779 // %U User Name
1780 StringSubst(aLog,"%U",fSessionP->getSyncUserName(),2,chs_ascii,lem_cstr,fConfigP->fQuotingMode);
1781 // %lD Datastore local path (complete with all CGI)
1782 StringSubst(aLog,"%lD",getRemoteViewOfLocalURI(),3,chs_ascii,lem_cstr,fConfigP->fQuotingMode);
1783 // %iR Remote Device ID (URI)
1784 StringSubst(aLog,"%iR",fSessionP->getRemoteURI(),3,chs_ascii,lem_cstr,fConfigP->fQuotingMode);
1785 // %dT Sync date
1786 i=0; while((i=aLog.find("%dT",i))!=string::npos) {
1787 s.erase();
1788 fAgentP->lineartimeToLiteralAppend(fCurrentSyncTime, s, true, false, TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)), fConfigP->fDataTimeZone);
1789 aLog.replace(i,3,s); i+=s.size();
1790 }
1791 // %tT Sync time
1792 i=0; while((i=aLog.find("%tT",i))!=string::npos) {
1793 s.erase();
1794 fAgentP->lineartimeToLiteralAppend(fCurrentSyncTime, s, false, true, TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)), fConfigP->fDataTimeZone);
1795 aLog.replace(i,3,s); i+=s.size();
1796 }
1797 // %T Sync timestamp
1798 i=0; while((i=aLog.find("%T",i))!=string::npos) {
1799 s.erase();
1800 fAgentP->lineartimeToLiteralAppend(fCurrentSyncTime, s, true, true, TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)), fConfigP->fDataTimeZone);
1801 aLog.replace(i,2,s); i+=s.size();
1802 }
1803 // %ssT Sync start time timestamp
1804 i=0; while((i=aLog.find("%ssT",i))!=string::npos) {
1805 s.erase();
1806 fAgentP->lineartimeToLiteralAppend(fSessionP->getSessionStarted(), s, true, true, TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)), fConfigP->fDataTimeZone);
1807 aLog.replace(i,4,s); i+=s.size();
1808 }
1809 // %seT Sync end time timestamp
1810 i=0; while((i=aLog.find("%seT",i))!=string::npos) {
1811 s.erase();
1812 fAgentP->lineartimeToLiteralAppend(getEndOfSyncTime(), s, true, true, TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)), fConfigP->fDataTimeZone);
1813 aLog.replace(i,4,s); i+=s.size();
1814 }
1815 }
1816 // - let ancestor process its own substitutions
1817 TStdLogicDS::DoLogSubstitutions(aLog,aPlaintext);
1818 // %t Target Key
1819 // %f Folder Key
1820 // %u User key
1821 // %d device key
1822 // - let standard SQL substitution handle this
1823 DoSQLSubstitutions(aLog);
1824} // TODBCApiDS::DoLogSubstitutions
1825
1826
1827// do substitutions for map table access
1828void TODBCApiDS::DoMapSubstitutions(
1829 string &aSQL, // string to apply substitutions to
1830 TMapEntryType aEntryType, // the entry type
1831 const char *aLocalID, // local ID
1832 const char *aRemoteID, // remote ID
1833 uInt32 aMapFlags // map flags
1834)
1835{
1836 // can be empty, but we don't do NULLs
1837 if (!aLocalID) aLocalID="";
1838 if (!aRemoteID) aRemoteID="";
1839 // %k = data key (local ID)
1840 StringSubst(aSQL,"%k",aLocalID,2,-1,fConfigP->sqlPrepCharSet(),fConfigP->fDataLineEndMode,fConfigP->fQuotingMode);
1841 // %r = remote ID (always a string)
1842 StringSubst(aSQL,"%r",aRemoteID,2,-1,fConfigP->sqlPrepCharSet(),fConfigP->fDataLineEndMode,fConfigP->fQuotingMode);
1843 // %x = flags (a uInt32)
1844 StringSubst(aSQL,"%x",(sInt32)aMapFlags,2);
1845 // %e = entry type (a small number, uInt8 is enough)
1846 StringSubst(aSQL,"%e",(sInt32)aEntryType,2);
1847 // now substitute standard: %f=folderkey, %u=userkey, %t=targetkey
1848 DoSQLSubstitutions(aSQL);
1849} // TODBCApiDS::DoMapSubstitutions
1850
1851
1852#endif // HAS_SQL_ADMIN
1853
1854
1855// Do the SQL substitutions common for all SQL statements
1856void TODBCApiDS::DoSQLSubstitutions(string &aSQL)
1857{
1858 string::size_type i;
1859
1860 // let session substitute session level stuff first
1861 fAgentP->DoSQLSubstitutions(aSQL);
1862 StringSubst(aSQL,"%f",fFolderKey,2,fConfigP->sqlPrepCharSet(),fConfigP->fDataLineEndMode,fConfigP->fQuotingMode);
1863 StringSubst(aSQL,"%t",fTargetKey,2,fConfigP->sqlPrepCharSet(),fConfigP->fDataLineEndMode,fConfigP->fQuotingMode);
1864 // ID generator
1865 // - new ID
1866 i=0; while((i=aSQL.find("%X",i))!=string::npos) {
1867 // - generate new one
1868 nextLocalID(fConfigP->fSpecialIDMode,SQL_NULL_HANDLE0);
1869 // - use it
1870 aSQL.replace(i,2,fLastGeneratedLocalID);
1871 i+=fLastGeneratedLocalID.size();
1872 }
1873 // last used ID
1874 StringSubst(aSQL,"%x",fLastGeneratedLocalID,2,fConfigP->sqlPrepCharSet(),fConfigP->fDataLineEndMode,fConfigP->fQuotingMode);
1875} // TODBCApiDS::DoSQLSubstitutions
1876
1877
1878
1879
1880typedef struct {
1881 const char *substTag;
1882 int substCode;
1883} TSubstHandlerDef;
1884
1885typedef enum {
1886 dsh_datakey,
1887 dsh_datakey_outparam_string,
1888 dsh_datakey_outparam_integer,
1889 dsh_fieldnamelist,
1890 dsh_fieldnamelist_a,
1891 dsh_namevaluelist,
1892 dsh_namevaluelist_a,
1893 dsh_valuelist,
1894 dsh_valuelist_a,
1895 dsh_param,
1896 dsh_field,
1897 dsh_recordsize,
1898 dsh_moddate,
1899 dsh_modtime,
1900 dsh_moddatetime,
1901 dsh_andfilter,
1902 dsh_wherefilter,
1903 // number of enums
1904 dsh_NUMENTRIES
1905} TDataSubstHandlers;
1906
1907static TSubstHandlerDef DataSubstHandlers[dsh_NUMENTRIES] = {
1908 { "k", dsh_datakey },
1909 { "pkos", dsh_datakey_outparam_string },
1910 { "pkoi", dsh_datakey_outparam_integer },
1911 { "N", dsh_fieldnamelist },
1912 { "aN", dsh_fieldnamelist_a },
1913 { "V", dsh_namevaluelist },
1914 { "aV", dsh_namevaluelist_a },
1915 { "v", dsh_valuelist },
1916 { "av", dsh_valuelist_a },
1917 { "p(", dsh_param },
1918 { "d(", dsh_field },
1919 { "S", dsh_recordsize },
1920 { "dM", dsh_moddate },
1921 { "tM", dsh_modtime },
1922 { "M", dsh_moddatetime },
1923 { "AF", dsh_andfilter },
1924 { "WF", dsh_wherefilter }
1925};
1926
1927
1928// do substitutions for data table access
1929void TODBCApiDS::DoDataSubstitutions(
1930 string &aSQL, // string to apply substitutions to
1931 TFieldMapList &aFieldMapList, // field map list for %N,%V and %v
1932 uInt16 aSetNo, // Map Set number
1933 bool aForWrite, // set if read-enabled or write-enabled fields are shown in %N,%V and %v
1934 bool aForUpdate, // set if for update (only assigned fields will be shown if !fUpdateAllFields)
1935 TMultiFieldItem *aItemP, // item to read values and localid from
1936 sInt16 aRepOffset, // array index/repeat offset
1937 bool *aAllEmptyP, // true if all %V or %v empty
1938 bool *aDoneP // true if no data at specified aRepOffset
1939)
1940{
1941 string::size_type i,j,k,m,m2,n;
1942 string s,s2;
1943 string inStr;
1944 int handlerid;
1945
1946 // default settings of flags
1947 bool done=true; // default, in case %V or %v is missing (which could limit repetitions for arrays)
1948 bool allempty=false; // not all empty unless we see that %V has all empty fields
1949
1950 // substitute one by one in the order the escape sequences appear in the SQL string
1951 // (this is important for correct parameter mapping!!)
1952 i=0;
1953 inStr=aSQL;
1954 aSQL.erase(); // will be rebuilt later
1955 while((i=inStr.find("%",i))!=string::npos) {
1956 // potential escape sequence found
1957 // - search in table
1958 for (handlerid=0; handlerid<dsh_NUMENTRIES; handlerid++) {
1959 // - get compare length
1960 n = strlen(DataSubstHandlers[handlerid].substTag);
1961 if (strnncmp(inStr.c_str()+i+1,DataSubstHandlers[handlerid].substTag,n)==0) {
1962 // found handler
1963 // - i = index of % lead-in
1964 n+=1;
1965 // - n = size of matched sequence
1966 s.erase();
1967 // - s = replacement string. If set to non-empty, n chars at i will be replaced by it
1968 // Now process
1969 bool upc=false,lowc=false,asci=false;
1970 TDBFieldType dbfty=dbft_numeric;
1971 sInt16 ty,idx;
1972 sInt32 sz=0;
1973 sInt16 arrindex=0;
1974 TItemField *fldP;
1975 int substCode = DataSubstHandlers[handlerid].substCode;
1976 //DEBUGPRINTFX(DBG_DBAPI+DBG_EXOTIC,("- found %%%s (%ld chars)",DataSubstHandlers[substCode].substTag,(long)n));
1977 switch (substCode) {
1978 case dsh_datakey:
1979 if (aItemP) s=aItemP->getLocalID();
1980 break;
1981 case dsh_datakey_outparam_string:
1982 // generated key output (string key)
1983 addSQLParameterMap(false,true,param_localid_str,NULL__null,aItemP,aRepOffset);
1984 goto paramsubst;
1985 case dsh_datakey_outparam_integer:
1986 // generated key output (integer key)
1987 addSQLParameterMap(false,true,param_localid_int,NULL__null,aItemP,aRepOffset);
1988 goto paramsubst;
1989 case dsh_fieldnamelist:
1990 case dsh_fieldnamelist_a:
1991 // %N and %aN = field name list
1992 addFieldNameList(s,aForWrite,aForUpdate,substCode!=dsh_fieldnamelist_a && !fConfigP->fUpdateAllFields && aForUpdate && aItemP,aItemP,aFieldMapList,aSetNo);
1993 break;
1994 case dsh_namevaluelist:
1995 case dsh_namevaluelist_a:
1996 if (!aItemP) break; // needs an item
1997 // %V,%aV = data field=value list
1998 addFieldNameValueList(s,substCode!=dsh_namevaluelist_a && !fConfigP->fUpdateAllFields && aForUpdate,done,allempty,*aItemP,true,aFieldMapList,aSetNo,aRepOffset,allempty);
1999 break;
2000 case dsh_valuelist:
2001 case dsh_valuelist_a:
2002 if (!aItemP) break; // needs an item
2003 // %v,%av = data value list
2004 addFieldNameValueList(s,substCode!=dsh_valuelist_a && !fConfigP->fUpdateAllFields && aForUpdate,done,allempty,*aItemP,false,aFieldMapList,aSetNo,aRepOffset,allempty);
2005 break;
2006 case dsh_param:
2007 // handle with global routine used in different substitution places
2008 // i=start of % sequence, n=size of matched basic sequence = %+identifier+( = %p(
2009 if (!fAgentP->ParseParamSubst(
2010 inStr,i,n,
2011 fParameterMaps,
2012 aItemP
2013 #ifdef SCRIPT_SUPPORT1
2014 ,fScriptContextP
2015 #endif
2016 )) break;
2017 // i=start of % sequence, n=size of entire sequence including params and closing paranthesis
2018 paramsubst:
2019 s="?";
2020 break;
2021 case dsh_field:
2022 // init options
2023 upc=false;
2024 lowc=false;
2025 asci=false;
2026 // skip lead-in
2027 j=i+n;
2028 // find closing paranthesis
2029 k = inStr.find(")",j);
2030 if (k==string::npos) { i=j; n=0; break; } // no closing paranthesis
2031 m = k; // assume end of name is here
2032 m2 = k;
2033 // look for dbfieldtype
2034 dbfty=dbft_numeric; // default to numeric (no quotes, to be compatible with old %d() definition)
2035 m2 = inStr.find(",",j);
2036 if (m2!=string::npos) {
2037 // get dbfieldtype
2038 if (StrToEnum(DBFieldTypeNames,numDBfieldTypes,ty,inStr.c_str()+m2+1,k-m2-1))
2039 dbfty=(TDBFieldType)ty;
2040 }
2041 else
2042 m2=k; // set end of name to closing paranthesis again
2043 #ifdef ARRAYFIELD_SUPPORT1
2044 // also look for array index
2045 m = inStr.find("#",j);
2046 if (m!=string::npos && m<m2) {
2047 // get array index
2048 StrToShort(inStr.c_str()+m+1,arrindex);
2049 }
2050 else
2051 m=m2; // set end of name to closing paranthesis or comma again
2052 #endif
2053 // extract options, if any
2054 if (inStr[j]=='[') {
2055 do {
2056 j++;
2057 if (inStr.size()<=j) break;
2058 if (inStr[j]==']') { j++; break; }; // end of options
2059 // check options
2060 if (inStr[j]=='u') upc=true;
2061 else if (inStr[j]=='l') lowc=true;
2062 else if (inStr[j]=='a') asci=true;
2063 } while(true);
2064 }
2065 // extract name (without possible array index or dbfieldtype)
2066 s.assign(inStr,j,m-j);
2067 // find field
2068 if (!aItemP) { i=k+1; n=0; break; } // no item, no action
2069 #ifdef SCRIPT_SUPPORT1
2070 if (fScriptContextP) {
2071 idx=fScriptContextP->getIdentifierIndex(OBJ_AUTO0, aItemP->getFieldDefinitions(),s.c_str());
2072 fldP=fScriptContextP->getFieldOrVar(aItemP,idx,arrindex);
2073 }
2074 else
2075 #endif
2076 fldP = aItemP->getArrayField(s.c_str(),arrindex,true);
2077 if (!fldP) { i=k+1; n=0; break; } // field not found, no action
2078 // produce DB literal
2079 sz=0;
2080 s.erase();
2081 fAgentP->appendFieldValueLiteral(
2082 *fldP,
2083 dbfty,
2084 0, // unlimited
2085 s,
2086 asci ? chs_ascii : fConfigP->sqlPrepCharSet(),
2087 fConfigP->fDataLineEndMode,
2088 fConfigP->fQuotingMode,
2089 fConfigP->fDataTimeZone,
2090 sz
2091 );
2092 // apply options
2093 if (upc) StringUpper(s);
2094 else if (lowc) StringLower(s);
2095 fRecordSize+=s.size();
2096 // set substitution parameters
2097 n=k+1-i;
2098 break;
2099 case dsh_recordsize:
2100 if (aItemP) StringObjPrintf(s,"%ld",(long)fRecordSize);
2101 break;
2102 #ifdef ODBCAPI_SUPPORT
2103 case dsh_moddate:
2104 // %dM = modified date
2105 fAgentP->lineartimeToODBCLiteralAppend(fCurrentSyncTime,s,true,false,TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)),fConfigP->fDataTimeZone);
2106 break;
2107 case dsh_modtime:
2108 // %tM = modified time
2109 fAgentP->lineartimeToODBCLiteralAppend(fCurrentSyncTime,s,false,true,TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)),fConfigP->fDataTimeZone);
2110 break;
2111 #endif // ODBCAPI_SUPPORT
2112 case dsh_moddatetime:
2113 // %M = modified datetimestamp
2114 #ifdef SQLITE_SUPPORT1
2115 if (fUseSQLite) {
2116 // integer timestamp modes
2117 fAgentP->lineartimeToIntLiteralAppend(fCurrentSyncTime,s,fConfigP->fLastModDBFieldType,TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)),fConfigP->fDataTimeZone);
2118 }
2119 #ifdef ODBCAPI_SUPPORT
2120 else
2121 #endif
2122 #endif
2123 #ifdef ODBCAPI_SUPPORT
2124 {
2125 if (fConfigP->fLastModDBFieldType==dbft_timestamp)
2126 fAgentP->lineartimeToODBCLiteralAppend(fCurrentSyncTime,s,true,true,TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)),fConfigP->fDataTimeZone);
2127 else
2128 fAgentP->lineartimeToIntLiteralAppend(fCurrentSyncTime,s,fConfigP->fLastModDBFieldType,TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)),fConfigP->fDataTimeZone);
2129 }
2130 #endif // ODBCAPI_SUPPORT
2131 ; // make sure we can't else out the break
2132 break;
2133 #ifdef OBJECT_FILTERING1
2134 case dsh_andfilter:
2135 if (fFilterWorksOnDBLevel && fFilterExpressionTested)
2136 appendFiltersClause(s,"AND");
2137 break;
2138 case dsh_wherefilter:
2139 if (fFilterWorksOnDBLevel && fFilterExpressionTested)
2140 appendFiltersClause(s,"WHERE");
2141 break;
2142 #endif
2143 default:
2144 i+=n; // continue parsing after unhandled tag
2145 n=0; // no replacement
2146 break;
2147 } // switch
2148 // apply substitution if any (n>0)
2149 // - i=pos of %, n=size of text to be replaced, s=replacement string
2150 // Note: if n==0, i must point to where parsing should continue
2151 if (n>0) {
2152 // get everything up to this % sequence
2153 string intermediateStr;
2154 intermediateStr.assign(inStr,0,i);
2155 // have non-processed sequences processed for the other substitution possibilities now
2156 // %f=folderkey, %u=userkey, %d=devicekey, %t=targetkey, %C=domain
2157 // Note: it is important that %t and %d is checked here, after %d(), %tL and %tS above!!
2158 //DEBUGPRINTFX(DBG_DBAPI+DBG_EXOTIC,("Intermediate before applying basic substitutions: '%s'",intermediateStr.c_str()));
2159 DoSQLSubstitutions(intermediateStr);
2160 //DEBUGPRINTFX(DBG_DBAPI+DBG_EXOTIC,("Intermediate after applying basic substitutions: '%s'",intermediateStr.c_str()));
2161 // add it to the output string
2162 aSQL+=intermediateStr;
2163 // add the substitution (will NOT BE PROCESSED AGAIN!!)
2164 aSQL+=s;
2165 // have the rest of the inStr processed
2166 inStr.erase(0,i+n); // remove already processed stuff and escape sequence now
2167 // reset parsing position in inStr
2168 i=0;
2169 //DEBUGPRINTFX(DBG_DBAPI+DBG_EXOTIC,("- substitution with '%s' done, SQL so far: '%s'",s.c_str(),aSQL.c_str()));
2170 //DEBUGPRINTFX(DBG_DBAPI+DBG_EXOTIC,("Remaining input string to process: '%s'",inStr.c_str()));
2171 }
2172 // sequence substitution done
2173 break;
2174 }
2175 } // for
2176 if (handlerid>=dsh_NUMENTRIES) {
2177 // unknown sequence
2178 // - check if this is an explicit literal % (two % in sequence)
2179 if (inStr[i+1]=='%') {
2180 // yes, replace %% by % in output
2181 inStr.replace(i,2,"%");
2182 i+=1;
2183 }
2184 else {
2185 // no, just leave % in input as it is
2186 i+=1;
2187 }
2188 }
2189 } // while % found in string
2190 // process rest of inStr (not containing any of the above sequences, but probably some from
2191 // parent implementation
2192 // have non-processed sequences processed for the other substitution possibilities now
2193 // %f=folderkey, %u=userkey, %d=devicekey, %t=targetkey, %C=domain
2194 // Note: it is important that %t and %d is checked here, after %d(), %tL and %tS above!!
2195 DoSQLSubstitutions(inStr);
2196 // Append end of inStr to output
2197 aSQL+=inStr;
2198 // return flags
2199 if (aAllEmptyP) *aAllEmptyP=allempty;
2200 if (aDoneP) *aDoneP=done;
2201} // TODBCApiDS::DoDataSubstitutions
2202
2203
2204
2205// insert additional data selection conditions, if any. Returns true if something appended
2206bool TODBCApiDS::appendFiltersClause(string &aSQL, const char *linktext)
2207{
2208 #ifndef OBJECT_FILTERING1
2209 return false; // no filters
2210 #else
2211
2212 if (
2213 (!fConfigP->fFilterOnDBLevel || (fLocalDBFilter.empty() && fSyncSetFilter.empty() && fConfigP->fInvisibleFilter.empty()))
2214 #ifdef SCRIPT_SUPPORT1
2215 && fSQLFilter.empty()
2216 #endif
2217 )
2218 return false; // no filter clause needed
2219 // some conditions, add link text
2220 if (linktext) { aSQL+=' '; aSQL+=linktext; aSQL+=' '; }
2221 linktext=NULL__null; // link within paranthesis
2222 #ifdef SCRIPT_SUPPORT1
2223 // - Explicit SQL filter
2224 if (!fSQLFilter.empty()) {
2225 if (linktext) aSQL+=linktext;
2226 linktext=" AND ";
2227 aSQL+='(';
2228 aSQL+=fSQLFilter;
2229 aSQL+=')';
2230 }
2231 #endif
2232 // - hardcoded filter conditions
2233 if (!fLocalDBFilter.empty()) {
2234 if (linktext) aSQL+=linktext;
2235 linktext=" AND ";
2236 aSQL+='(';
2237 if (!appendFilterConditions(aSQL,fLocalDBFilter))
2238 throw TSyncException("<localdbfilter> incompatible with SQL database");
2239 aSQL+=')';
2240 }
2241 // - filter invisible items
2242 if (!fConfigP->fInvisibleFilter.empty()) {
2243 if (linktext) aSQL+=linktext;
2244 linktext=" AND ";
2245 aSQL+="NOT (";
2246 if (!appendFilterConditions(aSQL,fConfigP->fInvisibleFilter))
2247 throw TSyncException("<invisiblefilter> incompatible with SQL database");
2248 aSQL+=')';
2249 }
2250 // - sync set filter conditions
2251 if (!fSyncSetFilter.empty()) {
2252 if (linktext) aSQL+=linktext;
2253 linktext=" AND ";
Value stored to 'linktext' is never read
2254 aSQL+='(';
2255 if (!appendFilterConditions(aSQL,fSyncSetFilter))
2256 throw TSyncException("sync set filter incompatible with SQL database");
2257 aSQL+=')';
2258 }
2259 return true; // something appended
2260 #endif // OBJECT_FILTERING
2261} // TODBCApiDS::appendFiltersClause
2262
2263
2264
2265// reset all mapped parameters
2266void TODBCApiDS::resetSQLParameterMaps(void)
2267{
2268 #ifdef SQLITE_SUPPORT1
2269 if (fUseSQLite) {
2270 // resetting the parameter map finalizes any possibly running statement
2271 if (fSQLiteStmtP) {
2272 sqlite3_finalize(fSQLiteStmtP);
2273 fSQLiteStmtP=NULL__null;
2274 }
2275 }
2276 #endif
2277 fAgentP->resetSQLParameterMaps(fParameterMaps);
2278} // TODBCApiDS::resetSQLParameterMaps
2279
2280
2281// add parameter definition to the datastore level parameter list
2282void TODBCApiDS::addSQLParameterMap(
2283 bool aInParam, bool aOutParam,
2284 TParamMode aParamMode,
2285 TODBCFieldMapItem *aFieldMapP,
2286 TMultiFieldItem *aItemP,
2287 sInt16 aRepOffset
2288)
2289{
2290 TParameterMap map;
2291
2292 // assign basics
2293 map.inparam=aInParam;
2294 map.outparam=aOutParam;
2295 map.parammode=aParamMode;
2296 map.mybuffer=false;
2297 map.ParameterValuePtr=NULL__null;
2298 map.BufferLength=0;
2299 map.StrLen_or_Ind=SQL_NULL_DATA(-1); // note that this is not zero (but -1)
2300 map.itemP=aItemP;
2301 map.outSiz=NULL__null;
2302 // get things from field map
2303 if (aFieldMapP) {
2304 map.fieldP=getMappedFieldOrVar(*aItemP,aFieldMapP->fid,aRepOffset);
2305 map.maxSize=aFieldMapP->maxsize;
2306 map.dbFieldType=aFieldMapP->dbfieldtype;
2307 }
2308 else {
2309 map.fieldP=NULL__null;
2310 map.maxSize=0;
2311 map.dbFieldType=dbft_string;
2312 }
2313 // save in list
2314 fParameterMaps.push_back(map);
2315} // TODBCApiDS::addSQLParameterMap
2316
2317
2318
2319// prepare SQL statment as far as needed for parameter binding
2320void TODBCApiDS::prepareSQLStatement(SQLHSTMTlong aStatement, cAppCharP aSQL, bool aForData, cAppCharP aComment)
2321{
2322 // show what statement will be executed
2323 #ifdef SYDEBUG2
2324 if (aComment && aSQL && PDEBUGTEST(DBG_DBAPI)(((0x02000000) & getDbgMask()) == (0x02000000))) {
2325 PDEBUGPRINTFX(DBG_DBAPI,("SQL for %s:",aComment)){ if (((0x02000000) & getDbgMask()) == (0x02000000)) getDbgLogger
()->setNextMask(0x02000000).DebugPrintfLastMask ("SQL for %s:"
,aComment); }
;
2326 PDEBUGPUTSX(DBG_DBAPI,aSQL){ if (((0x02000000) & getDbgMask()) == (0x02000000)) getDbgLogger
()->DebugPuts( 0x02000000,aSQL); }
;
2327 }
2328 #endif
2329
2330 #ifdef SQLITE_SUPPORT1
2331 if (fUseSQLite && aForData) {
2332 // bind params
2333 fAgentP->prepareSQLiteStatement(
2334 aSQL,
2335 fSQLiteP,
2336 fSQLiteStmtP
2337 );
2338 }
2339 #endif
2340} // TODBCApiDS::prepareSQLStatement
2341
2342
2343// bind parameters (and values for IN-Params) to the statement
2344void TODBCApiDS::bindSQLParameters(SQLHSTMTlong aStatement, bool aForData)
2345{
2346 #ifdef SQLITE_SUPPORT1
2347 if (fUseSQLite && aForData) {
2348 // bind params
2349 fAgentP->bindSQLiteParameters(
2350 fSessionP,
2351 fSQLiteStmtP,
2352 fParameterMaps,
2353 fConfigP->fDataCharSet,
2354 fConfigP->fDataLineEndMode
2355 );
2356 }
2357 else
2358 #endif
2359 {
2360 #ifdef ODBCAPI_SUPPORT
2361 fAgentP->bindSQLParameters(
2362 fSessionP,
2363 aStatement,
2364 fParameterMaps,
2365 fConfigP->fDataCharSet, // actual charset, including utf16, bindSQLiteParameters handles UTF16 case
2366 fConfigP->fDataLineEndMode
2367 );
2368 #endif
2369 }
2370} // TODBCApiDS::bindSQLParameters
2371
2372
2373// save out parameter values and clean up
2374void TODBCApiDS::saveAndCleanupSQLParameters(
2375 SQLHSTMTlong aStatement,
2376 bool aForData
2377)
2378{
2379 #ifdef SQLITE_SUPPORT1
2380 if (fUseSQLite && aForData) {
2381 // NOP here because SQLite has no out params
2382 return;
2383 }
2384 else
2385 #endif
2386 {
2387 #ifdef ODBCAPI_SUPPORT
2388 fAgentP->saveAndCleanupSQLParameters(
2389 fSessionP,
2390 aStatement,
2391 fParameterMaps,
2392 fConfigP->fDataCharSet, // actual charset, including utf16, saveAndCleanupSQLParameters handles UTF16 case
2393 fConfigP->fDataLineEndMode
2394 );
2395 #endif
2396 }
2397} // TODBCApiDS::bindSQLParameters
2398
2399
2400// Fetch next row from SQL statement, returns true if there is any
2401bool TODBCApiDS::fetchNextRow(SQLHSTMTlong aStatement, bool aForData)
2402{
2403 #ifdef SQLITE_SUPPORT1
2404 if (fUseSQLite && aForData) {
2405 int rc = fStepRc;
2406 if (rc!=SQLITE_ROW100) {
2407 if (rc==SQLITE_OK0) {
2408 // last step was ok, we need to do next step first
2409 rc = sqlite3_step(fSQLiteStmtP);
2410 }
2411 }
2412 // clean up right now if we don't have data
2413 if (rc!=SQLITE_ROW100) {
2414 rc=sqlite3_finalize(fSQLiteStmtP);
2415 fStepRc = rc;
2416 fSQLiteStmtP=NULL__null;
2417 fAgentP->checkSQLiteError(rc,fSQLiteP);
2418 return false; // no more data
2419 }
2420 else {
2421 // we are reporting data now, make sure we fetch data in next call
2422 fStepRc=SQLITE_OK0;
2423 }
2424 // more data found
2425 return true; // ok
2426 }
2427 else
2428 #endif
2429 #ifdef ODBCAPI_SUPPORT
2430 {
2431 if (aStatement==SQL_NULL_HANDLE0)
2432 return false; // no statement, nothing fetched
2433 SQLRETURNlong res=SafeSQLFetch(aStatement);
2434 // check for end
2435 return fAgentP->checkStatementHasData(res,aStatement);
2436 }
2437 #else
2438 return false; // no API, no result
2439 #endif // ODBCAPI_SUPPORT
2440} // TODBCApiDS::fetchNextRow
2441
2442
2443
2444
2445
2446// SQL statement complete, finalize it
2447void TODBCApiDS::finalizeSQLStatement(SQLHSTMTlong aStatement, bool aForData)
2448{
2449 #ifdef SQLITE_SUPPORT1
2450 if (fUseSQLite && aForData) {
2451 // finalize if not already done
2452 if (fSQLiteStmtP) {
2453 sqlite3_finalize(fSQLiteStmtP);
2454 fSQLiteStmtP=NULL__null;
2455 }
2456 }
2457 else
2458 #endif
2459 {
2460 #ifdef ODBCAPI_SUPPORT
2461 // just close cursor of the statement
2462 SafeSQLCloseCursor(aStatement);
2463 #endif // ODBCAPI_SUPPORT
2464 }
2465} // TODBCApiDS::prepareSQLStatement
2466
2467
2468
2469#ifdef OBJECT_FILTERING1
2470
2471// - returns true if DB implementation can filter the standard filters
2472// (LocalDBFilter, TargetFilter and InvisibleFilter) during database fetch
2473// - otherwise, fetched items will be filtered after being read from DB.
2474bool TODBCApiDS::dsFilteredFetchesFromDB(bool aFilterChanged)
2475{
2476 // can do filtering while fetching from DB, such that items that get (in)visible
2477 // because of changed filter conditions are correctly detected as added/deleted
2478 if (aFilterChanged || !fFilterExpressionTested) {
2479 fFilterExpressionTested=true;
2480 if (fConfigP->fFilterOnDBLevel) {
2481 // try to dummy-append the filterclause to check if this will work
2482 string s;
2483 try {
2484 appendFiltersClause(s,"");
2485 fFilterWorksOnDBLevel=true;
2486 }
2487 catch(exception &e) {
2488 PDEBUGPRINTFX(DBG_FILTER+DBG_HOT,("%s -> filter will be applied to fetched records",e.what())){ if (((0x08000000 +0x00000001) & getDbgMask()) == (0x08000000
+0x00000001)) getDbgLogger()->setNextMask(0x08000000 +0x00000001
).DebugPrintfLastMask ("%s -> filter will be applied to fetched records"
,e.what()); }
;
2489 fFilterWorksOnDBLevel=false;
2490 }
2491 }
2492 else
2493 fFilterWorksOnDBLevel=false; // reject all DB level filtering
2494 }
2495 // if we can filter, that's sufficient
2496 if (fFilterWorksOnDBLevel) return true;
2497 // otherwise, let ancestor test
2498 return inherited::dsFilteredFetchesFromDB(aFilterChanged);
2499} // TODBCApiDS::dsfilteredFetchesFromDB
2500
2501
2502// - appends logical condition to SQL from filter string
2503bool TODBCApiDS::appendFilterConditions(string &aSQL, const string &aFilter)
2504{
2505 const char *p=aFilter.c_str();
2506 return appendFilterTerm(aSQL,p,p+aFilter.size());
2507} // TODBCApiDS::appendFilterConditions
2508
2509
2510// - appends logical condition term to SQL from filter string
2511bool TODBCApiDS::appendFilterTerm(string &aSQL, const char *&aPos, const char *aStop)
2512{
2513 char c=0;
2514 const char *st;
2515 string str,cmp,val;
2516 bool result;
2517 sInt16 fid;
2518 bool specialValue;
2519 bool caseInsensitive;
2520
2521 // determine max length
2522 if (aStop==NULL__null) aStop=aPos+strlen(aPos);
2523 // empty expression is ok
2524 do {
2525 result=true;
2526 // process simple term (<ident><op><value>)
2527 // - get first non-space
2528 while (aPos<=aStop) {
2529 c=*aPos;
2530 if (c!=' ') break;
2531 aPos++;
2532 }
2533 // Term starts here, first char is c, aPos points to it
2534 // - check subexpression paranthesis
2535 if (c=='(') {
2536 // boolean term is grouped subexpression
2537 aSQL+='(';
2538 aPos++;
2539 result=appendFilterTerm(aSQL,aPos,aStop);
2540 // check if matching paranthesis
2541 if (*(aPos++)!=')') {
2542 PDEBUGPRINTFX(DBG_EXOTIC,("Filter expression syntax error at: %s",--aPos)){ if (((0x80000000) & getDbgMask()) == (0x80000000)) getDbgLogger
()->setNextMask(0x80000000).DebugPrintfLastMask ("Filter expression syntax error at: %s"
,--aPos); }
;
2543 aPos=aStop; // skip rest
2544 return false; // always fail
2545 }
2546 aSQL+=')';
2547 }
2548 else if (c==0) {
2549 // empty term, is ok
2550 return true;
2551 }
2552 else {
2553 // must be simple boolean term
2554 // - remember start of ident
2555 st=aPos;
2556 // - search end of ident
2557 while (isFilterIdent(c)(isalnum(c) || c=='_' || c=='.')) c=*(++aPos);
2558 // - c/aPos=char after ident, get ident
2559 str.assign(st,aPos-st);
2560 // - check for subscript index
2561 uInt16 subsIndex=0; // no index (is 1-based in DS 1.2 filter specs)
2562 if (c=='[') {
2563 // expect numeric index
2564 aPos++; // next
2565 aPos+=StrToUShort(aPos,subsIndex);
2566 if (*aPos!=']') {
2567 PDEBUGPRINTFX(DBG_ERROR,("Filter expression error (missing \"]\") at: %s",--aPos)){ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ("Filter expression error (missing \"]\") at: %s"
,--aPos); }
;
2568 return false; // syntax error, does not pass
2569 }
2570 c=*(++aPos); // process next after subscript
2571 }
2572 // - get operator
2573 while (isspace(c)) c=*(++aPos);
2574 if (c==':') c=*(++aPos); // ignore assign-to-make-true modifier
2575 specialValue = c=='*'; // special-value modifier
2576 if (specialValue) c=*(++aPos);
2577 caseInsensitive = c=='^'; // case insensitive modifier
2578 if (caseInsensitive) c=*(++aPos);
2579 aPos++; // one char at least
2580 cmp.erase();
2581 bool cmplike=c=='%' || c=='$';
2582 if (cmplike) cmp= (c=='%') ? " LIKE " : " NOT LIKE "; // "contains" special case, use SQL LIKE %val%
2583 else {
2584 cmp+=c; // simply use first char as is
2585 if (c=='>' || c=='<') {
2586 if (*aPos=='=' || *aPos=='>') {
2587 c=*aPos++;
2588 cmp+=c; // >=, <= and <>
2589 }
2590 }
2591 }
2592 // - get comparison value
2593 while (isspace(c)) c=*(++aPos);
2594 st=aPos; // should start here
2595 while (aPos<aStop && *aPos!='&' && *aPos!='|' && *aPos!=')') aPos++;
2596 // - assign value string
2597 if (cmplike) val='%'; else val.erase();
2598 val.append(st,aPos-st);
2599 if (cmplike) val+='%';
2600 // Now we have str=field identifier, cmp=operator, val=value string
2601 // - check if directly addressing DB field
2602 if (strucmp(str.c_str(),"D.",2)==0) {
2603 // this directly refers to a DB field
2604 // - translate for special values
2605 if (specialValue) {
2606 if (val=="N" || val=="E") {
2607 val=" NULL";
2608 caseInsensitive=false; // not needed for NULL
2609 cmp="IS";
2610 if (cmp!="=") cmp+=" NOT";
2611 }
2612 }
2613 if (caseInsensitive) {
2614 aSQL+="LOWER(";
2615 aSQL.append(str.c_str()+2);
2616 aSQL+=')';
2617 aSQL+=cmp; // operator
2618 aSQL+="LOWER(";
2619 aSQL+=val;
2620 aSQL+=')';
2621 }
2622 else {
2623 aSQL.append(str.c_str()+2);
2624 aSQL+=cmp; // operator
2625 aSQL+=val; // value must be specified in DB syntax (including quotes for strings)
2626 }
2627 }
2628 else {
2629 // get field ID for that ident (can be -1 if none found)
2630 TMultiFieldItemType *mfitP = dynamic_cast<TMultiFieldItemType *>(getLocalSendType());
2631 if (mfitP)
2632 fid=mfitP->getFilterIdentifierFieldIndex(str.c_str(),subsIndex);
2633 else
2634 return false; // field not known, bad syntax
2635 if (fid==FID_NOT_SUPPORTED-128) return false; // unknown field
2636 // search for map for that field id to obtain DB field name
2637 TFieldMapList &fml = fConfigP->fFieldMappings.fFieldMapList;
2638 TFieldMapList::iterator mainpos;
2639 TFieldMapList::iterator pos;
2640 bool mainfound=false;
2641 for (pos=fml.begin(); pos!=fml.end(); pos++) {
2642 // check field id, but search in setNo==0 only (fields of other sets are
2643 // not available when SELECTing syncset.
2644 if ((*pos)->setNo==0 && fid==(*pos)->fid) {
2645 if ((*pos)->isArray()) return false; // array fields cannot be used in filters
2646 // found
2647 if (mainfound) break; // second match just breaks loop (time for date, possibly)
2648 // main
2649 mainpos=pos; // save position of main field
2650 mainfound=true;
2651 // search for further fields with same fid (possibly needed for timefordate)
2652 }
2653 }
2654 if (!mainfound) {
2655 PDEBUGPRINTFX(DBG_EXOTIC,("Could not find DB field for fid=%hd",fid)){ if (((0x80000000) & getDbgMask()) == (0x80000000)) getDbgLogger
()->setNextMask(0x80000000).DebugPrintfLastMask ("Could not find DB field for fid=%hd"
,fid); }
;
2656 return false; // no such field
2657 }
2658 // - translate for special values
2659 if (specialValue) {
2660 if ((*mainpos)->dbfieldtype==dbft_string && val=="E") {
2661 // Empty is not NULL for strings
2662 val="''";
2663 }
2664 else if (val=="N" || val=="E") {
2665 // for other types, NULL and EMPTY are the same
2666 val="NULL";
2667 if (cmp!="=") cmp=" IS NOT ";
2668 else cmp=" IS ";
2669 }
2670 // now append (no check for caseInsensitive needed as NULL check does not need it
2671 aSQL+=(*mainpos)->fElementName;
2672 aSQL+=cmp;
2673 aSQL+=val;
2674 }
2675 else {
2676 // create field to convert value string correctly
2677 TItemField *valfldP = newItemField(
2678 mfitP->getFieldDefinition(fid)->type,
2679 getSessionZones()
2680 );
2681 // assign value as string
2682 valfldP->setAsString(val.c_str());
2683 // check for special case when timestamp is mapped to separate date/time fields
2684 if (
2685 pos!=fml.end() && ( // if there is another map with the same fid
2686 ((*mainpos)->dbfieldtype==dbft_date && (*pos)->dbfieldtype==dbft_timefordate)
2687 )
2688 ) {
2689 // separate date & time
2690 aSQL+='(';
2691 if (cmp=="=" || cmp=="<>") {
2692 // (datefield=val AND timefield=val)
2693 // (datefield<>val OR timefield<>val)
2694 aSQL+=(*mainpos)->fElementName;
2695 aSQL+=cmp;
2696 appendFieldValueLiteral(*valfldP, (*mainpos)->dbfieldtype,(*mainpos)->maxsize, (*mainpos)->floating_ts, aSQL);
2697 if (cmp=="=")
2698 aSQL+=" AND ";
2699 else
2700 aSQL+=" OR ";
2701 // - time field
2702 aSQL+=(*pos)->fElementName;
2703 aSQL+=cmp;
2704 appendFieldValueLiteral(*valfldP, (*pos)->dbfieldtype,(*pos)->maxsize, (*mainpos)->floating_ts, aSQL);
2705 }
2706 else {
2707 // (datefield >< val OR (datefield = val AND timefield >< val))
2708 aSQL+=(*mainpos)->fElementName;
2709 aSQL+=cmp;
2710 appendFieldValueLiteral(*valfldP, (*mainpos)->dbfieldtype,(*mainpos)->maxsize, (*mainpos)->floating_ts, aSQL);
2711 aSQL+=" OR (";
2712 aSQL+=(*mainpos)->fElementName;
2713 aSQL+='=';
2714 appendFieldValueLiteral(*valfldP, (*mainpos)->dbfieldtype,(*mainpos)->maxsize, (*mainpos)->floating_ts, aSQL);
2715 aSQL+=" AND ";
2716 // - time field
2717 aSQL+=(*pos)->fElementName;
2718 aSQL+=cmp;
2719 appendFieldValueLiteral(*valfldP, (*pos)->dbfieldtype,(*pos)->maxsize, (*mainpos)->floating_ts, aSQL);
2720 aSQL+=')';
2721 }
2722 aSQL+=')';
2723 }
2724 else {
2725 // standard case
2726 // field ><= val
2727 if (caseInsensitive) {
2728 aSQL+="LOWER(";
2729 aSQL+=(*mainpos)->fElementName;
2730 aSQL+=')';
2731 aSQL+=cmp; // operator
2732 aSQL+="LOWER(";
2733 appendFieldValueLiteral(*valfldP, (*mainpos)->dbfieldtype,(*mainpos)->maxsize, (*mainpos)->floating_ts, aSQL);
2734 aSQL+=')';
2735 }
2736 else {
2737 aSQL+=(*mainpos)->fElementName;
2738 aSQL+=cmp;
2739 appendFieldValueLiteral(*valfldP, (*mainpos)->dbfieldtype,(*mainpos)->maxsize, (*mainpos)->floating_ts, aSQL);
2740 }
2741 }
2742 // field can be deleted now
2743 delete valfldP;
2744 } // not special value
2745 } // refers to field
2746 } // simple boolean term
2747 // now check logical operators
2748 // - skip spaces
2749 c=*aPos++;
2750 while (c==' ') c=*(++aPos);
2751 // - check char at aPos
2752 if (c=='|') aSQL+=" OR ";
2753 else if (c=='&') aSQL+= " AND ";
2754 else {
2755 aPos--; // let caller check it
2756 break; // end of logical term
2757 }
2758 } while (true); // process terms until all done
2759 // conversion ok
2760 return true;
2761} // TODBCApiDS::appendFilterTerm
2762
2763
2764#endif // OBJECT_FILTERING
2765
2766
2767#ifdef ODBCAPI_SUPPORT
2768
2769// get one or two successive columns as time stamp depending on config
2770void TODBCApiDS::getColumnsAsTimestamp(
2771 SQLHSTMTlong aStatement,
2772 sInt16 &aColNumber, // will be updated by 1 or 2
2773 bool aCombined,
2774 lineartime_t &aTimestamp,
2775 timecontext_t aTargetContext
2776)
2777{
2778 lineartime_t t;
2779
2780 if (aCombined) {
2781 // combined date/time
2782 fAgentP->getColumnValueAsTimestamp(aStatement,aColNumber++,aTimestamp);
2783 }
2784 else {
2785 // date, then time
2786 fAgentP->getColumnValueAsDate(aStatement,aColNumber++,aTimestamp);
2787 fAgentP->getColumnValueAsTime(aStatement,aColNumber++,t);
2788 aTimestamp+=t; // add time to date
2789 }
2790 // convert to target zone requested
2791 if (!TCTX_IS_UNKNOWN(aTargetContext)) {
2792 TzConvertTimestamp(aTimestamp,fConfigP->fDataTimeZone,aTargetContext,getSessionZones(),TCTX_UNKNOWN((timecontext_t) ((tctx_tz_unknown) | TCTX_SYMBOLIC_TZ)));
2793 }
2794} // TODBCApiDS::getColumnsAsTimestamp
2795
2796#endif // ODBCAPI_SUPPORT
2797
2798
2799// - get a column as integer based timestamp
2800lineartime_t TODBCApiDS::dbIntToLineartimeAs(
2801 sInt64 aDBInt, TDBFieldType aDbfty,
2802 timecontext_t aTargetContext
2803)
2804{
2805 lineartime_t ts = dbIntToLineartime(aDBInt, aDbfty);
2806 // convert to target zone requested
2807 if (!TCTX_IS_UNKNOWN(aTargetContext)) {
2808 TzConvertTimestamp(ts,fConfigP->fDataTimeZone,aTargetContext,getSessionZones(),TCTX_UNKNOWN((timecontext_t) ((tctx_tz_unknown) | TCTX_SYMBOLIC_TZ)));
2809 }
2810 return ts;
2811} // TODBCApiDS::dbIntToLineartimeAs
2812
2813
2814
2815// create local ID with special algorithm
2816void TODBCApiDS::createLocalID(string &aLocalID,TSpecialIDMode aSpecialIDMode)
2817{
2818 lineartime_t unixms;
2819 switch (aSpecialIDMode) {
2820 case sidm_unixmsrnd6:
2821 // ID is generated by UNIX time in milliseconds, with 6 digits of random
2822 // appended.
2823 // - get Unix time in milliseconds:
2824 unixms =
2825 (getSession()->getSystemNowAs(TCTX_SYSTEM((timecontext_t) ((tctx_tz_system) | TCTX_SYMBOLIC_TZ)))-UnixToLineartimeOffset) // unix time in lineartime_t units
2826 *(1000/secondToLinearTimeFactor); // convert into ms
2827 unixms*=1000000; // room for 6 more digits
2828 // - add random number between 0 and 999999
2829 unixms+=(sInt32)rand()*1000000/RAND_MAX2147483647;
2830 // - make numeric ID out of this
2831 StringObjPrintf(aLocalID,"%lld",(long long)unixms);
2832 break;
2833 default:
2834 aLocalID="<error>";
2835 }
2836} // TODBCApiDS::createLocalID
2837
2838
2839#ifdef HAS_SQL_ADMIN
2840
2841// update sync target
2842localstatus TODBCApiDS::updateSyncTarget(SQLHSTMTlong aStatement, bool aSessionFinished)
2843{
2844 string sql,s;
2845 localstatus sta=LOCERR_OK;
2846
2847 resetSQLParameterMaps();
2848 sql = fConfigP->fUpdateSyncTargetSQL;
2849 sInt32 i;
2850 // Suspend/Resume
2851 // - alert code for next resume
2852 StringSubst(sql,"%SUA",fResumeAlertCode,4);
2853 // - suspend reference time
2854 i=0; while((i=sql.find("%dSU",i))!=string::npos) {
2855 s.erase();
2856 fAgentP->lineartimeToLiteralAppend(fPreviousSuspendCmpRef, s, true, false, TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)), fConfigP->fDataTimeZone);
2857 sql.replace(i,4,s); i+=s.size();
2858 }
2859 // - suspend reference time
2860 i=0; while((i=sql.find("%tSU",i))!=string::npos) {
2861 s.erase();
2862 fAgentP->lineartimeToLiteralAppend(fPreviousSuspendCmpRef, s, false, true, TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)), fConfigP->fDataTimeZone);
2863 sql.replace(i,4,s); i+=s.size();
2864 }
2865 // - suspend reference time
2866 i=0; while((i=sql.find("%SU",i))!=string::npos) {
2867 s.erase();
2868 fAgentP->lineartimeToLiteralAppend(fPreviousSuspendCmpRef, s, true, true, TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)), fConfigP->fDataTimeZone);
2869 sql.replace(i,3,s); i+=s.size();
2870 }
2871 // - last suspend identifier (for derived datastores that might need another token than time)
2872 StringSubst(sql,"%iSU",fPreviousSuspendIdentifier,4,fConfigP->sqlPrepCharSet(),fConfigP->fDataLineEndMode,fConfigP->fQuotingMode);
2873 // - anchor
2874 StringSubst(sql,"%A",fLastRemoteAnchor,2,fConfigP->sqlPrepCharSet(),fConfigP->fDataLineEndMode,fConfigP->fQuotingMode);
2875 // Suspend/Resume in mid-chunk
2876 // - lastitem
2877 StringSubst(sql,"%pSU",fLastSourceURI,4,fConfigP->sqlPrepCharSet(),fConfigP->fDataLineEndMode,fConfigP->fQuotingMode);
2878 StringSubst(sql,"%pTU",fLastTargetURI,4,fConfigP->sqlPrepCharSet(),fConfigP->fDataLineEndMode,fConfigP->fQuotingMode);
2879 // - lastitemstatus
2880 StringSubst(sql,"%pSt",fLastItemStatus,4);
2881 // - partial item State/Mode
2882 long pista;
2883 if (fPartialItemState==pi_state_save_incoming)
2884 pista=(long)pi_state_loaded_incoming;
2885 else if (fPartialItemState==pi_state_save_outgoing)
2886 pista=(long)pi_state_loaded_outgoing;
2887 else
2888 pista=(long)pi_state_none;
2889 StringSubst(sql,"%pM",pista,3);
2890 // - size info
2891 StringSubst(sql,"%pTS",fPITotalSize,4);
2892 StringSubst(sql,"%pUS",fPIUnconfirmedSize,4);
2893 StringSubst(sql,"%pSS",fPIStoredSize,4);
2894 // - buffered data
2895 i=0; while((i=sql.find("%pDAT",i))!=string::npos) {
2896 sql.replace(i,5,"?"); i+=1;
2897 // now bind data to param
2898 TParameterMap map;
2899 map.inparam=true;
2900 map.outparam=false;
2901 map.parammode=param_buffer;
2902 map.outSiz=NULL__null;
2903 map.dbFieldType=dbft_blob;
2904 // - pass the buffer
2905 map.mybuffer=false; // not owned by ODBC api
2906 if (fPIStoredDataP) {
2907 map.BufferLength=fPIStoredSize;
2908 map.StrLen_or_Ind=fPIStoredSize;
2909 map.ParameterValuePtr=fPIStoredDataP; // the BLOB data to store
2910 }
2911 else {
2912 map.BufferLength=0;
2913 map.StrLen_or_Ind=SQL_NULL_DATA(-1); // no data
2914 map.ParameterValuePtr=NULL__null;
2915 }
2916 // save in list
2917 fParameterMaps.push_back(map);
2918 }
2919 // - last to remote sync reference date
2920 // Note: If we DON'T HAVE fSyncTimeStampAtEnd, but HAVE fOneWayFromRemoteSupported,
2921 // we MUST NOT store the reference time here, but save save session start time.
2922 // The reference time will be saved under %dRS
2923 lineartime_t dltime;
2924 if (!fConfigP->fSyncTimeStampAtEnd && fConfigP->fOneWayFromRemoteSupported)
2925 dltime = fPreviousSyncTime;
2926 else
2927 dltime = fPreviousToRemoteSyncCmpRef;
2928 i=0; while((i=sql.find("%dL",i))!=string::npos) {
2929 s.erase();
2930 fAgentP->lineartimeToLiteralAppend(dltime, s, true, false, TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)), fConfigP->fDataTimeZone);
2931 sql.replace(i,3,s); i+=s.size();
2932 }
2933 // - last to remote sync reference time
2934 i=0; while((i=sql.find("%tL",i))!=string::npos) {
2935 s.erase();
2936 fAgentP->lineartimeToLiteralAppend(dltime, s, false, true, TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)), fConfigP->fDataTimeZone);
2937 sql.replace(i,3,s); i+=s.size();
2938 }
2939 // - last to remote sync reference date/timestamp
2940 i=0; while((i=sql.find("%L",i))!=string::npos) {
2941 s.erase();
2942 fAgentP->lineartimeToLiteralAppend(dltime, s, true, true, TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)), fConfigP->fDataTimeZone);
2943 sql.replace(i,2,s); i+=s.size();
2944 }
2945 // - last sync session start date
2946 i=0; while((i=sql.find("%dS",i))!=string::npos) {
2947 s.erase();
2948 fAgentP->lineartimeToLiteralAppend(fPreviousSyncTime, s, true, false, TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)), fConfigP->fDataTimeZone);
2949 sql.replace(i,3,s); i+=s.size();
2950 }
2951 // - last sync session start time
2952 i=0; while((i=sql.find("%tS",i))!=string::npos) {
2953 s.erase();
2954 fAgentP->lineartimeToLiteralAppend(fPreviousSyncTime, s, false, true, TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)), fConfigP->fDataTimeZone);
2955 sql.replace(i,3,s); i+=s.size();
2956 }
2957 // - last sync session start date/timestamp
2958 i=0; while((i=sql.find("%S",i))!=string::npos) {
2959 s.erase();
2960 fAgentP->lineartimeToLiteralAppend(fPreviousSyncTime, s, true, true, TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)), fConfigP->fDataTimeZone);
2961 sql.replace(i,2,s); i+=s.size();
2962 }
2963 // - last sync with data to remote date
2964 i=0; while((i=sql.find("%dRL",i))!=string::npos) {
2965 s.erase();
2966 fAgentP->lineartimeToLiteralAppend(fPreviousToRemoteSyncCmpRef, s, true, false, TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)), fConfigP->fDataTimeZone);
2967 sql.replace(i,4,s); i+=s.size();
2968 }
2969 // - last sync with data to remote time
2970 i=0; while((i=sql.find("%tRL",i))!=string::npos) {
2971 s.erase();
2972 fAgentP->lineartimeToLiteralAppend(fPreviousToRemoteSyncCmpRef, s, false, true, TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)), fConfigP->fDataTimeZone);
2973 sql.replace(i,4,s); i+=s.size();
2974 }
2975 // - last sync with data to remote date/timestamp
2976 i=0; while((i=sql.find("%RL",i))!=string::npos) {
2977 s.erase();
2978 fAgentP->lineartimeToLiteralAppend(fPreviousToRemoteSyncCmpRef, s, true, true, TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)), fConfigP->fDataTimeZone);
2979 sql.replace(i,3,s); i+=s.size();
2980 }
2981 // - last sync with data to remote identifier (for derived datastores that might need another token than time)
2982 StringSubst(sql,"%iRL",fPreviousToRemoteSyncIdentifier,4,fConfigP->sqlPrepCharSet(),fConfigP->fDataLineEndMode,fConfigP->fQuotingMode);
2983 // - last anchor for sync with data to remote date
2984 // Note: this is only needed if we DON'T HAVE fSyncTimeStampAtEnd, but HAVE fOneWayFromRemoteSupported,
2985 // because then the %L cannot be used as reference time (it must save session time)
2986 i=0; while((i=sql.find("%dRS",i))!=string::npos) {
2987 s.erase();
2988 fAgentP->lineartimeToLiteralAppend(fPreviousToRemoteSyncCmpRef, s, true, false, TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)), fConfigP->fDataTimeZone);
2989 sql.replace(i,4,s); i+=s.size();
2990 }
2991 // - last server anchor with data to remote time
2992 i=0; while((i=sql.find("%tRS",i))!=string::npos) {
2993 s.erase();
2994 fAgentP->lineartimeToLiteralAppend(fPreviousToRemoteSyncCmpRef, s, false, true, TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)), fConfigP->fDataTimeZone);
2995 sql.replace(i,4,s); i+=s.size();
2996 }
2997 // - last server anchor with data to remote date/timestamp
2998 i=0; while((i=sql.find("%RS",i))!=string::npos) {
2999 s.erase();
3000 fAgentP->lineartimeToLiteralAppend(fPreviousToRemoteSyncCmpRef, s, true, true, TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)), fConfigP->fDataTimeZone);
3001 sql.replace(i,3,s); i+=s.size();
3002 }
3003 // now substitute standard: %f=folderkey, %u=userkey, %d=devicekey, %t=targetkey
3004 // - Note: it is important that %t,%d is checked here, after %dL, %dS, %tL and %tS above!!
3005 DoSQLSubstitutions(sql);
3006 // - bind possible params
3007 bindSQLParameters(aStatement,false);
3008 // - issue
3009 execSQLStatement(aStatement,sql,false,"updating anchor/lastsync",false);
3010 return LOCERR_OK;
3011} // TODBCApiDS::updateSyncTarget
3012
3013
3014
3015// update map changes from memory list into actual map table
3016localstatus TODBCApiDS::updateODBCMap(SQLHSTMTlong aStatement, bool aSessionFinishedSuccessfully)
3017{
3018 string sql,s;
3019 localstatus sta=LOCERR_OK;
3020 TMapContainer::iterator pos;
3021
3022 // now save the entire list differentially
3023 pos=fMapTable.begin();
3024 DEBUGPRINTFX(DBG_ADMIN+DBG_EXOTIC,("updateODBCMap: internal map table has %ld entries (normal and others)",fMapTable.size())){ if (((0x00000040 +0x80000000) & getDbgMask()) == (0x00000040
+0x80000000)) getDbgLogger()->setNextMask(0x00000040 +0x80000000
).DebugPrintfLastMask ("updateODBCMap: internal map table has %ld entries (normal and others)"
,fMapTable.size()); }
;
3025 while (pos!=fMapTable.end()) {
3026 DEBUGPRINTFX(DBG_ADMIN+DBG_EXOTIC,({ if (((0x00000040 +0x80000000) & getDbgMask()) == (0x00000040
+0x80000000)) getDbgLogger()->setNextMask(0x00000040 +0x80000000
).DebugPrintfLastMask ( "updateODBCMap: entryType=%s, localid='%s', remoteID='%s', mapflags=0x%lX, changed=%d, deleted=%d, added=%d, markforresume=%d, savedmark=%d"
, MapEntryTypeNames[(*pos).entrytype], (*pos).localid.c_str()
, (*pos).remoteid.c_str(), (*pos).mapflags, (int)(*pos).changed
, (int)(*pos).deleted, (int)(*pos).added, (int)(*pos).markforresume
, (int)(*pos).savedmark ); }
3027 "updateODBCMap: entryType=%s, localid='%s', remoteID='%s', mapflags=0x%lX, changed=%d, deleted=%d, added=%d, markforresume=%d, savedmark=%d",{ if (((0x00000040 +0x80000000) & getDbgMask()) == (0x00000040
+0x80000000)) getDbgLogger()->setNextMask(0x00000040 +0x80000000
).DebugPrintfLastMask ( "updateODBCMap: entryType=%s, localid='%s', remoteID='%s', mapflags=0x%lX, changed=%d, deleted=%d, added=%d, markforresume=%d, savedmark=%d"
, MapEntryTypeNames[(*pos).entrytype], (*pos).localid.c_str()
, (*pos).remoteid.c_str(), (*pos).mapflags, (int)(*pos).changed
, (int)(*pos).deleted, (int)(*pos).added, (int)(*pos).markforresume
, (int)(*pos).savedmark ); }
3028 MapEntryTypeNames[(*pos).entrytype],{ if (((0x00000040 +0x80000000) & getDbgMask()) == (0x00000040
+0x80000000)) getDbgLogger()->setNextMask(0x00000040 +0x80000000
).DebugPrintfLastMask ( "updateODBCMap: entryType=%s, localid='%s', remoteID='%s', mapflags=0x%lX, changed=%d, deleted=%d, added=%d, markforresume=%d, savedmark=%d"
, MapEntryTypeNames[(*pos).entrytype], (*pos).localid.c_str()
, (*pos).remoteid.c_str(), (*pos).mapflags, (int)(*pos).changed
, (int)(*pos).deleted, (int)(*pos).added, (int)(*pos).markforresume
, (int)(*pos).savedmark ); }
3029 (*pos).localid.c_str(),{ if (((0x00000040 +0x80000000) & getDbgMask()) == (0x00000040
+0x80000000)) getDbgLogger()->setNextMask(0x00000040 +0x80000000
).DebugPrintfLastMask ( "updateODBCMap: entryType=%s, localid='%s', remoteID='%s', mapflags=0x%lX, changed=%d, deleted=%d, added=%d, markforresume=%d, savedmark=%d"
, MapEntryTypeNames[(*pos).entrytype], (*pos).localid.c_str()
, (*pos).remoteid.c_str(), (*pos).mapflags, (int)(*pos).changed
, (int)(*pos).deleted, (int)(*pos).added, (int)(*pos).markforresume
, (int)(*pos).savedmark ); }
3030 (*pos).remoteid.c_str(),{ if (((0x00000040 +0x80000000) & getDbgMask()) == (0x00000040
+0x80000000)) getDbgLogger()->setNextMask(0x00000040 +0x80000000
).DebugPrintfLastMask ( "updateODBCMap: entryType=%s, localid='%s', remoteID='%s', mapflags=0x%lX, changed=%d, deleted=%d, added=%d, markforresume=%d, savedmark=%d"
, MapEntryTypeNames[(*pos).entrytype], (*pos).localid.c_str()
, (*pos).remoteid.c_str(), (*pos).mapflags, (int)(*pos).changed
, (int)(*pos).deleted, (int)(*pos).added, (int)(*pos).markforresume
, (int)(*pos).savedmark ); }
3031 (*pos).mapflags,{ if (((0x00000040 +0x80000000) & getDbgMask()) == (0x00000040
+0x80000000)) getDbgLogger()->setNextMask(0x00000040 +0x80000000
).DebugPrintfLastMask ( "updateODBCMap: entryType=%s, localid='%s', remoteID='%s', mapflags=0x%lX, changed=%d, deleted=%d, added=%d, markforresume=%d, savedmark=%d"
, MapEntryTypeNames[(*pos).entrytype], (*pos).localid.c_str()
, (*pos).remoteid.c_str(), (*pos).mapflags, (int)(*pos).changed
, (int)(*pos).deleted, (int)(*pos).added, (int)(*pos).markforresume
, (int)(*pos).savedmark ); }
3032 (int)(*pos).changed,{ if (((0x00000040 +0x80000000) & getDbgMask()) == (0x00000040
+0x80000000)) getDbgLogger()->setNextMask(0x00000040 +0x80000000
).DebugPrintfLastMask ( "updateODBCMap: entryType=%s, localid='%s', remoteID='%s', mapflags=0x%lX, changed=%d, deleted=%d, added=%d, markforresume=%d, savedmark=%d"
, MapEntryTypeNames[(*pos).entrytype], (*pos).localid.c_str()
, (*pos).remoteid.c_str(), (*pos).mapflags, (int)(*pos).changed
, (int)(*pos).deleted, (int)(*pos).added, (int)(*pos).markforresume
, (int)(*pos).savedmark ); }
3033 (int)(*pos).deleted,{ if (((0x00000040 +0x80000000) & getDbgMask()) == (0x00000040
+0x80000000)) getDbgLogger()->setNextMask(0x00000040 +0x80000000
).DebugPrintfLastMask ( "updateODBCMap: entryType=%s, localid='%s', remoteID='%s', mapflags=0x%lX, changed=%d, deleted=%d, added=%d, markforresume=%d, savedmark=%d"
, MapEntryTypeNames[(*pos).entrytype], (*pos).localid.c_str()
, (*pos).remoteid.c_str(), (*pos).mapflags, (int)(*pos).changed
, (int)(*pos).deleted, (int)(*pos).added, (int)(*pos).markforresume
, (int)(*pos).savedmark ); }
3034 (int)(*pos).added,{ if (((0x00000040 +0x80000000) & getDbgMask()) == (0x00000040
+0x80000000)) getDbgLogger()->setNextMask(0x00000040 +0x80000000
).DebugPrintfLastMask ( "updateODBCMap: entryType=%s, localid='%s', remoteID='%s', mapflags=0x%lX, changed=%d, deleted=%d, added=%d, markforresume=%d, savedmark=%d"
, MapEntryTypeNames[(*pos).entrytype], (*pos).localid.c_str()
, (*pos).remoteid.c_str(), (*pos).mapflags, (int)(*pos).changed
, (int)(*pos).deleted, (int)(*pos).added, (int)(*pos).markforresume
, (int)(*pos).savedmark ); }
3035 (int)(*pos).markforresume,{ if (((0x00000040 +0x80000000) & getDbgMask()) == (0x00000040
+0x80000000)) getDbgLogger()->setNextMask(0x00000040 +0x80000000
).DebugPrintfLastMask ( "updateODBCMap: entryType=%s, localid='%s', remoteID='%s', mapflags=0x%lX, changed=%d, deleted=%d, added=%d, markforresume=%d, savedmark=%d"
, MapEntryTypeNames[(*pos).entrytype], (*pos).localid.c_str()
, (*pos).remoteid.c_str(), (*pos).mapflags, (int)(*pos).changed
, (int)(*pos).deleted, (int)(*pos).added, (int)(*pos).markforresume
, (int)(*pos).savedmark ); }
3036 (int)(*pos).savedmark{ if (((0x00000040 +0x80000000) & getDbgMask()) == (0x00000040
+0x80000000)) getDbgLogger()->setNextMask(0x00000040 +0x80000000
).DebugPrintfLastMask ( "updateODBCMap: entryType=%s, localid='%s', remoteID='%s', mapflags=0x%lX, changed=%d, deleted=%d, added=%d, markforresume=%d, savedmark=%d"
, MapEntryTypeNames[(*pos).entrytype], (*pos).localid.c_str()
, (*pos).remoteid.c_str(), (*pos).mapflags, (int)(*pos).changed
, (int)(*pos).deleted, (int)(*pos).added, (int)(*pos).markforresume
, (int)(*pos).savedmark ); }
3037 )){ if (((0x00000040 +0x80000000) & getDbgMask()) == (0x00000040
+0x80000000)) getDbgLogger()->setNextMask(0x00000040 +0x80000000
).DebugPrintfLastMask ( "updateODBCMap: entryType=%s, localid='%s', remoteID='%s', mapflags=0x%lX, changed=%d, deleted=%d, added=%d, markforresume=%d, savedmark=%d"
, MapEntryTypeNames[(*pos).entrytype], (*pos).localid.c_str()
, (*pos).remoteid.c_str(), (*pos).mapflags, (int)(*pos).changed
, (int)(*pos).deleted, (int)(*pos).added, (int)(*pos).markforresume
, (int)(*pos).savedmark ); }
;
3038 try {
3039 // check if item has changed since map table was read, or if its markforresume has changed
3040 // or if this is a successful end of a session, when we can safely assume that any pending maps
3041 // are from adds to the client that have never reached the client (otherwise, we'd have got
3042 // a map for it, even if the add was in a previous session or session attempt)
3043 if (
3044 (*pos).changed || (*pos).added || (*pos).deleted || // update of DB needed
3045 ((*pos).markforresume!=(*pos).savedmark) // mark for resume changed
3046 ) {
3047 // make sure it does not get written again if not really modified again
3048 (*pos).changed=false;
3049 // update new mapflags w/o changing mapflag_useforresume in the actual flags (as we still need it while session goes on)
3050 uInt32 newmapflags = (*pos).mapflags & ~mapflag_useforresume0x00000001;
3051 if ((*pos).markforresume)
3052 newmapflags |= mapflag_useforresume0x00000001;
3053 // remember last saved state
3054 (*pos).savedmark=(*pos).markforresume;
3055 // do something!
3056 if ((*pos).deleted) {
3057 if (!(*pos).added) {
3058 // delete this entry (only needed if it was not also added since last save - otherwise, map entry was never saved to the DB yet)
3059 IssueMapSQL(aStatement,fConfigP->fMapDeleteSQL,"deleting a map entry",(*pos).entrytype,(*pos).localid.c_str(),NULL__null,newmapflags);
3060 }
3061 // now remove it from the list, such that we don't try to delete it again
3062 TMapContainer::iterator delpos=pos++; // that's the next to have a look at
3063 fMapTable.erase(delpos); // remove it now
3064 continue; // pos is already updated
3065 } // deleted
3066 else if ((*pos).added) {
3067 // add a new entry
3068 IssueMapSQL(aStatement,fConfigP->fMapInsertSQL,"inserting a map entry",(*pos).entrytype,(*pos).localid.c_str(),(*pos).remoteid.c_str(),newmapflags);
3069 // is now added, don't add again later
3070 (*pos).added=false;
3071 }
3072 else {
3073 // explicitly changed or needs update because of resume mark or pendingmap flag
3074 // change existing entry
3075 IssueMapSQL(aStatement,fConfigP->fMapUpdateSQL,"changing a map entry",(*pos).entrytype,(*pos).localid.c_str(),(*pos).remoteid.c_str(),newmapflags);
3076 }
3077 } // if something changed
3078 // anyway - reset mark for resume, it must be reconstructed before next save
3079 (*pos).markforresume=false;
3080 // next
3081 pos++;
3082 }
3083 // catch exceptions, but nevertheless continue writing
3084 catch (exception &e) {
3085 PDEBUGPRINTFX(DBG_ERROR,("******** TODBCApiDS::updateODBCMap exception: %s",e.what())){ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ("******** TODBCApiDS::updateODBCMap exception: %s"
,e.what()); }
;
3086 sta=510;
3087 break;
3088 }
3089 catch (...) {
3090 DEBUGPRINTFX(DBG_ERROR,("******** TODBCApiDS::updateODBCMap unknown exception")){ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ("******** TODBCApiDS::updateODBCMap unknown exception"
); }
;
3091 sta=510;
3092 break;
3093 }
3094 } // while
3095 return sta;
3096} // TODBCApiDS::updateMap
3097
3098
3099#endif // HAS_SQL_ADMIN
3100
3101
3102// called when message processing
3103void TODBCApiDS::dsEndOfMessage(void)
3104{
3105 string msg,state;
3106
3107 #ifdef ODBCAPI_SUPPORT
3108 SQLRETURNlong res;
3109 // commit data if not anyway committed at end of every item
3110 if (
3111 fODBCConnectionHandle!=SQL_NULL_HANDLE0 &&
3112 !fConfigP->fCommitItems
3113 ) {
3114 DEBUGPRINTFX(DBG_DATA+DBG_DBAPI,("dsEndOfMessage - committing transactions at end of message")){ if (((0x00000080 +0x02000000) & getDbgMask()) == (0x00000080
+0x02000000)) getDbgLogger()->setNextMask(0x00000080 +0x02000000
).DebugPrintfLastMask ("dsEndOfMessage - committing transactions at end of message"
); }
;
3115 res=SafeSQLEndTran(SQL_HANDLE_DBC,getODBCConnectionHandle(),SQL_COMMIT);
3116 if (fAgentP->getODBCError(res,msg,state,SQL_HANDLE_DBC,getODBCConnectionHandle())) {
3117 DEBUGPRINTFX(DBG_ERROR,("dsEndOfMessage: SQLEndTran failed: %s",msg.c_str())){ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ("dsEndOfMessage: SQLEndTran failed: %s"
,msg.c_str()); }
;
3118 }
3119 }
3120 #endif // ODBCAPI_SUPPORT
3121
3122 // let ancestor do things
3123 inherited::dsEndOfMessage();
3124} // TODBCApiDS::dsEndOfMessage
3125
3126
3127
3128// Simple DB API access interface methods
3129
3130
3131// Zap all data in syncset (note that everything outside the sync set will remain intact)
3132localstatus TODBCApiDS::apiZapSyncSet(void)
3133{
3134 localstatus sta = LOCERR_OK;
3135
3136 try {
3137 string sql=fConfigP->fDataZapSQL;
3138 if (sql.empty()) {
3139 // we have no statement that can zap everything at once, so we'll have to do
3140 // use generic one by one syncset deletion
3141 sta = zapSyncSetOneByOne();
3142 }
3143 else {
3144 // we have SQL statement(s) for zapping, use it
3145 sInt16 i=0;
3146 uInt16 setno;
3147 while (getNextSQLStatement(fConfigP->fDataZapSQL,i,sql,setno)) {
3148 DoDataSubstitutions(sql,fConfigP->fFieldMappings.fFieldMapList,0,false,false);
3149 // - issue
3150 prepareSQLStatement(fODBCWriteStatement, sql.c_str(), true, "slow-refresh from remote: deleting all records in sync set first");
3151 execSQLStatement(fODBCWriteStatement, sql, true, NULL__null, true);
3152 finalizeSQLStatement(fODBCWriteStatement, true);
3153 } // while more statements
3154 }
3155 }
3156 catch (exception &e) {
3157 PDEBUGPRINTFX(DBG_ERROR,("apiZapSyncSet exception: %s",e.what())){ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ("apiZapSyncSet exception: %s"
,e.what()); }
;
3158 return 510;
3159 }
3160 // done
3161 return sta;
3162} // TODBCApiDS::apiZapSyncSet
3163
3164
3165// read sync set IDs and mod dates.
3166// - If aNeedAll is set, all data fields are needed, so apiReadSyncSet MAY
3167// read items here already. Note that apiReadSyncSet MAY read items here
3168// even if aNeedAll is not set (if it is more efficient than reading
3169// them separately afterwards).
3170localstatus TODBCApiDS::apiReadSyncSet(bool aNeedAll)
3171{
3172 string sql;
3173 localstatus sta = LOCERR_OK;
3174
3175 // get field map list
3176 TFieldMapList &fml = fConfigP->fFieldMappings.fFieldMapList;
3177 #ifdef SQLITE_SUPPORT1
3178 // start reading
3179 if (fUseSQLite) {
3180 // open the SQLite file
3181 PDEBUGPRINTFX(DBG_DBAPI,("Opening SQLite3 file '%s'",fConfigP->fSQLiteFileName.c_str())){ if (((0x02000000) & getDbgMask()) == (0x02000000)) getDbgLogger
()->setNextMask(0x02000000).DebugPrintfLastMask ("Opening SQLite3 file '%s'"
,fConfigP->fSQLiteFileName.c_str()); }
;
3182 int rc = sqlite3_open(fConfigP->fSQLiteFileName.c_str(), &fSQLiteP);
3183 fAgentP->checkSQLiteError(rc,fSQLiteP);
3184 // set the database (lock) timeout
3185 rc = sqlite3_busy_timeout(fSQLiteP, fConfigP->fSQLiteBusyTimeout*1000);
3186 fAgentP->checkSQLiteError(rc,fSQLiteP);
3187 }
3188 else
3189 #endif
3190 #ifdef ODBCAPI_SUPPORT
3191 {
3192 fODBCReadStatement = fAgentP->newStatementHandle(getODBCConnectionHandle());
3193 }
3194 #else
3195 return 510; // no API -> DB error
3196 #endif // ODBCAPI_SUPPORT
3197 #ifdef SCRIPT_SUPPORT1
3198 // process mappings init script
3199 fWriting=false;
3200 fInserting=false;
3201 fDeleting=false;
3202 fAgentP->fScriptContextDatastore=this;
3203 if (!TScriptContext::executeTest(true,fScriptContextP,fConfigP->fFieldMappings.fInitScript,fConfigP->getDSFuncTableP(),fAgentP))
3204 throw TSyncException("<initscript> failed");
3205 #endif
3206 // read list of all local IDs that are in the current sync set
3207 DeleteSyncSet();
3208 #ifdef SYDEBUG2
3209 string ts;
3210 StringObjTimestamp(ts,getPreviousToRemoteSyncCmpRef());
3211 PDEBUGPRINTFX(DBG_DATA,({ if (((0x00000080) & getDbgMask()) == (0x00000080)) getDbgLogger
()->setNextMask(0x00000080).DebugPrintfLastMask ( "Now reading local sync set: last sync to remote was at %s"
, ts.c_str() ); }
3212 "Now reading local sync set: last sync to remote was at %s",{ if (((0x00000080) & getDbgMask()) == (0x00000080)) getDbgLogger
()->setNextMask(0x00000080).DebugPrintfLastMask ( "Now reading local sync set: last sync to remote was at %s"
, ts.c_str() ); }
3213 ts.c_str(){ if (((0x00000080) & getDbgMask()) == (0x00000080)) getDbgLogger
()->setNextMask(0x00000080).DebugPrintfLastMask ( "Now reading local sync set: last sync to remote was at %s"
, ts.c_str() ); }
3214 )){ if (((0x00000080) & getDbgMask()) == (0x00000080)) getDbgLogger
()->setNextMask(0x00000080).DebugPrintfLastMask ( "Now reading local sync set: last sync to remote was at %s"
, ts.c_str() ); }
;
3215 #endif
3216 // we don't need to load the syncset if we are only refreshing from remote
3217 // but we also must load it if we can't zap without it on slow refresh
3218 // or if the syncset is needed to retrieve items
3219 if (!fRefreshOnly || (fSlowSync && apiNeedSyncSetToZap()) || implNeedSyncSetToRetrieve()) {
3220 // %%% add checking for aNeedAll and decide to use another (tbd) SQL to get all
3221 // records with all fields with this single query
3222 // - SELECT localid,modifieddate(,modifiedtime) FROM data WHERE <folderkey, filter conds, other conds>
3223 // NOTE: this must always be a SINGLE statement (no %GO() are allowed) !
3224 // Expects 2 or 3 (when date&time are separate) columns: localid,modifieddate(,modifiedtime)
3225 sql=fConfigP->fLocalIDAndTimestampFetchSQL;
3226 DoDataSubstitutions(sql,fml,0,false);
3227 // - prepare
3228 prepareSQLStatement(fODBCReadStatement, sql.c_str(), true, "reading of all localIDs/moddates in sync set");
3229 // - issue
3230 execSQLStatement(fODBCReadStatement, sql, true, NULL__null, true);
3231 // - fetch data
3232 while (fetchNextRow(fODBCReadStatement, true)) {
3233 // get local ID and mod date
3234 TSyncSetItem *syncsetitemP = new TSyncSetItem;
3235 if (!syncsetitemP) throw TSyncException(DEBUGTEXT("cannot allocate new syncsetitem","odds12")"cannot allocate new syncsetitem");
3236 syncsetitemP->isModified=false;
3237 syncsetitemP->isModifiedAfterSuspend=false;
3238 lineartime_t lastmodified = 0;
3239 #ifdef SQLITE_SUPPORT1
3240 if (fUseSQLite) {
3241 // SQLite
3242 sInt16 col=0; // SQLite has 0 based column index
3243 // - localid
3244 syncsetitemP->localid = (const char *)sqlite3_column_text(fSQLiteStmtP,col++);
3245 // - modified timestamp
3246 lastmodified = dbIntToLineartimeAs(sqlite3_column_int64(fSQLiteStmtP,col++), fConfigP->fLastModDBFieldType, TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)));
3247 }
3248 else
3249 #endif
3250 {
3251 #ifdef ODBCAPI_SUPPORT
3252 // ODBC
3253 sInt16 col=1; // ODBC has 1 based column index
3254 fAgentP->getColumnValueAsString(fODBCReadStatement, col++, syncsetitemP->localid, chs_ascii);
3255 // get modified timestamp
3256 if (fConfigP->fLastModDBFieldType==dbft_timestamp)
3257 getColumnsAsTimestamp(fODBCReadStatement, col, fConfigP->fModifiedTimestamp, lastmodified, TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)));
3258 else {
3259 uInt32 u;
3260 fAgentP->getColumnValueAsULong(fODBCReadStatement, col, u);
3261 lastmodified = dbIntToLineartimeAs(u, fConfigP->fLastModDBFieldType, TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)));
3262 }
3263 #endif // ODBCAPI_SUPPORT
3264 }
3265 // compare now
3266 syncsetitemP->isModified = lastmodified > getPreviousToRemoteSyncCmpRef();
3267 syncsetitemP->isModifiedAfterSuspend = lastmodified > getPreviousSuspendCmpRef();
3268 #ifdef SYDEBUG2
3269 StringObjTimestamp(ts,lastmodified);
3270 PDEBUGPRINTFX(DBG_DATA+DBG_EXOTIC,({ if (((0x00000080 +0x80000000) & getDbgMask()) == (0x00000080
+0x80000000)) getDbgLogger()->setNextMask(0x00000080 +0x80000000
).DebugPrintfLastMask ( "read local item info in sync set: localid='%s', last modified %s%s%s"
, syncsetitemP->localid.c_str(), ts.c_str(), syncsetitemP->
isModified ? " -> MODIFIED since last sync" : "", syncsetitemP
->isModifiedAfterSuspend ? " AND since last suspend" : "" )
; }
3271 "read local item info in sync set: localid='%s', last modified %s%s%s",{ if (((0x00000080 +0x80000000) & getDbgMask()) == (0x00000080
+0x80000000)) getDbgLogger()->setNextMask(0x00000080 +0x80000000
).DebugPrintfLastMask ( "read local item info in sync set: localid='%s', last modified %s%s%s"
, syncsetitemP->localid.c_str(), ts.c_str(), syncsetitemP->
isModified ? " -> MODIFIED since last sync" : "", syncsetitemP
->isModifiedAfterSuspend ? " AND since last suspend" : "" )
; }
3272 syncsetitemP->localid.c_str(),{ if (((0x00000080 +0x80000000) & getDbgMask()) == (0x00000080
+0x80000000)) getDbgLogger()->setNextMask(0x00000080 +0x80000000
).DebugPrintfLastMask ( "read local item info in sync set: localid='%s', last modified %s%s%s"
, syncsetitemP->localid.c_str(), ts.c_str(), syncsetitemP->
isModified ? " -> MODIFIED since last sync" : "", syncsetitemP
->isModifiedAfterSuspend ? " AND since last suspend" : "" )
; }
3273 ts.c_str(),{ if (((0x00000080 +0x80000000) & getDbgMask()) == (0x00000080
+0x80000000)) getDbgLogger()->setNextMask(0x00000080 +0x80000000
).DebugPrintfLastMask ( "read local item info in sync set: localid='%s', last modified %s%s%s"
, syncsetitemP->localid.c_str(), ts.c_str(), syncsetitemP->
isModified ? " -> MODIFIED since last sync" : "", syncsetitemP
->isModifiedAfterSuspend ? " AND since last suspend" : "" )
; }
3274 syncsetitemP->isModified ? " -> MODIFIED since last sync" : "",{ if (((0x00000080 +0x80000000) & getDbgMask()) == (0x00000080
+0x80000000)) getDbgLogger()->setNextMask(0x00000080 +0x80000000
).DebugPrintfLastMask ( "read local item info in sync set: localid='%s', last modified %s%s%s"
, syncsetitemP->localid.c_str(), ts.c_str(), syncsetitemP->
isModified ? " -> MODIFIED since last sync" : "", syncsetitemP
->isModifiedAfterSuspend ? " AND since last suspend" : "" )
; }
3275 syncsetitemP->isModifiedAfterSuspend ? " AND since last suspend" : ""{ if (((0x00000080 +0x80000000) & getDbgMask()) == (0x00000080
+0x80000000)) getDbgLogger()->setNextMask(0x00000080 +0x80000000
).DebugPrintfLastMask ( "read local item info in sync set: localid='%s', last modified %s%s%s"
, syncsetitemP->localid.c_str(), ts.c_str(), syncsetitemP->
isModified ? " -> MODIFIED since last sync" : "", syncsetitemP
->isModifiedAfterSuspend ? " AND since last suspend" : "" )
; }
3276 )){ if (((0x00000080 +0x80000000) & getDbgMask()) == (0x00000080
+0x80000000)) getDbgLogger()->setNextMask(0x00000080 +0x80000000
).DebugPrintfLastMask ( "read local item info in sync set: localid='%s', last modified %s%s%s"
, syncsetitemP->localid.c_str(), ts.c_str(), syncsetitemP->
isModified ? " -> MODIFIED since last sync" : "", syncsetitemP
->isModifiedAfterSuspend ? " AND since last suspend" : "" )
; }
;
3277 #endif
3278 // %%% for now, we do not read item contents yet
3279 syncsetitemP->itemP=NULL__null; // no item data
3280 // save ID in list
3281 fSyncSetList.push_back(syncsetitemP);
3282 }
3283 // - no more records
3284 finalizeSQLStatement(fODBCReadStatement, true);
3285 } // not refreshing
3286 PDEBUGPRINTFX(DBG_DATA,({ if (((0x00000080) & getDbgMask()) == (0x00000080)) getDbgLogger
()->setNextMask(0x00000080).DebugPrintfLastMask ( "Fetched %ld items from database (not necessarily all visible in SyncSet!)"
, (long)fSyncSetList.size() ); }
3287 "Fetched %ld items from database (not necessarily all visible in SyncSet!)",{ if (((0x00000080) & getDbgMask()) == (0x00000080)) getDbgLogger
()->setNextMask(0x00000080).DebugPrintfLastMask ( "Fetched %ld items from database (not necessarily all visible in SyncSet!)"
, (long)fSyncSetList.size() ); }
3288 (long)fSyncSetList.size(){ if (((0x00000080) & getDbgMask()) == (0x00000080)) getDbgLogger
()->setNextMask(0x00000080).DebugPrintfLastMask ( "Fetched %ld items from database (not necessarily all visible in SyncSet!)"
, (long)fSyncSetList.size() ); }
3289 )){ if (((0x00000080) & getDbgMask()) == (0x00000080)) getDbgLogger
()->setNextMask(0x00000080).DebugPrintfLastMask ( "Fetched %ld items from database (not necessarily all visible in SyncSet!)"
, (long)fSyncSetList.size() ); }
;
3290 return sta;
3291} // TODBCApiDS::apiReadSyncSet
3292
3293
3294// fetch actual record from DB by localID
3295localstatus TODBCApiDS::apiFetchItem(TMultiFieldItem &aItem, bool aReadPhase, TSyncSetItem *aSyncSetItemP)
3296{
3297 // decide what statement to use
3298 SQLHSTMTlong statement = SQL_NULL_HANDLE0;
3299 #ifdef SQLITE_SUPPORT1
3300 if (!fUseSQLite)
3301 #endif
3302 {
3303 #ifdef ODBCAPI_SUPPORT
3304 if (aReadPhase)
3305 statement = fODBCReadStatement; // we can reuse the statement that is already here
3306 else
3307 statement = fAgentP->newStatementHandle(getODBCConnectionHandle());
3308 #endif // ODBCAPI_SUPPORT
3309 }
3310 // get field map list
3311 TFieldMapList &fml = fConfigP->fFieldMappings.fFieldMapList;
3312 // assume ok
3313 localstatus sta=LOCERR_OK;
3314 // now fetch
3315 try {
3316 string sql;
3317 TMultiFieldItem *myitemP = (TMultiFieldItem *)&aItem;
3318 // execute statements needed to fetch record data
3319 sInt16 i=0;
3320 uInt16 setno;
3321 while (getNextSQLStatement(fConfigP->fDataFetchSQL,i,sql,setno)) {
3322 // - something like: SELECT %N FROM datatable WHERE localid=%k AND folderkey=%f
3323 resetSQLParameterMaps();
3324 DoDataSubstitutions(sql,fml,setno,false,false,myitemP); // item needed for %k
3325 // - prepare parameters
3326 prepareSQLStatement(statement, sql.c_str(), true, "getting item data");
3327 bindSQLParameters(statement,true);
3328 // - issue
3329 execSQLStatement(statement,sql,true,NULL__null,true);
3330 // fetch
3331 if (!fetchNextRow(statement,true)) {
3332 // - close cursor anyway
3333 finalizeSQLStatement(statement,true);
3334 // No data for select statement
3335 // This is ignored for all setno except setno=0
3336 if (setno==0) {
3337 // this record cannot be found
3338 sta=404; // not found
3339 break; // no need to execute further statements
3340 } // setno==0, that is, non-optional data
3341 // if optional data is not present, just NOP
3342 } // has no data
3343 else {
3344 // - fill item with fields from SQL query
3345 sInt16 col=1;
3346 fillFieldsFromSQLResult(statement,col,*myitemP,fml,setno,0);
3347 // close cursor of main fetch
3348 finalizeSQLStatement(statement,true);
3349 // get out params
3350 saveAndCleanupSQLParameters(statement,true);
3351 }
3352 } // while more statements
3353 // Finish reading record (if one fetched at all, and not converted to delete etc.)
3354 if (sta==LOCERR_OK) {
3355 #ifdef ARRAYDBTABLES_SUPPORT1
3356 // - also read array fields from auxiliary tables, if any
3357 if (fHasArrayFields) {
3358 // get data from linked array tables as well
3359 readArrayFields(
3360 statement,
3361 *myitemP,
3362 fml
3363 );
3364 }
3365 #endif
3366 #ifdef SCRIPT_SUPPORT1
3367 // - finally process afterread script of entire record
3368 fArrIdx=0; // base item
3369 fParentKey=myitemP->getLocalID();
3370 fAgentP->fScriptContextDatastore=this;
3371 if (!TScriptContext::execute(fScriptContextP,fConfigP->fFieldMappings.fAfterReadScript,fConfigP->getDSFuncTableP(),fAgentP,myitemP,true))
3372 throw TSyncException("<afterreadscript> failed");
3373 #endif
3374 }
3375 #ifdef SQLITE_SUPPORT1
3376 if (!fUseSQLite)
3377 #endif
3378 {
3379 #ifdef ODBCAPI_SUPPORT
3380 // dispose statement handle if we have allocated it
3381 if (!aReadPhase) SafeSQLFreeHandle(SQL_HANDLE_STMT,statement);
3382 #endif
3383 }
3384 }
3385 catch (...) {
3386 #ifdef SQLITE_SUPPORT1
3387 if (!fUseSQLite)
3388 #endif
3389 {
3390 #ifdef ODBCAPI_SUPPORT
3391 // dispose statement handle if we have allocated it
3392 if (!aReadPhase) SafeSQLFreeHandle(SQL_HANDLE_STMT,statement);
3393 #endif
3394 }
3395 // re-throw
3396 throw;
3397 }
3398 // return status
3399 return sta;
3400} // TODBCApiDS::apiFetchItem
3401
3402
3403/// end of syncset reading phase
3404localstatus TODBCApiDS::apiEndDataRead(void)
3405{
3406 #ifdef ODBCAPI_SUPPORT
3407 SQLRETURNlong res=SQL_SUCCESS0;
3408 #endif
3409 localstatus sta = LOCERR_OK;
3410
3411 #ifdef ODBCAPI_SUPPORT
3412 // release the statement handle (if any)
3413 try {
3414 DEBUGPRINTFX(DBG_DATA+DBG_DBAPI,("EndDataRead: committing read phase")){ if (((0x00000080 +0x02000000) & getDbgMask()) == (0x00000080
+0x02000000)) getDbgLogger()->setNextMask(0x00000080 +0x02000000
).DebugPrintfLastMask ("EndDataRead: committing read phase");
}
;
3415 if (fODBCReadStatement!=SQL_NULL_HANDLE0) {
3416 res=SafeSQLFreeHandle(SQL_HANDLE_STMT,fODBCReadStatement);
3417 fODBCReadStatement=SQL_NULL_HANDLE0;
3418 checkConnectionError(res);
3419 // Commit the transaction (to make sure a new one begins when starting to write)
3420 SafeSQLEndTran(SQL_HANDLE_DBC,getODBCConnectionHandle(),SQL_COMMIT);
3421 checkConnectionError(res);
3422 }
3423 }
3424 catch (exception &e) {
3425 PDEBUGPRINTFX(DBG_ERROR,("******** EndDataRead exception: %s",e.what())){ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ("******** EndDataRead exception: %s"
,e.what()); }
;
3426 sta=510;
3427 }
3428 #endif // ODBCAPI_SUPPORT
3429 return sta;
3430} // TODBCApiDS::apiEndDataRead
3431
3432
3433// start of data write
3434localstatus TODBCApiDS::apiStartDataWrite(void)
3435{
3436 // create statement handle for writing if we don't have one already
3437 #ifdef SQLITE_SUPPORT1
3438 if (!fUseSQLite)
3439 #endif
3440 {
3441 #ifdef ODBCAPI_SUPPORT
3442 // for ODBC only
3443 if (fODBCWriteStatement==SQL_NULL_HANDLE0)
3444 fODBCWriteStatement = fAgentP->newStatementHandle(getODBCConnectionHandle());
3445 #endif
3446 }
3447 return LOCERR_OK;
3448} // TODBCApiDS::apiStartDataWrite
3449
3450
3451// generate new LocalID into fLastGeneratedLocalID.
3452// - can be used with %X,%x escape in SQL statements.
3453void TODBCApiDS::nextLocalID(TSpecialIDMode aMode,SQLHSTMTlong aStatement)
3454{
3455 if (aMode!=sidm_none) {
3456 createLocalID(fLastGeneratedLocalID,aMode);
3457 }
3458 else {
3459 if (fConfigP->fDetermineNewIDOnce) {
3460 // we use incremented starting value
3461 StringObjPrintf(fLastGeneratedLocalID,"%ld",(long)fNextLocalID++);
3462 }
3463 else {
3464 #ifdef SCRIPT_SUPPORT1
3465 // first check for script
3466 if (!fConfigP->fLocalIDScript.empty()) {
3467 // call script tro obtain new localID
3468 TItemField *resP=NULL__null;
3469 fAgentP->fScriptContextDatastore=this;
3470 if (!TScriptContext::executeWithResult(
3471 resP, // can be default result or NULL, will contain result or NULL if no result
3472 fScriptContextP,
3473 fConfigP->fLocalIDScript,
3474 fConfigP->getDSFuncTableP(), // context function table
3475 fAgentP, // context data (myself)
3476 NULL__null, false, NULL__null, false
3477 ))
3478 throw TSyncException("<localidscript> failed");
3479 if (resP) {
3480 // get ID
3481 resP->getAsString(fLastGeneratedLocalID);
3482 delete resP;
3483 }
3484 }
3485 #endif
3486 // get new statement if none was passed
3487 string sql=fConfigP->fObtainNewLocalIDSql;
3488 // obtainNewLocalIDSql can be empty, for example if ID is returned in an
3489 // out param from the inserting statement(s) or generated by fLocalIDScript
3490 if (!sql.empty()) {
3491 #ifdef SQLITE_SUPPORT1
3492 if (fUseSQLite) {
3493 throw TSyncException("SQLite does not support <obtainlocalidsql>");
3494 }
3495 else
3496 #endif
3497 {
3498 #ifdef ODBCAPI_SUPPORT
3499 // there is a statement to execute
3500 SQLHSTMTlong statement=aStatement;
3501 if (!statement) statement=fAgentP->newStatementHandle(getODBCConnectionHandle());
3502 // issue the SQL
3503 try {
3504 // execute query BEFORE insert to get unique ID value for new record
3505 DoSQLSubstitutions(sql);
3506 execSQLStatement(statement,sql,true,"getting local ID for new record",true);
3507 // - fetch result row
3508 SQLRETURNlong res=SafeSQLFetch(statement);
3509 fAgentP->checkStatementError(res,statement);
3510 // - get value of first field as string
3511 if (!fAgentP->getColumnValueAsString(statement,1,fLastGeneratedLocalID,chs_ascii)) {
3512 throw TSyncException("Failed getting new localID");
3513 }
3514 // close cursor (for re-using statement handle, this is needed)
3515 SafeSQLCloseCursor(statement);
3516 }
3517 catch (...) {
3518 if (!aStatement) SafeSQLFreeHandle(SQL_HANDLE_STMT,statement);
3519 throw;
3520 }
3521 // get rid of local statement
3522 if (!aStatement) SafeSQLFreeHandle(SQL_HANDLE_STMT,statement);
3523 #endif
3524 }
3525 }
3526 }
3527 }
3528 PDEBUGPRINTFX(DBG_DATA,("Generated new localID: %s",fLastGeneratedLocalID.c_str())){ if (((0x00000080) & getDbgMask()) == (0x00000080)) getDbgLogger
()->setNextMask(0x00000080).DebugPrintfLastMask ("Generated new localID: %s"
,fLastGeneratedLocalID.c_str()); }
;
3529} // TODBCApiDS::nextLocalID
3530
3531
3532// private helper: start writing item
3533void TODBCApiDS::startWriteItem(void)
3534{
3535 // make sure transaction is complete if we are in item commit mode
3536 #ifdef ODBCAPI_SUPPORT
3537 SQLRETURNlong res;
3538 if (fConfigP->fCommitItems) {
3539 PDEBUGPRINTFX(DBG_DATA+DBG_DBAPI,("startWriteItem: commititems=true, item processing starts with new transaction")){ if (((0x00000080 +0x02000000) & getDbgMask()) == (0x00000080
+0x02000000)) getDbgLogger()->setNextMask(0x00000080 +0x02000000
).DebugPrintfLastMask ("startWriteItem: commititems=true, item processing starts with new transaction"
); }
;
3540 res=SafeSQLEndTran(SQL_HANDLE_DBC,getODBCConnectionHandle(),SQL_COMMIT);
3541 checkConnectionError(res);
3542 }
3543 #endif
3544} // TODBCApiDS::startWriteItem
3545
3546
3547// private helper: end writing item
3548void TODBCApiDS::endWriteItem(void)
3549{
3550 #ifdef ODBCAPI_SUPPORT
3551 SQLRETURNlong res;
3552 if (fConfigP->fCommitItems) {
3553 PDEBUGPRINTFX(DBG_DATA+DBG_DBAPI,("endWriteItem: commititems=true, commit changes to DB")){ if (((0x00000080 +0x02000000) & getDbgMask()) == (0x00000080
+0x02000000)) getDbgLogger()->setNextMask(0x00000080 +0x02000000
).DebugPrintfLastMask ("endWriteItem: commititems=true, commit changes to DB"
); }
;
3554 res=SafeSQLEndTran(SQL_HANDLE_DBC,getODBCConnectionHandle(),SQL_COMMIT);
3555 checkConnectionError(res);
3556 }
3557 #endif
3558} // TODBCApiDS::endWriteItem
3559
3560
3561
3562// add new item to datastore, returns created localID
3563localstatus TODBCApiDS::apiAddItem(TMultiFieldItem &aItem, string &aLocalID)
3564{
3565 localstatus sta=LOCERR_OK;
3566
3567 startWriteItem();
3568
3569 // get field map list
3570 TFieldMapList &fml = fConfigP->fFieldMappings.fFieldMapList;
3571 try {
3572 // determine ID
3573 aLocalID.erase(); // make sure we don't assign bogus ID (fObtainNewIDAfterInsert case)
3574 if (fConfigP->fSpecialIDMode==sidm_none && !fConfigP->fObtainNewIDAfterInsert) {
3575 // No repeatable ID creator, and not obtain-after-insert: do it once here
3576 // - create next local ID using standard method
3577 nextLocalID(sidm_none,fODBCWriteStatement);
3578 // - use it
3579 aLocalID=fLastGeneratedLocalID;
3580 } // if no special algorithm for IDs (such as system time/random based)
3581 // add new record
3582 sInt16 rep=0;
3583 do {
3584 try {
3585 // - make sure we have a new ID in case it is time based
3586 if (fConfigP->fSpecialIDMode!=sidm_none) {
3587 // - create next local ID using special method
3588 nextLocalID(fConfigP->fSpecialIDMode,fODBCWriteStatement);
3589 // - use it
3590 aLocalID=fLastGeneratedLocalID;
3591 }
3592 // - assign localID to be used (empty here if fObtainNewIDAfterInsert=true)
3593 aItem.setLocalID(aLocalID.c_str());
3594 #ifdef SCRIPT_SUPPORT1
3595 // - process beforewrite script
3596 fWriting=true;
3597 fInserting=true; // might be reset by ADDBYUPDATING()
3598 fDeleting=false;
3599 fAgentP->fScriptContextDatastore=this;
3600 if (!TScriptContext::execute(fScriptContextP,fConfigP->fFieldMappings.fBeforeWriteScript,fConfigP->getDSFuncTableP(),fAgentP,&aItem,true))
3601 throw TSyncException("<beforewritescript> failed");
3602 if (!fInserting) {
3603 // inserting was turned off by beforewritescript, in particular by ADDBYUPDATING()
3604 // -> perform an update using the localid set by ADDBYUPDATING()
3605 PDEBUGPRINTFX(DBG_DATA+DBG_DBAPI,("Updating existing DB row '%s' instead of inserting (ADDBYUPDATING)",aItem.getLocalID())){ if (((0x00000080 +0x02000000) & getDbgMask()) == (0x00000080
+0x02000000)) getDbgLogger()->setNextMask(0x00000080 +0x02000000
).DebugPrintfLastMask ("Updating existing DB row '%s' instead of inserting (ADDBYUPDATING)"
,aItem.getLocalID()); }
;
3606 // - return localID to caller (for caller, this looks like a regular add)
3607 aLocalID = aItem.getLocalID();
3608 // - perform update instead of insert
3609 if (!IssueDataWriteSQL(
3610 fODBCWriteStatement,
3611 fConfigP->fDataUpdateSQL,
3612 "updating instead of inserting",
3613 true, // for update
3614 fml, // field map
3615 &aItem // item to read values and localid from
3616 )) {
3617 // not found in data table - do a normal add (even if ADDBYUPDATING() wanted an update)
3618 fInserting = true; // revert back to inserting
3619 PDEBUGPRINTFX(DBG_ERROR,("Updating existing not possible -> reverting to insert")){ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ("Updating existing not possible -> reverting to insert"
); }
;
3620 }
3621 }
3622 // check if we still need to insert
3623 if (fInserting)
3624 #endif
3625 {
3626 // Normal insert
3627 // - issue statement(s)
3628 #ifdef ODBCAPI_SUPPORT
3629 bool hasdata=
3630 #endif
3631 IssueDataWriteSQL(
3632 fODBCWriteStatement,
3633 fConfigP->fDataInsertSQL,
3634 "adding record",
3635 false, // not for update
3636 fml, // field map
3637 &aItem // item to read values and localid from
3638 );
3639 // copy ID in case we've got it via an output param
3640 aLocalID=aItem.getLocalID();
3641 #ifdef SQLITE_SUPPORT1
3642 if (!fUseSQLite)
3643 #endif
3644 {
3645 #ifdef ODBCAPI_SUPPORT
3646 if (fConfigP->fInsertReturnsID) {
3647 // insert statement should return ID of record inserted
3648 if (hasdata) {
3649 SQLRETURNlong res=SafeSQLFetch(fODBCWriteStatement);
3650 if (fAgentP->checkStatementHasData(res,fODBCWriteStatement)) {
3651 // get id (must be in first result column of first and only result row)
3652 if (!fAgentP->getColumnValueAsString(fODBCWriteStatement,1,fLastGeneratedLocalID,chs_ascii)) {
3653 throw TSyncException("Failed getting localID from <datainsertsql> result set");
3654 }
3655 // - use it
3656 aLocalID=fLastGeneratedLocalID;
3657 }
3658 else
3659 hasdata=false;
3660 // - close cursor anyway
3661 SafeSQLCloseCursor(fODBCWriteStatement);
3662 }
3663 if (!hasdata)
3664 throw TSyncException("<datainsertsql> statement did not return insert ID");
3665 }
3666 #endif // ODBCAPI_SUPPORT
3667 }
3668 } // normal insert
3669 // done ok
3670 break;
3671 }
3672 catch (TSyncException &e) {
3673 // error while issuing write
3674 if (++rep>fConfigP->fInsertRetries) throw; // error, let it show
3675 PDEBUGPRINTFX(DBG_DATA+DBG_DBAPI,({ if (((0x00000080 +0x02000000) & getDbgMask()) == (0x00000080
+0x02000000)) getDbgLogger()->setNextMask(0x00000080 +0x02000000
).DebugPrintfLastMask ( "Insert failed with localid='%s': %s --> retrying"
, aLocalID.c_str(), e.what() ); }
3676 "Insert failed with localid='%s': %s --> retrying",{ if (((0x00000080 +0x02000000) & getDbgMask()) == (0x00000080
+0x02000000)) getDbgLogger()->setNextMask(0x00000080 +0x02000000
).DebugPrintfLastMask ( "Insert failed with localid='%s': %s --> retrying"
, aLocalID.c_str(), e.what() ); }
3677 aLocalID.c_str(),{ if (((0x00000080 +0x02000000) & getDbgMask()) == (0x00000080
+0x02000000)) getDbgLogger()->setNextMask(0x00000080 +0x02000000
).DebugPrintfLastMask ( "Insert failed with localid='%s': %s --> retrying"
, aLocalID.c_str(), e.what() ); }
3678 e.what(){ if (((0x00000080 +0x02000000) & getDbgMask()) == (0x00000080
+0x02000000)) getDbgLogger()->setNextMask(0x00000080 +0x02000000
).DebugPrintfLastMask ( "Insert failed with localid='%s': %s --> retrying"
, aLocalID.c_str(), e.what() ); }
3679 )){ if (((0x00000080 +0x02000000) & getDbgMask()) == (0x00000080
+0x02000000)) getDbgLogger()->setNextMask(0x00000080 +0x02000000
).DebugPrintfLastMask ( "Insert failed with localid='%s': %s --> retrying"
, aLocalID.c_str(), e.what() ); }
;
3680 // repeat once more
3681 continue;
3682 }
3683 } while(true);
3684 // check if we want to use local ID created implicitly by inserting row
3685 #ifdef SCRIPT_SUPPORT1
3686 if (fInserting) // only if really inserting (but not in ADDBYUPDATING() case)
3687 #endif
3688 {
3689 if (!fConfigP->fInsertReturnsID && fConfigP->fSpecialIDMode==sidm_none && fConfigP->fObtainNewIDAfterInsert) {
3690 // determine new ID AFTER insert
3691 #ifdef SQLITE_SUPPORT1
3692 if (fUseSQLite) {
3693 // For SQLite, localID is always autocreated ROWID, get it now
3694 StringObjPrintf(aLocalID,"%lld",sqlite3_last_insert_rowid(fSQLiteP));
3695 PDEBUGPRINTFX(DBG_DBAPI,("sqlite3_last_insert_rowid() returned %s",aLocalID.c_str())){ if (((0x02000000) & getDbgMask()) == (0x02000000)) getDbgLogger
()->setNextMask(0x02000000).DebugPrintfLastMask ("sqlite3_last_insert_rowid() returned %s"
,aLocalID.c_str()); }
;
3696 }
3697 else
3698 #endif // SQLITE_SUPPORT
3699 {
3700 #ifdef ODBCAPI_SUPPORT
3701 // get local ID using standard method (SQL statement to get ID)
3702 nextLocalID(sidm_none,fODBCWriteStatement);
3703 // - use it
3704 aLocalID=fLastGeneratedLocalID;
3705 #endif // ODBCAPI_SUPPORT
3706 }
3707 }
3708 // make sure localID is known now
3709 aItem.setLocalID(aLocalID.c_str());
3710 } // determine ID of inserted record
3711 // also write array fields, if any
3712 #ifdef ARRAYDBTABLES_SUPPORT1
3713 if (fHasArrayFields) {
3714 // write data to linked array tables as well
3715 // - now write
3716 writeArrayFields(
3717 fODBCWriteStatement,
3718 aItem,
3719 fml,
3720 #ifdef SCRIPT_SUPPORT1
3721 fInserting // if really performed insert (i.e. not ADDBYUPDATING()), child record cleaning is not necessary (but can be forced by fAlwaysCleanArray)
3722 #else
3723 true // is an insert, so child record cleaning not necessary (but can be forced by fAlwaysCleanArray)
3724 #endif
3725 );
3726 }
3727 #endif
3728 #ifdef SCRIPT_SUPPORT1
3729 // process oevrall afterwrite script
3730 // Note: fInserting is still valid from above (and can be false in ADDBYUPDATING() case)
3731 fWriting=true;
3732 fDeleting=false;
3733 fAgentP->fScriptContextDatastore=this;
3734 if (!TScriptContext::execute(fScriptContextP,fConfigP->fFieldMappings.fAfterWriteScript,fConfigP->getDSFuncTableP(),fAgentP,&aItem,true))
3735 throw TSyncException("<afterwritescript> failed");
3736 #endif
3737 }
3738 catch (...) {
3739 endWriteItem();
3740 throw;
3741 }
3742 // end writing
3743 endWriteItem();
3744 return sta;
3745} // TODBCApiDS::apiAddItem
3746
3747
3748// update existing item in datastore, returns 404 if item not found
3749localstatus TODBCApiDS::apiUpdateItem(TMultiFieldItem &aItem)
3750{
3751 localstatus sta=LOCERR_OK;
3752
3753 startWriteItem();
3754 // get field map list
3755 TFieldMapList &fml = fConfigP->fFieldMappings.fFieldMapList;
3756 try {
3757 // update record with this localID
3758 #ifdef SCRIPT_SUPPORT1
3759 // - process beforewrite script
3760 fWriting=true;
3761 fInserting=false;
3762 fDeleting=false;
3763 fAgentP->fScriptContextDatastore=this;
3764 if (!TScriptContext::execute(fScriptContextP,fConfigP->fFieldMappings.fBeforeWriteScript,fConfigP->getDSFuncTableP(),fAgentP,&aItem,true))
3765 throw TSyncException("<beforewritescript> failed");
3766 #endif
3767 // - issue
3768 if (!IssueDataWriteSQL(
3769 fODBCWriteStatement,
3770 fConfigP->fDataUpdateSQL,
3771 "replacing record",
3772 true, // for update
3773 fml, // field map
3774 &aItem // item to read values and localid from
3775 )) {
3776 // not found in data table
3777 sta=404;
3778 }
3779 else {
3780 // also write array fields, if any
3781 #ifdef ARRAYDBTABLES_SUPPORT1
3782 if (fHasArrayFields) {
3783 // write data to linked array tables as well
3784 writeArrayFields(
3785 fODBCWriteStatement,
3786 aItem, // item to read values from
3787 fml,
3788 false // is not an insert, update needs erasing first in all cases!
3789 );
3790 }
3791 #endif
3792 #ifdef SCRIPT_SUPPORT1
3793 // process oevrall afterwrite script
3794 fWriting=true;
3795 fInserting=false;
3796 fDeleting=false;
3797 fAgentP->fScriptContextDatastore=this;
3798 if (!TScriptContext::execute(fScriptContextP,fConfigP->fFieldMappings.fAfterWriteScript,fConfigP->getDSFuncTableP(),fAgentP,&aItem,true))
3799 throw TSyncException("<afterwritescript> failed");
3800 #endif
3801 }
3802 }
3803 catch (...) {
3804 endWriteItem();
3805 throw;
3806 }
3807 // end writing
3808 endWriteItem();
3809 return sta;
3810} // TODBCApiDS::apiUpdateItem
3811
3812
3813// delete existing item in datastore, returns 211 if not existing any more
3814localstatus TODBCApiDS::apiDeleteItem(TMultiFieldItem &aItem)
3815{
3816 localstatus sta=LOCERR_OK;
3817
3818 startWriteItem();
3819 // get field map list
3820 TFieldMapList &fml = fConfigP->fFieldMappings.fFieldMapList;
3821 try {
3822 // - issue statements for delete (could be UPDATE or DELETE)
3823 if (!IssueDataWriteSQL(
3824 fODBCWriteStatement,
3825 fConfigP->fDataDeleteSQL,
3826 "deleting record",
3827 false, // not for update
3828 fml, // field map
3829 &aItem // item to read values and localid from
3830 )) {
3831 sta=211; // item not deleted, was not there any more
3832 }
3833 else {
3834 // also delete array fields, if any
3835 #ifdef ARRAYDBTABLES_SUPPORT1
3836 // get data from linked array tables as well
3837 deleteArrayFields(
3838 fODBCWriteStatement,
3839 aItem,
3840 fConfigP->fFieldMappings.fFieldMapList
3841 );
3842 #endif
3843 #ifdef SCRIPT_SUPPORT1
3844 // process overall afterwrite script
3845 fWriting=true;
3846 fInserting=false;
3847 fDeleting=true;
3848 fAgentP->fScriptContextDatastore=this;
3849 if (!TScriptContext::execute(fScriptContextP,fConfigP->fFieldMappings.fAfterWriteScript,fConfigP->getDSFuncTableP(),fAgentP,&aItem,true))
3850 throw TSyncException("<afterwritescript> failed");
3851 #endif
3852 }
3853 }
3854 catch (...) {
3855 endWriteItem();
3856 throw;
3857 }
3858 // end writing
3859 endWriteItem();
3860 return sta;
3861} // TODBCApiDS::apiDeleteItem
3862
3863
3864/// @brief Load admin data from ODBC database
3865/// Must search for existing target record matching the triple (aDeviceID,aDatabaseID,aRemoteDBID)
3866/// - if there is a matching record: load it
3867/// - if there is no matching record, set fFirstTimeSync=true. The implementation may already create a
3868/// new record with the key (aDeviceID,aDatabaseID,aRemoteDBID) and initialize it with the data from
3869/// the items as shown below. At least, fTargetKey must be set to a value that will allow apiSaveAdminData to
3870/// update the record. In case implementation chooses not create the record only in apiSaveAdminData, it must
3871/// buffer the triple (aDeviceID,aDatabaseID,aRemoteDBID) such that it is available at apiSaveAdminData.
3872/// If a record exists implementation must load the following items:
3873/// - fTargetKey = some key value that can be used to re-identify the target record later at SaveAdminData.
3874/// If the database implementation has other means to re-identify the target, this can be
3875/// left unassigned.
3876/// - fLastRemoteAnchor = anchor string used by remote party for last session (and saved to DB then)
3877/// - fPreviousSyncTime = anchor (beginning of session) timestamp of last session.
3878/// - fPreviousToRemoteSyncCmpRef = Reference time to determine items modified since last time sending data to remote
3879/// - fPreviousToRemoteSyncIdentifier = string identifying last session that sent data to remote (needs only be saved
3880/// if derived datastore cannot work with timestamps and has its own identifier).
3881/// - fMapTable = list<TMapEntry> containing map entries. The implementation must load all map entries
3882/// related to the current sync target identified by the triple of (aDeviceID,aDatabaseID,aRemoteDBID)
3883/// or by fTargetKey. The entries added to fMapTable must have "changed", "added" and "deleted" flags
3884/// set to false.
3885/// For resumable datastores:
3886/// - fMapTable = In addition to the above, the markforresume flag must be saved in the mapflags
3887// when it is not equal to the savedmark flag - independently of added/deleted/changed.
3888/// - fResumeAlertCode = alert code of current suspend state, 0 if none
3889/// - fPreviousSuspendCmpRef = reference time of last suspend (used to detect items modified during a suspend / resume)
3890/// - fPreviousSuspendIdentifier = identifier of last suspend (used to detect items modified during a suspend / resume)
3891/// (needs only be saved if derived datastore cannot work with timestamps and has
3892/// its own identifier)
3893/// - fPendingAddMaps = map<string,string>. The implementation must load all all pending maps (client only) into
3894/// fPendingAddMaps (and fUnconfirmedMaps must be left empty).
3895/// - fTempGUIDMap = map<string,string>. The implementation must save all entries as temporary LUID to GUID mappings
3896/// (server only)
3897localstatus TODBCApiDS::apiLoadAdminData(
3898 const char *aDeviceID, // remote device URI (device ID)
3899 const char *aDatabaseID, // database ID
3900 const char *aRemoteDBID // database ID of remote device
3901)
3902{
3903 #ifndef HAS_SQL_ADMIN
3904 return 510; // must use plugin, no ODBC
3905 #else // HAS_SQL_ADMIN
3906 localstatus sta=0; // assume ok
3907 string sql;
3908
3909 // ODBC based target/map
3910 fODBCAdminData=true;
3911 // determine Folder key, if any
3912 sta=LOCERR_OK;
3913 // - determine folder name, if any
3914 string bname,foldername;
3915 analyzeName(aDatabaseID,&bname,&foldername);
3916 // - search on foldername
3917 SQLRETURNlong res;
3918 SQLHSTMTlong statement=fAgentP->newStatementHandle(getODBCConnectionHandle());
3919 try {
3920 // get SQL
3921 sql = fConfigP->fFolderKeySQL;
3922 if (!sql.empty()) {
3923 // substitute: %F = foldername
3924 StringSubst(sql,"%F",foldername,2,fConfigP->sqlPrepCharSet(),fConfigP->fDataLineEndMode,fConfigP->fQuotingMode);
3925 DoSQLSubstitutions(sql); // for %u = userkey, %d=devicekey
3926 // issue
3927 execSQLStatement(statement,sql,true,"getting folder key",false);
3928 // - fetch result row
3929 res=SafeSQLFetch(statement);
3930 if (!fAgentP->checkStatementHasData(res,statement)) {
3931 // No data: user does not have permission for this folder
3932 PDEBUGPRINTFX(DBG_ERROR,("apiLoadAdminData: User has no permission for acessing folder '%s'",foldername.c_str())){ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ("apiLoadAdminData: User has no permission for acessing folder '%s'"
,foldername.c_str()); }
;
3933 sta=403;
3934 }
3935 else {
3936 // get folder key
3937 fAgentP->getColumnValueAsString(statement,1,fFolderKey,chs_ascii);
3938 PDEBUGPRINTFX(DBG_ADMIN,("apiLoadAdminData: Folder key is '%s'",fFolderKey.c_str())){ if (((0x00000040) & getDbgMask()) == (0x00000040)) getDbgLogger
()->setNextMask(0x00000040).DebugPrintfLastMask ("apiLoadAdminData: Folder key is '%s'"
,fFolderKey.c_str()); }
;
3939 }
3940 SafeSQLCloseCursor(statement);
3941 } // if folderkeySQL
3942 else {
3943 // no folderkey, just copy user key
3944 fFolderKey=fAgentP->getUserKey();
3945 }
3946 if (sta==LOCERR_OK) {
3947 // now determine sync target key
3948 bool madenew=false;
3949 do {
3950 // get SQL
3951 sql=fConfigP->fGetSyncTargetSQL;
3952 // substitute standard: %f=folderkey, %u=userkey, %d=devicekey, %t=targetkey
3953 DoSQLSubstitutions(sql);
3954 // substitute specific: %D=deviceid, %P=devicedbpath
3955 StringSubst(sql,"%D",aDeviceID,2,-1,fConfigP->sqlPrepCharSet(),fConfigP->fDataLineEndMode,fConfigP->fQuotingMode);
3956 StringSubst(sql,"%P",aRemoteDBID,2,-1,fConfigP->sqlPrepCharSet(),fConfigP->fDataLineEndMode,fConfigP->fQuotingMode);
3957 // issue
3958 execSQLStatement(statement,sql,true,"getting target info",false);
3959 // - fetch result row
3960 res=SafeSQLFetch(statement);
3961 if (!fAgentP->checkStatementHasData(res,statement)) {
3962 if (madenew)
3963 throw TSyncException("apiLoadAdminData: new created SyncTarget is not accessible");
3964 // target does not yet exist, create new
3965 DEBUGPRINTFX(DBG_ADMIN,("apiLoadAdminData: Target does not yet exist, create new")){ if (((0x00000040) & getDbgMask()) == (0x00000040)) getDbgLogger
()->setNextMask(0x00000040).DebugPrintfLastMask ("apiLoadAdminData: Target does not yet exist, create new"
); }
;
3966 SafeSQLCloseCursor(statement);
3967 // create new sync target entry
3968 sql=fConfigP->fNewSyncTargetSQL;
3969 // substitute standard: %f=folderkey, %u=userkey, %d=devicekey, %t=targetkey
3970 DoSQLSubstitutions(sql);
3971 // substitute specific: %D=deviceid, %P=devicedbpath
3972 StringSubst(sql,"%D",aDeviceID,2,-1,fConfigP->sqlPrepCharSet(),fConfigP->fDataLineEndMode,fConfigP->fQuotingMode);
3973 StringSubst(sql,"%P",aRemoteDBID,2,-1,fConfigP->sqlPrepCharSet(),fConfigP->fDataLineEndMode,fConfigP->fQuotingMode);
3974 // now issue
3975 execSQLStatement(statement,sql,false,"creating new target info",false);
3976 // try to read again
3977 madenew=true;
3978 continue;
3979 }
3980 else {
3981 // get data: IN ASCENDING COL ORDER!!!
3982 sInt16 col=1;
3983 fAgentP->getColumnValueAsString(statement,col++,fTargetKey,chs_ascii);
3984 fAgentP->getColumnValueAsString(statement,col++,fLastRemoteAnchor,chs_ascii);
3985 PDEBUGPRINTFX(DBG_ADMIN,("apiLoadAdminData: Target key is '%s'",fTargetKey.c_str())){ if (((0x00000040) & getDbgMask()) == (0x00000040)) getDbgLogger
()->setNextMask(0x00000040).DebugPrintfLastMask ("apiLoadAdminData: Target key is '%s'"
,fTargetKey.c_str()); }
;
3986 PDEBUGPRINTFX(DBG_ADMIN,("apiLoadAdminData: Saved Remote Sync Anchor is '%s'",fLastRemoteAnchor.c_str())){ if (((0x00000040) & getDbgMask()) == (0x00000040)) getDbgLogger
()->setNextMask(0x00000040).DebugPrintfLastMask ("apiLoadAdminData: Saved Remote Sync Anchor is '%s'"
,fLastRemoteAnchor.c_str()); }
;
3987 // first sync if target record is new or if remote anchor is (still) empty
3988 // NOTE: this is because only a successful sync makes next attempt non-firsttime!
3989 fFirstTimeSync=madenew || fLastRemoteAnchor.empty();
3990 // last to remote sync reference date/time OR start of last session date/time
3991 // Note: If we DON'T HAVE fSyncTimeStampAtEnd, but HAVE fOneWayFromRemoteSupported,
3992 // this is NOT the reference time here, but the session start time;
3993 // the reference time is saved separately (see below)
3994 lineartime_t dltime;
3995 getColumnsAsTimestamp(statement,col,fConfigP->fSyncTimestamp,dltime, TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)));
3996 if (!fConfigP->fSyncTimeStampAtEnd && fConfigP->fOneWayFromRemoteSupported)
3997 fPreviousSyncTime = dltime;
3998 else {
3999 fPreviousToRemoteSyncCmpRef = dltime;
4000 }
4001 // In case we DON'T HAVE fSyncTimeStampAtEnd, and DON'T HAVE fOneWayFromRemoteSupported,
4002 // the above loaded fPreviousToRemoteSyncCmpRef is the ONLY time we get, so for this
4003 // case we won't get separate session start time below, so initialize it here.
4004 fPreviousSyncTime = dltime;
4005 // check for separate start-of-session time
4006 if (fConfigP->fSyncTimeStampAtEnd) {
4007 // @note For new V3.0 architecture, this is now a bit the wrong way around, as
4008 // we only need the anchor (fPreviousSyncTime) and a reference for the
4009 // last-to-remote sync (fPreviousToRemoteSyncCmpRef). However, to remain compatible with
4010 // existing DB schemas we need to swap values a bit.
4011 // In case database cannot explicitly set modification timestamps, we need the start-of-session
4012 // (which is used for creation of anchor for remote) and we need a comparison reference.
4013 // - override anchor time with separately stored value
4014 getColumnsAsTimestamp(statement,col,fConfigP->fSyncTimestamp,fPreviousSyncTime, TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)));
4015 }
4016 // get date and time of last two-way-sync if database has it stored separately
4017 // Note: when we have fSyncTimeStampAtEnd, fPreviousToRemoteSyncCmpRef is already set here,
4018 // but we'll get it again (it is redundantly stored if we have both fSyncTimeStampAtEnd and fPreviousToRemoteSyncCmpRef)
4019 // - get from DB if available
4020 if (fConfigP->fOneWayFromRemoteSupported) {
4021 // one-way from remote is supported: we have a separate date for last sync that sent changes to remote
4022 // - override fPreviousToRemoteSyncCmpRef (when one-way-from-remote is supported, this is not
4023 // necessarily same as start of last session (fPreviousSyncTime) any more)
4024 getColumnsAsTimestamp(statement,col,fConfigP->fSyncTimestamp,fPreviousToRemoteSyncCmpRef, TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)));
4025 if (fConfigP->fSyncTimeStampAtEnd) {
4026 // Note: for V3.0, this may be redundant and already loaded in case we also have fSyncTimeStampAtEnd
4027 getColumnsAsTimestamp(statement,col,fConfigP->fSyncTimestamp,fPreviousToRemoteSyncCmpRef, TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)));
4028 }
4029 }
4030 // get opaque reference identifier from DB, if it stores them
4031 // (otherwise, implMakeAdminReady will create ISO timestamp strings as standard identifiers)
4032 if (fConfigP->fStoreSyncIdentifiers) {
4033 // we have stored the identifier (and do now know its meaning, it's just a string that
4034 // the DB implementation needs to sort out changed items in the datastore
4035 fAgentP->getColumnValueAsString(statement,col++,fPreviousToRemoteSyncIdentifier,chs_ascii);
4036 }
4037 // Summarize
4038 #ifdef SYDEBUG2
4039 string ts;
4040 StringObjTimestamp(ts,fPreviousSyncTime);
4041 DEBUGPRINTFX(DBG_ADMIN,("apiLoadAdminData: start time of last sync is: %s",ts.c_str())){ if (((0x00000040) & getDbgMask()) == (0x00000040)) getDbgLogger
()->setNextMask(0x00000040).DebugPrintfLastMask ("apiLoadAdminData: start time of last sync is: %s"
,ts.c_str()); }
;
4042 StringObjTimestamp(ts,fPreviousToRemoteSyncCmpRef);
4043 DEBUGPRINTFX(DBG_ADMIN,("apiLoadAdminData: compare reference time of last-to-remote-sync is: %s",ts.c_str())){ if (((0x00000040) & getDbgMask()) == (0x00000040)) getDbgLogger
()->setNextMask(0x00000040).DebugPrintfLastMask ("apiLoadAdminData: compare reference time of last-to-remote-sync is: %s"
,ts.c_str()); }
;
4044 DEBUGPRINTFX(DBG_ADMIN,("apiLoadAdminData: stored reference identifier of last-to-remote-sync is: '%s'",fPreviousToRemoteSyncIdentifier.c_str())){ if (((0x00000040) & getDbgMask()) == (0x00000040)) getDbgLogger
()->setNextMask(0x00000040).DebugPrintfLastMask ("apiLoadAdminData: stored reference identifier of last-to-remote-sync is: '%s'"
,fPreviousToRemoteSyncIdentifier.c_str()); }
;
4045 #endif
4046 // Resume support if there
4047 if (fConfigP->fResumeSupport) {
4048 // next column is alert code for resuming
4049 uInt32 templong;
4050 fAgentP->getColumnValueAsULong(statement,col++,templong);
4051 fResumeAlertCode=templong;
4052 PDEBUGPRINTFX(DBG_ADMIN+DBG_EXOTIC,("apiLoadAdminData: fResumeAlertCode is %hd",fResumeAlertCode)){ if (((0x00000040 +0x80000000) & getDbgMask()) == (0x00000040
+0x80000000)) getDbgLogger()->setNextMask(0x00000040 +0x80000000
).DebugPrintfLastMask ("apiLoadAdminData: fResumeAlertCode is %hd"
,fResumeAlertCode); }
;
4053 // next column(s) is/are last suspend date/time
4054 getColumnsAsTimestamp(statement,col,fConfigP->fSyncTimestamp,fPreviousSuspendCmpRef, TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)));
4055 #ifdef SYDEBUG2
4056 StringObjTimestamp(ts,fPreviousSuspendCmpRef);
4057 PDEBUGPRINTFX(DBG_ADMIN+DBG_EXOTIC,("apiLoadAdminData: compare reference time of last suspend is %s",ts.c_str())){ if (((0x00000040 +0x80000000) & getDbgMask()) == (0x00000040
+0x80000000)) getDbgLogger()->setNextMask(0x00000040 +0x80000000
).DebugPrintfLastMask ("apiLoadAdminData: compare reference time of last suspend is %s"
,ts.c_str()); }
;
4058 #endif
4059 // Get last suspend identifier from DB
4060 if (fConfigP->fStoreSyncIdentifiers) {
4061 // next line is opaque string idenifier of last sync with transfer to remote
4062 fAgentP->getColumnValueAsString(statement,col++,fPreviousSuspendIdentifier,chs_ascii);
4063 PDEBUGPRINTFX(DBG_ADMIN+DBG_EXOTIC,("apiLoadAdminData: stored reference identifier of last suspend is '%s'",fPreviousSuspendIdentifier.c_str())){ if (((0x00000040 +0x80000000) & getDbgMask()) == (0x00000040
+0x80000000)) getDbgLogger()->setNextMask(0x00000040 +0x80000000
).DebugPrintfLastMask ("apiLoadAdminData: stored reference identifier of last suspend is '%s'"
,fPreviousSuspendIdentifier.c_str()); }
;
4064 }
4065 // get partial item suspend data, if any:
4066 // - Source,Target,LastStatus,PIState,Totalsize,Unconfirmedsize,StoredSize,BLOB
4067 if (fConfigP->fResumeItemSupport) {
4068 // - last item URIs
4069 fAgentP->getColumnValueAsString(statement,col++,fLastSourceURI,chs_ascii);
4070 fAgentP->getColumnValueAsString(statement,col++,fLastTargetURI,chs_ascii);
4071 // - last status
4072 fAgentP->getColumnValueAsULong(statement,col++,templong); fLastItemStatus=templong;
4073 // - partial item state
4074 fAgentP->getColumnValueAsULong(statement,col++,templong); fPartialItemState=(TPartialItemState)templong;
4075 // - sizes
4076 fAgentP->getColumnValueAsULong(statement,col++,fPITotalSize);
4077 fAgentP->getColumnValueAsULong(statement,col++,fPIUnconfirmedSize);
4078 fAgentP->getColumnValueAsULong(statement,col++,fPIStoredSize);
4079 // - the BLOB data itself
4080 string blob;
4081 fAgentP->getColumnValueAsString(statement,col++,blob,chs_ascii,true);
4082 // - move it into data
4083 if (fPIStoredDataP && fPIStoredDataAllocated) smlLibFree(fPIStoredDataP);
4084 fPIStoredDataAllocated=false;
4085 fPIStoredDataP=smlLibMalloc(blob.size()+1); // plus terminator for string interpretation
4086 if (fPIStoredDataP) {
4087 fPIStoredDataAllocated=true;
4088 smlLibMemcpy(fPIStoredDataP,blob.c_str(),blob.size());
4089 if (fPIStoredSize>blob.size()) fPIStoredSize=blob.size(); // security
4090 *((uInt8 *)fPIStoredDataP+fPIStoredSize)=0; // string terminator
4091 }
4092 PDEBUGPRINTFX(DBG_ADMIN+DBG_EXOTIC,({ if (((0x00000040 +0x80000000) & getDbgMask()) == (0x00000040
+0x80000000)) getDbgLogger()->setNextMask(0x00000040 +0x80000000
).DebugPrintfLastMask ( "apiLoadAdminData: partial item resume info: src='%s', targ='%s', lastStatus=%hd, state=%hd, totalSz=%ld, unconfSz=%ld, storedSz=%ld"
, fLastSourceURI.c_str(), fLastTargetURI.c_str(), fLastItemStatus
, (TSyError)fPartialItemState, fPITotalSize, fPIUnconfirmedSize
, fPIStoredSize ); }
4093 "apiLoadAdminData: partial item resume info: src='%s', targ='%s', lastStatus=%hd, state=%hd, totalSz=%ld, unconfSz=%ld, storedSz=%ld",{ if (((0x00000040 +0x80000000) & getDbgMask()) == (0x00000040
+0x80000000)) getDbgLogger()->setNextMask(0x00000040 +0x80000000
).DebugPrintfLastMask ( "apiLoadAdminData: partial item resume info: src='%s', targ='%s', lastStatus=%hd, state=%hd, totalSz=%ld, unconfSz=%ld, storedSz=%ld"
, fLastSourceURI.c_str(), fLastTargetURI.c_str(), fLastItemStatus
, (TSyError)fPartialItemState, fPITotalSize, fPIUnconfirmedSize
, fPIStoredSize ); }
4094 fLastSourceURI.c_str(),{ if (((0x00000040 +0x80000000) & getDbgMask()) == (0x00000040
+0x80000000)) getDbgLogger()->setNextMask(0x00000040 +0x80000000
).DebugPrintfLastMask ( "apiLoadAdminData: partial item resume info: src='%s', targ='%s', lastStatus=%hd, state=%hd, totalSz=%ld, unconfSz=%ld, storedSz=%ld"
, fLastSourceURI.c_str(), fLastTargetURI.c_str(), fLastItemStatus
, (TSyError)fPartialItemState, fPITotalSize, fPIUnconfirmedSize
, fPIStoredSize ); }
4095 fLastTargetURI.c_str(),{ if (((0x00000040 +0x80000000) & getDbgMask()) == (0x00000040
+0x80000000)) getDbgLogger()->setNextMask(0x00000040 +0x80000000
).DebugPrintfLastMask ( "apiLoadAdminData: partial item resume info: src='%s', targ='%s', lastStatus=%hd, state=%hd, totalSz=%ld, unconfSz=%ld, storedSz=%ld"
, fLastSourceURI.c_str(), fLastTargetURI.c_str(), fLastItemStatus
, (TSyError)fPartialItemState, fPITotalSize, fPIUnconfirmedSize
, fPIStoredSize ); }
4096 fLastItemStatus,{ if (((0x00000040 +0x80000000) & getDbgMask()) == (0x00000040
+0x80000000)) getDbgLogger()->setNextMask(0x00000040 +0x80000000
).DebugPrintfLastMask ( "apiLoadAdminData: partial item resume info: src='%s', targ='%s', lastStatus=%hd, state=%hd, totalSz=%ld, unconfSz=%ld, storedSz=%ld"
, fLastSourceURI.c_str(), fLastTargetURI.c_str(), fLastItemStatus
, (TSyError)fPartialItemState, fPITotalSize, fPIUnconfirmedSize
, fPIStoredSize ); }
4097 (TSyError)fPartialItemState,{ if (((0x00000040 +0x80000000) & getDbgMask()) == (0x00000040
+0x80000000)) getDbgLogger()->setNextMask(0x00000040 +0x80000000
).DebugPrintfLastMask ( "apiLoadAdminData: partial item resume info: src='%s', targ='%s', lastStatus=%hd, state=%hd, totalSz=%ld, unconfSz=%ld, storedSz=%ld"
, fLastSourceURI.c_str(), fLastTargetURI.c_str(), fLastItemStatus
, (TSyError)fPartialItemState, fPITotalSize, fPIUnconfirmedSize
, fPIStoredSize ); }
4098 fPITotalSize,{ if (((0x00000040 +0x80000000) & getDbgMask()) == (0x00000040
+0x80000000)) getDbgLogger()->setNextMask(0x00000040 +0x80000000
).DebugPrintfLastMask ( "apiLoadAdminData: partial item resume info: src='%s', targ='%s', lastStatus=%hd, state=%hd, totalSz=%ld, unconfSz=%ld, storedSz=%ld"
, fLastSourceURI.c_str(), fLastTargetURI.c_str(), fLastItemStatus
, (TSyError)fPartialItemState, fPITotalSize, fPIUnconfirmedSize
, fPIStoredSize ); }
4099 fPIUnconfirmedSize,{ if (((0x00000040 +0x80000000) & getDbgMask()) == (0x00000040
+0x80000000)) getDbgLogger()->setNextMask(0x00000040 +0x80000000
).DebugPrintfLastMask ( "apiLoadAdminData: partial item resume info: src='%s', targ='%s', lastStatus=%hd, state=%hd, totalSz=%ld, unconfSz=%ld, storedSz=%ld"
, fLastSourceURI.c_str(), fLastTargetURI.c_str(), fLastItemStatus
, (TSyError)fPartialItemState, fPITotalSize, fPIUnconfirmedSize
, fPIStoredSize ); }
4100 fPIStoredSize{ if (((0x00000040 +0x80000000) & getDbgMask()) == (0x00000040
+0x80000000)) getDbgLogger()->setNextMask(0x00000040 +0x80000000
).DebugPrintfLastMask ( "apiLoadAdminData: partial item resume info: src='%s', targ='%s', lastStatus=%hd, state=%hd, totalSz=%ld, unconfSz=%ld, storedSz=%ld"
, fLastSourceURI.c_str(), fLastTargetURI.c_str(), fLastItemStatus
, (TSyError)fPartialItemState, fPITotalSize, fPIUnconfirmedSize
, fPIStoredSize ); }
4101 )){ if (((0x00000040 +0x80000000) & getDbgMask()) == (0x00000040
+0x80000000)) getDbgLogger()->setNextMask(0x00000040 +0x80000000
).DebugPrintfLastMask ( "apiLoadAdminData: partial item resume info: src='%s', targ='%s', lastStatus=%hd, state=%hd, totalSz=%ld, unconfSz=%ld, storedSz=%ld"
, fLastSourceURI.c_str(), fLastTargetURI.c_str(), fLastItemStatus
, (TSyError)fPartialItemState, fPITotalSize, fPIUnconfirmedSize
, fPIStoredSize ); }
;
4102 }
4103 }
4104 SafeSQLCloseCursor(statement);
4105 // now read current map entries of target
4106 sql=fConfigP->fMapFetchAllSQL;
4107 DoSQLSubstitutions(sql); // only folderkey,targetkey,userkey is available
4108 // - issue
4109 execSQLStatement(statement,sql,true,"reading map into internal list",false);
4110 // - fetch results
4111 do {
4112 res=SafeSQLFetch(statement);
4113 // check for end
4114 if (!fAgentP->checkStatementHasData(res,statement)) break;
4115 // get map entry
4116 TMapEntry entry;
4117 fAgentP->getColumnValueAsString(statement,1,entry.localid,chs_ascii);
4118 fAgentP->getColumnValueAsString(statement,2,entry.remoteid,chs_ascii);
4119 if (fConfigP->fResumeSupport) {
4120 // we have a separate entry type
4121 uInt32 et;
4122 fAgentP->getColumnValueAsULong(statement,3,et);
4123 if (et>=numMapEntryTypes)
4124 entry.entrytype = mapentry_invalid;
4125 else
4126 entry.entrytype = (TMapEntryType)et;
4127 // and some map flags
4128 fAgentP->getColumnValueAsULong(statement,4,entry.mapflags);
4129 }
4130 else {
4131 entry.mapflags=0; // no flags stored
4132 entry.entrytype=mapentry_normal; // normal entry
4133 }
4134 PDEBUGPRINTFX(DBG_ADMIN+DBG_EXOTIC,({ if (((0x00000040 +0x80000000) & getDbgMask()) == (0x00000040
+0x80000000)) getDbgLogger()->setNextMask(0x00000040 +0x80000000
).DebugPrintfLastMask ( "read map entry (type=%s): localid='%s', remoteid='%s', mapflags=0x%lX"
, MapEntryTypeNames[entry.entrytype], entry.localid.c_str(), entry
.remoteid.c_str(), entry.mapflags ); }
4135 "read map entry (type=%s): localid='%s', remoteid='%s', mapflags=0x%lX",{ if (((0x00000040 +0x80000000) & getDbgMask()) == (0x00000040
+0x80000000)) getDbgLogger()->setNextMask(0x00000040 +0x80000000
).DebugPrintfLastMask ( "read map entry (type=%s): localid='%s', remoteid='%s', mapflags=0x%lX"
, MapEntryTypeNames[entry.entrytype], entry.localid.c_str(), entry
.remoteid.c_str(), entry.mapflags ); }
4136 MapEntryTypeNames[entry.entrytype],{ if (((0x00000040 +0x80000000) & getDbgMask()) == (0x00000040
+0x80000000)) getDbgLogger()->setNextMask(0x00000040 +0x80000000
).DebugPrintfLastMask ( "read map entry (type=%s): localid='%s', remoteid='%s', mapflags=0x%lX"
, MapEntryTypeNames[entry.entrytype], entry.localid.c_str(), entry
.remoteid.c_str(), entry.mapflags ); }
4137 entry.localid.c_str(),{ if (((0x00000040 +0x80000000) & getDbgMask()) == (0x00000040
+0x80000000)) getDbgLogger()->setNextMask(0x00000040 +0x80000000
).DebugPrintfLastMask ( "read map entry (type=%s): localid='%s', remoteid='%s', mapflags=0x%lX"
, MapEntryTypeNames[entry.entrytype], entry.localid.c_str(), entry
.remoteid.c_str(), entry.mapflags ); }
4138 entry.remoteid.c_str(),{ if (((0x00000040 +0x80000000) & getDbgMask()) == (0x00000040
+0x80000000)) getDbgLogger()->setNextMask(0x00000040 +0x80000000
).DebugPrintfLastMask ( "read map entry (type=%s): localid='%s', remoteid='%s', mapflags=0x%lX"
, MapEntryTypeNames[entry.entrytype], entry.localid.c_str(), entry
.remoteid.c_str(), entry.mapflags ); }
4139 entry.mapflags{ if (((0x00000040 +0x80000000) & getDbgMask()) == (0x00000040
+0x80000000)) getDbgLogger()->setNextMask(0x00000040 +0x80000000
).DebugPrintfLastMask ( "read map entry (type=%s): localid='%s', remoteid='%s', mapflags=0x%lX"
, MapEntryTypeNames[entry.entrytype], entry.localid.c_str(), entry
.remoteid.c_str(), entry.mapflags ); }
4140 )){ if (((0x00000040 +0x80000000) & getDbgMask()) == (0x00000040
+0x80000000)) getDbgLogger()->setNextMask(0x00000040 +0x80000000
).DebugPrintfLastMask ( "read map entry (type=%s): localid='%s', remoteid='%s', mapflags=0x%lX"
, MapEntryTypeNames[entry.entrytype], entry.localid.c_str(), entry
.remoteid.c_str(), entry.mapflags ); }
;
4141 // save entry in list
4142 entry.changed=false; // not yet changed
4143 entry.added=false; // already there
4144 // remember saved state of suspend mark
4145 entry.markforresume=false; // not yet marked for this session (mark of last session is in mapflag_useforresume!)
4146 entry.savedmark=entry.mapflags & mapflag_useforresume0x00000001;
4147 // IMPORTANT: non-normal entries must be saved as deleted in the main map - they will be re-activated at the
4148 // next save if needed
4149 entry.deleted = entry.entrytype!=mapentry_normal; // only normal ones may be saved as existing in the main map
4150 // save to main map list anyway to allow differential SQL updates to map table (instead of writing everything all the time)
4151 fMapTable.push_back(entry);
4152 // now save special maps to extra lists according to type
4153 // Note: in the main map, these are marked deleted. Before the next saveAdminData, these will
4154 // be re-added (=re-activated) from the extra lists if they still exist.
4155 switch (entry.entrytype) {
4156 #ifdef SYSYNC_SERVER1
4157 case mapentry_tempidmap:
4158 if (IS_SERVER(getSyncAppBase()->isServer()))
4159 fTempGUIDMap[entry.remoteid]=entry.localid; // tempGUIDs are accessed by remoteID=tempID
4160 break;
4161 #endif
4162 #ifdef SYSYNC_CLIENT1
4163 case mapentry_pendingmap:
4164 if (IS_CLIENT(!getSyncAppBase()->isServer()))
4165 fPendingAddMaps[entry.localid]=entry.remoteid;
4166 break;
4167 #endif
4168 }
4169 } while(true);
4170 // no more records
4171 SafeSQLCloseCursor(statement);
4172 // done
4173 break;
4174 } // target record found
4175 } while(true);
4176 } // if ok
4177 if (sta==LOCERR_OK) {
4178 // determine how to create new IDs for database
4179 fNextLocalID=-1; // no global ID, must be determined at actual insert
4180 // for some databases w/o usable ID variable (FMPro), starting point for ids
4181 // might be some "SELECT MAX()" query, which would be executed here
4182 if (fConfigP->fDetermineNewIDOnce) {
4183 uInt32 maxid;
4184 SQLRETURNlong res;
4185 sql=fConfigP->fObtainNewLocalIDSql.c_str();
4186 DoSQLSubstitutions(sql);
4187 execSQLStatement(statement,sql,true,"Next-to-be-used localID (determined once in makeAdminReady)",false);
4188 // - fetch result row
4189 res=SafeSQLFetch(statement);
4190 fAgentP->checkStatementError(res,statement);
4191 // - get value of first field as long
4192 if (fAgentP->getColumnValueAsULong(statement,1,maxid)) {
4193 // next ID found
4194 if (maxid<fConfigP->fMinNextID)
4195 maxid=fConfigP->fMinNextID; // don't use value smaller than defined minimum
4196 }
4197 else {
4198 // no max value found, start at MinNextID
4199 maxid=fConfigP->fMinNextID;
4200 }
4201 SafeSQLCloseCursor(statement);
4202 // now assign
4203 fNextLocalID=maxid;
4204 #ifdef SYDEBUG2
4205 PDEBUGPRINTFX(DBG_ADMIN,("apiLoadAdminData: next local ID is %ld",fNextLocalID)){ if (((0x00000040) & getDbgMask()) == (0x00000040)) getDbgLogger
()->setNextMask(0x00000040).DebugPrintfLastMask ("apiLoadAdminData: next local ID is %ld"
,fNextLocalID); }
;
4206 #endif
4207 }
4208 } // if ok
4209 // release the statement handle
4210 SafeSQLFreeHandle(SQL_HANDLE_STMT,statement);
4211 }
4212 catch (...) {
4213 // release the statement handle
4214 SafeSQLFreeHandle(SQL_HANDLE_STMT,statement);
4215 throw;
4216 }
4217 PDEBUGPRINTFX(DBG_ADMIN,("apiLoadAdminData: Number of map entries read = %ld",fMapTable.size())){ if (((0x00000040) & getDbgMask()) == (0x00000040)) getDbgLogger
()->setNextMask(0x00000040).DebugPrintfLastMask ("apiLoadAdminData: Number of map entries read = %ld"
,fMapTable.size()); }
;
4218 return sta;
4219 #endif // HAS_SQL_ADMIN
4220} // TODBCApiDS::apiLoadAdminData
4221
4222
4223/// @brief Save admin data to ODBC database
4224/// @param[in] aSessionFinished if true, this is a end-of-session save (and not only a suspend save)
4225/// Must save to the target record addressed at LoadAdminData() by the triple (aDeviceID,aDatabaseID,aRemoteDBID)
4226/// Implementation must save the following items:
4227/// - fLastRemoteAnchor = anchor string used by remote party for this session (and saved to DB then)
4228/// - fPreviousSyncTime = anchor (beginning of session) timestamp of this session.
4229/// - fPreviousToRemoteSyncCmpRef = Reference time to determine items modified since last time sending data to remote
4230/// - fPreviousToRemoteSyncIdentifier = string identifying last session that sent data to remote (needs only be saved
4231/// if derived datastore cannot work with timestamps and has its own identifier).
4232/// - fMapTable = list<TMapEntry> containing map entries. The implementation must save all map entries
4233/// that have changed, are new or are deleted. See below for additional resume requirements.
4234/// For resumable datastores:
4235/// - fMapTable = In addition to the above, the markforresume flag must be saved in the mapflags
4236// when it is not equal to the savedmark flag - independently of added/deleted/changed.
4237/// - fResumeAlertCode = alert code of current suspend state, 0 if none
4238/// - fPreviousSuspendCmpRef = reference time of last suspend (used to detect items modified during a suspend / resume)
4239/// - fPreviousSuspendIdentifier = identifier of last suspend (used to detect items modified during a suspend / resume)
4240/// (needs only be saved if derived datastore cannot work with timestamps and has
4241/// its own identifier)
4242/// - fPendingAddMaps and fUnconfirmedMaps = map<string,string>. The implementation must save all entries as
4243/// pending maps (client only). Note that fPendingAddMaps might contain temporary localIDs,
4244/// so call dsFinalizeLocalID() to ensure these are converted to final before saving.
4245/// - fTempGUIDMap = map<string,string>. The implementation must save all entries as temporary LUID to GUID mappings
4246/// (server only)
4247localstatus TODBCApiDS::apiSaveAdminData(bool aSessionFinished, bool aSuccessful)
4248{
4249 #ifndef HAS_SQL_ADMIN
4250 return 510; // must use plugin, no ODBC
4251 #else
4252 SQLRETURNlong res;
4253 localstatus sta=LOCERR_OK;
4254 try {
4255 // if data is not handled by ODBC, we might have no write statement here, so open one
4256 if (fODBCWriteStatement==SQL_NULL_HANDLE0)
4257 fODBCWriteStatement = fAgentP->newStatementHandle(getODBCConnectionHandle());
4258 // now save target head record admin data
4259 sta=updateSyncTarget(fODBCWriteStatement,aSessionFinished && aSuccessful);
4260 if (sta==LOCERR_OK) {
4261 // then apply Map modifications (target detail records)
4262 sta=updateODBCMap(fODBCWriteStatement,aSessionFinished && aSuccessful);
4263 }
4264 // dispose of Write statement handle
4265 if (fODBCWriteStatement!=SQL_NULL_HANDLE0 && aSessionFinished) {
4266 DEBUGPRINTFX(DBG_DBAPI,("SaveAdminData: End of session - Freeing Write statement handle...")){ if (((0x02000000) & getDbgMask()) == (0x02000000)) getDbgLogger
()->setNextMask(0x02000000).DebugPrintfLastMask ("SaveAdminData: End of session - Freeing Write statement handle..."
); }
;
4267 res=SafeSQLFreeHandle(SQL_HANDLE_STMT,fODBCWriteStatement);
4268 fODBCWriteStatement=SQL_NULL_HANDLE0;
4269 checkConnectionError(res);
4270 }
4271 // commit transaction
4272 if (fODBCConnectionHandle!=SQL_NULL_HANDLE0) {
4273 // commit DB transaction
4274 DEBUGPRINTFX(DBG_DBAPI,("SaveAdminData(commit): Committing DB transaction...")){ if (((0x02000000) & getDbgMask()) == (0x02000000)) getDbgLogger
()->setNextMask(0x02000000).DebugPrintfLastMask ("SaveAdminData(commit): Committing DB transaction..."
); }
;
4275 res=SafeSQLEndTran(SQL_HANDLE_DBC,getODBCConnectionHandle(),SQL_COMMIT);
4276 checkConnectionError(res);
4277 }
4278 }
4279 catch (exception &e) {
4280 PDEBUGPRINTFX(DBG_ERROR,("******** SaveAdminData exception: %s",e.what())){ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ("******** SaveAdminData exception: %s"
,e.what()); }
;
4281 sta=510; // failed, DB error
4282 }
4283 // return status
4284 return sta;
4285 #endif // HAS_SQL_ADMIN
4286} // TODBCApiDS::apiSaveAdminData
4287
4288
4289// - end DB data write sequence (but not yet admin data)
4290localstatus TODBCApiDS::apiEndDataWrite(string &aThisSyncIdentifier)
4291{
4292 // we do not have a separate sync identifier
4293 aThisSyncIdentifier.erase();
4294 // make sure we commit the transaction here in case admin data is not in ODBC
4295 #ifdef ODBCAPI_SUPPORT
4296 try {
4297 SQLRETURNlong res;
4298 if (fODBCWriteStatement!=SQL_NULL_HANDLE0) {
4299 DEBUGPRINTFX(DBG_DBAPI,("EndDBDataWrite: we have no ODBC admin data, freeing Write statement handle now...")){ if (((0x02000000) & getDbgMask()) == (0x02000000)) getDbgLogger
()->setNextMask(0x02000000).DebugPrintfLastMask ("EndDBDataWrite: we have no ODBC admin data, freeing Write statement handle now..."
); }
;
4300 res=SafeSQLFreeHandle(SQL_HANDLE_STMT,fODBCWriteStatement);
4301 fODBCWriteStatement=SQL_NULL_HANDLE0;
4302 checkConnectionError(res);
4303 }
4304 // commit DB transaction
4305 if (fODBCConnectionHandle!=SQL_NULL_HANDLE0) {
4306 DEBUGPRINTFX(DBG_DBAPI,("EndDBDataWrite: ...and commit DB transaction")){ if (((0x02000000) & getDbgMask()) == (0x02000000)) getDbgLogger
()->setNextMask(0x02000000).DebugPrintfLastMask ("EndDBDataWrite: ...and commit DB transaction"
); }
;
4307 res=SafeSQLEndTran(SQL_HANDLE_DBC,getODBCConnectionHandle(),SQL_COMMIT);
4308 checkConnectionError(res);
4309 }
4310 }
4311 catch (exception &e) {
4312 PDEBUGPRINTFX(DBG_ERROR,("******** EndDBDataWrite exception: %s",e.what())){ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger
()->setNextMask(0x00000002).DebugPrintfLastMask ("******** EndDBDataWrite exception: %s"
,e.what()); }
;
4313 return 510;
4314 }
4315 #endif // ODBCAPI_SUPPORT
4316 return LOCERR_OK;
4317} // TODBCApiDS::apiEndDataWrite
4318
4319
4320/* end of TODBCApiDS implementation */
4321
4322
4323#ifdef STREAMFIELD_SUPPORT1
4324
4325// TODBCFieldProxy
4326// ===============
4327
4328TODBCFieldProxy::TODBCFieldProxy(
4329 TODBCApiDS *aODBCdsP,
4330 TODBCFieldMapItem *aFieldMapP,
4331 const char *aMasterKey,
4332 const char *aDetailKey
4333)
4334{
4335 // save values
4336 fODBCdsP = aODBCdsP;
4337 fFieldMapP = aFieldMapP;
4338 fMasterKey = aMasterKey;
4339 fDetailKey = aDetailKey;
4340 fFetched = false;
4341 fValue.erase();
4342} // TODBCFieldProxy::TODBCFieldProxy
4343
4344
4345TODBCFieldProxy::~TODBCFieldProxy()
4346{
4347 // nop at this time
4348} // TODBCFieldProxy::~TODBCFieldProxy
4349
4350
4351// fetch BLOB from DPAPI
4352void TODBCFieldProxy::fetchBlob(void)
4353{
4354 if (!fFetched) {
4355 // if do not have anything yet and need something, read it now
4356 string sql = fFieldMapP->fReadBlobSQL;
4357 // the only possible substitutions are %k and %K
4358 StringSubst(sql,"%k",fMasterKey,2); // the localID of the entire record
4359 StringSubst(sql,"%K",fDetailKey,2); // the key of this specific array element
4360 // fetch now
4361 SQLHSTMTlong statement = 0;
4362 #ifdef SQLITE_SUPPORT1
4363 if (!fODBCdsP->fUseSQLite)
4364 #endif
4365 {
4366 #ifdef ODBCAPI_SUPPORT
4367 statement=fODBCdsP->fAgentP->newStatementHandle(fODBCdsP->getODBCConnectionHandle());
4368 #endif
4369 }
4370 try {
4371 fODBCdsP->prepareSQLStatement(statement, sql.c_str(), true, "Proxy Field Fetch");
4372 fODBCdsP->execSQLStatement(statement, sql, true, NULL__null, true);
4373 if (!fODBCdsP->fetchNextRow(statement, true)) {
4374 // no data
4375 fValue.erase();
4376 }
4377 else {
4378 // get data
4379 #ifdef SQLITE_SUPPORT1
4380 if (fODBCdsP->fUseSQLite) {
4381 size_t siz = sqlite3_column_bytes(fODBCdsP->fSQLiteStmtP,0);
4382 if (siz>0) {
4383 if (fFieldMapP->dbfieldtype==dbft_blob)
4384 fValue.assign((cAppCharP)sqlite3_column_blob(fODBCdsP->fSQLiteStmtP,0),siz);
4385 else
4386 appendStringAsUTF8((const char *)sqlite3_column_text(fODBCdsP->fSQLiteStmtP,0), fValue, fODBCdsP->fConfigP->sqlPrepCharSet(), lem_cstr); // Convert to app-charset (UTF8) and C-type lineends
4387 }
4388 else {
4389 fValue.erase();
4390 }
4391 }
4392 else
4393 #endif
4394 {
4395 #ifdef ODBCAPI_SUPPORT
4396 // pass in actual charset (including utf16 - getColumnValueAsString handles UTF16 case internally)
4397 fODBCdsP->fAgentP->getColumnValueAsString(statement,1,fValue,fODBCdsP->fConfigP->fDataCharSet,fFieldMapP->dbfieldtype==dbft_blob);
4398 #endif
4399 }
4400 }
4401 // fetched now
4402 fFetched=true;
4403 // done
4404 fODBCdsP->finalizeSQLStatement(statement,true);
4405 // release the statement handle
4406 #ifdef SQLITE_SUPPORT1
4407 if (!fODBCdsP->fUseSQLite)
4408 #endif
4409 {
4410 #ifdef ODBCAPI_SUPPORT
4411 SafeSQLFreeHandle(SQL_HANDLE_STMT,statement);
4412 #endif
4413 }
4414 }
4415 catch (exception &e) {
4416 // release the statement handle
4417 #ifdef SQLITE_SUPPORT1
4418 if (!fODBCdsP->fUseSQLite)
4419 #endif
4420 {
4421 #ifdef ODBCAPI_SUPPORT
4422 SafeSQLFreeHandle(SQL_HANDLE_STMT,statement);
4423 #endif
4424 }
4425 throw;
4426 }
4427 }
4428} // TODBCFieldProxy::fetchBlob
4429
4430
4431// returns size of entire blob
4432size_t TODBCFieldProxy::getBlobSize(TStringField *aFieldP)
4433{
4434 fetchBlob();
4435 return fValue.size();
4436} // TODBCFieldProxy::getBlobSize
4437
4438
4439// read from Blob from specified stream position and update stream pos
4440size_t TODBCFieldProxy::readBlobStream(TStringField *aFieldP, size_t &aPos, void *aBuffer, size_t aMaxBytes)
4441{
4442 if (!fFetched) {
4443 // we need to read the body
4444 fetchBlob();
4445 }
4446 // now copy from our value
4447 if (aPos>fValue.size()) return 0;
4448 if (aPos+aMaxBytes>fValue.size()) aMaxBytes=fValue.size()-aPos;
4449 if (aMaxBytes==0) return 0;
4450 // copy data from ODBC answer buffer to caller's buffer
4451 memcpy(aBuffer,fValue.c_str()+aPos,aMaxBytes);
4452 aPos+= aMaxBytes;
4453 return aMaxBytes; // return number of bytes actually read
4454} // TODBCFieldProxy::readBlobStream
4455
4456#endif // STREAMFIELD_SUPPORT
4457
4458
4459} // namespace
4460
4461#endif // SQL_SUPPORT
4462
4463// eof