Store.ecl 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  1. /*##############################################################################
  2. ## HPCC SYSTEMS software Copyright (C) 2019 HPCC Systems®. All rights reserved.
  3. ############################################################################## */
  4. IMPORT Std;
  5. /**
  6. * A specific instance of this module needs connection and user credential
  7. * information, provided as arguments.
  8. *
  9. * @param username The username of the user requesting access to the
  10. * key value store; this is typically the same
  11. * username used to login to ECL Watch; set to an
  12. * empty string if authentication is not required;
  13. * OPTIONAL, defaults to an empty string
  14. * @param userPW The password of the user requesting access to the
  15. * key value store; this is typically the same
  16. * password used to login to ECL Watch; set to an
  17. * empty string if authentication is not required;
  18. * OPTIONAL, defaults to an empty string
  19. * @param espURL The full URL for accessing the esp process running
  20. * on the HPCC Systems cluster (this is typically the
  21. * same URL as used to access ECL Watch); set to an
  22. * empty string to use the URL of the current esp
  23. * process as found via Std.File.GetEspURL();
  24. * OPTIONAL, defaults to an empty string
  25. *
  26. * @return A reference to the module, correctly initialized with the
  27. * given access parameters.
  28. */
  29. EXPORT Store(STRING username = '',
  30. STRING userPW = '',
  31. STRING espURL = '') := MODULE
  32. SHARED MY_USERNAME := TRIM(username, ALL);
  33. SHARED MY_USER_PW := TRIM(userPW, LEFT, RIGHT);
  34. SHARED ENCODED_CREDENTIALS := IF
  35. (
  36. MY_USERNAME != '',
  37. 'Basic ' + Std.Str.EncodeBase64((DATA)(MY_USERNAME + ':' + MY_USER_PW)),
  38. ''
  39. );
  40. // The URL that will be used by all SOAPCALL invocations
  41. TRIMMED_URL := TRIM(espURL, ALL);
  42. SHARED MY_ESP_URL := IF(TRIMMED_URL != '', TRIMMED_URL, Std.File.GetEspURL(MY_USERNAME, MY_USER_PW)) + '/WsStore/?ver_=1.02';
  43. /**
  44. * Helper for function for setting the has_exception field within a
  45. * response record.
  46. *
  47. * @param rec The single RECORD containing a WsStore response
  48. *
  49. * @return The same record with the has_exceptions field set to TRUE
  50. * or FALSE based on examining the exception list.
  51. */
  52. SHARED UpdateForExceptions(res) := FUNCTIONMACRO
  53. RETURN PROJECT
  54. (
  55. res,
  56. TRANSFORM
  57. (
  58. RECORDOF(LEFT),
  59. SELF.has_exceptions := EXISTS(LEFT.exceptions.exceptions),
  60. SELF := LEFT
  61. )
  62. );
  63. ENDMACRO;
  64. //--------------------------------------------------------------------------
  65. // Exported record definitions; all used as results of SOAPCALL invocations
  66. //--------------------------------------------------------------------------
  67. EXPORT ExceptionLayout := RECORD
  68. STRING code {XPATH('Code')};
  69. STRING audience {XPATH('Audience')};
  70. STRING source {XPATH('Source')};
  71. STRING message {XPATH('Message')};
  72. END;
  73. EXPORT ExceptionListLayout := RECORD
  74. STRING source {XPATH('Source')};
  75. DATASET(ExceptionLayout) exceptions {XPATH('Exception')};
  76. END;
  77. EXPORT StoreInfoRec := RECORD
  78. STRING store_name {XPATH('Name')};
  79. STRING description {XPATH('Description')};
  80. STRING owner {XPATH('Owner')};
  81. STRING create_time {XPATH('CreateTime')};
  82. UNSIGNED4 max_value_size {XPATH('MaxValSize')};
  83. BOOLEAN is_default {XPATH('IsDefault')};
  84. END;
  85. EXPORT ListStoresResponseRec := RECORD
  86. DATASET(StoreInfoRec) stores {XPATH('Stores/Store')};
  87. BOOLEAN has_exceptions := FALSE;
  88. ExceptionListLayout exceptions {XPATH('Exceptions')};
  89. END;
  90. EXPORT CreateStoreResponseRec := RECORD
  91. BOOLEAN succeeded {XPATH('Success')}; // Will be TRUE if a new store was created, FALSE if store already existed or an error occurred
  92. BOOLEAN already_present := FALSE; // Will be TRUE if store already existed
  93. STRING store_name {XPATH('Name')};
  94. STRING description {XPATH('Description')};
  95. BOOLEAN has_exceptions := FALSE;
  96. ExceptionListLayout exceptions {XPATH('Exceptions')};
  97. END;
  98. EXPORT SetKeyValueResponseRec := RECORD
  99. BOOLEAN succeeded {XPATH('Success')};
  100. BOOLEAN has_exceptions := FALSE;
  101. ExceptionListLayout exceptions {XPATH('Exceptions')};
  102. END;
  103. EXPORT GetKeyValueResponseRec := RECORD
  104. BOOLEAN was_found := TRUE; // Will be FALSE if key was not found
  105. STRING value {XPATH('Value')};
  106. BOOLEAN has_exceptions := FALSE;
  107. ExceptionListLayout exceptions {XPATH('Exceptions')};
  108. END;
  109. EXPORT DeleteKeyValueResponseRec := RECORD
  110. BOOLEAN succeeded {XPATH('Success')};
  111. BOOLEAN has_exceptions := FALSE;
  112. ExceptionListLayout exceptions {XPATH('Exceptions')};
  113. END;
  114. SHARED KeySetRec := RECORD
  115. STRING key {XPATH('')};
  116. END;
  117. EXPORT GetAllKeysResponseRec := RECORD
  118. STRING namespace {XPATH('Namespace')};
  119. DATASET(KeySetRec) keys {XPATH('KeySet/Key')};
  120. BOOLEAN has_exceptions := FALSE;
  121. ExceptionListLayout exceptions {XPATH('Exceptions')};
  122. END;
  123. SHARED KeyValueRec := RECORD
  124. STRING key {XPATH('Key')};
  125. STRING value {XPATH('Value')};
  126. END;
  127. EXPORT GetAllKeyValuesResponseRec := RECORD
  128. STRING namespace {XPATH('Namespace')};
  129. DATASET(KeyValueRec) key_values {XPATH('Pairs/Pair')};
  130. BOOLEAN has_exceptions := FALSE;
  131. ExceptionListLayout exceptions {XPATH('Exceptions')};
  132. END;
  133. SHARED NamespaceLayout := RECORD
  134. STRING namespace {XPATH('')};
  135. END;
  136. EXPORT ListNamespacesResponseRec := RECORD
  137. DATASET(NamespaceLayout) namespaces {XPATH('Namespaces/Namespace')};
  138. BOOLEAN has_exceptions := FALSE;
  139. ExceptionListLayout exceptions {XPATH('Exceptions')};
  140. END;
  141. EXPORT DeleteNamespaceResponseRec := RECORD
  142. BOOLEAN succeeded {XPATH('Success')};
  143. BOOLEAN has_exceptions := FALSE;
  144. ExceptionListLayout exceptions {XPATH('Exceptions')};
  145. END;
  146. //--------------------------------------------------------------------------
  147. /**
  148. * Creates a key/value store if it has not been created before. If the
  149. * store has already been created then this function has no effect.
  150. *
  151. * @param storeName A STRING naming the store to create; this
  152. * cannot be an empty string; REQUIRED
  153. * @param description A STRING describing the purpose of the
  154. * store; may be an empty string; OPTIONAL,
  155. * defaults to an empty string
  156. * @param maxValueSize The maximum size of any value stored within
  157. * this store, in bytes; use a value of zero to
  158. * indicate an unlimited maximum size; OPTIONAL,
  159. * defaults to 1024
  160. * @param isUserSpecific If TRUE, this store will be visible only
  161. * to the user indicated by the (username, userPW)
  162. * arguments provided when the module was
  163. * defined; if FALSE, the store will be global
  164. * and visible to all users; OPTIONAL, defaults
  165. * to FALSE
  166. * @param timeoutInSeconds The number of seconds to wait for the
  167. * underlying SOAPCALL to complete; set to zero
  168. * to wait forever; OPTIONAL, defaults to zero
  169. *
  170. * @return A CreateStoreResponseRec RECORD. If the store already exists
  171. * then the result will show succeeded = FALSE and
  172. * already_present = TRUE.
  173. */
  174. EXPORT CreateStore(STRING storeName,
  175. STRING description = '',
  176. UNSIGNED4 maxValueSize = 1024,
  177. BOOLEAN isUserSpecific = FALSE,
  178. UNSIGNED2 timeoutInSeconds = 0) := FUNCTION
  179. soapResponse := SOAPCALL
  180. (
  181. MY_ESP_URL,
  182. 'CreateStore',
  183. {
  184. STRING pStoreName {XPATH('Name')} := storeName;
  185. STRING pDescription {XPATH('Description')} := description;
  186. UNSIGNED4 pMaxValueSize {XPATH('MaxValueSize')} := maxValueSize;
  187. BOOLEAN pUserSpecific {XPATH('UserSpecific')} := isUserSpecific;
  188. },
  189. DATASET(CreateStoreResponseRec),
  190. XPATH('CreateStoreResponse'),
  191. HTTPHEADER('Authorization', ENCODED_CREDENTIALS),
  192. TIMEOUT(timeoutInSeconds)
  193. );
  194. finalResponse := PROJECT
  195. (
  196. soapResponse,
  197. TRANSFORM
  198. (
  199. RECORDOF(LEFT),
  200. SELF.already_present := (NOT LEFT.succeeded) AND (NOT EXISTS(LEFT.exceptions.exceptions)),
  201. SELF := LEFT
  202. )
  203. );
  204. RETURN UpdateForExceptions(finalResponse)[1];
  205. END;
  206. /**
  207. * Gets a list of available stores.
  208. *
  209. * @param nameFilter A STRING defining a filter to be applied
  210. * to the store's name; the filter accepts
  211. * the '*' wildcard character to indicate
  212. * 'match anything' and '?' to match any
  213. * single character; string comparisons are
  214. * case-insensitive; an empty string is
  215. * equivalent to '*'; OPTIONAL, defaults
  216. * to '*'
  217. * @param ownerFilter A STRING defining a filter to be applied
  218. * to the store's owner; the filter accepts
  219. * the '*' wildcard character to indicate
  220. * 'match anything' and '?' to match any
  221. * single character; string comparisons are
  222. * case-insensitive; an empty string is
  223. * equivalent to '*'; OPTIONAL, defaults
  224. * to '*'
  225. * @param timeoutInSeconds The number of seconds to wait for the
  226. * underlying SOAPCALL to complete; set to zero
  227. * to wait forever; OPTIONAL, defaults to zero
  228. *
  229. * @return A ListStoresResponseRec RECORD.
  230. */
  231. EXPORT ListStores(STRING nameFilter = '*',
  232. STRING ownerFilter = '*',
  233. UNSIGNED2 timeoutInSeconds = 0) := FUNCTION
  234. soapResponse := SOAPCALL
  235. (
  236. MY_ESP_URL,
  237. 'ListStores',
  238. {
  239. STRING pNameFilter {XPATH('NameFilter')} := nameFilter;
  240. STRING pOwnerFilter {XPATH('OwnerFilter')} := ownerFilter;
  241. },
  242. DATASET(ListStoresResponseRec),
  243. XPATH('ListStoresResponse'),
  244. HTTPHEADER('Authorization', ENCODED_CREDENTIALS),
  245. TIMEOUT(timeoutInSeconds)
  246. );
  247. RETURN UpdateForExceptions(soapResponse)[1];
  248. END;
  249. /**
  250. * Gets a list of namespaces defined in the current store.
  251. *
  252. * @param storeName A STRING naming the store containing the
  253. * namespaces you are interested in; set this
  254. * to an empty string to reference the default
  255. * store in the cluster, if one has been
  256. * defined; REQUIRED
  257. * @param isUserSpecific If TRUE, the system will look only for
  258. * private keys; if FALSE then the system will
  259. * look for global keys; OPTIONAL, defaults
  260. * to FALSE
  261. * @param timeoutInSeconds The number of seconds to wait for the
  262. * underlying SOAPCALL to complete; set to zero
  263. * to wait forever; OPTIONAL, defaults to zero
  264. *
  265. * @return A ListNamespacesResponseRec RECORD.
  266. */
  267. EXPORT ListNamespaces(STRING storeName,
  268. BOOLEAN isUserSpecific = FALSE,
  269. UNSIGNED2 timeoutInSeconds = 0) := FUNCTION
  270. soapResponse := SOAPCALL
  271. (
  272. MY_ESP_URL,
  273. 'ListNamespaces',
  274. {
  275. STRING pStoreName {XPATH('StoreName')} := storeName;
  276. BOOLEAN pUserSpecific {XPATH('UserSpecific')} := isUserSpecific;
  277. },
  278. DATASET(ListNamespacesResponseRec),
  279. XPATH('ListNamespacesResponse'),
  280. HTTPHEADER('Authorization', ENCODED_CREDENTIALS),
  281. TIMEOUT(timeoutInSeconds)
  282. );
  283. RETURN UpdateForExceptions(soapResponse)[1];
  284. END;
  285. /**
  286. * This submodule nails down a specific namespace to access within a
  287. * key/value store on the cluster
  288. *
  289. * @param namespace A STRING naming the namespace partition that will
  290. * be used for this module; cannot be an empty string;
  291. * REQUIRED
  292. * @param storeName A STRING naming the store that this module
  293. * will access; set this to an empty string to
  294. * reference the default store in the cluster, if
  295. * one has been defined; OPTIONAL, defaults to
  296. * an empty string
  297. *
  298. * @return A reference to the module, correctly initialized with the
  299. * given namespace.
  300. */
  301. EXPORT WithNamespace(STRING namespace,
  302. STRING storeName = '') := MODULE
  303. /**
  304. * Sets a value for a key within a namespace. If the key already exists
  305. * then its value is overridden. The namespace will be created if it has
  306. * not already been defined.
  307. *
  308. * @param keyName A STRING naming the key; may not be an
  309. * empty string; REQUIRED
  310. * @param keyValue A STRING representing the value to store
  311. * for the key; may be an empty string;
  312. * REQUIRED
  313. * @param isUserSpecific If TRUE, this key will be visible only
  314. * to the user indicated by the (username, userPW)
  315. * arguments provided when the module was
  316. * defined; if FALSE, the key will be global
  317. * and visible to all users; OPTIONAL, defaults
  318. * to FALSE
  319. * @param timeoutInSeconds The number of seconds to wait for the
  320. * underlying SOAPCALL to complete; set to zero
  321. * to wait forever; OPTIONAL, defaults to zero
  322. *
  323. * @return A SetKeyValueResponseRec RECORD.
  324. */
  325. EXPORT SetKeyValue(STRING keyName,
  326. STRING keyValue,
  327. BOOLEAN isUserSpecific = FALSE,
  328. UNSIGNED2 timeoutInSeconds = 0) := FUNCTION
  329. soapResponse := SOAPCALL
  330. (
  331. MY_ESP_URL,
  332. 'Set',
  333. {
  334. STRING pStoreName {XPATH('StoreName')} := storeName;
  335. STRING pNamespace {XPATH('Namespace')} := namespace;
  336. STRING pKey {XPATH('Key')} := keyName;
  337. STRING pValue {XPATH('Value')} := keyValue;
  338. BOOLEAN pUserSpecific {XPATH('UserSpecific')} := isUserSpecific;
  339. },
  340. DATASET(SetKeyValueResponseRec),
  341. XPATH('SetResponse'),
  342. HTTPHEADER('Authorization', ENCODED_CREDENTIALS),
  343. TIMEOUT(timeoutInSeconds)
  344. );
  345. RETURN UpdateForExceptions(soapResponse)[1];
  346. END;
  347. /**
  348. * Gets a previously-set value for a key within a namespace.
  349. *
  350. * @param keyName A STRING naming the key; may not be an
  351. * empty string; REQUIRED
  352. * @param isUserSpecific If TRUE, the system will look only for
  353. * private keys; if FALSE then the system will
  354. * look for global keys; OPTIONAL, defaults
  355. * to FALSE
  356. * @param timeoutInSeconds The number of seconds to wait for the
  357. * underlying SOAPCALL to complete; set to zero
  358. * to wait forever; OPTIONAL, defaults to zero
  359. *
  360. * @return A GetKeyValueResponseRec RECORD. Note that the record will have
  361. * was_found set to TRUE or FALSE, depending on whether the key
  362. * was actually found in the key/value store.
  363. */
  364. EXPORT GetKeyValue(STRING keyName,
  365. BOOLEAN isUserSpecific = FALSE,
  366. UNSIGNED2 timeoutInSeconds = 0) := FUNCTION
  367. soapResponse := SOAPCALL
  368. (
  369. MY_ESP_URL,
  370. 'Fetch',
  371. {
  372. STRING pStoreName {XPATH('StoreName')} := storeName;
  373. STRING pNamespace {XPATH('Namespace')} := namespace;
  374. STRING pKey {XPATH('Key')} := keyName;
  375. BOOLEAN pUserSpecific {XPATH('UserSpecific')} := isUserSpecific;
  376. },
  377. DATASET(GetKeyValueResponseRec),
  378. XPATH('FetchResponse'),
  379. HTTPHEADER('Authorization', ENCODED_CREDENTIALS),
  380. TIMEOUT(timeoutInSeconds)
  381. );
  382. finalResponse := PROJECT
  383. (
  384. soapResponse,
  385. TRANSFORM
  386. (
  387. RECORDOF(LEFT),
  388. SELF.was_found := LEFT.value != '' AND NOT EXISTS(LEFT.exceptions.exceptions),
  389. SELF := LEFT
  390. )
  391. );
  392. RETURN UpdateForExceptions(finalResponse)[1];
  393. END;
  394. /**
  395. * Deletes a previously-set key and value within a namespace.
  396. *
  397. * @param keyName A STRING naming the key; may not be an
  398. * empty string; REQUIRED
  399. * @param isUserSpecific If TRUE, the system will look only for
  400. * private keys; if FALSE then the system will
  401. * look for global keys; OPTIONAL, defaults
  402. * to FALSE
  403. * @param timeoutInSeconds The number of seconds to wait for the
  404. * underlying SOAPCALL to complete; set to zero
  405. * to wait forever; OPTIONAL, defaults to zero
  406. *
  407. * @return A DeleteKeyValueResponseRec RECORD.
  408. */
  409. EXPORT DeleteKeyValue(STRING keyName,
  410. BOOLEAN isUserSpecific = FALSE,
  411. UNSIGNED2 timeoutInSeconds = 0) := FUNCTION
  412. soapResponse := SOAPCALL
  413. (
  414. MY_ESP_URL,
  415. 'Delete',
  416. {
  417. STRING pStoreName {XPATH('StoreName')} := storeName;
  418. STRING pNamespace {XPATH('Namespace')} := namespace;
  419. STRING pKey {XPATH('Key')} := keyName;
  420. BOOLEAN pUserSpecific {XPATH('UserSpecific')} := isUserSpecific;
  421. },
  422. DATASET(DeleteKeyValueResponseRec),
  423. XPATH('DeleteResponse'),
  424. HTTPHEADER('Authorization', ENCODED_CREDENTIALS),
  425. TIMEOUT(timeoutInSeconds)
  426. );
  427. RETURN UpdateForExceptions(soapResponse)[1];
  428. END;
  429. /**
  430. * Gets a list of all keys currently defined within a namespace.
  431. *
  432. * @param isUserSpecific If TRUE, the system will look only for
  433. * private keys; if FALSE then the system will
  434. * look for global keys; OPTIONAL, defaults
  435. * to FALSE
  436. * @param timeoutInSeconds The number of seconds to wait for the
  437. * underlying SOAPCALL to complete; set to zero
  438. * to wait forever; OPTIONAL, defaults to zero
  439. *
  440. * @return A GetAllKeysResponseRec RECORD.
  441. */
  442. EXPORT GetAllKeys(BOOLEAN isUserSpecific = FALSE,
  443. UNSIGNED2 timeoutInSeconds = 0) := FUNCTION
  444. soapResponse := SOAPCALL
  445. (
  446. MY_ESP_URL,
  447. 'ListKeys',
  448. {
  449. STRING pStoreName {XPATH('StoreName')} := storeName;
  450. STRING pNamespace {XPATH('Namespace')} := namespace;
  451. BOOLEAN pUserSpecific {XPATH('UserSpecific')} := isUserSpecific;
  452. },
  453. DATASET(GetAllKeysResponseRec),
  454. XPATH('ListKeysResponse'),
  455. HTTPHEADER('Authorization', ENCODED_CREDENTIALS),
  456. TIMEOUT(timeoutInSeconds)
  457. );
  458. RETURN UpdateForExceptions(soapResponse)[1];
  459. END;
  460. /**
  461. * Gets a list of all key and their associated values currently defined
  462. * within a namespace.
  463. *
  464. * @param isUserSpecific If TRUE, the system will look only for
  465. * private keys; if FALSE then the system will
  466. * look for global keys; OPTIONAL, defaults
  467. * to FALSE
  468. * @param timeoutInSeconds The number of seconds to wait for the
  469. * underlying SOAPCALL to complete; set to zero
  470. * to wait forever; OPTIONAL, defaults to zero
  471. *
  472. * @return A GetAllKeyValuesResponseRec RECORD.
  473. */
  474. EXPORT GetAllKeyValues(BOOLEAN isUserSpecific = FALSE,
  475. UNSIGNED2 timeoutInSeconds = 0) := FUNCTION
  476. soapResponse := SOAPCALL
  477. (
  478. MY_ESP_URL,
  479. 'FetchAll',
  480. {
  481. STRING pStoreName {XPATH('StoreName')} := storeName;
  482. STRING pNamespace {XPATH('Namespace')} := namespace;
  483. BOOLEAN pUserSpecific {XPATH('UserSpecific')} := isUserSpecific;
  484. },
  485. DATASET(GetAllKeyValuesResponseRec),
  486. XPATH('FetchAllResponse'),
  487. HTTPHEADER('Authorization', ENCODED_CREDENTIALS),
  488. TIMEOUT(timeoutInSeconds)
  489. );
  490. RETURN UpdateForExceptions(soapResponse)[1];
  491. END;
  492. /**
  493. * Deletes the namespace defined for this module and all keys and values
  494. * defined within it.
  495. *
  496. * @param isUserSpecific If TRUE, the system will look only for
  497. * private keys; if FALSE then the system will
  498. * look for global keys; OPTIONAL, defaults
  499. * to FALSE
  500. * @param timeoutInSeconds The number of seconds to wait for the
  501. * underlying SOAPCALL to complete; set to zero
  502. * to wait forever; OPTIONAL, defaults to zero
  503. *
  504. * @return A DeleteNamespaceResponseRec RECORD.
  505. */
  506. EXPORT DeleteNamespace(BOOLEAN isUserSpecific = FALSE,
  507. UNSIGNED2 timeoutInSeconds = 0) := FUNCTION
  508. soapResponse := SOAPCALL
  509. (
  510. MY_ESP_URL,
  511. 'DeleteNamespace',
  512. {
  513. STRING pStoreName {XPATH('StoreName')} := storeName;
  514. STRING pNamespace {XPATH('Namespace')} := namespace;
  515. BOOLEAN pUserSpecific {XPATH('UserSpecific')} := isUserSpecific;
  516. },
  517. DATASET(DeleteNamespaceResponseRec),
  518. XPATH('DeleteNamespaceResponse'),
  519. HTTPHEADER('Authorization', ENCODED_CREDENTIALS),
  520. TIMEOUT(timeoutInSeconds)
  521. );
  522. RETURN UpdateForExceptions(soapResponse)[1];
  523. END;
  524. END; // WithNamespace module
  525. END; // Store module