File: | libsynthesis/src/DB_interfaces/odbc_db/odbcapids.cpp |
Warning: | line 2031, column 13 Value stored to 'm' is never read |
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 | |
31 | namespace sysync { |
32 | |
33 | // special ID mode names |
34 | const char * const SpecialIDModeNames[numSpecialIDModes] = { |
35 | "none", |
36 | "unixmsrnd6" |
37 | }; |
38 | |
39 | |
40 | |
41 | #ifdef SCRIPT_SUPPORT1 |
42 | |
43 | class TODBCDSfuncs { |
44 | public: |
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 | |
93 | const uInt8 param_OneStr[] = { VAL(fty_string)( (uInt8)fty_string) }; |
94 | |
95 | // builtin function table for datastore level |
96 | const 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 |
106 | static 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 | |
114 | const 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 | |
129 | TOdbcDSConfig::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 | |
139 | TOdbcDSConfig::~TOdbcDSConfig() |
140 | { |
141 | // nop so far |
142 | } // TOdbcDSConfig::~TOdbcDSConfig |
143 | |
144 | |
145 | // init defaults |
146 | void 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 |
237 | bool 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 |
385 | void 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 |
594 | void 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 |
603 | TLocalEngineDS *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 | |
624 | TODBCFieldMapItem::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 | |
634 | void 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 | |
650 | TODBCFieldMapArrayItem::TODBCFieldMapArrayItem(TCustomDSConfig *aCustomDSConfigP, TConfigElement *aParentElement) : |
651 | inherited(aCustomDSConfigP,aParentElement) |
652 | { |
653 | clear(); |
654 | } // TODBCFieldMapArrayItem::TODBCFieldMapArrayItem |
655 | |
656 | |
657 | TODBCFieldMapArrayItem::~TODBCFieldMapArrayItem() |
658 | { |
659 | // nop so far |
660 | clear(); |
661 | } // TODBCFieldMapArrayItem::~TODBCFieldMapArrayItem |
662 | |
663 | |
664 | // init defaults |
665 | void 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 |
679 | bool 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 |
698 | void 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 |
717 | TODBCApiDS::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 | |
758 | TODBCApiDS::~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 |
765 | void 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() |
779 | void 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() |
834 | void 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 |
845 | SQLHDBC 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 |
858 | void 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 |
873 | bool 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) |
934 | bool 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 |
994 | void 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 |
1134 | void 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 |
1224 | void 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 |
1365 | void 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 | |
1378 | void 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 | |
1392 | void 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 |
1412 | bool 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; |
1425 | novalue: |
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 |
1435 | bool 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). |
1453 | bool 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. |
1529 | bool 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 |
1558 | bool 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) |
1643 | bool 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 |
1695 | localstatus 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 |
1704 | localstatus 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 |
1723 | void 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 |
1764 | void 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 |
1828 | void 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 |
1856 | void 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 | |
1880 | typedef struct { |
1881 | const char *substTag; |
1882 | int substCode; |
1883 | } TSubstHandlerDef; |
1884 | |
1885 | typedef 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 | |
1907 | static 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 |
1929 | void 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 |
Value stored to 'm' is never read | |
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 |
2206 | bool 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 "; |
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 |
2266 | void 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 |
2282 | void 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 |
2320 | void 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 |
2344 | void 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 |
2374 | void 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 |
2401 | bool 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 |
2447 | void 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. |
2474 | bool 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 |
2503 | bool 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 |
2511 | bool 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 |
2770 | void 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 |
2800 | lineartime_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 |
2816 | void 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 |
2842 | localstatus 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 |
3016 | localstatus 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 |
3103 | void 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) |
3132 | localstatus 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). |
3170 | localstatus 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 |
3295 | localstatus 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 |
3404 | localstatus 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 |
3434 | localstatus 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. |
3453 | void 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 |
3533 | void 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 |
3548 | void 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 |
3563 | localstatus 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 |
3749 | localstatus 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 |
3814 | localstatus 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) |
3897 | localstatus 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) |
4247 | localstatus 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) |
4290 | localstatus 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 | |
4328 | TODBCFieldProxy::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 | |
4345 | TODBCFieldProxy::~TODBCFieldProxy() |
4346 | { |
4347 | // nop at this time |
4348 | } // TODBCFieldProxy::~TODBCFieldProxy |
4349 | |
4350 | |
4351 | // fetch BLOB from DPAPI |
4352 | void 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 |
4432 | size_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 |
4440 | size_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 |