/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

Large files are truncated click here to view the full file

  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…