PageRenderTime 57ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/core/externals/google-toolbox-for-mac/Foundation/GTMSQLite.m

http://macfuse.googlecode.com/
Objective C | 1995 lines | 1509 code | 161 blank | 325 comment | 324 complexity | a0a2be83e3a95ef1aad9241e431574de MD5 | raw file
Possible License(s): Apache-2.0, BSD-3-Clause, GPL-2.0
  1. //
  2. // GTMSQLite.m
  3. //
  4. // Convenience wrapper for SQLite storage see the header for details.
  5. //
  6. // Copyright 2007-2008 Google Inc.
  7. //
  8. // Licensed under the Apache License, Version 2.0 (the "License"); you may not
  9. // use this file except in compliance with the License. You may obtain a copy
  10. // of the License at
  11. //
  12. // http://www.apache.org/licenses/LICENSE-2.0
  13. //
  14. // Unless required by applicable law or agreed to in writing, software
  15. // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  16. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  17. // License for the specific language governing permissions and limitations under
  18. // the License.
  19. //
  20. #import <Foundation/Foundation.h>
  21. #if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
  22. #import <dlfcn.h>
  23. #endif
  24. #import "GTMSQLite.h"
  25. #import "GTMMethodCheck.h"
  26. #import "GTMDefines.h"
  27. #include <limits.h>
  28. typedef struct {
  29. BOOL upperCase;
  30. int textRep;
  31. } UpperLowerUserArgs;
  32. typedef struct {
  33. BOOL reverse;
  34. CFOptionFlags compareOptions;
  35. int textRep;
  36. } CollateUserArgs;
  37. typedef struct {
  38. CFOptionFlags *compareOptionPtr;
  39. int textRep;
  40. } LikeGlobUserArgs;
  41. #if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
  42. // While we want to be compatible with Tiger, some operations are more
  43. // efficient when implemented with Leopard APIs. We look those up dynamically.
  44. // CFStringCreateWithBytesNoCopy
  45. static const char* const kCFStringCreateWithBytesNoCopySymbolName =
  46. "CFStringCreateWithBytesNoCopy";
  47. typedef CFStringRef (*CFStringCreateWithBytesNoCopyPtrType)(CFAllocatorRef,
  48. const UInt8 *,
  49. CFIndex,
  50. CFStringEncoding,
  51. Boolean,
  52. CFAllocatorRef);
  53. static CFStringCreateWithBytesNoCopyPtrType gCFStringCreateWithBytesNoCopySymbol = NULL;
  54. #endif // MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
  55. // Helper inline for SQLite text type to CF endcoding
  56. GTM_INLINE CFStringEncoding SqliteTextEncodingToCFStringEncoding(int enc) {
  57. // Default should never happen, but assume UTF 8
  58. CFStringEncoding encoding = kCFStringEncodingUTF8;
  59. _GTMDevAssert(enc == SQLITE_UTF16BE ||
  60. enc == SQLITE_UTF16LE,
  61. @"Passed in encoding was not a UTF16 encoding");
  62. switch(enc) {
  63. case SQLITE_UTF16BE:
  64. encoding = kCFStringEncodingUTF16BE;
  65. break;
  66. case SQLITE_UTF16LE:
  67. encoding = kCFStringEncodingUTF16LE;
  68. break;
  69. }
  70. return encoding;
  71. }
  72. // Helper inline for filtering CFStringCompareFlags
  73. GTM_INLINE CFOptionFlags FilteredStringCompareFlags(CFOptionFlags inOptions) {
  74. CFOptionFlags outOptions = 0;
  75. if (inOptions & kCFCompareCaseInsensitive) {
  76. outOptions |= kCFCompareCaseInsensitive;
  77. }
  78. if (inOptions & kCFCompareNonliteral) outOptions |= kCFCompareNonliteral;
  79. if (inOptions & kCFCompareLocalized) outOptions |= kCFCompareLocalized;
  80. if (inOptions & kCFCompareNumerically) outOptions |= kCFCompareNumerically;
  81. #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
  82. if (inOptions & kCFCompareDiacriticInsensitive) {
  83. outOptions |= kCFCompareDiacriticInsensitive;
  84. }
  85. if (inOptions & kCFCompareWidthInsensitive) {
  86. outOptions |= kCFCompareWidthInsensitive;
  87. }
  88. #endif
  89. return outOptions;
  90. }
  91. // Function prototypes for our custom implementations of UPPER/LOWER using
  92. // CFString so that we handle Unicode and localization more cleanly than
  93. // native SQLite.
  94. static void UpperLower8(sqlite3_context *context,
  95. int argc,
  96. sqlite3_value **argv);
  97. static void UpperLower16(sqlite3_context *context,
  98. int argc,
  99. sqlite3_value **argv);
  100. // Function prototypes for CFString-based collation sequences
  101. static void CollateNeeded(void *userContext, sqlite3 *db,
  102. int textRep, const char *name);
  103. static int Collate8(void *userContext, int length1, const void *str1,
  104. int length2, const void *str2);
  105. static int Collate16(void *userContext, int length1, const void *str1,
  106. int length2, const void *str2);
  107. // Function prototypes for CFString LIKE and GLOB
  108. static void Like8(sqlite3_context *context, int argc, sqlite3_value **argv);
  109. static void Like16(sqlite3_context *context, int argc, sqlite3_value **argv);
  110. static void Glob8(sqlite3_context *context, int argc, sqlite3_value **argv);
  111. static void Glob16(sqlite3_context *context, int argc, sqlite3_value **argv);
  112. // The CFLocale of the current user at process start
  113. static CFLocaleRef gCurrentLocale = NULL;
  114. // Private methods
  115. @interface GTMSQLiteDatabase (PrivateMethods)
  116. - (int)installCFAdditions;
  117. - (void)collationArgumentRetain:(NSData *)collationArgs;
  118. // Convenience method to clean up resources. Called from both
  119. // dealloc & finalize
  120. //
  121. - (void)cleanupDB;
  122. @end
  123. @implementation GTMSQLiteDatabase
  124. + (void)initialize {
  125. // Need the locale for some CFString enhancements
  126. gCurrentLocale = CFLocaleCopyCurrent();
  127. #if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
  128. // Compiling pre-Leopard try to find some symbols dynamically
  129. gCFStringCreateWithBytesNoCopySymbol =
  130. (CFStringCreateWithBytesNoCopyPtrType)dlsym(
  131. RTLD_DEFAULT,
  132. kCFStringCreateWithBytesNoCopySymbolName);
  133. #endif // MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
  134. }
  135. + (int)sqliteVersionNumber {
  136. return sqlite3_libversion_number();
  137. }
  138. + (NSString *)sqliteVersionString {
  139. return [NSString stringWithUTF8String:sqlite3_libversion()];
  140. }
  141. - (id)initWithPath:(NSString *)path
  142. withCFAdditions:(BOOL)additions
  143. utf8:(BOOL)useUTF8
  144. errorCode:(int *)err {
  145. int rc = SQLITE_INTERNAL;
  146. if ((self = [super init])) {
  147. path_ = [path copy];
  148. if (useUTF8) {
  149. rc = sqlite3_open([path_ fileSystemRepresentation], &db_);
  150. } else {
  151. CFStringEncoding cfEncoding;
  152. #if TARGET_RT_BIG_ENDIAN
  153. cfEncoding = kCFStringEncodingUTF16BE;
  154. #else
  155. cfEncoding = kCFStringEncodingUTF16LE;
  156. #endif
  157. NSStringEncoding nsEncoding
  158. = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
  159. NSData *data = [path dataUsingEncoding:nsEncoding];
  160. // Using -[NSString cStringUsingEncoding] causes sqlite3_open16
  161. // to fail because it expects 2 null-terminating bytes and
  162. // cStringUsingEncoding only has 1
  163. NSMutableData *mutable = [NSMutableData dataWithData:data];
  164. [mutable increaseLengthBy:2];
  165. rc = sqlite3_open16([mutable bytes], &db_);
  166. }
  167. if ((rc == SQLITE_OK) && db_) {
  168. if (additions) {
  169. userArgDataPool_ = [[NSMutableArray array] retain];
  170. if (!userArgDataPool_) {
  171. // Leave *err as internal err
  172. // COV_NF_START - not sure how to fail Cocoa initializers
  173. [self release];
  174. return nil;
  175. // COV_NF_END
  176. }
  177. rc = [self installCFAdditions];
  178. }
  179. }
  180. if (err) *err = rc;
  181. if (rc != SQLITE_OK) {
  182. // COV_NF_START
  183. [self release];
  184. self = nil;
  185. // COV_NF_END
  186. }
  187. }
  188. return self;
  189. }
  190. - (id)initInMemoryWithCFAdditions:(BOOL)additions
  191. utf8:(BOOL)useUTF8
  192. errorCode:(int *)err {
  193. return [self initWithPath:@":memory:"
  194. withCFAdditions:additions
  195. utf8:useUTF8
  196. errorCode:err];
  197. }
  198. - (void)dealloc {
  199. [self cleanupDB];
  200. [super dealloc];
  201. }
  202. - (void)cleanupDB {
  203. if (db_) {
  204. int rc = sqlite3_close(db_);
  205. if (rc != SQLITE_OK) {
  206. _GTMDevLog(@"Unable to close \"%@\", error code: %d\r"
  207. @"Did you forget to call -[GTMSQLiteStatement"
  208. @" finalizeStatement] on one of your statements?",
  209. self, rc);
  210. }
  211. }
  212. [path_ release];
  213. [userArgDataPool_ release];
  214. }
  215. // Private method to install our custom CoreFoundation additions to SQLite
  216. // behavior
  217. - (int)installCFAdditions {
  218. int rc = SQLITE_OK;
  219. // Install our custom functions for improved text handling
  220. // UPPER/LOWER
  221. const struct {
  222. const char *sqlName;
  223. UpperLowerUserArgs userArgs;
  224. void *function;
  225. } customUpperLower[] = {
  226. { "upper", { YES, SQLITE_UTF8 }, &UpperLower8 },
  227. { "upper", { YES, SQLITE_UTF16 }, &UpperLower16 },
  228. { "upper", { YES, SQLITE_UTF16BE }, &UpperLower16 },
  229. { "upper", { YES, SQLITE_UTF16LE }, &UpperLower16 },
  230. { "lower", { NO, SQLITE_UTF8 }, &UpperLower8 },
  231. { "lower", { NO, SQLITE_UTF16 }, &UpperLower16 },
  232. { "lower", { NO, SQLITE_UTF16BE }, &UpperLower16 },
  233. { "lower", { NO, SQLITE_UTF16LE }, &UpperLower16 },
  234. };
  235. for (size_t i = 0;
  236. i < (sizeof(customUpperLower) / sizeof(customUpperLower[0]));
  237. i++) {
  238. rc = sqlite3_create_function(db_,
  239. customUpperLower[i].sqlName,
  240. 1,
  241. customUpperLower[i].userArgs.textRep,
  242. (void *)&customUpperLower[i].userArgs,
  243. customUpperLower[i].function,
  244. NULL,
  245. NULL);
  246. if (rc != SQLITE_OK)
  247. return rc; // COV_NF_LINE because sqlite3_create_function is
  248. // called with input defined at compile-time
  249. }
  250. // Fixed collation sequences
  251. const struct {
  252. const char *sqlName;
  253. CollateUserArgs userArgs;
  254. void *function;
  255. } customCollationSequence[] = {
  256. { "nocase", { NO, kCFCompareCaseInsensitive, SQLITE_UTF8 }, &Collate8 },
  257. { "nocase", { NO, kCFCompareCaseInsensitive, SQLITE_UTF16 }, &Collate16 },
  258. { "nocase", { NO, kCFCompareCaseInsensitive, SQLITE_UTF16BE }, &Collate16 },
  259. { "nocase", { NO, kCFCompareCaseInsensitive, SQLITE_UTF16LE }, &Collate16 },
  260. };
  261. for (size_t i = 0;
  262. i < (sizeof(customCollationSequence) / sizeof(customCollationSequence[0]));
  263. i++) {
  264. rc = sqlite3_create_collation(db_,
  265. customCollationSequence[i].sqlName,
  266. customCollationSequence[i].userArgs.textRep,
  267. (void *)&customCollationSequence[i].userArgs,
  268. customCollationSequence[i].function);
  269. if (rc != SQLITE_OK)
  270. return rc; // COV_NF_LINE because the input to
  271. // sqlite3_create_collation is set at compile time
  272. }
  273. // Install handler for dynamic collation sequences
  274. const struct {
  275. const char *sqlName;
  276. int numArgs;
  277. int textRep;
  278. void *function;
  279. } customLike[] = {
  280. { "like", 2, SQLITE_UTF8, &Like8 },
  281. { "like", 2, SQLITE_UTF16, &Like16 },
  282. { "like", 2, SQLITE_UTF16BE, &Like16 },
  283. { "like", 2, SQLITE_UTF16LE, &Like16 },
  284. { "like", 3, SQLITE_UTF8, &Like8 },
  285. { "like", 3, SQLITE_UTF16, &Like16 },
  286. { "like", 3, SQLITE_UTF16BE, &Like16 },
  287. { "like", 3, SQLITE_UTF16LE, &Like16 },
  288. };
  289. rc = sqlite3_collation_needed(db_, self, &CollateNeeded);
  290. if (rc != SQLITE_OK)
  291. return rc; // COV_NF_LINE because input to
  292. // sqlite3_collation_needed is static
  293. // Start LIKE as case-insensitive and non-literal
  294. // (sqlite defaults LIKE to case-insensitive)
  295. likeOptions_ = kCFCompareCaseInsensitive | kCFCompareNonliteral;
  296. for (size_t i = 0; i < (sizeof(customLike) / sizeof(customLike[0])); i++) {
  297. // Each implementation gets its own user args
  298. NSMutableData *argsData
  299. = [NSMutableData dataWithLength:sizeof(LikeGlobUserArgs)];
  300. if (!argsData) return SQLITE_INTERNAL;
  301. [userArgDataPool_ addObject:argsData];
  302. LikeGlobUserArgs *args = (LikeGlobUserArgs *)[argsData bytes];
  303. args->compareOptionPtr = &likeOptions_;
  304. args->textRep = customLike[i].textRep;
  305. rc = sqlite3_create_function(db_,
  306. customLike[i].sqlName,
  307. customLike[i].numArgs,
  308. customLike[i].textRep,
  309. args,
  310. customLike[i].function,
  311. NULL,
  312. NULL);
  313. if (rc != SQLITE_OK)
  314. return rc; // COV_NF_LINE because input to
  315. // sqlite3_create_function is static
  316. }
  317. // Start GLOB just non-literal but case-sensitive (same as SQLite defaults)
  318. const struct {
  319. const char *sqlName;
  320. int textRep;
  321. void *function;
  322. } customGlob[] = {
  323. { "glob", SQLITE_UTF8, &Glob8 },
  324. { "glob", SQLITE_UTF16, &Glob16 },
  325. { "glob", SQLITE_UTF16BE, &Glob16 },
  326. { "glob", SQLITE_UTF16LE, &Glob16 },
  327. };
  328. globOptions_ = kCFCompareNonliteral;
  329. for (size_t i = 0; i < (sizeof(customGlob) / sizeof(customGlob[0])); i++) {
  330. // Each implementation gets its own user args
  331. NSMutableData *argsData
  332. = [NSMutableData dataWithLength:sizeof(LikeGlobUserArgs)];
  333. if (!argsData) return SQLITE_INTERNAL;
  334. [userArgDataPool_ addObject:argsData];
  335. LikeGlobUserArgs *args = (LikeGlobUserArgs *)[argsData bytes];
  336. args->compareOptionPtr = &globOptions_;
  337. args->textRep = customGlob[i].textRep;
  338. rc = sqlite3_create_function(db_,
  339. customGlob[i].sqlName,
  340. 2,
  341. customGlob[i].textRep,
  342. args,
  343. customGlob[i].function,
  344. NULL,
  345. NULL);
  346. if (rc != SQLITE_OK)
  347. return rc; // COV_NF_LINE because input to
  348. // sqlite3_create_function is static
  349. }
  350. hasCFAdditions_ = YES;
  351. return SQLITE_OK;
  352. }
  353. // Private method used by collation creation callback
  354. - (void)collationArgumentRetain:(NSData *)collationArgs {
  355. [userArgDataPool_ addObject:collationArgs];
  356. }
  357. - (sqlite3 *)sqlite3DB {
  358. return db_;
  359. }
  360. - (void)synchronousMode:(BOOL)enable {
  361. if (enable) {
  362. [self executeSQL:@"PRAGMA synchronous = NORMAL;"];
  363. [self executeSQL:@"PRAGMA fullfsync = 1;"];
  364. } else {
  365. [self executeSQL:@"PRAGMA fullfsync = 0;"];
  366. [self executeSQL:@"PRAGMA synchronous = OFF;"];
  367. }
  368. }
  369. - (BOOL)hasCFAdditions {
  370. return hasCFAdditions_;
  371. }
  372. - (void)setLikeComparisonOptions:(CFOptionFlags)options {
  373. if (hasCFAdditions_)
  374. likeOptions_ = FilteredStringCompareFlags(options);
  375. }
  376. - (CFOptionFlags)likeComparisonOptions {
  377. CFOptionFlags flags = 0;
  378. if (hasCFAdditions_)
  379. flags = likeOptions_;
  380. return flags;
  381. }
  382. - (void)setGlobComparisonOptions:(CFOptionFlags)options {
  383. if (hasCFAdditions_)
  384. globOptions_ = FilteredStringCompareFlags(options);
  385. }
  386. - (CFOptionFlags)globComparisonOptions {
  387. CFOptionFlags globOptions = 0;
  388. if (hasCFAdditions_)
  389. globOptions = globOptions_;
  390. return globOptions;
  391. }
  392. - (int)lastErrorCode {
  393. return sqlite3_errcode(db_);
  394. }
  395. - (NSString *)lastErrorString {
  396. const char *errMsg = sqlite3_errmsg(db_);
  397. if (!errMsg) return nil;
  398. return [NSString stringWithCString:errMsg encoding:NSUTF8StringEncoding];
  399. }
  400. - (int)lastChangeCount {
  401. return sqlite3_changes(db_);
  402. }
  403. - (int)totalChangeCount {
  404. return sqlite3_total_changes(db_);
  405. }
  406. - (unsigned long long)lastInsertRowID {
  407. return sqlite3_last_insert_rowid(db_);
  408. }
  409. - (void)interrupt {
  410. sqlite3_interrupt(db_);
  411. }
  412. - (int)setBusyTimeoutMS:(int)timeoutMS {
  413. int rc = sqlite3_busy_timeout(db_, timeoutMS);
  414. if (rc == SQLITE_OK) {
  415. timeoutMS_ = timeoutMS;
  416. }
  417. return rc;
  418. }
  419. - (int)busyTimeoutMS {
  420. return timeoutMS_;
  421. }
  422. - (int)executeSQL:(NSString *)sql {
  423. int rc;
  424. // Sanity
  425. if (!sql) {
  426. rc = SQLITE_MISUSE; // Reasonable return for this case
  427. } else {
  428. if (hasCFAdditions_) {
  429. rc = sqlite3_exec(db_,
  430. [[sql precomposedStringWithCanonicalMapping]
  431. UTF8String],
  432. NULL, NULL, NULL);
  433. } else {
  434. rc = sqlite3_exec(db_, [sql UTF8String], NULL, NULL, NULL);
  435. }
  436. }
  437. return rc;
  438. }
  439. - (BOOL)beginDeferredTransaction {
  440. int err;
  441. err = [self executeSQL:@"BEGIN DEFERRED TRANSACTION;"];
  442. return (err == SQLITE_OK) ? YES : NO;
  443. }
  444. - (BOOL)rollback {
  445. int err = [self executeSQL:@"ROLLBACK TRANSACTION;"];
  446. return (err == SQLITE_OK) ? YES : NO;
  447. }
  448. - (BOOL)commit {
  449. int err = [self executeSQL:@"COMMIT TRANSACTION;"];
  450. return (err == SQLITE_OK) ? YES : NO;
  451. }
  452. - (NSString *)description {
  453. return [NSString stringWithFormat:@"<%@: %p - %@>",
  454. [self class], self, path_];
  455. }
  456. @end
  457. #pragma mark Upper/Lower
  458. // Private helper to handle upper/lower conversions for UTF8
  459. static void UpperLower8(sqlite3_context *context, int argc, sqlite3_value **argv) {
  460. // Args
  461. if ((argc < 1) || (sqlite3_value_type(argv[0]) == SQLITE_NULL)) {
  462. // COV_NF_START
  463. sqlite3_result_error(context, "LOWER/UPPER CF implementation got bad args",
  464. -1);
  465. return;
  466. // COV_NF_END
  467. }
  468. const char *sqlText8 = (void *)sqlite3_value_text(argv[0]);
  469. if (!sqlText8) {
  470. // COV_NF_START
  471. sqlite3_result_error(context, "LOWER/UPPER CF implementation no input UTF8",
  472. -1);
  473. return;
  474. // COV_NF_END
  475. }
  476. // Get user data
  477. UpperLowerUserArgs *userArgs = sqlite3_user_data(context);
  478. if (!userArgs) {
  479. // COV_NF_START
  480. sqlite3_result_error(context, "LOWER/UPPER CF implementation no user args",
  481. -1);
  482. return;
  483. // COV_NF_END
  484. }
  485. _GTMDevAssert(userArgs->textRep == SQLITE_UTF8,
  486. @"Received non UTF8 encoding in UpperLower8");
  487. // Worker string, must be mutable for case conversion so order our calls
  488. // to only copy once
  489. CFMutableStringRef workerString =
  490. CFStringCreateMutable(kCFAllocatorDefault, 0);
  491. GTMCFAutorelease(workerString);
  492. if (!workerString) {
  493. // COV_NF_START
  494. sqlite3_result_error(context,
  495. "LOWER/UPPER CF implementation failed " \
  496. "to allocate CFMutableStringRef", -1);
  497. return;
  498. // COV_NF_END
  499. }
  500. CFStringAppendCString(workerString, sqlText8, kCFStringEncodingUTF8);
  501. // Perform the upper/lower
  502. if (userArgs->upperCase) {
  503. CFStringUppercase(workerString, gCurrentLocale);
  504. } else {
  505. CFStringLowercase(workerString, gCurrentLocale);
  506. }
  507. // Convert to our canonical composition
  508. CFStringNormalize(workerString, kCFStringNormalizationFormC);
  509. // Get the bytes we will return, using the more efficient accessor if we can
  510. const char *returnString = CFStringGetCStringPtr(workerString,
  511. kCFStringEncodingUTF8);
  512. if (returnString) {
  513. // COV_NF_START
  514. // Direct buffer, but have SQLite copy it
  515. sqlite3_result_text(context, returnString, -1, SQLITE_TRANSIENT);
  516. // COV_NF_END
  517. } else {
  518. // Need to get a copy
  519. CFIndex workerLength = CFStringGetLength(workerString);
  520. CFIndex bufferSize =
  521. CFStringGetMaximumSizeForEncoding(workerLength,
  522. kCFStringEncodingUTF8);
  523. void *returnBuffer = malloc(bufferSize);
  524. if (!returnBuffer) {
  525. // COV_NF_START
  526. sqlite3_result_error(context,
  527. "LOWER/UPPER failed to allocate return buffer", -1);
  528. return;
  529. // COV_NF_END
  530. }
  531. CFIndex convertedBytes = 0;
  532. CFIndex convertedChars = CFStringGetBytes(workerString,
  533. CFRangeMake(0, workerLength),
  534. kCFStringEncodingUTF8,
  535. 0,
  536. false,
  537. returnBuffer,
  538. bufferSize,
  539. &convertedBytes);
  540. if (convertedChars != workerLength) {
  541. // COV_NF_START
  542. free(returnBuffer);
  543. sqlite3_result_error(context,
  544. "CFStringGetBytes() failed to " \
  545. "convert all characters", -1);
  546. // COV_NF_END
  547. } else {
  548. // Set the result, letting SQLite take ownership and using free() as
  549. // the destructor
  550. // We cast the 3rd parameter to an int because sqlite3 doesn't appear
  551. // to support 64-bit mode.
  552. sqlite3_result_text(context, returnBuffer, (int)convertedBytes, &free);
  553. }
  554. }
  555. }
  556. // Private helper to handle upper/lower conversions for UTF16 variants
  557. static void UpperLower16(sqlite3_context *context,
  558. int argc, sqlite3_value **argv) {
  559. // Args
  560. if ((argc < 1) || (sqlite3_value_type(argv[0]) == SQLITE_NULL)) {
  561. // COV_NF_START
  562. sqlite3_result_error(context, "LOWER/UPPER CF implementation got bad args", -1);
  563. return;
  564. // COV_NF_END
  565. }
  566. // For UTF16 variants we want our working string to be in native-endian
  567. // UTF16. This gives us the fewest number of copies (since SQLite converts
  568. // in-place). There is no advantage to breaking out the string construction
  569. // to use UTF16BE or UTF16LE because all that does is move the conversion
  570. // work into the CFString constructor, so just use simple code.
  571. int sqlText16ByteCount = sqlite3_value_bytes16(argv[0]);
  572. const UniChar *sqlText16 = (void *)sqlite3_value_text16(argv[0]);
  573. if (!sqlText16ByteCount || !sqlText16) {
  574. // COV_NF_START
  575. sqlite3_result_error(context,
  576. "LOWER/UPPER CF implementation no input UTF16", -1);
  577. return;
  578. // COV_NF_END
  579. }
  580. // Get user data
  581. UpperLowerUserArgs *userArgs = sqlite3_user_data(context);
  582. if (!userArgs) {
  583. // COV_NF_START
  584. sqlite3_result_error(context, "LOWER/UPPER CF implementation no user args", -1);
  585. return;
  586. // COV_NF_END
  587. }
  588. CFStringEncoding encoding = SqliteTextEncodingToCFStringEncoding(userArgs->textRep);
  589. // Mutable worker for upper/lower
  590. CFMutableStringRef workerString = CFStringCreateMutable(kCFAllocatorDefault, 0);
  591. GTMCFAutorelease(workerString);
  592. if (!workerString) {
  593. // COV_NF_START
  594. sqlite3_result_error(context,
  595. "LOWER/UPPER CF implementation failed " \
  596. "to allocate CFMutableStringRef", -1);
  597. return;
  598. // COV_NF_END
  599. }
  600. CFStringAppendCharacters(workerString, sqlText16,
  601. sqlText16ByteCount / sizeof(UniChar));
  602. // Perform the upper/lower
  603. if (userArgs->upperCase) {
  604. CFStringUppercase(workerString, gCurrentLocale);
  605. } else {
  606. CFStringLowercase(workerString, gCurrentLocale);
  607. }
  608. // Convert to our canonical composition
  609. CFStringNormalize(workerString, kCFStringNormalizationFormC);
  610. // Length after normalization matters
  611. CFIndex workerLength = CFStringGetLength(workerString);
  612. // If we can give direct byte access use it
  613. const UniChar *returnString = CFStringGetCharactersPtr(workerString);
  614. if (returnString) {
  615. // COV_NF_START details of whether cfstringgetcharactersptr returns
  616. // a buffer or NULL are internal; not something we can depend on.
  617. // When building for Leopard+, CFIndex is a 64-bit type, which is
  618. // why we cast it to an int when we call the sqlite api.
  619. _GTMDevAssert((workerLength * sizeof(UniChar) <= INT_MAX),
  620. @"sqlite methods do not support buffers greater "
  621. @"than 32 bit sizes");
  622. // Direct access to the internal buffer, hand it to sqlite for copy and
  623. // conversion
  624. sqlite3_result_text16(context, returnString,
  625. (int)(workerLength * sizeof(UniChar)),
  626. SQLITE_TRANSIENT);
  627. // COV_NF_END
  628. } else {
  629. // Need to get a copy since we can't get direct access
  630. CFIndex bufferSize = CFStringGetMaximumSizeForEncoding(workerLength,
  631. encoding);
  632. void *returnBuffer = malloc(bufferSize);
  633. if (!returnBuffer) {
  634. // COV_NF_START
  635. sqlite3_result_error(context,
  636. "LOWER/UPPER CF implementation failed " \
  637. "to allocate return buffer", -1);
  638. return;
  639. // COV_NF_END
  640. }
  641. CFIndex convertedBytes = 0;
  642. CFIndex convertedChars = CFStringGetBytes(workerString,
  643. CFRangeMake(0, workerLength),
  644. encoding,
  645. 0,
  646. false,
  647. returnBuffer,
  648. bufferSize,
  649. &convertedBytes);
  650. if (convertedChars != workerLength) {
  651. // COV_NF_START
  652. free(returnBuffer);
  653. sqlite3_result_error(context,
  654. "LOWER/UPPER CF implementation CFStringGetBytes() " \
  655. "failed to convert all characters", -1);
  656. // COV_NF_END
  657. } else {
  658. // When building for Leopard+, CFIndex is a 64-bit type, but
  659. // sqlite3's functions all take ints. Assert the error for dev
  660. // builds and cast down.
  661. _GTMDevAssert((convertedBytes <= INT_MAX),
  662. @"sqlite methods do not support buffers greater "
  663. @"than 32-bit sizes");
  664. int convertedBytesForSQLite = (int)convertedBytes;
  665. // Set the result, letting SQLite take ownership and using free() as
  666. // the destructor. For output since we're copying out the bytes anyway
  667. // we might as well use the preferred encoding of the original call.
  668. _GTMDevAssert(userArgs->textRep == SQLITE_UTF16BE ||
  669. userArgs->textRep == SQLITE_UTF16LE,
  670. @"Received non UTF8 encoding in UpperLower8");
  671. switch (userArgs->textRep) {
  672. case SQLITE_UTF16BE:
  673. sqlite3_result_text16be(context, returnBuffer,
  674. convertedBytesForSQLite, &free);
  675. break;
  676. case SQLITE_UTF16LE:
  677. sqlite3_result_text16le(context, returnBuffer,
  678. convertedBytesForSQLite, &free);
  679. break;
  680. default:
  681. free(returnBuffer);
  682. // COV_NF_START no way to tell sqlite to not use utf8 or utf16?
  683. sqlite3_result_error(context,
  684. "LOWER/UPPER CF implementation " \
  685. "had unhandled encoding", -1);
  686. // COV_NF_END
  687. }
  688. }
  689. }
  690. }
  691. #pragma mark Collations
  692. static void CollateNeeded(void *userContext, sqlite3 *db, int textRep,
  693. const char *name) {
  694. // Cast
  695. GTMSQLiteDatabase *gtmdb = (GTMSQLiteDatabase *)userContext;
  696. _GTMDevAssert(gtmdb, @"Invalid database parameter from sqlite");
  697. // Create space for the collation args
  698. NSMutableData *collationArgsData =
  699. [NSMutableData dataWithLength:sizeof(CollateUserArgs)];
  700. CollateUserArgs *userArgs = (CollateUserArgs *)[collationArgsData bytes];
  701. bzero(userArgs, sizeof(CollateUserArgs));
  702. userArgs->textRep = textRep;
  703. // Parse the name into the flags we need
  704. NSString *collationName =
  705. [[NSString stringWithUTF8String:name] lowercaseString];
  706. NSArray *collationComponents =
  707. [collationName componentsSeparatedByString:@"_"];
  708. NSString *collationFlag = nil;
  709. BOOL atLeastOneValidFlag = NO;
  710. GTM_FOREACH_OBJECT(collationFlag, collationComponents) {
  711. if ([collationFlag isEqualToString:@"reverse"]) {
  712. userArgs->reverse = YES;
  713. atLeastOneValidFlag = YES;
  714. } else if ([collationFlag isEqualToString:@"nocase"]) {
  715. userArgs->compareOptions |= kCFCompareCaseInsensitive;
  716. atLeastOneValidFlag = YES;
  717. } else if ([collationFlag isEqualToString:@"nonliteral"]) {
  718. userArgs->compareOptions |= kCFCompareNonliteral;
  719. atLeastOneValidFlag = YES;
  720. } else if ([collationFlag isEqualToString:@"localized"]) {
  721. userArgs->compareOptions |= kCFCompareLocalized;
  722. atLeastOneValidFlag = YES;
  723. } else if ([collationFlag isEqualToString:@"numeric"]) {
  724. userArgs->compareOptions |= kCFCompareNumerically;
  725. atLeastOneValidFlag = YES;
  726. #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
  727. } else if ([collationFlag isEqualToString:@"nodiacritic"]) {
  728. userArgs->compareOptions |= kCFCompareDiacriticInsensitive;
  729. atLeastOneValidFlag = YES;
  730. } else if ([collationFlag isEqualToString:@"widthinsensitive"]) {
  731. userArgs->compareOptions |= kCFCompareWidthInsensitive;
  732. atLeastOneValidFlag = YES;
  733. #else
  734. } else if (([collationFlag isEqualToString:@"nodiacritic"]) ||
  735. ([collationFlag isEqualToString:@"widthinsensitive"])) {
  736. _GTMDevLog(@"GTMSQLiteDatabase 10.5 collating not "
  737. @"available on 10.4 or earlier");
  738. #endif
  739. }
  740. }
  741. // No valid tokens means nothing to do
  742. if (!atLeastOneValidFlag) return;
  743. int err;
  744. // Add the collation
  745. switch (textRep) {
  746. case SQLITE_UTF8:
  747. err = sqlite3_create_collation([gtmdb sqlite3DB], name,
  748. textRep, userArgs, &Collate8);
  749. if (err != SQLITE_OK) return;
  750. break;
  751. case SQLITE_UTF16:
  752. case SQLITE_UTF16BE:
  753. case SQLITE_UTF16LE:
  754. err = sqlite3_create_collation([gtmdb sqlite3DB], name,
  755. textRep, userArgs, &Collate16);
  756. if (err != SQLITE_OK) return;
  757. break;
  758. default:
  759. return;
  760. }
  761. // Have the db retain our collate function args
  762. [gtmdb collationArgumentRetain:collationArgsData];
  763. }
  764. static int Collate8(void *userContext, int length1, const void *str1,
  765. int length2, const void *str2) {
  766. // User args
  767. CollateUserArgs *userArgs = (CollateUserArgs *)userContext;
  768. _GTMDevAssert(userArgs, @"Invalid user arguments from sqlite");
  769. // Sanity and zero-lengths
  770. if (!(str1 && str2) || (!length1 && !length2)) {
  771. return kCFCompareEqualTo; // Best we can do and stable sort
  772. }
  773. if (!length1 && length2) {
  774. if (userArgs->reverse) {
  775. return kCFCompareGreaterThan;
  776. } else {
  777. return kCFCompareLessThan;
  778. }
  779. } else if (length1 && !length2) {
  780. if (userArgs->reverse) {
  781. return kCFCompareLessThan;
  782. } else {
  783. return kCFCompareGreaterThan;
  784. }
  785. }
  786. // We have UTF8 strings with no terminating null, we want to compare
  787. // with as few copies as possible. Leopard introduced a no-copy string
  788. // creation function, we'll use it when we can but we want to stay compatible
  789. // with Tiger.
  790. CFStringRef string1 = NULL, string2 = NULL;
  791. #if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
  792. if (gCFStringCreateWithBytesNoCopySymbol) {
  793. string1 = gCFStringCreateWithBytesNoCopySymbol(kCFAllocatorDefault,
  794. str1,
  795. length1,
  796. kCFStringEncodingUTF8,
  797. false,
  798. kCFAllocatorNull);
  799. string2 = gCFStringCreateWithBytesNoCopySymbol(kCFAllocatorDefault,
  800. str2,
  801. length2,
  802. kCFStringEncodingUTF8,
  803. false,
  804. kCFAllocatorNull);
  805. } else {
  806. // Have to use the copy-based variants
  807. string1 = CFStringCreateWithBytes(kCFAllocatorDefault,
  808. str1,
  809. length1,
  810. kCFStringEncodingUTF8,
  811. false);
  812. string2 = CFStringCreateWithBytes(kCFAllocatorDefault,
  813. str2,
  814. length2,
  815. kCFStringEncodingUTF8,
  816. false);
  817. }
  818. #else // MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
  819. string1 = CFStringCreateWithBytesNoCopy(kCFAllocatorDefault,
  820. str1,
  821. length1,
  822. kCFStringEncodingUTF8,
  823. false,
  824. kCFAllocatorNull);
  825. string2 = CFStringCreateWithBytesNoCopy(kCFAllocatorDefault,
  826. str2,
  827. length2,
  828. kCFStringEncodingUTF8,
  829. false,
  830. kCFAllocatorNull);
  831. #endif // MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
  832. GTMCFAutorelease(string1);
  833. GTMCFAutorelease(string2);
  834. // Allocation failures can't really be sanely handled from a collator
  835. int sqliteResult;
  836. if (!(string1 && string2)) {
  837. // COV_NF_START
  838. sqliteResult = (int)kCFCompareEqualTo;
  839. // COV_NF_END
  840. } else {
  841. // Compare
  842. // We have to cast to int because SQLite takes functions that
  843. // return an int, but when compiling for Leopard+,
  844. // CFComparisonResult is a signed long, but on Tiger it's an int
  845. CFComparisonResult result;
  846. result = CFStringCompare(string1,
  847. string2,
  848. userArgs->compareOptions);
  849. sqliteResult = (int)result;
  850. // Reverse
  851. if (userArgs->reverse && sqliteResult) {
  852. sqliteResult = -sqliteResult;
  853. }
  854. }
  855. return sqliteResult;
  856. }
  857. static int Collate16(void *userContext, int length1, const void *str1,
  858. int length2, const void *str2) {
  859. // User args
  860. CollateUserArgs *userArgs = (CollateUserArgs *)userContext;
  861. _GTMDevAssert(userArgs, @"Invalid user arguments from sqlite");
  862. // Sanity and zero-lengths
  863. if (!(str1 && str2) || (!length1 && !length2)) {
  864. return kCFCompareEqualTo; // Best we can do and stable sort
  865. }
  866. if (!length1 && length2) {
  867. if (userArgs->reverse) {
  868. return kCFCompareGreaterThan;
  869. } else {
  870. return kCFCompareLessThan;
  871. }
  872. } else if (length1 && !length2) {
  873. if (userArgs->reverse) {
  874. return kCFCompareLessThan;
  875. } else {
  876. return kCFCompareGreaterThan;
  877. }
  878. }
  879. // Target encoding
  880. CFStringEncoding encoding =
  881. SqliteTextEncodingToCFStringEncoding(userArgs->textRep);
  882. // We have UTF16 strings, we want to compare with as few copies as
  883. // possible. Since endianness matters we want to use no-copy
  884. // variants where possible and copy (and endian convert) only when
  885. // we must.
  886. CFStringRef string1 = NULL, string2 = NULL;
  887. if ((userArgs->textRep == SQLITE_UTF16) ||
  888. #if TARGET_RT_BIG_ENDIAN
  889. (userArgs->textRep == SQLITE_UTF16BE)
  890. #else
  891. (userArgs->textRep == SQLITE_UTF16LE)
  892. #endif
  893. ) {
  894. string1 = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault,
  895. str1,
  896. length1 / sizeof(UniChar),
  897. kCFAllocatorNull);
  898. string2 = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault,
  899. str2,
  900. length2 / sizeof(UniChar),
  901. kCFAllocatorNull);
  902. } else {
  903. // No point in using the "no copy" version of the call here. If the
  904. // bytes were in the native order we'd be in the other part of this
  905. // conditional, so we know we have to copy the string to endian convert
  906. // it.
  907. string1 = CFStringCreateWithBytes(kCFAllocatorDefault,
  908. str1,
  909. length1,
  910. encoding,
  911. false);
  912. string2 = CFStringCreateWithBytes(kCFAllocatorDefault,
  913. str2,
  914. length2,
  915. encoding,
  916. false);
  917. }
  918. GTMCFAutorelease(string1);
  919. GTMCFAutorelease(string2);
  920. int sqliteResult;
  921. // Allocation failures can't really be sanely handled from a collator
  922. if (!(string1 && string2)) {
  923. // COV_NF_START
  924. sqliteResult = (int)kCFCompareEqualTo;
  925. // COV_NF_END
  926. } else {
  927. // Compare
  928. // We cast the return value to an int because CFComparisonResult
  929. // is a long in Leopard+ builds. I have no idea why we need
  930. // 64-bits for a 3-value enum, but that's how it is...
  931. CFComparisonResult result;
  932. result = CFStringCompare(string1,
  933. string2,
  934. userArgs->compareOptions);
  935. sqliteResult = (int)result;
  936. //Reverse
  937. if (userArgs->reverse && sqliteResult) {
  938. sqliteResult = -sqliteResult;
  939. }
  940. }
  941. return sqliteResult;
  942. }
  943. #pragma mark Like/Glob
  944. // Private helper to handle LIKE and GLOB with different encodings. This
  945. // is essentially a reimplementation of patternCompare() in func.c of the
  946. // SQLite sources.
  947. static void LikeGlobCompare(sqlite3_context *context,
  948. CFStringRef pattern,
  949. CFStringRef targetString,
  950. UniChar matchAll,
  951. UniChar matchOne,
  952. UniChar escape,
  953. BOOL setSupport,
  954. CFOptionFlags compareOptions) {
  955. // Setup for pattern walk
  956. CFIndex patternLength = CFStringGetLength(pattern);
  957. CFStringInlineBuffer patternBuffer;
  958. CFStringInitInlineBuffer(pattern,
  959. &patternBuffer,
  960. CFRangeMake(0, patternLength));
  961. UniChar patternChar;
  962. CFIndex patternIndex = 0;
  963. CFIndex targetStringLength = CFStringGetLength(targetString);
  964. CFIndex targetStringIndex = 0;
  965. BOOL isAnchored = YES;
  966. size_t dataSize = patternLength * sizeof(UniChar);
  967. NSMutableData *tempData = [NSMutableData dataWithLength:dataSize];
  968. // Temp string buffer can be no larger than the whole pattern
  969. UniChar *findBuffer = [tempData mutableBytes];
  970. if (!findBuffer) {
  971. // COV_NF_START
  972. sqlite3_result_error(context,
  973. "LIKE or GLOB CF implementation failed to " \
  974. "allocate temporary buffer", -1);
  975. return;
  976. // COV_NF_END
  977. }
  978. // We'll use a mutable string we can just reset as we wish
  979. CFMutableStringRef findString =
  980. CFStringCreateMutableWithExternalCharactersNoCopy(kCFAllocatorDefault,
  981. NULL,
  982. 0,
  983. 0,
  984. kCFAllocatorNull);
  985. GTMCFAutorelease(findString);
  986. if (!findString) {
  987. // COV_NF_START
  988. sqlite3_result_error(context,
  989. "LIKE or GLOB CF implementation failed to " \
  990. "allocate temporary CFString", -1);
  991. return;
  992. // COV_NF_END
  993. }
  994. // Walk the pattern
  995. while (patternIndex < patternLength) {
  996. patternChar = CFStringGetCharacterFromInlineBuffer(&patternBuffer,
  997. patternIndex);
  998. // Match all character has no effect other than to unanchor the search
  999. if (patternChar == matchAll) {
  1000. isAnchored = NO;
  1001. patternIndex++;
  1002. continue;
  1003. }
  1004. // Match one character pushes the string index forward by one composed
  1005. // character
  1006. if (patternChar == matchOne) {
  1007. // If this single char match would walk us off the end of the string
  1008. // we're already done, no match
  1009. if (targetStringIndex >= targetStringLength) {
  1010. sqlite3_result_int(context, 0);
  1011. return;
  1012. }
  1013. // There's still room in the string, so move the string index forward one
  1014. // composed character and go back around.
  1015. CFRange nextCharRange =
  1016. CFStringGetRangeOfComposedCharactersAtIndex(targetString,
  1017. targetStringIndex);
  1018. targetStringIndex = nextCharRange.location + nextCharRange.length;
  1019. patternIndex++;
  1020. continue;
  1021. }
  1022. // Character set matches require the parsing of the character set
  1023. if (setSupport && (patternChar == 0x5B)) { // "["
  1024. // A character set must match one character, if there's not at least one
  1025. // character left in the string, don't bother
  1026. if (targetStringIndex >= targetStringLength) {
  1027. sqlite3_result_int(context, 0);
  1028. return;
  1029. }
  1030. // There's at least one character, try to match the remainder of the
  1031. // string using a CFCharacterSet
  1032. CFMutableCharacterSetRef charSet
  1033. = CFCharacterSetCreateMutable(kCFAllocatorDefault);
  1034. GTMCFAutorelease(charSet);
  1035. if (!charSet) {
  1036. // COV_NF_START
  1037. sqlite3_result_error(context,
  1038. "LIKE or GLOB CF implementation failed to " \
  1039. "allocate temporary CFMutableCharacterSet", -1);
  1040. return;
  1041. // COV_NF_END
  1042. }
  1043. BOOL invert = NO;
  1044. // Walk one character forward
  1045. patternIndex++;
  1046. if (patternIndex >= patternLength) {
  1047. // Oops, out of room
  1048. sqlite3_result_error(context,
  1049. "LIKE or GLOB CF implementation found " \
  1050. "unclosed character set", -1);
  1051. return;
  1052. }
  1053. // First character after pattern open is special-case
  1054. patternChar = CFStringGetCharacterFromInlineBuffer(&patternBuffer,
  1055. patternIndex);
  1056. if (patternChar == 0x5E) { // "^"
  1057. invert = YES;
  1058. // Bump forward one character, can still be an unescaped "]" after
  1059. // negation
  1060. patternIndex++;
  1061. if (patternIndex >= patternLength) {
  1062. // Oops, out of room
  1063. sqlite3_result_error(context,
  1064. "LIKE or GLOB CF implementation found " \
  1065. "unclosed character set after negation", -1);
  1066. return;
  1067. }
  1068. patternChar = CFStringGetCharacterFromInlineBuffer(&patternBuffer,
  1069. patternIndex);
  1070. }
  1071. // First char in set or first char in negation can be a literal "]" not
  1072. // considered a close
  1073. if (patternChar == 0x5D) { // "]"
  1074. CFCharacterSetAddCharactersInRange(charSet,
  1075. CFRangeMake(patternChar, 1));
  1076. patternIndex++;
  1077. if (patternIndex >= patternLength) {
  1078. // Oops, out of room
  1079. sqlite3_result_error(context,
  1080. "LIKE or GLOB CF implementation found " \
  1081. "unclosed character set after escaped ]", -1);
  1082. return;
  1083. }
  1084. patternChar = CFStringGetCharacterFromInlineBuffer(&patternBuffer,
  1085. patternIndex);
  1086. }
  1087. while ((patternIndex < patternLength) &&
  1088. patternChar &&
  1089. (patternChar != 0x5D)) { // "]"
  1090. // Check for possible character range, for this to be true we
  1091. // must have a hyphen at the next position and at least 3
  1092. // characters of room (for hyphen, range end, and set
  1093. // close). Hyphens at the end without a trailing range are
  1094. // treated as literals
  1095. if (((patternLength - patternIndex) >= 3) &&
  1096. // Second char must be "-"
  1097. (CFStringGetCharacterFromInlineBuffer(&patternBuffer,
  1098. // 0x2D is "-"
  1099. patternIndex + 1) == 0x2D) &&
  1100. // And third char must be anything other than set close in
  1101. // case the hyphen is at the end of the set and needs to
  1102. // be treated as a literal
  1103. (CFStringGetCharacterFromInlineBuffer(&patternBuffer,
  1104. patternIndex + 2)
  1105. != 0x5D)) { // "]"
  1106. // Get the range close
  1107. UniChar rangeClose =
  1108. CFStringGetCharacterFromInlineBuffer(&patternBuffer,
  1109. patternIndex + 2);
  1110. // Add the whole range
  1111. int rangeLen = rangeClose - patternChar + 1;
  1112. CFCharacterSetAddCharactersInRange(charSet,
  1113. CFRangeMake(patternChar,
  1114. rangeLen));
  1115. // Move past the end of the range
  1116. patternIndex += 3;
  1117. } else {
  1118. // Single Raw character
  1119. CFCharacterSetAddCharactersInRange(charSet,
  1120. CFRangeMake(patternChar, 1));
  1121. patternIndex++;
  1122. }
  1123. // Load next char for loop
  1124. if (patternIndex < patternLength) {
  1125. patternChar =
  1126. CFStringGetCharacterFromInlineBuffer(&patternBuffer, patternIndex);
  1127. } else {
  1128. patternChar = 0;
  1129. }
  1130. }
  1131. // Check for closure
  1132. if (patternChar != 0x5D) { // "]"
  1133. sqlite3_result_error(context,
  1134. "LIKE or GLOB CF implementation found " \
  1135. "unclosed character set", -1);
  1136. return;
  1137. } else {
  1138. // Increment past the end of the set
  1139. patternIndex++;
  1140. }
  1141. // Invert the set if needed
  1142. if (invert) CFCharacterSetInvert(charSet);
  1143. // Do the search
  1144. CFOptionFlags findOptions = 0;
  1145. if (isAnchored) findOptions |= kCFCompareAnchored;
  1146. CFRange foundRange;
  1147. unsigned long rangeLen = targetStringLength - targetStringIndex;
  1148. BOOL found = CFStringFindCharacterFromSet(targetString,
  1149. charSet,
  1150. CFRangeMake(targetStringIndex,
  1151. rangeLen),
  1152. findOptions,
  1153. &foundRange);
  1154. // If no match then the whole pattern fails
  1155. if (!found) {
  1156. sqlite3_result_int(context, 0);
  1157. return;
  1158. }
  1159. // If we did match then we need to push the string index to the
  1160. // character past the end of the match and then go back around
  1161. // the loop.
  1162. targetStringIndex = foundRange.location + foundRange.length;
  1163. // At this point patternIndex is either at the end of the
  1164. // string, or at the next special character which will be picked
  1165. // up and handled at the top of the loop. We do, however, need
  1166. // to reset the anchor status
  1167. isAnchored = YES;
  1168. // End of character sets, back around
  1169. continue;
  1170. }
  1171. // Otherwise the pattern character is a normal or escaped
  1172. // character we should consume and match with normal string
  1173. // matching
  1174. CFIndex findBufferIndex = 0;
  1175. while ((patternIndex < patternLength) && patternChar &&
  1176. !((patternChar == matchAll) || (patternChar == matchOne) ||
  1177. (setSupport && (patternChar == 0x5B)))) { // "["
  1178. if (patternChar == escape) {
  1179. // No matter what the character follows the escape copy it to the
  1180. // buffer
  1181. patternIndex++;
  1182. if (patternIndex >= patternLength) {
  1183. // COV_NF_START
  1184. // Oops, escape came at end of pattern, that's an error
  1185. sqlite3_result_error(context,
  1186. "LIKE or GLOB CF implementation found " \
  1187. "escape character at end of pattern", -1);
  1188. return;
  1189. // COV_NF_END
  1190. }
  1191. patternChar = CFStringGetCharacterFromInlineBuffer(&patternBuffer,
  1192. patternIndex);
  1193. }
  1194. // At this point the patternChar is either the escaped character or the
  1195. // original normal character
  1196. findBuffer[findBufferIndex++] = patternChar;
  1197. // Set up for next loop
  1198. patternIndex++;
  1199. if (patternIndex < patternLength) {
  1200. patternChar = CFStringGetCharacterFromInlineBuffer(&patternBuffer,
  1201. patternIndex);
  1202. } else {
  1203. patternChar = 0;
  1204. }
  1205. }
  1206. // On loop exit we have a string ready for comparision, if that
  1207. // string is too long then it can't be a match.
  1208. if (findBufferIndex > (targetStringLength - targetStringIndex)) {
  1209. sqlite3_result_int(context, 0);
  1210. return;
  1211. }
  1212. // We actually need to do a comparison
  1213. CFOptionFlags findOptions = compareOptions;
  1214. if (isAnchored) findOptions |= kCFCompareAnchored;
  1215. CFStringSetExternalCharactersNoCopy(findString,
  1216. findBuffer,
  1217. findBufferIndex,
  1218. findBufferIndex);
  1219. CFRange foundRange;
  1220. unsigned long rangeLen = targetStringLength - targetStringIndex;
  1221. BOOL found = CFStringFindWithOptions(targetString,
  1222. findString,
  1223. CFRangeMake(targetStringIndex,
  1224. rangeLen),
  1225. findOptions,
  1226. &foundRange);
  1227. // If no match then the whole pattern fails
  1228. if (!found) {
  1229. sqlite3_result_int(context, 0);
  1230. return;
  1231. }
  1232. // If we did match then we need to push the string index to the
  1233. // character past the end of the match and then go back around the
  1234. // loop.
  1235. targetStringIndex = foundRange.location + foundRange.length;
  1236. // At this point patternIndex is either at the end of the string,
  1237. // or at the next special character which will be picked up and
  1238. // handled at the top of the loop. We do, however, need to reset
  1239. // the anchor status
  1240. isAnchored = YES;
  1241. }
  1242. // On loop exit all pattern characters have been considered. If we're still
  1243. // alive it means that we've matched the entire pattern, except for trailing
  1244. // wildcards, we need to handle that case.
  1245. if (isAnchored) {
  1246. // If we're still anchored there was no trailing matchAll, in which case
  1247. // we have to have run to exactly the end of the string
  1248. if (targetStringIndex == targetStringLength) {
  1249. sqlite3_result_int(context, 1);
  1250. } else {
  1251. sqlite3_result_int(context, 0);
  1252. }
  1253. } else {
  1254. // If we're not anchored any remaining characters are OK
  1255. sqlite3_result_int(context, 1);
  1256. }
  1257. }
  1258. static void Like8(sqlite3_context *context, int argc, sqlite3_value **argv) {
  1259. // Get our LIKE options
  1260. LikeGlobUserArgs *likeArgs = sqlite3_user_data(context);
  1261. if (!likeArgs) {
  1262. // COV_NF_START
  1263. sqlite3_result_error(context, "LIKE CF implementation no user args", -1);
  1264. return;
  1265. // COV_NF_END
  1266. }
  1267. // Read the strings
  1268. const char *pattern = (const char *)sqlite3_value_text(argv[0]);
  1269. const char *target = (const char *)sqlite3_value_text(argv[1]);
  1270. if (!pattern || !target) {
  1271. // COV_NF_START
  1272. sqlite3_result_error(context,
  1273. "LIKE CF implementation missing pattern or value", -1);
  1274. return;
  1275. // COV_NF_END
  1276. }
  1277. CFStringRef patternString =
  1278. CFStringCreateWithCStringNoCopy(kCFAllocatorDefault,
  1279. pattern,
  1280. kCFStringEncodingUTF8,
  1281. kCFAllocatorNull);
  1282. CFStringRef targetString =
  1283. CFStringCreateWithCStringNoCopy(kCFAllocatorDefault,
  1284. target,
  1285. kCFStringEncodingUTF8,
  1286. kCFAllocatorNull);
  1287. GTMCFAutorelease(patternString);
  1288. GTMCFAutorelease(targetString);
  1289. if (!(patternString && targetString)) {
  1290. // COV_NF_START
  1291. sqlite3_result_error(context,
  1292. "LIKE CF implementation failed " \
  1293. "to allocate CFStrings", -1);
  1294. return;
  1295. // COV_NF_END
  1296. }
  1297. UniChar escapeChar = 0;
  1298. // If there is a third argument it is the escape character
  1299. if (argc == 3) {
  1300. const char *escape = (const char *)sqlite3_value_text(argv[2]);
  1301. if (!escape) {
  1302. sqlite3_result_error(context,
  1303. "LIKE CF implementation missing " \
  1304. "escape character", -1);
  1305. return;
  1306. }
  1307. CFStringRef escapeString =
  1308. CFStringCreateWithCStringNoCopy(kCFAllocatorDefault,
  1309. escape,
  1310. kCFStringEncodingUTF8,
  1311. kCFAllocatorNull);
  1312. GTMCFAutorelease(escapeString);
  1313. if (!escapeString) {
  1314. // COV_NF_START
  1315. sqlite3_result_error(context,
  1316. "LIKE CF implementation failed to " \
  1317. "allocate CFString for ESCAPE", -1);
  1318. return;
  1319. // COV_NF_END
  1320. }
  1321. if (CFStringGetLength(escapeString) != 1) {
  1322. sqlite3_result_error(context,
  1323. "CF implementation ESCAPE expression " \
  1324. "must be single character", -1);
  1325. return;
  1326. }
  1327. escapeChar = CFStringGetCharacterAtIndex(escapeString, 0);
  1328. }
  1329. // Do the compare
  1330. LikeGlobCompare(context,
  1331. patternString,
  1332. targetString,
  1333. 0x25, // %
  1334. 0x5F, // _
  1335. escapeChar,
  1336. NO, // LIKE does not support character sets
  1337. *(likeArgs->compareOptionPtr));
  1338. }
  1339. static void Like16(sqlite3_context *context, int argc, sqlite3_value **argv) {
  1340. // Get our LIKE options
  1341. LikeGlobUserArgs *likeArgs = sqlite3_user_data(context);
  1342. if (!likeArgs) {
  1343. // COV_NF_START - sql parser chokes if we feed any input
  1344. // that could trigger this
  1345. sqlite3_result_error(context, "LIKE CF implementation no user args", -1);
  1346. return;
  1347. // COV_NF_END
  1348. }
  1349. // For UTF16 variants we want our working string to be in native-endian
  1350. // UTF16. This gives us the fewest number of copies (since SQLite converts
  1351. // in-place). There is no advantage to breaking out the string construction
  1352. // to use UTF16BE or UTF16LE because all that does is move the conversion
  1353. // work into the CFString constructor, so just use simple code.
  1354. int patternByteCount = sqlite3_value_bytes16(argv[0]);
  1355. const UniChar *patternText = (void *)sqlite3_value_text16(argv[0]);
  1356. int targetByteCount = sqlite3_value_bytes16(argv[1]);
  1357. const UniChar *targetText = (void *)sqlite3_value_text16(argv[1]);
  1358. if (!patternByteCount || !patternText || !targetByteCount || !targetText) {
  1359. // COV_NF_START
  1360. sqlite3_result_error(context,
  1361. "LIKE CF implementation missing pattern or value", -1);
  1362. return;
  1363. // COV_NF_END
  1364. }
  1365. CFStringRef patternString =
  1366. CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault,
  1367. patternText,
  1368. patternByteCount / sizeof(UniChar),
  1369. kCFAllocatorNull);
  1370. CFStringRef targetString =
  1371. CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault,
  1372. targetText,
  1373. targetByteCount / sizeof(UniChar),
  1374. kCFAllocatorNull);
  1375. GTMCFAutorelease(patternString);
  1376. GTMCFAutorelease(targetString);
  1377. if (!(patternString && targetString)) {
  1378. // COV_NF_START
  1379. sqlite3_result_error(context,
  1380. "LIKE CF implementation failed " \
  1381. "to allocate CFStrings", -1);
  1382. return;
  1383. // COV_NF_END
  1384. }
  1385. // If there is a third argument it is the escape character, force a
  1386. // UTF8 conversion for simplicity
  1387. UniChar escapeChar = 0;
  1388. if (argc == 3) {
  1389. const char *escape = (const char *)sqlite3_value_text(argv[2]);
  1390. if (!escape) {
  1391. sqlite3_result_error(context,
  1392. "LIKE CF implementation " \
  1393. "missing escape character", -1);
  1394. return;
  1395. }
  1396. CFStringRef escapeString =
  1397. CFStringCreateWithCStringNoCopy(kCFAllocatorDefault,
  1398. escape,
  1399. kCFStringEncodingUTF8,
  1400. kCFAllocatorNull);
  1401. GTMCFAutorelease(escapeString);
  1402. if (!escapeString) {
  1403. // COV_NF_START
  1404. sqlite3_result_error(context,
  1405. "LIKE CF implementation failed to " \
  1406. "allocate CFString for ESCAPE", -1);
  1407. return;
  1408. // COV_NF_END
  1409. }
  1410. if (CFStringGetLength(escapeString) != 1) {
  1411. sqlite3_result_error(context,
  1412. "CF implementation ESCAPE expression " \
  1413. "must be single character", -1);
  1414. return;
  1415. }
  1416. escapeChar = CFStringGetCharacterAtIndex(escapeString, 0);
  1417. }
  1418. // Do the compare
  1419. LikeGlobCompare(context,
  1420. patternString,
  1421. targetString,
  1422. 0x25, // %
  1423. 0x5F, // _
  1424. escapeChar,
  1425. NO, // LIKE does not support character sets
  1426. *(likeArgs->compareOptionPtr));
  1427. }
  1428. static void Glob8(sqlite3_context *context, int argc, sqlite3_value **argv) {
  1429. // Get our GLOB options
  1430. LikeGlobUserArgs *globArgs = sqlite3_user_data(context);
  1431. if (!globArgs) {
  1432. // COV_NF_START
  1433. sqlite3_result_error(context, "GLOB CF implementation no user args", -1);
  1434. return;
  1435. // COV_NF_END
  1436. }
  1437. // Read the strings
  1438. const char *pattern = (const char *)sqlite3_value_text(argv[0]);
  1439. const char *target = (const char *)sqlite3_value_text(argv[1]);
  1440. if (!pattern || !target) {
  1441. // COV_NF_START
  1442. sqlite3_result_error(context,
  1443. "GLOB CF implementation missing " \
  1444. "pattern or value", -1);
  1445. return;
  1446. // COV_NF_END
  1447. }
  1448. CFStringRef patternString =
  1449. CFStringCreateWithCStringNoCopy(kCFAllocatorDefault,
  1450. pattern,
  1451. kCFStringEncodingUTF8,
  1452. kCFAllocatorNull);
  1453. CFStringRef targetString =
  1454. CFStringCreateWithCStringNoCopy(kCFAllocatorDefault,
  1455. target,
  1456. kCFStringEncodingUTF8,
  1457. kCFAllocatorNull);
  1458. GTMCFAutorelease(patternString);
  1459. GTMCFAutorelease(targetString);
  1460. if (!(patternString && targetString)) {
  1461. // COV_NF_START
  1462. sqlite3_result_error(context,
  1463. "GLOB CF implementation failed to " \
  1464. "allocate CFStrings", -1);
  1465. // COV_NF_END
  1466. } else {
  1467. // Do the compare
  1468. LikeGlobCompare(context,
  1469. patternString,
  1470. targetString,
  1471. 0x2A, // *
  1472. 0x3F, // ?
  1473. 0, // GLOB does not support escape characters
  1474. YES, // GLOB supports character sets
  1475. *(globArgs->compareOptionPtr));
  1476. }
  1477. }
  1478. static void Glob16(sqlite3_context *context, int argc, sqlite3_value **argv) {
  1479. // Get our GLOB options
  1480. LikeGlobUserArgs *globArgs = sqlite3_user_data(context);
  1481. if (!globArgs) {
  1482. // COV_NF_START
  1483. sqlite3_result_error(context, "GLOB CF implementation no user args", -1);
  1484. return;
  1485. // COV_NF_END
  1486. }
  1487. // For UTF16 variants we want our working string to be in
  1488. // native-endian UTF16. This gives us the fewest number of copies
  1489. // (since SQLite converts in-place). There is no advantage to
  1490. // breaking out the string construction to use UTF16BE or UTF16LE
  1491. // because all that does is move the conversion work into the
  1492. // CFString constructor, so just use simple code.
  1493. int patternByteCount = sqlite3_value_bytes16(argv[0]);
  1494. const UniChar *patternText = (void *)sqlite3_value_text16(argv[0]);
  1495. int targetByteCount = sqlite3_value_bytes16(argv[1]);
  1496. const UniChar *targetText = (void *)sqlite3_value_text16(argv[1]);
  1497. if (!patternByteCount || !patternText || !targetByteCount || !targetText) {
  1498. // COV_NF_START
  1499. sqlite3_result_error(context,
  1500. "GLOB CF implementation missing pattern or value", -1);
  1501. return;
  1502. // COV_NF_END
  1503. }
  1504. CFStringRef patternString =
  1505. CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault,
  1506. patternText,
  1507. patternByteCount / sizeof(UniChar),
  1508. kCFAllocatorNull);
  1509. CFStringRef targetString =
  1510. CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault,
  1511. targetText,
  1512. targetByteCount / sizeof(UniChar),
  1513. kCFAllocatorNull);
  1514. GTMCFAutorelease(patternString);
  1515. GTMCFAutorelease(targetString);
  1516. if (!(patternString && targetString)) {
  1517. // COV_NF_START
  1518. sqlite3_result_error(context,
  1519. "GLOB CF implementation failed to "\
  1520. "allocate CFStrings", -1);
  1521. // COV_NF_END
  1522. } else {
  1523. // Do the compare
  1524. LikeGlobCompare(context,
  1525. patternString,
  1526. targetString,
  1527. 0x2A, // *
  1528. 0x3F, // ?
  1529. 0, // GLOB does not support escape characters
  1530. YES, // GLOB supports character sets
  1531. *(globArgs->compareOptionPtr));
  1532. }
  1533. }
  1534. // -----------------------------------------------------------------------------
  1535. @implementation GTMSQLiteStatement
  1536. #pragma mark Creation, Access and Finalization
  1537. + (id)statementWithSQL:(NSString *)sql
  1538. inDatabase:(GTMSQLiteDatabase *)gtmdb
  1539. errorCode:(int *)err {
  1540. return [[[GTMSQLiteStatement alloc] initWithSQL:sql
  1541. inDatabase:gtmdb
  1542. errorCode:err]
  1543. autorelease];
  1544. }
  1545. - (id)initWithSQL:(NSString *)sql
  1546. inDatabase:(GTMSQLiteDatabase *)gtmdb
  1547. errorCode:(int *)err {
  1548. int rc;
  1549. id obj;
  1550. if ((self = [super init])) {
  1551. // Sanity
  1552. obj = self;
  1553. if (sql && gtmdb) {
  1554. // Find out if the database is using our CF extensions
  1555. hasCFAdditions_ = [gtmdb hasCFAdditions];
  1556. // Prepare
  1557. if (hasCFAdditions_) {
  1558. sql = [sql precomposedStringWithCanonicalMapping];
  1559. }
  1560. if (sql) {
  1561. rc = sqlite3_prepare([gtmdb sqlite3DB],
  1562. [sql UTF8String],
  1563. -1,
  1564. &statement_,
  1565. NULL);
  1566. if (rc != SQLITE_OK) {
  1567. [self release];
  1568. obj = nil;
  1569. }
  1570. } else {
  1571. // COV_NF_START
  1572. rc = SQLITE_INTERNAL;
  1573. [self release];
  1574. obj = nil;
  1575. // COV_NF_END
  1576. }
  1577. } else {
  1578. rc = SQLITE_MISUSE;
  1579. [self release];
  1580. obj = nil;
  1581. }
  1582. } else {
  1583. // COV_NF_START
  1584. rc = SQLITE_INTERNAL;
  1585. obj = nil;
  1586. // COV_NF_END
  1587. }
  1588. if (err) *err = rc;
  1589. return obj;
  1590. }
  1591. - (void)dealloc {
  1592. if (statement_) {
  1593. _GTMDevLog(@"-[GTMSQLiteStatement finalizeStatement] must be called when"
  1594. @" statement is no longer needed");
  1595. }
  1596. [super dealloc];
  1597. }
  1598. - (sqlite3_stmt *)sqlite3Statement {
  1599. return statement_;
  1600. }
  1601. - (int)finalizeStatement {
  1602. if (!statement_) return SQLITE_MISUSE;
  1603. int rc = sqlite3_finalize(statement_);
  1604. statement_ = NULL;
  1605. return rc;
  1606. }
  1607. #pragma mark Parameters and Binding
  1608. - (int)parameterCount {
  1609. if (!statement_) return -1;
  1610. return sqlite3_bind_parameter_count(statement_);
  1611. }
  1612. - (int)positionOfParameterNamed:(NSString *)paramName {
  1613. if (!statement_) return -1;
  1614. if (hasCFAdditions_) {
  1615. NSString *cleanedString =
  1616. [paramName precomposedStringWithCanonicalMapping];
  1617. if (!cleanedString) return -1;
  1618. return sqlite3_bind_parameter_index(statement_, [cleanedString UTF8String]);
  1619. } else {
  1620. return sqlite3_bind_parameter_index(statement_, [paramName UTF8String]);
  1621. }
  1622. }
  1623. - (NSString *)nameOfParameterAtPosition:(int)position {
  1624. if ((position < 1) || !statement_) return nil;
  1625. const char *name = sqlite3_bind_parameter_name(statement_, position);
  1626. if (!name) return nil;
  1627. NSString *nameString = [NSString stringWithUTF8String:name];
  1628. if (hasCFAdditions_) {
  1629. return [nameString precomposedStringWithCanonicalMapping];
  1630. } else {
  1631. return nameString;
  1632. }
  1633. }
  1634. - (int)bindSQLNullAtPosition:(int)position {
  1635. if (!statement_) return SQLITE_MISUSE;
  1636. return sqlite3_bind_null(statement_, position);
  1637. }
  1638. - (int)bindBlobAtPosition:(int)position bytes:(void *)bytes length:(int)length {
  1639. if (!statement_ || !bytes || !length) return SQLITE_MISUSE;
  1640. return sqlite3_bind_blob(statement_,
  1641. position,
  1642. bytes,
  1643. length,
  1644. SQLITE_TRANSIENT);
  1645. }
  1646. - (int)bindBlobAtPosition:(int)position data:(NSData *)data {
  1647. if (!statement_ || !data || !position) return SQLITE_MISUSE;
  1648. int blobLength = (int)[data length];
  1649. _GTMDevAssert((blobLength < INT_MAX),
  1650. @"sqlite methods do not support data lengths "
  1651. @"exceeding 32 bit sizes");
  1652. return [self bindBlobAtPosition:position
  1653. bytes:(void *)[data bytes]
  1654. length:blobLength];
  1655. }
  1656. - (int)bindDoubleAtPosition:(int)position value:(double)value {
  1657. if (!statement_) return SQLITE_MISUSE;
  1658. return sqlite3_bind_double(statement_, position, value);
  1659. }
  1660. - (int)bindNumberAsDoubleAtPosition:(int)position number:(NSNumber *)number {
  1661. if (!number || !statement_) return SQLITE_MISUSE;
  1662. return sqlite3_bind_double(statement_, position, [number doubleValue]);
  1663. }
  1664. - (int)bindInt32AtPosition:(int)position value:(int)value {
  1665. if (!statement_) return SQLITE_MISUSE;
  1666. return sqlite3_bind_int(statement_, position, value);
  1667. }
  1668. - (int)bindNumberAsInt32AtPosition:(int)position number:(NSNumber *)number {
  1669. if (!number || !statement_) return SQLITE_MISUSE;
  1670. return sqlite3_bind_int(statement_, position, [number intValue]);
  1671. }
  1672. - (int)bindLongLongAtPosition:(int)position value:(long long)value {
  1673. if (!statement_) return SQLITE_MISUSE;
  1674. return sqlite3_bind_int64(statement_, position, value);
  1675. }
  1676. - (int)bindNumberAsLongLongAtPosition:(int)position number:(NSNumber *)number {
  1677. if (!number || !statement_) return SQLITE_MISUSE;
  1678. return sqlite3_bind_int64(statement_, position, [number longLongValue]);
  1679. }
  1680. - (int)bindStringAtPosition:(int)position string:(NSString *)string {
  1681. if (!string || !statement_) return SQLITE_MISUSE;
  1682. if (hasCFAdditions_) {
  1683. string = [string precomposedStringWithCanonicalMapping];
  1684. if (!string) return SQLITE_INTERNAL;
  1685. }
  1686. return sqlite3_bind_text(statement_,
  1687. position,
  1688. [string UTF8String],
  1689. -1,
  1690. SQLITE_TRANSIENT);
  1691. }
  1692. #pragma mark Results
  1693. - (int)resultColumnCount {
  1694. if (!statement_) return -1;
  1695. return sqlite3_column_count(statement_);
  1696. }
  1697. - (NSString *)resultColumnNameAtPosition:(int)position {
  1698. if (!statement_) return nil;
  1699. const char *name = sqlite3_column_name(statement_, position);
  1700. if (!name) return nil;
  1701. NSString *nameString = [NSString stringWithUTF8String:name];
  1702. if (hasCFAdditions_) {
  1703. return [nameString precomposedStringWithCanonicalMapping];
  1704. } else {
  1705. return nameString;
  1706. }
  1707. }
  1708. - (int)rowDataCount {
  1709. if (!statement_) return -1;
  1710. return sqlite3_data_count(statement_);
  1711. }
  1712. - (int)resultColumnTypeAtPosition:(int)position {
  1713. if (!statement_) return -1;
  1714. return sqlite3_column_type(statement_, position);
  1715. }
  1716. - (NSData *)resultBlobDataAtPosition:(int)position {
  1717. if (!statement_) return nil;
  1718. const void *bytes = sqlite3_column_blob(statement_, position);
  1719. int length = sqlite3_column_bytes(statement_, position);
  1720. if (!(bytes && length)) return nil;
  1721. return [NSData dataWithBytes:bytes length:length];
  1722. }
  1723. - (double)resultDoubleAtPosition:(int)position {
  1724. if (!statement_) return 0;
  1725. return sqlite3_column_double(statement_, position);
  1726. }
  1727. - (int)resultInt32AtPosition:(int)position {
  1728. if (!statement_) return 0;
  1729. return sqlite3_column_int(statement_, position);
  1730. }
  1731. - (long long)resultLongLongAtPosition:(int)position {
  1732. if (!statement_) return 0;
  1733. return sqlite3_column_int64(statement_, position);
  1734. }
  1735. - (NSNumber *)resultNumberAtPosition:(int)position {
  1736. if (!statement_) return nil;
  1737. int type = [self resultColumnTypeAtPosition:position];
  1738. if (type == SQLITE_FLOAT) {
  1739. // Special case for floats
  1740. return [NSNumber numberWithDouble:[self resultDoubleAtPosition:position]];
  1741. } else {
  1742. // Everything else is cast to int
  1743. long long result = [self resultLongLongAtPosition:position];
  1744. return [NSNumber numberWithLongLong:result];
  1745. }
  1746. }
  1747. - (NSString *)resultStringAtPosition:(int)position {
  1748. if (!statement_) return nil;
  1749. const char *text = (const char *)sqlite3_column_text(statement_, position);
  1750. if (!text) return nil;
  1751. NSString *result = [NSString stringWithUTF8String:text];
  1752. if (hasCFAdditions_) {
  1753. return [result precomposedStringWithCanonicalMapping];
  1754. } else {
  1755. return result;
  1756. }
  1757. }
  1758. - (id)resultFoundationObjectAtPosition:(int)position {
  1759. if (!statement_) return nil;
  1760. int type = [self resultColumnTypeAtPosition:position];
  1761. id result = nil;
  1762. switch (type) {
  1763. case SQLITE_INTEGER:
  1764. case SQLITE_FLOAT:
  1765. result = [self resultNumberAtPosition:position];
  1766. break;
  1767. case SQLITE_TEXT:
  1768. result = [self resultStringAtPosition:position];
  1769. break;
  1770. case SQLITE_BLOB:
  1771. result = [self resultBlobDataAtPosition:position];
  1772. break;
  1773. case SQLITE_NULL:
  1774. result = [NSNull null];
  1775. break;
  1776. }
  1777. return result;
  1778. }
  1779. - (NSArray *)resultRowArray {
  1780. int count = [self rowDataCount];
  1781. if (count < 1) return nil;
  1782. NSMutableArray *finalArray = [NSMutableArray array];
  1783. for (int i = 0; i < count; i++) {
  1784. id coldata = [self resultFoundationObjectAtPosition:i];
  1785. if (!coldata) return nil; // Oops
  1786. [finalArray addObject:coldata];
  1787. }
  1788. if (![finalArray count]) return nil;
  1789. return finalArray;
  1790. }
  1791. - (NSDictionary *)resultRowDictionary {
  1792. int count = [self rowDataCount];
  1793. if (count < 1) return nil;
  1794. NSMutableDictionary *finalDict = [NSMutableDictionary dictionary];
  1795. for (int i = 0; i < count; i++) {
  1796. id coldata = [self resultFoundationObjectAtPosition:i];
  1797. NSString *colname = [self resultColumnNameAtPosition:i];
  1798. if (!(coldata && colname)) continue;
  1799. [finalDict setObject:coldata forKey:colname];
  1800. }
  1801. if (![finalDict count]) return nil;
  1802. return finalDict;
  1803. }
  1804. #pragma mark Rows
  1805. - (int)stepRow {
  1806. int rc = SQLITE_BUSY;
  1807. while (rc == SQLITE_BUSY) {
  1808. rc = [self stepRowWithTimeout];
  1809. }
  1810. return rc;
  1811. }
  1812. - (int)stepRowWithTimeout {
  1813. if (!statement_) return SQLITE_MISUSE;
  1814. return sqlite3_step(statement_);
  1815. }
  1816. - (int)reset {
  1817. if (!statement_) return SQLITE_MISUSE;
  1818. return sqlite3_reset(statement_);
  1819. }
  1820. + (BOOL)isCompleteStatement:(NSString *)statement {
  1821. BOOL isComplete = NO;
  1822. if (statement) {
  1823. isComplete = (sqlite3_complete([statement UTF8String]) ? YES : NO);
  1824. }
  1825. return isComplete;
  1826. }
  1827. + (NSString*)quoteAndEscapeString:(NSString *)string {
  1828. char *quoted = sqlite3_mprintf("'%q'", [string UTF8String]);
  1829. if (!quoted) return nil;
  1830. NSString *quotedString = [NSString stringWithUTF8String:quoted];
  1831. sqlite3_free(quoted);
  1832. return quotedString;
  1833. }
  1834. @end