PageRenderTime 288ms CodeModel.GetById 24ms app.highlight 252ms RepoModel.GetById 1ms 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

Large files files are truncated, but you can 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
  21
  22#import <Foundation/Foundation.h>
  23#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
  24#import <dlfcn.h>
  25#endif
  26#import "GTMSQLite.h"
  27#import "GTMMethodCheck.h"
  28#import "GTMDefines.h"
  29#include <limits.h>
  30
  31typedef struct {
  32  BOOL upperCase;
  33  int  textRep;
  34} UpperLowerUserArgs;
  35
  36typedef struct {
  37  BOOL            reverse;
  38  CFOptionFlags   compareOptions;
  39  int             textRep;
  40} CollateUserArgs;
  41
  42typedef struct {
  43  CFOptionFlags   *compareOptionPtr;
  44  int             textRep;
  45} LikeGlobUserArgs;
  46
  47#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
  48// While we want to be compatible with Tiger, some operations are more
  49// efficient when implemented with Leopard APIs. We look those up dynamically.
  50// CFStringCreateWithBytesNoCopy
  51static const char* const kCFStringCreateWithBytesNoCopySymbolName =
  52  "CFStringCreateWithBytesNoCopy";
  53
  54typedef CFStringRef (*CFStringCreateWithBytesNoCopyPtrType)(CFAllocatorRef,
  55                                                            const UInt8 *,
  56                                                            CFIndex,
  57                                                            CFStringEncoding,
  58                                                            Boolean,
  59                                                            CFAllocatorRef);
  60static CFStringCreateWithBytesNoCopyPtrType gCFStringCreateWithBytesNoCopySymbol = NULL;
  61#endif  // MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
  62
  63// Helper inline for SQLite text type to CF endcoding
  64GTM_INLINE CFStringEncoding SqliteTextEncodingToCFStringEncoding(int enc) {
  65  // Default should never happen, but assume UTF 8
  66  CFStringEncoding encoding = kCFStringEncodingUTF8;
  67  _GTMDevAssert(enc == SQLITE_UTF16BE ||
  68                enc == SQLITE_UTF16LE,
  69                @"Passed in encoding was not a UTF16 encoding");
  70  switch(enc) {
  71    case SQLITE_UTF16BE:
  72      encoding = kCFStringEncodingUTF16BE;
  73      break;
  74    case SQLITE_UTF16LE:
  75      encoding = kCFStringEncodingUTF16LE;
  76      break;
  77  }
  78  return encoding;
  79}
  80
  81// Helper inline for filtering CFStringCompareFlags
  82GTM_INLINE CFOptionFlags FilteredStringCompareFlags(CFOptionFlags inOptions) {
  83  CFOptionFlags outOptions = 0;
  84  if (inOptions & kCFCompareCaseInsensitive) {
  85    outOptions |= kCFCompareCaseInsensitive;
  86  }
  87  if (inOptions & kCFCompareNonliteral) outOptions |= kCFCompareNonliteral;
  88  if (inOptions & kCFCompareLocalized) outOptions |= kCFCompareLocalized;
  89  if (inOptions & kCFCompareNumerically) outOptions |= kCFCompareNumerically;
  90#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
  91  if (inOptions & kCFCompareDiacriticInsensitive) {
  92    outOptions |= kCFCompareDiacriticInsensitive;
  93  }
  94  if (inOptions & kCFCompareWidthInsensitive) {
  95    outOptions |= kCFCompareWidthInsensitive;
  96  }
  97#endif
  98  return outOptions;
  99}
 100
 101//  Function prototypes for our custom implementations of UPPER/LOWER using
 102//  CFString so that we handle Unicode and localization more cleanly than
 103//  native SQLite.
 104static void UpperLower8(sqlite3_context *context,
 105                        int argc,
 106                        sqlite3_value **argv);
 107static void UpperLower16(sqlite3_context *context,
 108                         int argc,
 109                         sqlite3_value **argv);
 110
 111//  Function prototypes for CFString-based collation sequences
 112static void CollateNeeded(void *userContext, sqlite3 *db,
 113                          int textRep, const char *name);
 114static int Collate8(void *userContext, int length1, const void *str1,
 115                    int length2, const void *str2);
 116static int Collate16(void *userContext, int length1, const void *str1,
 117                     int length2, const void *str2);
 118
 119//  Function prototypes for CFString LIKE and GLOB
 120static void Like8(sqlite3_context *context, int argc, sqlite3_value **argv);
 121static void Like16(sqlite3_context *context, int argc, sqlite3_value **argv);
 122static void Glob8(sqlite3_context *context, int argc, sqlite3_value **argv);
 123static void Glob16(sqlite3_context *context, int argc, sqlite3_value **argv);
 124
 125//  The CFLocale of the current user at process start
 126static CFLocaleRef gCurrentLocale = NULL;
 127
 128// Private methods
 129@interface GTMSQLiteDatabase (PrivateMethods)
 130
 131- (int)installCFAdditions;
 132- (void)collationArgumentRetain:(NSData *)collationArgs;
 133//  Convenience method to clean up resources.  Called from both
 134//  dealloc & finalize
 135//
 136- (void)cleanupDB;
 137@end
 138
 139@implementation GTMSQLiteDatabase
 140
 141+ (void)initialize {
 142  // Need the locale for some CFString enhancements
 143  gCurrentLocale = CFLocaleCopyCurrent();
 144
 145#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
 146  // Compiling pre-Leopard try to find some symbols dynamically
 147  gCFStringCreateWithBytesNoCopySymbol =
 148      (CFStringCreateWithBytesNoCopyPtrType)dlsym(
 149        RTLD_DEFAULT,
 150        kCFStringCreateWithBytesNoCopySymbolName);
 151#endif  // MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
 152}
 153
 154+ (int)sqliteVersionNumber {
 155  return sqlite3_libversion_number();
 156}
 157
 158+ (NSString *)sqliteVersionString {
 159  return [NSString stringWithUTF8String:sqlite3_libversion()];
 160}
 161
 162- (id)initWithPath:(NSString *)path
 163   withCFAdditions:(BOOL)additions
 164              utf8:(BOOL)useUTF8
 165         errorCode:(int *)err {
 166  int rc = SQLITE_INTERNAL;
 167
 168  if ((self = [super init])) {
 169    path_ = [path copy];
 170    if (useUTF8) {
 171      rc = sqlite3_open([path_ fileSystemRepresentation], &db_);
 172    } else {
 173      CFStringEncoding cfEncoding;
 174#if TARGET_RT_BIG_ENDIAN
 175      cfEncoding = kCFStringEncodingUTF16BE;
 176#else
 177      cfEncoding = kCFStringEncodingUTF16LE;
 178#endif
 179      NSStringEncoding nsEncoding 
 180        = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
 181      NSData *data = [path dataUsingEncoding:nsEncoding];
 182      // Using -[NSString cStringUsingEncoding] causes sqlite3_open16
 183      // to fail because it expects 2 null-terminating bytes and
 184      // cStringUsingEncoding only has 1
 185      NSMutableData *mutable = [NSMutableData dataWithData:data];
 186      [mutable increaseLengthBy:2];
 187      rc = sqlite3_open16([mutable bytes], &db_);
 188    }
 189
 190    if ((rc == SQLITE_OK) && db_) {
 191      if (additions) {
 192        userArgDataPool_ = [[NSMutableArray array] retain];
 193        if (!userArgDataPool_) {
 194          // Leave *err as internal err
 195          // COV_NF_START - not sure how to fail Cocoa initializers
 196          [self release];
 197          return nil;
 198          // COV_NF_END
 199        }
 200        rc = [self installCFAdditions];
 201      }
 202    }
 203
 204    if (err) *err = rc;
 205
 206    if (rc != SQLITE_OK) {
 207      // COV_NF_START
 208      [self release];
 209      self = nil;
 210      // COV_NF_END
 211    }
 212  }
 213
 214  return self;
 215}
 216
 217- (id)initInMemoryWithCFAdditions:(BOOL)additions
 218                             utf8:(BOOL)useUTF8
 219                        errorCode:(int *)err {
 220  return [self initWithPath:@":memory:"
 221            withCFAdditions:additions
 222                       utf8:useUTF8
 223                  errorCode:err];
 224}
 225
 226- (void)dealloc {
 227  [self cleanupDB];
 228  [super dealloc];
 229}
 230
 231- (void)cleanupDB {
 232  if (db_) {
 233    int rc = sqlite3_close(db_);
 234    if (rc != SQLITE_OK) {
 235      _GTMDevLog(@"Unable to close \"%@\", error code: %d\r"
 236                 @"Did you forget to call -[GTMSQLiteStatement"
 237                 @" finalizeStatement] on one of your statements?",
 238                 self, rc);
 239    }
 240  }
 241  [path_ release];
 242  [userArgDataPool_ release];
 243}
 244
 245//  Private method to install our custom CoreFoundation additions to SQLite
 246//  behavior
 247- (int)installCFAdditions {
 248  int rc = SQLITE_OK;
 249  // Install our custom functions for improved text handling
 250  // UPPER/LOWER
 251  const struct {
 252    const char           *sqlName;
 253    UpperLowerUserArgs   userArgs;
 254    void                 *function;
 255  } customUpperLower[] = {
 256    { "upper", { YES, SQLITE_UTF8 }, &UpperLower8 },
 257    { "upper", { YES, SQLITE_UTF16 }, &UpperLower16 },
 258    { "upper", { YES, SQLITE_UTF16BE }, &UpperLower16 },
 259    { "upper", { YES, SQLITE_UTF16LE }, &UpperLower16 },
 260    { "lower", { NO, SQLITE_UTF8 }, &UpperLower8 },
 261    { "lower", { NO, SQLITE_UTF16 }, &UpperLower16 },
 262    { "lower", { NO, SQLITE_UTF16BE }, &UpperLower16 },
 263    { "lower", { NO, SQLITE_UTF16LE }, &UpperLower16 },
 264  };
 265
 266  for (size_t i = 0;
 267       i < (sizeof(customUpperLower) / sizeof(customUpperLower[0]));
 268       i++) {
 269    rc = sqlite3_create_function(db_,
 270                                 customUpperLower[i].sqlName,
 271                                 1,
 272                                 customUpperLower[i].userArgs.textRep,
 273                                 (void *)&customUpperLower[i].userArgs,
 274                                 customUpperLower[i].function,
 275                                 NULL,
 276                                 NULL);
 277    if (rc != SQLITE_OK)
 278      return rc; // COV_NF_LINE because sqlite3_create_function is
 279                 // called with input defined at compile-time
 280  }
 281
 282  // Fixed collation sequences
 283  const struct {
 284    const char           *sqlName;
 285    CollateUserArgs      userArgs;
 286    void                 *function;
 287  } customCollationSequence[] = {
 288    { "nocase", { NO, kCFCompareCaseInsensitive, SQLITE_UTF8 }, &Collate8 },
 289    { "nocase", { NO, kCFCompareCaseInsensitive, SQLITE_UTF16 }, &Collate16 },
 290    { "nocase", { NO, kCFCompareCaseInsensitive, SQLITE_UTF16BE }, &Collate16 },
 291    { "nocase", { NO, kCFCompareCaseInsensitive, SQLITE_UTF16LE }, &Collate16 },
 292  };
 293
 294  for (size_t i = 0;
 295       i < (sizeof(customCollationSequence) / sizeof(customCollationSequence[0]));
 296       i++) {
 297    rc = sqlite3_create_collation(db_,
 298                                  customCollationSequence[i].sqlName,
 299                                  customCollationSequence[i].userArgs.textRep,
 300                                  (void *)&customCollationSequence[i].userArgs,
 301                                  customCollationSequence[i].function);
 302    if (rc != SQLITE_OK)
 303      return rc; // COV_NF_LINE because the input to
 304                 // sqlite3_create_collation is set at compile time
 305  }
 306
 307  // Install handler for dynamic collation sequences
 308  const struct {
 309    const char          *sqlName;
 310    int                 numArgs;
 311    int                 textRep;
 312    void                *function;
 313  } customLike[] = {
 314    { "like", 2, SQLITE_UTF8, &Like8 },
 315    { "like", 2, SQLITE_UTF16, &Like16 },
 316    { "like", 2, SQLITE_UTF16BE, &Like16 },
 317    { "like", 2, SQLITE_UTF16LE, &Like16 },
 318    { "like", 3, SQLITE_UTF8, &Like8 },
 319    { "like", 3, SQLITE_UTF16, &Like16 },
 320    { "like", 3, SQLITE_UTF16BE, &Like16 },
 321    { "like", 3, SQLITE_UTF16LE, &Like16 },
 322  };
 323
 324  rc = sqlite3_collation_needed(db_, self, &CollateNeeded);
 325  if (rc != SQLITE_OK)
 326    return rc; // COV_NF_LINE because input to
 327               // sqlite3_collation_needed is static
 328
 329  // Start LIKE as case-insensitive and non-literal
 330  // (sqlite defaults LIKE to case-insensitive)
 331  likeOptions_ = kCFCompareCaseInsensitive | kCFCompareNonliteral;
 332  for (size_t i = 0; i < (sizeof(customLike) / sizeof(customLike[0])); i++) {
 333    // Each implementation gets its own user args
 334    NSMutableData *argsData
 335      = [NSMutableData dataWithLength:sizeof(LikeGlobUserArgs)];
 336    if (!argsData) return SQLITE_INTERNAL;
 337    [userArgDataPool_ addObject:argsData];
 338    LikeGlobUserArgs *args = (LikeGlobUserArgs *)[argsData bytes];
 339    args->compareOptionPtr = &likeOptions_;
 340    args->textRep = customLike[i].textRep;
 341    rc = sqlite3_create_function(db_,
 342                                 customLike[i].sqlName,
 343                                 customLike[i].numArgs,
 344                                 customLike[i].textRep,
 345                                 args,
 346                                 customLike[i].function,
 347                                 NULL,
 348                                 NULL);
 349    if (rc != SQLITE_OK)
 350      return rc; // COV_NF_LINE because input to
 351                 // sqlite3_create_function is static
 352  }
 353
 354  // Start GLOB just non-literal but case-sensitive (same as SQLite defaults)
 355  const struct {
 356    const char          *sqlName;
 357    int                 textRep;
 358    void                *function;
 359  } customGlob[] = {
 360    { "glob", SQLITE_UTF8, &Glob8 },
 361    { "glob", SQLITE_UTF16, &Glob16 },
 362    { "glob", SQLITE_UTF16BE, &Glob16 },
 363    { "glob", SQLITE_UTF16LE, &Glob16 },
 364  };
 365
 366  globOptions_ = kCFCompareNonliteral;
 367  for (size_t i = 0; i < (sizeof(customGlob) / sizeof(customGlob[0])); i++) {
 368    // Each implementation gets its own user args
 369    NSMutableData *argsData
 370      = [NSMutableData dataWithLength:sizeof(LikeGlobUserArgs)];
 371    if (!argsData) return SQLITE_INTERNAL;
 372    [userArgDataPool_ addObject:argsData];
 373    LikeGlobUserArgs *args = (LikeGlobUserArgs *)[argsData bytes];
 374    args->compareOptionPtr = &globOptions_;
 375    args->textRep = customGlob[i].textRep;
 376    rc = sqlite3_create_function(db_,
 377                                 customGlob[i].sqlName,
 378                                 2,
 379                                 customGlob[i].textRep,
 380                                 args,
 381                                 customGlob[i].function,
 382                                 NULL,
 383                                 NULL);
 384    if (rc != SQLITE_OK)
 385      return rc; // COV_NF_LINE because input to
 386                 // sqlite3_create_function is static
 387  }
 388
 389  hasCFAdditions_ = YES;
 390  return SQLITE_OK;
 391}
 392
 393// Private method used by collation creation callback
 394- (void)collationArgumentRetain:(NSData *)collationArgs {
 395  [userArgDataPool_ addObject:collationArgs];
 396}
 397
 398- (sqlite3 *)sqlite3DB {
 399  return db_;
 400}
 401
 402- (void)synchronousMode:(BOOL)enable {
 403  if (enable) {
 404    [self executeSQL:@"PRAGMA synchronous = NORMAL;"];
 405    [self executeSQL:@"PRAGMA fullfsync = 1;"];
 406  } else {
 407    [self executeSQL:@"PRAGMA fullfsync = 0;"];
 408    [self executeSQL:@"PRAGMA synchronous = OFF;"];
 409  }
 410}
 411
 412- (BOOL)hasCFAdditions {
 413  return hasCFAdditions_;
 414}
 415
 416- (void)setLikeComparisonOptions:(CFOptionFlags)options {
 417  if (hasCFAdditions_)
 418    likeOptions_ = FilteredStringCompareFlags(options);
 419}
 420
 421- (CFOptionFlags)likeComparisonOptions {
 422  CFOptionFlags flags = 0;
 423  if (hasCFAdditions_) 
 424    flags = likeOptions_;
 425  return flags;
 426}
 427
 428- (void)setGlobComparisonOptions:(CFOptionFlags)options {
 429  if (hasCFAdditions_)
 430    globOptions_ = FilteredStringCompareFlags(options);
 431}
 432
 433- (CFOptionFlags)globComparisonOptions {
 434  CFOptionFlags globOptions = 0;
 435  if (hasCFAdditions_)
 436    globOptions = globOptions_;
 437  return globOptions;
 438}
 439
 440- (int)lastErrorCode {
 441  return sqlite3_errcode(db_);
 442}
 443
 444- (NSString *)lastErrorString {
 445  const char *errMsg = sqlite3_errmsg(db_);
 446  if (!errMsg) return nil;
 447  return [NSString stringWithCString:errMsg encoding:NSUTF8StringEncoding];
 448}
 449
 450- (int)lastChangeCount {
 451  return sqlite3_changes(db_);
 452}
 453
 454- (int)totalChangeCount {
 455  return sqlite3_total_changes(db_);
 456}
 457
 458- (unsigned long long)lastInsertRowID {
 459  return sqlite3_last_insert_rowid(db_);
 460}
 461
 462- (void)interrupt {
 463  sqlite3_interrupt(db_);
 464}
 465
 466- (int)setBusyTimeoutMS:(int)timeoutMS {
 467  int rc = sqlite3_busy_timeout(db_, timeoutMS);
 468  if (rc == SQLITE_OK) {
 469    timeoutMS_ = timeoutMS;
 470  }
 471  return rc;
 472}
 473
 474- (int)busyTimeoutMS {
 475  return timeoutMS_;
 476}
 477
 478- (int)executeSQL:(NSString *)sql {
 479  int rc;
 480  // Sanity
 481  if (!sql) {
 482    rc = SQLITE_MISUSE;  // Reasonable return for this case
 483  } else {
 484    if (hasCFAdditions_) {
 485      rc = sqlite3_exec(db_,
 486                        [[sql precomposedStringWithCanonicalMapping]
 487                          UTF8String],
 488                        NULL, NULL, NULL);
 489    } else {
 490      rc = sqlite3_exec(db_, [sql UTF8String], NULL, NULL, NULL);
 491    }
 492  }
 493  return rc;
 494}
 495
 496- (BOOL)beginDeferredTransaction {
 497  int err;
 498  err = [self executeSQL:@"BEGIN DEFERRED TRANSACTION;"];
 499  return (err == SQLITE_OK) ? YES : NO;
 500}
 501
 502- (BOOL)rollback {
 503  int err = [self executeSQL:@"ROLLBACK TRANSACTION;"];
 504  return (err == SQLITE_OK) ? YES : NO;
 505}
 506
 507- (BOOL)commit {
 508  int err = [self executeSQL:@"COMMIT TRANSACTION;"];
 509  return (err == SQLITE_OK) ? YES : NO;
 510}
 511
 512- (NSString *)description {
 513  return [NSString stringWithFormat:@"<%@: %p - %@>", 
 514          [self class], self, path_];
 515}
 516@end
 517
 518
 519#pragma mark Upper/Lower
 520
 521// Private helper to handle upper/lower conversions for UTF8
 522static void UpperLower8(sqlite3_context *context, int argc, sqlite3_value **argv) {
 523  // Args
 524  if ((argc < 1) || (sqlite3_value_type(argv[0]) == SQLITE_NULL)) {
 525    // COV_NF_START
 526    sqlite3_result_error(context, "LOWER/UPPER CF implementation got bad args",
 527                         -1);
 528    return;
 529    // COV_NF_END
 530  }
 531  const char *sqlText8 = (void *)sqlite3_value_text(argv[0]);
 532  if (!sqlText8) {
 533    // COV_NF_START
 534    sqlite3_result_error(context, "LOWER/UPPER CF implementation no input UTF8",
 535                         -1);
 536    return;
 537    // COV_NF_END
 538  }
 539
 540  // Get user data
 541  UpperLowerUserArgs *userArgs = sqlite3_user_data(context);
 542  if (!userArgs) {
 543    // COV_NF_START
 544    sqlite3_result_error(context, "LOWER/UPPER CF implementation no user args",
 545                         -1);
 546    return;
 547    // COV_NF_END
 548  }
 549
 550  _GTMDevAssert(userArgs->textRep == SQLITE_UTF8,
 551                @"Received non UTF8 encoding in UpperLower8");
 552
 553  // Worker string, must be mutable for case conversion so order our calls
 554  // to only copy once
 555  CFMutableStringRef workerString =
 556    CFStringCreateMutable(kCFAllocatorDefault, 0);
 557  GTMCFAutorelease(workerString);
 558  if (!workerString) {
 559    // COV_NF_START
 560    sqlite3_result_error(context,
 561                         "LOWER/UPPER CF implementation failed " \
 562                         "to allocate CFMutableStringRef", -1);
 563    return;
 564    // COV_NF_END
 565  }
 566  CFStringAppendCString(workerString, sqlText8, kCFStringEncodingUTF8);
 567
 568  // Perform the upper/lower
 569  if (userArgs->upperCase) {
 570    CFStringUppercase(workerString, gCurrentLocale);
 571  } else {
 572    CFStringLowercase(workerString, gCurrentLocale);
 573  }
 574
 575  // Convert to our canonical composition
 576  CFStringNormalize(workerString, kCFStringNormalizationFormC);
 577
 578  // Get the bytes we will return, using the more efficient accessor if we can
 579  const char *returnString = CFStringGetCStringPtr(workerString,
 580                                                   kCFStringEncodingUTF8);
 581  if (returnString) {
 582    // COV_NF_START
 583    // Direct buffer, but have SQLite copy it
 584    sqlite3_result_text(context, returnString, -1, SQLITE_TRANSIENT);
 585    // COV_NF_END
 586  } else {
 587    // Need to get a copy
 588    CFIndex workerLength = CFStringGetLength(workerString);
 589    CFIndex bufferSize =
 590      CFStringGetMaximumSizeForEncoding(workerLength,
 591                                        kCFStringEncodingUTF8);
 592    void *returnBuffer = malloc(bufferSize);
 593    if (!returnBuffer) {
 594      // COV_NF_START
 595      sqlite3_result_error(context,
 596                           "LOWER/UPPER failed to allocate return buffer", -1);
 597      return;
 598      // COV_NF_END
 599    }
 600    CFIndex convertedBytes = 0;
 601    CFIndex convertedChars = CFStringGetBytes(workerString,
 602                                              CFRangeMake(0, workerLength),
 603                                              kCFStringEncodingUTF8,
 604                                              0,
 605                                              false,
 606                                              returnBuffer,
 607                                              bufferSize,
 608                                              &convertedBytes);
 609    if (convertedChars != workerLength) {
 610      // COV_NF_START
 611      free(returnBuffer);
 612      sqlite3_result_error(context,
 613                           "CFStringGetBytes() failed to " \
 614                           "convert all characters", -1);
 615      // COV_NF_END
 616    } else {
 617      // Set the result, letting SQLite take ownership and using free() as
 618      // the destructor
 619      // We cast the 3rd parameter to an int because sqlite3 doesn't appear
 620      // to support 64-bit mode.
 621      sqlite3_result_text(context, returnBuffer, (int)convertedBytes, &free);
 622    }
 623  }
 624}
 625
 626// Private helper to handle upper/lower conversions for UTF16 variants
 627static void UpperLower16(sqlite3_context *context,
 628                         int argc, sqlite3_value **argv) {
 629  // Args
 630  if ((argc < 1) || (sqlite3_value_type(argv[0]) == SQLITE_NULL)) {
 631    // COV_NF_START
 632    sqlite3_result_error(context, "LOWER/UPPER CF implementation got bad args", -1);
 633    return;
 634    // COV_NF_END
 635  }
 636
 637  // For UTF16 variants we want our working string to be in native-endian
 638  // UTF16. This gives us the fewest number of copies (since SQLite converts
 639  // in-place). There is no advantage to breaking out the string construction
 640  // to use UTF16BE or UTF16LE because all that does is move the conversion
 641  // work into the CFString constructor, so just use simple code.
 642  int sqlText16ByteCount = sqlite3_value_bytes16(argv[0]);
 643  const UniChar *sqlText16 = (void *)sqlite3_value_text16(argv[0]);
 644  if (!sqlText16ByteCount || !sqlText16) {
 645    // COV_NF_START
 646    sqlite3_result_error(context,
 647                         "LOWER/UPPER CF implementation no input UTF16", -1);
 648    return;
 649    // COV_NF_END
 650  }
 651
 652  // Get user data
 653  UpperLowerUserArgs *userArgs = sqlite3_user_data(context);
 654  if (!userArgs) {
 655    // COV_NF_START
 656    sqlite3_result_error(context, "LOWER/UPPER CF implementation no user args", -1);
 657    return;
 658    // COV_NF_END
 659  }
 660  CFStringEncoding encoding = SqliteTextEncodingToCFStringEncoding(userArgs->textRep);
 661
 662  // Mutable worker for upper/lower
 663  CFMutableStringRef workerString = CFStringCreateMutable(kCFAllocatorDefault, 0);
 664  GTMCFAutorelease(workerString);
 665  if (!workerString) {
 666    // COV_NF_START
 667    sqlite3_result_error(context,
 668                         "LOWER/UPPER CF implementation failed " \
 669                         "to allocate CFMutableStringRef", -1);
 670    return;
 671    // COV_NF_END
 672  }
 673  CFStringAppendCharacters(workerString, sqlText16,
 674                           sqlText16ByteCount / sizeof(UniChar));
 675  // Perform the upper/lower
 676  if (userArgs->upperCase) {
 677    CFStringUppercase(workerString, gCurrentLocale);
 678  } else {
 679    CFStringLowercase(workerString, gCurrentLocale);
 680  }
 681  // Convert to our canonical composition
 682  CFStringNormalize(workerString, kCFStringNormalizationFormC);
 683
 684  // Length after normalization matters
 685  CFIndex workerLength = CFStringGetLength(workerString);
 686
 687  // If we can give direct byte access use it
 688  const UniChar *returnString = CFStringGetCharactersPtr(workerString);
 689  if (returnString) {
 690    // COV_NF_START details of whether cfstringgetcharactersptr returns
 691    // a buffer or NULL are internal; not something we can depend on.
 692    // When building for Leopard+, CFIndex is a 64-bit type, which is
 693    // why we cast it to an int when we call the sqlite api.
 694    _GTMDevAssert((workerLength * sizeof(UniChar) <= INT_MAX),
 695                  @"sqlite methods do not support buffers greater "
 696                  @"than 32 bit sizes");
 697    // Direct access to the internal buffer, hand it to sqlite for copy and
 698    // conversion
 699    sqlite3_result_text16(context, returnString,
 700                          (int)(workerLength * sizeof(UniChar)),
 701                          SQLITE_TRANSIENT);
 702    // COV_NF_END
 703  } else {
 704    // Need to get a copy since we can't get direct access
 705    CFIndex bufferSize = CFStringGetMaximumSizeForEncoding(workerLength,
 706                                                           encoding);
 707    void *returnBuffer = malloc(bufferSize);
 708    if (!returnBuffer) {
 709      // COV_NF_START
 710      sqlite3_result_error(context,
 711                           "LOWER/UPPER CF implementation failed " \
 712                           "to allocate return buffer", -1);
 713      return;
 714      // COV_NF_END
 715    }
 716    CFIndex convertedBytes = 0;
 717    CFIndex convertedChars = CFStringGetBytes(workerString,
 718                                              CFRangeMake(0, workerLength),
 719                                              encoding,
 720                                              0,
 721                                              false,
 722                                              returnBuffer,
 723                                              bufferSize,
 724                                              &convertedBytes);
 725    if (convertedChars != workerLength) {
 726      // COV_NF_START
 727      free(returnBuffer);
 728      sqlite3_result_error(context,
 729                           "LOWER/UPPER CF implementation CFStringGetBytes() " \
 730                           "failed to convert all characters", -1);
 731      // COV_NF_END
 732    } else {
 733      // When building for Leopard+, CFIndex is a 64-bit type, but
 734      // sqlite3's functions all take ints.  Assert the error for dev
 735      // builds and cast down.
 736      _GTMDevAssert((convertedBytes <= INT_MAX),
 737                    @"sqlite methods do not support buffers greater "
 738                    @"than 32-bit sizes");
 739      int convertedBytesForSQLite = (int)convertedBytes;
 740      // Set the result, letting SQLite take ownership and using free() as
 741      // the destructor. For output since we're copying out the bytes anyway
 742      // we might as well use the preferred encoding of the original call.
 743      _GTMDevAssert(userArgs->textRep == SQLITE_UTF16BE ||
 744                    userArgs->textRep == SQLITE_UTF16LE,
 745                    @"Received non UTF8 encoding in UpperLower8");
 746      switch (userArgs->textRep) {
 747      case SQLITE_UTF16BE:
 748        sqlite3_result_text16be(context, returnBuffer,
 749                                convertedBytesForSQLite, &free);
 750        break;
 751      case SQLITE_UTF16LE:
 752        sqlite3_result_text16le(context, returnBuffer,
 753                                convertedBytesForSQLite, &free);
 754        break;
 755      default:
 756        free(returnBuffer);
 757        // COV_NF_START no way to tell sqlite to not use utf8 or utf16?
 758        sqlite3_result_error(context,
 759                             "LOWER/UPPER CF implementation " \
 760                             "had unhandled encoding", -1);
 761        // COV_NF_END
 762      }
 763    }
 764  }
 765}
 766
 767
 768#pragma mark Collations
 769
 770static void CollateNeeded(void *userContext, sqlite3 *db, int textRep,
 771                          const char *name) {
 772  // Cast
 773  GTMSQLiteDatabase *gtmdb = (GTMSQLiteDatabase *)userContext;
 774  _GTMDevAssert(gtmdb, @"Invalid database parameter from sqlite");
 775
 776  // Create space for the collation args
 777  NSMutableData *collationArgsData =
 778    [NSMutableData dataWithLength:sizeof(CollateUserArgs)];
 779  CollateUserArgs *userArgs = (CollateUserArgs *)[collationArgsData bytes];
 780  bzero(userArgs, sizeof(CollateUserArgs));
 781  userArgs->textRep = textRep;
 782
 783  // Parse the name into the flags we need
 784  NSString *collationName =
 785    [[NSString stringWithUTF8String:name] lowercaseString];
 786  NSArray *collationComponents =
 787    [collationName componentsSeparatedByString:@"_"];
 788  NSString *collationFlag = nil;
 789  BOOL atLeastOneValidFlag = NO;
 790  GTM_FOREACH_OBJECT(collationFlag, collationComponents) {
 791    if ([collationFlag isEqualToString:@"reverse"]) {
 792      userArgs->reverse = YES;
 793      atLeastOneValidFlag = YES;
 794    } else if ([collationFlag isEqualToString:@"nocase"]) {
 795      userArgs->compareOptions |= kCFCompareCaseInsensitive;
 796      atLeastOneValidFlag = YES;
 797    } else if ([collationFlag isEqualToString:@"nonliteral"]) {
 798      userArgs->compareOptions |= kCFCompareNonliteral;
 799      atLeastOneValidFlag = YES;
 800    } else if ([collationFlag isEqualToString:@"localized"]) {
 801      userArgs->compareOptions |= kCFCompareLocalized;
 802      atLeastOneValidFlag = YES;
 803    } else if ([collationFlag isEqualToString:@"numeric"]) {
 804      userArgs->compareOptions |= kCFCompareNumerically;
 805      atLeastOneValidFlag = YES;
 806#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
 807    } else if ([collationFlag isEqualToString:@"nodiacritic"]) {
 808      userArgs->compareOptions |= kCFCompareDiacriticInsensitive;
 809      atLeastOneValidFlag = YES;
 810    } else if ([collationFlag isEqualToString:@"widthinsensitive"]) {
 811      userArgs->compareOptions |= kCFCompareWidthInsensitive;
 812      atLeastOneValidFlag = YES;
 813#else
 814    } else if (([collationFlag isEqualToString:@"nodiacritic"]) ||
 815              ([collationFlag isEqualToString:@"widthinsensitive"])) {
 816      _GTMDevLog(@"GTMSQLiteDatabase 10.5 collating not "
 817                 @"available on 10.4 or earlier");
 818#endif
 819    }
 820  }
 821
 822  // No valid tokens means nothing to do
 823  if (!atLeastOneValidFlag) return;
 824
 825  int err;
 826  // Add the collation
 827  switch (textRep) {
 828    case SQLITE_UTF8:
 829      err = sqlite3_create_collation([gtmdb sqlite3DB], name,
 830                                     textRep, userArgs, &Collate8);
 831      if (err != SQLITE_OK) return;
 832      break;
 833    case SQLITE_UTF16:
 834    case SQLITE_UTF16BE:
 835    case SQLITE_UTF16LE:
 836      err = sqlite3_create_collation([gtmdb sqlite3DB], name,
 837                                     textRep, userArgs, &Collate16);
 838      if (err != SQLITE_OK) return;
 839      break;
 840    default:
 841      return;
 842  }
 843
 844  // Have the db retain our collate function args
 845  [gtmdb collationArgumentRetain:collationArgsData];
 846}
 847
 848static int Collate8(void *userContext, int length1, const void *str1,
 849                    int length2, const void *str2) {
 850  // User args
 851  CollateUserArgs *userArgs = (CollateUserArgs *)userContext;
 852  _GTMDevAssert(userArgs, @"Invalid user arguments from sqlite");
 853
 854  // Sanity and zero-lengths
 855  if (!(str1 && str2) || (!length1 && !length2)) {
 856    return kCFCompareEqualTo;  // Best we can do and stable sort
 857  }
 858  if (!length1 && length2) {
 859    if (userArgs->reverse) {
 860      return kCFCompareGreaterThan;
 861    } else {
 862      return kCFCompareLessThan;
 863    }
 864  } else if (length1 && !length2) {
 865    if (userArgs->reverse) {
 866      return kCFCompareLessThan;
 867    } else {
 868      return kCFCompareGreaterThan;
 869    }
 870  }
 871
 872  // We have UTF8 strings with no terminating null, we want to compare
 873  // with as few copies as possible. Leopard introduced a no-copy string
 874  // creation function, we'll use it when we can but we want to stay compatible
 875  // with Tiger.
 876  CFStringRef string1 = NULL, string2 = NULL;
 877#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
 878  if (gCFStringCreateWithBytesNoCopySymbol) {
 879    string1 = gCFStringCreateWithBytesNoCopySymbol(kCFAllocatorDefault,
 880                                                   str1,
 881                                                   length1,
 882                                                   kCFStringEncodingUTF8,
 883                                                   false,
 884                                                   kCFAllocatorNull);
 885    string2 = gCFStringCreateWithBytesNoCopySymbol(kCFAllocatorDefault,
 886                                                   str2,
 887                                                   length2,
 888                                                   kCFStringEncodingUTF8,
 889                                                   false,
 890                                                   kCFAllocatorNull);
 891  } else {
 892    // Have to use the copy-based variants
 893    string1 = CFStringCreateWithBytes(kCFAllocatorDefault,
 894                                      str1,
 895                                      length1,
 896                                      kCFStringEncodingUTF8,
 897                                      false);
 898    string2 = CFStringCreateWithBytes(kCFAllocatorDefault,
 899                                      str2,
 900                                      length2,
 901                                      kCFStringEncodingUTF8,
 902                                      false);
 903  }
 904#else  // MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
 905  string1 = CFStringCreateWithBytesNoCopy(kCFAllocatorDefault,
 906                                          str1,
 907                                          length1,
 908                                          kCFStringEncodingUTF8,
 909                                          false,
 910                                          kCFAllocatorNull);
 911  string2 = CFStringCreateWithBytesNoCopy(kCFAllocatorDefault,
 912                                          str2,
 913                                          length2,
 914                                          kCFStringEncodingUTF8,
 915                                          false,
 916                                          kCFAllocatorNull);
 917#endif  // MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
 918  GTMCFAutorelease(string1);
 919  GTMCFAutorelease(string2);
 920  // Allocation failures can't really be sanely handled from a collator
 921  int sqliteResult;
 922  if (!(string1 && string2)) {
 923    // COV_NF_START
 924    sqliteResult = (int)kCFCompareEqualTo;
 925    // COV_NF_END
 926  } else {
 927    // Compare
 928    // We have to cast to int because SQLite takes functions that
 929    // return an int, but when compiling for Leopard+,
 930    // CFComparisonResult is a signed long, but on Tiger it's an int
 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  }
 942  return sqliteResult;
 943}
 944
 945static int Collate16(void *userContext, int length1, const void *str1,
 946                     int length2, const void *str2) {
 947  // User args
 948  CollateUserArgs *userArgs = (CollateUserArgs *)userContext;
 949  _GTMDevAssert(userArgs, @"Invalid user arguments from sqlite");
 950
 951  // Sanity and zero-lengths
 952  if (!(str1 && str2) || (!length1 && !length2)) {
 953    return kCFCompareEqualTo;  // Best we can do and stable sort
 954  }
 955  if (!length1 && length2) {
 956    if (userArgs->reverse) {
 957      return kCFCompareGreaterThan;
 958    } else {
 959      return kCFCompareLessThan;
 960    }
 961  } else if (length1 && !length2) {
 962    if (userArgs->reverse) {
 963      return kCFCompareLessThan;
 964    } else {
 965      return kCFCompareGreaterThan;
 966    }
 967  }
 968
 969  // Target encoding
 970  CFStringEncoding encoding =
 971    SqliteTextEncodingToCFStringEncoding(userArgs->textRep);
 972
 973  // We have UTF16 strings, we want to compare with as few copies as
 974  // possible.  Since endianness matters we want to use no-copy
 975  // variants where possible and copy (and endian convert) only when
 976  // we must.
 977  CFStringRef string1 = NULL, string2 = NULL;
 978  if ((userArgs->textRep == SQLITE_UTF16) ||
 979#if TARGET_RT_BIG_ENDIAN
 980      (userArgs->textRep == SQLITE_UTF16BE)
 981#else
 982      (userArgs->textRep == SQLITE_UTF16LE)
 983#endif
 984  ) {
 985    string1 = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault,
 986                                                 str1,
 987                                                 length1 / sizeof(UniChar),
 988                                                 kCFAllocatorNull);
 989    string2 = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault,
 990                                                 str2,
 991                                                 length2 / sizeof(UniChar),
 992                                                 kCFAllocatorNull);
 993  } else {
 994    // No point in using the "no copy" version of the call here. If the
 995    // bytes were in the native order we'd be in the other part of this
 996    // conditional, so we know we have to copy the string to endian convert
 997    // it.
 998    string1 = CFStringCreateWithBytes(kCFAllocatorDefault,
 999                                      str1,
1000                                      length1,
1001                                      encoding,
1002                                      false);
1003    string2 = CFStringCreateWithBytes(kCFAllocatorDefault,
1004                                      str2,
1005                                      length2,
1006                                      encoding,
1007                                      false);
1008  }
1009
1010  GTMCFAutorelease(string1);
1011  GTMCFAutorelease(string2);
1012  int sqliteResult;
1013  // Allocation failures can't really be sanely handled from a collator
1014  if (!(string1 && string2)) {
1015    // COV_NF_START
1016    sqliteResult = (int)kCFCompareEqualTo;
1017    // COV_NF_END
1018  } else {
1019    // Compare
1020    // We cast the return value to an int because CFComparisonResult
1021    // is a long in Leopard+ builds.  I have no idea why we need
1022    // 64-bits for a 3-value enum, but that's how it is...
1023    CFComparisonResult result;
1024    result = CFStringCompare(string1,
1025                             string2,
1026                             userArgs->compareOptions);
1027
1028    sqliteResult = (int)result; 
1029    //Reverse
1030    if (userArgs->reverse && sqliteResult) {
1031      sqliteResult = -sqliteResult;
1032    }
1033  }
1034
1035  return sqliteResult;
1036}
1037
1038
1039#pragma mark Like/Glob
1040
1041// Private helper to handle LIKE and GLOB with different encodings. This
1042// is essentially a reimplementation of patternCompare() in func.c of the
1043// SQLite sources.
1044static void LikeGlobCompare(sqlite3_context *context,
1045                            CFStringRef pattern,
1046                            CFStringRef targetString,
1047                            UniChar matchAll,
1048                            UniChar matchOne,
1049                            UniChar escape,
1050                            BOOL setSupport,
1051                            CFOptionFlags compareOptions) {
1052  // Setup for pattern walk
1053  CFIndex patternLength = CFStringGetLength(pattern);
1054  CFStringInlineBuffer patternBuffer;
1055  CFStringInitInlineBuffer(pattern, 
1056                           &patternBuffer, 
1057                           CFRangeMake(0, patternLength));
1058  UniChar patternChar;
1059  CFIndex patternIndex = 0;
1060  CFIndex targetStringLength = CFStringGetLength(targetString);
1061  CFIndex targetStringIndex = 0;
1062  BOOL isAnchored = YES;
1063
1064  size_t dataSize = patternLength * sizeof(UniChar);
1065  NSMutableData *tempData = [NSMutableData dataWithLength:dataSize];
1066  // Temp string buffer can be no larger than the whole pattern
1067  UniChar *findBuffer = [tempData mutableBytes];
1068  if (!findBuffer) {
1069    // COV_NF_START
1070    sqlite3_result_error(context,
1071                         "LIKE or GLOB CF implementation failed to " \
1072                         "allocate temporary buffer", -1);
1073    return;
1074    // COV_NF_END
1075  }
1076
1077  // We'll use a mutable string we can just reset as we wish
1078  CFMutableStringRef findString =
1079    CFStringCreateMutableWithExternalCharactersNoCopy(kCFAllocatorDefault,
1080                                                      NULL,
1081                                                      0,
1082                                                      0,
1083                                                      kCFAllocatorNull);
1084  GTMCFAutorelease(findString);
1085  if (!findString) {
1086    // COV_NF_START
1087    sqlite3_result_error(context,
1088                         "LIKE or GLOB CF implementation failed to " \
1089                         "allocate temporary CFString", -1);
1090    return;
1091    // COV_NF_END
1092  }
1093  // Walk the pattern
1094  while (patternIndex < patternLength) {
1095    patternChar = CFStringGetCharacterFromInlineBuffer(&patternBuffer,
1096                                                       patternIndex);
1097    // Match all character has no effect other than to unanchor the search
1098    if (patternChar == matchAll) {
1099      isAnchored = NO;
1100      patternIndex++;
1101      continue;
1102    }
1103    // Match one character pushes the string index forward by one composed
1104    // character
1105    if (patternChar == matchOne) {
1106      // If this single char match would walk us off the end of the string
1107      // we're already done, no match
1108      if (targetStringIndex >= targetStringLength) {
1109        sqlite3_result_int(context, 0);
1110        return;
1111      }
1112      // There's still room in the string, so move the string index forward one
1113      // composed character and go back around.
1114      CFRange nextCharRange =
1115        CFStringGetRangeOfComposedCharactersAtIndex(targetString,
1116                                                    targetStringIndex);
1117      targetStringIndex = nextCharRange.location + nextCharRange.length;
1118      patternIndex++;
1119      continue;
1120    }
1121    // Character set matches require the parsing of the character set
1122    if (setSupport && (patternChar == 0x5B)) {  // "["
1123      // A character set must match one character, if there's not at least one
1124      // character left in the string, don't bother
1125      if (targetStringIndex >= targetStringLength) {
1126        sqlite3_result_int(context, 0);
1127        return;
1128      }
1129      // There's at least one character, try to match the remainder of the
1130      // string using a CFCharacterSet
1131      CFMutableCharacterSetRef charSet 
1132        = CFCharacterSetCreateMutable(kCFAllocatorDefault);
1133      GTMCFAutorelease(charSet);
1134      if (!charSet) {
1135        // COV_NF_START
1136        sqlite3_result_error(context,
1137                             "LIKE or GLOB CF implementation failed to " \
1138                             "allocate temporary CFMutableCharacterSet", -1);
1139        return;
1140        // COV_NF_END
1141      }
1142
1143      BOOL invert = NO;
1144      // Walk one character forward
1145      patternIndex++;
1146      if (patternIndex >= patternLength) {
1147        // Oops, out of room
1148        sqlite3_result_error(context,
1149                             "LIKE or GLOB CF implementation found " \
1150                             "unclosed character set", -1);
1151        return;
1152      }
1153      // First character after pattern open is special-case
1154      patternChar = CFStringGetCharacterFromInlineBuffer(&patternBuffer,
1155                                                         patternIndex);
1156      if (patternChar == 0x5E) {  // "^"
1157        invert = YES;
1158        // Bump forward one character, can still be an unescaped "]" after
1159        // negation
1160        patternIndex++;
1161        if (patternIndex >= patternLength) {
1162          // Oops, out of room
1163          sqlite3_result_error(context,
1164                               "LIKE or GLOB CF implementation found " \
1165                               "unclosed character set after negation", -1);
1166          return;
1167        }
1168        patternChar = CFStringGetCharacterFromInlineBuffer(&patternBuffer,
1169                                                           patternIndex);
1170      }
1171      // First char in set or first char in negation can be a literal "]" not
1172      // considered a close
1173      if (patternChar == 0x5D) {  // "]"
1174        CFCharacterSetAddCharactersInRange(charSet,
1175                                           CFRangeMake(patternChar, 1));
1176        patternIndex++;
1177        if (patternIndex >= patternLength) {
1178          // Oops, out of room
1179          sqlite3_result_error(context,
1180                               "LIKE or GLOB CF implementation found " \
1181                               "unclosed character set after escaped ]", -1);
1182          return;
1183        }
1184        patternChar = CFStringGetCharacterFromInlineBuffer(&patternBuffer,
1185                                                           patternIndex);
1186      }
1187      while ((patternIndex < patternLength) &&
1188             patternChar &&
1189             (patternChar != 0x5D)) {  // "]"
1190        // Check for possible character range, for this to be true we
1191        // must have a hyphen at the next position and at least 3
1192        // characters of room (for hyphen, range end, and set
1193        // close). Hyphens at the end without a trailing range are
1194        // treated as literals
1195        if (((patternLength - patternIndex) >= 3) &&
1196            // Second char must be "-"
1197            (CFStringGetCharacterFromInlineBuffer(&patternBuffer,
1198                                                  // 0x2D is "-"
1199                                                  patternIndex + 1) == 0x2D) &&
1200            // And third char must be anything other than set close in
1201            // case the hyphen is at the end of the set and needs to
1202            // be treated as a literal
1203            (CFStringGetCharacterFromInlineBuffer(&patternBuffer,
1204                                                  patternIndex + 2)
1205             != 0x5D)) {  // "]"
1206          // Get the range close
1207          UniChar rangeClose =
1208            CFStringGetCharacterFromInlineBuffer(&patternBuffer,
1209                                                 patternIndex + 2);
1210          // Add the whole range
1211          int rangeLen = rangeClose - patternChar + 1;
1212          CFCharacterSetAddCharactersInRange(charSet,
1213                                             CFRangeMake(patternChar,
1214                                                         rangeLen));
1215          // Move past the end of the range
1216          patternIndex += 3;
1217        } else {
1218          // Single Raw character
1219          CFCharacterSetAddCharactersInRange(charSet,
1220                                             CFRangeMake(patternChar, 1));
1221          patternIndex++;
1222        }
1223        // Load next char for loop
1224        if (patternIndex < patternLength) {
1225          patternChar =
1226            CFStringGetCharacterFromInlineBuffer(&patternBuffer, patternIndex);
1227        } else {
1228          patternChar = 0;
1229        }
1230      }
1231      // Check for closure
1232      if (patternChar != 0x5D) {  // "]"
1233        sqlite3_result_error(context,
1234                             "LIKE or GLOB CF implementation found " \
1235                             "unclosed character set", -1);
1236        return;
1237      } else {
1238        // Increment past the end of the set
1239        patternIndex++;
1240      }
1241      // Invert the set if needed
1242      if (invert) CFCharacterSetInvert(charSet);
1243      // Do the search
1244      CFOptionFlags findOptions = 0;
1245      if (isAnchored) findOptions |= kCFCompareAnchored;
1246      CFRange foundRange;
1247      unsigned long rangeLen = targetStringLength - targetStringIndex;
1248      BOOL found = CFStringFindCharacterFromSet(targetString,
1249                                                charSet,
1250                                                CFRangeMake(targetStringIndex,
1251                                                            rangeLen),
1252                                                findOptions,
1253                                                &foundRange);
1254      // If no match then the whole pattern fails
1255      if (!found) {
1256        sqlite3_result_int(context, 0);
1257        return;
1258      }
1259      // If we did match then we need to push the string index to the
1260      // character past the end of the match and then go back around
1261      // the loop.
1262      targetStringIndex = foundRange.location + foundRange.length;
1263      // At this point patternIndex is either at the end of the
1264      // string, or at the next special character which will be picked
1265      // up and handled at the top of the loop. We do, however, need
1266      // to reset the anchor status
1267      isAnchored = YES;
1268      // End of character sets, back around
1269      continue;
1270    }
1271    // Otherwise the pattern character is a normal or escaped
1272    // character we should consume and match with normal string
1273    // matching
1274    CFIndex findBufferIndex = 0;
1275    while ((patternIndex < patternLength) && patternChar &&
1276           !((patternChar == matchAll) || (patternChar == matchOne) ||
1277           (setSupport && (patternChar == 0x5B)))) {  // "["
1278      if (patternChar == escape) {
1279        // No matter what the character follows the escape copy it to the
1280        // buffer
1281        patternIndex++;
1282        if (patternIndex >= patternLength) {
1283          // COV_NF_START
1284          // Oops, escape came at end of pattern, that's an error
1285          sqlite3_result_error(context,
1286                               "LIKE or GLOB CF implementation found " \
1287                               "escape character at end of pattern", -1);
1288          return;
1289          // COV_NF_END
1290        }
1291        patternChar = CFStringGetCharacterFromInlineBuffer(&patternBuffer,
1292                                                           patternIndex);
1293      }
1294      // At this point the patternChar is either the escaped character or the
1295      // original normal character
1296      findBuffer[findBufferIndex++] = patternChar;
1297      // Set up for next loop
1298      patternIndex++;
1299      if (patternIndex < patternLength) {
1300        patternChar = CFStringGetCharacterFromInlineBuffer(&patternBuffer,
1301                                                           patternIndex);
1302      } else {
1303        patternChar = 0;
1304      }
1305    }
1306    // On loop exit we have a string ready for comparision, if that
1307    // string is too long then it can't be a match.
1308    if (findBufferIndex > (targetStringLength - targetStringIndex)) {
1309      sqlite3_result_int(context, 0);
1310      return;
1311    }
1312
1313    // We actually need to do a comparison
1314    CFOptionFlags findOptions = compareOptions;
1315    if (isAnchored) findOptions |= kCFCompareAnchored;
1316    CFStringSetExternalCharactersNoCopy(findString,
1317                                        findBuffer,
1318                                        findBufferIndex,
1319                                        findBufferIndex);
1320    CFRange foundRange;
1321    unsigned long rangeLen = targetStringLength - targetStringIndex;
1322    BOOL found = CFStringFindWithOptions(targetString,
1323                                         findString,
1324                                         CFRangeMake(targetStringIndex,
1325                                                     rangeLen),
1326                                         findOptions,
1327                                         &foundRange);
1328    // If no match then the whole pattern fails
1329    if (!found) {
1330      sqlite3_result_int(context, 0);
1331      return;
1332    }
1333    // If we did match then we need to push the string index to the
1334    // character past the end of the match and then go back around the
1335    // loop.
1336    targetStringIndex = foundRange.location + foundRange.length;
1337    // At this point patternIndex is either

Large files files are truncated, but you can click here to view the full file