2004-04-26 15:13:27 +00:00
|
|
|
|
/* -*-objc-*- */
|
|
|
|
|
|
|
|
|
|
/** Implementation of SQLClient for GNUStep
|
|
|
|
|
Copyright (C) 2004 Free Software Foundation, Inc.
|
|
|
|
|
|
|
|
|
|
Written by: Richard Frith-Macdonald <rfm@gnu.org>
|
|
|
|
|
Date: April 2004
|
|
|
|
|
|
|
|
|
|
This file is part of the SQLClient Library.
|
|
|
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or
|
2007-09-14 13:02:05 +00:00
|
|
|
|
modify it under the terms of the GNU Lesser General Public
|
2004-04-26 15:13:27 +00:00
|
|
|
|
License as published by the Free Software Foundation; either
|
2007-09-14 13:02:05 +00:00
|
|
|
|
version 3 of the License, or (at your option) any later version.
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
2007-09-14 13:02:05 +00:00
|
|
|
|
Lesser General Public License for more details.
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
2007-09-14 13:02:05 +00:00
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
2004-04-26 15:13:27 +00:00
|
|
|
|
License along with this library; if not, write to the Free
|
|
|
|
|
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA.
|
|
|
|
|
|
|
|
|
|
$Date$ $Revision$
|
|
|
|
|
*/
|
|
|
|
|
|
2007-04-01 08:03:21 +00:00
|
|
|
|
#import <Foundation/NSArray.h>
|
|
|
|
|
#import <Foundation/NSAutoreleasePool.h>
|
|
|
|
|
#import <Foundation/NSBundle.h>
|
|
|
|
|
#import <Foundation/NSCalendarDate.h>
|
|
|
|
|
#import <Foundation/NSCharacterSet.h>
|
|
|
|
|
#import <Foundation/NSData.h>
|
|
|
|
|
#import <Foundation/NSDate.h>
|
|
|
|
|
#import <Foundation/NSDebug.h>
|
|
|
|
|
#import <Foundation/NSDictionary.h>
|
|
|
|
|
#import <Foundation/NSEnumerator.h>
|
|
|
|
|
#import <Foundation/NSException.h>
|
|
|
|
|
#import <Foundation/NSKeyValueCoding.h>
|
|
|
|
|
#import <Foundation/NSLock.h>
|
2014-06-19 21:26:25 +00:00
|
|
|
|
#import <Foundation/NSHashTable.h>
|
2007-04-01 08:03:21 +00:00
|
|
|
|
#import <Foundation/NSMapTable.h>
|
|
|
|
|
#import <Foundation/NSNotification.h>
|
|
|
|
|
#import <Foundation/NSNull.h>
|
2017-03-06 17:23:37 +00:00
|
|
|
|
#import <Foundation/NSObjCRuntime.h>
|
2007-04-01 08:03:21 +00:00
|
|
|
|
#import <Foundation/NSPathUtilities.h>
|
|
|
|
|
#import <Foundation/NSProcessInfo.h>
|
2008-02-21 16:23:23 +00:00
|
|
|
|
#import <Foundation/NSRunLoop.h>
|
2007-04-01 08:03:21 +00:00
|
|
|
|
#import <Foundation/NSSet.h>
|
|
|
|
|
#import <Foundation/NSString.h>
|
2008-02-21 16:23:23 +00:00
|
|
|
|
#import <Foundation/NSThread.h>
|
2007-04-01 08:03:21 +00:00
|
|
|
|
#import <Foundation/NSTimer.h>
|
|
|
|
|
#import <Foundation/NSUserDefaults.h>
|
|
|
|
|
#import <Foundation/NSValue.h>
|
|
|
|
|
|
|
|
|
|
#import <Performance/GSCache.h>
|
|
|
|
|
#import <Performance/GSTicker.h>
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
2017-06-30 10:39:24 +00:00
|
|
|
|
#define SQLCLIENT_PRIVATE @public
|
|
|
|
|
#define SQLCLIENT_COMPILE_TIME_QUOTE_CHECK 1
|
2009-11-18 11:11:29 +00:00
|
|
|
|
|
|
|
|
|
#include <memory.h>
|
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
#include "SQLClient.h"
|
|
|
|
|
|
2007-04-01 08:03:21 +00:00
|
|
|
|
#if defined(GNUSTEP_BASE_LIBRARY)
|
|
|
|
|
#define SUBCLASS_RESPONSIBILITY [self subclassResponsibility: _cmd];
|
|
|
|
|
#else
|
|
|
|
|
#define SUBCLASS_RESPONSIBILITY
|
|
|
|
|
#endif
|
|
|
|
|
|
2007-08-01 15:40:12 +00:00
|
|
|
|
NSString * const SQLClientDidConnectNotification
|
2007-07-09 17:08:22 +00:00
|
|
|
|
= @"SQLClientDidConnectNotification";
|
|
|
|
|
|
2007-08-01 15:40:12 +00:00
|
|
|
|
NSString * const SQLClientDidDisconnectNotification
|
2007-07-09 17:08:22 +00:00
|
|
|
|
= @"SQLClientDidDisconnectNotification";
|
|
|
|
|
|
2015-04-28 11:47:23 +00:00
|
|
|
|
static unsigned int classDebugging = 0;
|
|
|
|
|
static NSTimeInterval classDuration = -1;
|
|
|
|
|
|
2006-08-26 12:49:59 +00:00
|
|
|
|
static NSNull *null = nil;
|
2008-02-21 16:23:23 +00:00
|
|
|
|
static NSArray *queryModes = nil;
|
|
|
|
|
static NSThread *mainThread = nil;
|
2014-06-20 05:15:24 +00:00
|
|
|
|
static Class NSStringClass = Nil;
|
|
|
|
|
static Class NSArrayClass = Nil;
|
|
|
|
|
static Class NSDateClass = Nil;
|
|
|
|
|
static Class NSSetClass = Nil;
|
|
|
|
|
static Class SQLClientClass = Nil;
|
2017-06-30 10:46:17 +00:00
|
|
|
|
static Class LitProxyClass = Nil;
|
2017-03-06 17:23:37 +00:00
|
|
|
|
static Class LitStringClass = Nil;
|
2020-02-27 14:55:59 +00:00
|
|
|
|
static Class TinyStringClass = Nil;
|
2017-03-06 17:23:37 +00:00
|
|
|
|
static Class SQLStringClass = Nil;
|
2017-06-29 08:32:09 +00:00
|
|
|
|
static unsigned SQLStringSize = 0;
|
2017-03-06 17:23:37 +00:00
|
|
|
|
|
|
|
|
|
static BOOL autoquote = NO;
|
2017-08-31 10:05:08 +00:00
|
|
|
|
static BOOL autoquoteWarning = NO;
|
2017-03-06 17:23:37 +00:00
|
|
|
|
|
2020-02-14 17:04:24 +00:00
|
|
|
|
static BOOL
|
|
|
|
|
isByteCoding(NSStringEncoding encoding)
|
|
|
|
|
{
|
|
|
|
|
if (NSASCIIStringEncoding == encoding
|
|
|
|
|
|| NSNEXTSTEPStringEncoding == encoding
|
|
|
|
|
|| NSISOLatin1StringEncoding == encoding
|
|
|
|
|
|| NSISOLatin2StringEncoding == encoding
|
|
|
|
|
|| NSISOThaiStringEncoding == encoding
|
|
|
|
|
|| NSISOLatin9StringEncoding == encoding)
|
|
|
|
|
{
|
|
|
|
|
return YES;
|
|
|
|
|
}
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Determine the length of the UTF-8 string as a unicode (UTF-16) string.
|
|
|
|
|
* sets the ascii flag according to the content found.
|
|
|
|
|
*/
|
|
|
|
|
static NSUInteger
|
|
|
|
|
lengthUTF8(const uint8_t *p, unsigned l, BOOL *ascii, BOOL *latin1)
|
|
|
|
|
{
|
|
|
|
|
const uint8_t *e = p + l;
|
|
|
|
|
BOOL a = YES;
|
|
|
|
|
BOOL l1 = YES;
|
|
|
|
|
|
|
|
|
|
l = 0;
|
|
|
|
|
while (p < e)
|
|
|
|
|
{
|
|
|
|
|
uint8_t c = *p;
|
|
|
|
|
uint32_t u = c;
|
|
|
|
|
|
|
|
|
|
if (c > 0x7f)
|
|
|
|
|
{
|
|
|
|
|
int i, sle = 0;
|
|
|
|
|
|
|
|
|
|
a = NO;
|
|
|
|
|
/* calculated the expected sequence length */
|
|
|
|
|
while (c & 0x80)
|
|
|
|
|
{
|
|
|
|
|
c = c << 1;
|
|
|
|
|
sle++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* legal ? */
|
|
|
|
|
if ((sle < 2) || (sle > 6))
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"Bad sequence length in constant string"];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (p + sle > e)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"Short data in constant string"];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* get the codepoint */
|
|
|
|
|
for (i = 1; i < sle; i++)
|
|
|
|
|
{
|
|
|
|
|
if (p[i] < 0x80 || p[i] >= 0xc0)
|
|
|
|
|
break;
|
|
|
|
|
u = (u << 6) | (p[i] & 0x3f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (i < sle)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"Codepoint out of range in constant string"];
|
|
|
|
|
}
|
|
|
|
|
u = u & ~(0xffffffff << ((5 * sle) + 1));
|
|
|
|
|
p += sle;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* We check for invalid codepoints here.
|
|
|
|
|
*/
|
|
|
|
|
if (u > 0x10ffff)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"Codepoint invalid in constant string"];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((u >= 0xd800) && (u <= 0xdfff))
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"Bad surrogate pair in constant string"];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
p++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Add codepoint as either a single unichar for BMP
|
|
|
|
|
* or as a pair of surrogates for codepoints over 16 bits.
|
|
|
|
|
*/
|
|
|
|
|
if (u < 0x10000)
|
|
|
|
|
{
|
|
|
|
|
l++;
|
|
|
|
|
if (u > 255)
|
|
|
|
|
{
|
|
|
|
|
l1 = NO;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
l += 2;
|
|
|
|
|
l1 = NO;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (0 != ascii)
|
|
|
|
|
{
|
|
|
|
|
*ascii = a;
|
|
|
|
|
}
|
|
|
|
|
if (0 != latin1)
|
|
|
|
|
{
|
|
|
|
|
*latin1 = l1;
|
|
|
|
|
}
|
|
|
|
|
return l;
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-06 17:23:37 +00:00
|
|
|
|
/* This is the layout of the instance variables of the constant string class
|
|
|
|
|
* produced by the compiler.
|
|
|
|
|
* The pointer give us the start of a UTF8 string, so we can create our own
|
|
|
|
|
* subclass using all the methods of the original class as long as we set
|
|
|
|
|
* that pointer to a buffer of UTF8 data stored in the object after the
|
|
|
|
|
* instance variables.
|
|
|
|
|
*/
|
2017-06-30 10:39:24 +00:00
|
|
|
|
@interface SQLString: SQLLiteral
|
2017-03-06 17:23:37 +00:00
|
|
|
|
{
|
|
|
|
|
@public
|
2020-02-14 17:04:24 +00:00
|
|
|
|
NSUInteger hash;
|
|
|
|
|
BOOL hasHash;
|
|
|
|
|
BOOL ascii;
|
|
|
|
|
BOOL latin1;
|
|
|
|
|
const uint8_t *utf8Bytes;
|
|
|
|
|
NSUInteger byteLen;
|
|
|
|
|
NSUInteger charLen;
|
2017-03-06 17:23:37 +00:00
|
|
|
|
}
|
|
|
|
|
@end
|
|
|
|
|
|
2017-07-03 14:39:28 +00:00
|
|
|
|
@interface SQLLiteralProxy: SQLLiteral
|
2017-06-30 10:39:24 +00:00
|
|
|
|
{
|
|
|
|
|
@public
|
|
|
|
|
NSString *content;
|
|
|
|
|
}
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
BOOL
|
|
|
|
|
SQLClientIsLiteral(NSString *aString)
|
|
|
|
|
{
|
|
|
|
|
if (nil != aString)
|
|
|
|
|
{
|
|
|
|
|
Class c = object_getClass(aString);
|
|
|
|
|
|
2020-02-27 14:55:59 +00:00
|
|
|
|
if (c == LitStringClass || c == TinyStringClass
|
|
|
|
|
|| c == SQLStringClass || c == LitProxyClass)
|
2017-06-30 10:39:24 +00:00
|
|
|
|
{
|
|
|
|
|
return YES;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SQLLiteral *
|
2017-06-30 12:43:45 +00:00
|
|
|
|
SQLClientNewLiteral(const char *bytes, unsigned count)
|
2017-03-06 17:23:37 +00:00
|
|
|
|
{
|
|
|
|
|
SQLString *s;
|
2020-02-14 17:04:24 +00:00
|
|
|
|
uint8_t *p;
|
2017-03-06 17:23:37 +00:00
|
|
|
|
|
2017-06-30 12:43:45 +00:00
|
|
|
|
s = NSAllocateObject(SQLStringClass, count+1, NSDefaultMallocZone());
|
2020-02-14 17:04:24 +00:00
|
|
|
|
s->utf8Bytes = p = ((uint8_t*)(void*)s) + SQLStringSize;
|
|
|
|
|
s->byteLen = count;
|
|
|
|
|
memcpy(p, bytes, count);
|
|
|
|
|
p[count] = '\0';
|
|
|
|
|
s->charLen = lengthUTF8(s->utf8Bytes, s->byteLen, &s->ascii, &s->latin1);
|
2017-03-06 17:23:37 +00:00
|
|
|
|
return s;
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-30 10:39:24 +00:00
|
|
|
|
SQLLiteral *
|
|
|
|
|
SQLClientCopyLiteral(NSString *aString)
|
2017-06-29 10:30:33 +00:00
|
|
|
|
{
|
|
|
|
|
if (nil != aString)
|
|
|
|
|
{
|
|
|
|
|
Class c = object_getClass(aString);
|
|
|
|
|
|
2017-06-30 10:46:17 +00:00
|
|
|
|
if (c == LitProxyClass)
|
2017-06-29 11:40:38 +00:00
|
|
|
|
{
|
2017-06-30 10:46:17 +00:00
|
|
|
|
aString = ((SQLLiteralProxy*)aString)->content;
|
2017-06-29 11:40:38 +00:00
|
|
|
|
c = object_getClass(aString);
|
|
|
|
|
}
|
2020-02-27 14:55:59 +00:00
|
|
|
|
if (c != LitStringClass && c != TinyStringClass && c != SQLStringClass)
|
2017-06-29 10:30:33 +00:00
|
|
|
|
{
|
|
|
|
|
const char *p = [aString UTF8String];
|
|
|
|
|
int l = strlen(p);
|
|
|
|
|
|
|
|
|
|
aString = SQLClientNewLiteral(p, l);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
aString = [aString copy];
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-06-30 10:39:24 +00:00
|
|
|
|
return (SQLLiteral*)aString;
|
2017-06-29 10:30:33 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-06-30 10:39:24 +00:00
|
|
|
|
SQLLiteral *
|
|
|
|
|
SQLClientMakeLiteral(NSString *aString)
|
2017-03-06 17:23:37 +00:00
|
|
|
|
{
|
|
|
|
|
if (nil != aString)
|
|
|
|
|
{
|
|
|
|
|
Class c = object_getClass(aString);
|
|
|
|
|
|
2017-06-30 10:46:17 +00:00
|
|
|
|
if (c == LitProxyClass)
|
2017-06-29 11:40:38 +00:00
|
|
|
|
{
|
2017-06-30 10:46:17 +00:00
|
|
|
|
aString = ((SQLLiteralProxy*)aString)->content;
|
2017-06-29 11:40:38 +00:00
|
|
|
|
c = object_getClass(aString);
|
|
|
|
|
}
|
2020-02-27 14:55:59 +00:00
|
|
|
|
if (c != LitStringClass && c != TinyStringClass && c != SQLStringClass)
|
2017-03-06 17:23:37 +00:00
|
|
|
|
{
|
|
|
|
|
const char *p = [aString UTF8String];
|
|
|
|
|
int l = strlen(p);
|
2017-06-29 10:30:33 +00:00
|
|
|
|
NSString *s = SQLClientNewLiteral(p, l);
|
2017-03-06 17:23:37 +00:00
|
|
|
|
|
|
|
|
|
aString = [s autorelease];
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-06-30 10:39:24 +00:00
|
|
|
|
return (SQLLiteral*)aString;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SQLLiteral *
|
|
|
|
|
SQLClientProxyLiteral(NSString *aString)
|
|
|
|
|
{
|
|
|
|
|
if (nil == aString)
|
|
|
|
|
{
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
else if (SQLClientIsLiteral(aString))
|
|
|
|
|
{
|
|
|
|
|
return (SQLLiteral*)[[aString retain] autorelease];
|
|
|
|
|
}
|
|
|
|
|
else if (NO == [aString isKindOfClass: NSStringClass])
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"Attempt to cast non-string to SQLLiteral"];
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2017-06-30 10:46:17 +00:00
|
|
|
|
SQLLiteralProxy *l;
|
2017-06-30 10:39:24 +00:00
|
|
|
|
|
2017-06-30 10:46:17 +00:00
|
|
|
|
l = (SQLLiteralProxy*)
|
|
|
|
|
NSAllocateObject(LitProxyClass, 0, NSDefaultMallocZone());
|
2017-06-30 10:39:24 +00:00
|
|
|
|
l->content = [aString retain];
|
2017-07-03 14:13:54 +00:00
|
|
|
|
return (SQLLiteral*)[l autorelease];
|
2017-06-30 10:39:24 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NSString *
|
2017-06-30 10:55:33 +00:00
|
|
|
|
SQLClientUnProxyLiteral(id aString)
|
2017-06-30 10:39:24 +00:00
|
|
|
|
{
|
2019-02-28 16:51:49 +00:00
|
|
|
|
if (nil != aString)
|
2017-06-30 10:39:24 +00:00
|
|
|
|
{
|
2019-02-28 16:51:49 +00:00
|
|
|
|
Class c = object_getClass(aString);
|
|
|
|
|
|
|
|
|
|
if (c == LitProxyClass)
|
|
|
|
|
{
|
|
|
|
|
aString = ((SQLLiteralProxy*)aString)->content;
|
|
|
|
|
}
|
2020-02-27 14:55:59 +00:00
|
|
|
|
else if (c != LitStringClass
|
|
|
|
|
&& c != TinyStringClass
|
|
|
|
|
&& c != SQLStringClass)
|
2019-02-28 16:51:49 +00:00
|
|
|
|
{
|
|
|
|
|
aString = [aString description];
|
|
|
|
|
if (YES == autoquoteWarning)
|
|
|
|
|
{
|
2020-02-27 14:55:59 +00:00
|
|
|
|
NSLog(@"SQLClient expected SQLLiteral type for %@ (%@)",
|
|
|
|
|
aString, NSStringFromClass(c));
|
2019-02-28 16:51:49 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-06-30 10:39:24 +00:00
|
|
|
|
}
|
|
|
|
|
return (NSString*)aString;
|
2017-03-06 17:23:37 +00:00
|
|
|
|
}
|
2006-08-26 12:49:59 +00:00
|
|
|
|
|
2017-07-10 09:04:32 +00:00
|
|
|
|
static SQLLiteral *
|
|
|
|
|
quoteBigInteger(int64_t i)
|
|
|
|
|
{
|
|
|
|
|
char buf[32];
|
|
|
|
|
unsigned len;
|
|
|
|
|
SQLLiteral *s;
|
|
|
|
|
|
|
|
|
|
len = sprintf(buf, "%"PRId64, i);
|
|
|
|
|
s = SQLClientNewLiteral(buf, len);
|
|
|
|
|
return [s autorelease];
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-28 12:57:08 +00:00
|
|
|
|
@interface SQLClientPool (Swallow)
|
2015-11-03 21:56:09 +00:00
|
|
|
|
- (BOOL) _swallowClient: (SQLClient*)client explicit: (BOOL)swallowed;
|
2015-05-28 12:57:08 +00:00
|
|
|
|
@end
|
2015-06-26 12:06:13 +00:00
|
|
|
|
@interface SQLTransaction (Creation)
|
2015-06-27 15:28:03 +00:00
|
|
|
|
+ (SQLTransaction*) _transactionUsing: (id)clientOrPool
|
|
|
|
|
batch: (BOOL)isBatched
|
|
|
|
|
stop: (BOOL)stopOnFailure;
|
2015-06-26 12:06:13 +00:00
|
|
|
|
@end
|
2015-05-28 12:57:08 +00:00
|
|
|
|
|
2017-06-30 10:39:24 +00:00
|
|
|
|
@implementation SQLLiteral
|
|
|
|
|
+ (id) allocWithZone: (NSZone*)z
|
2017-06-29 11:40:38 +00:00
|
|
|
|
{
|
2017-06-30 10:39:24 +00:00
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"Illegal attempt to allocate instance of SQLLiteral"];
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
@end
|
2017-06-29 11:40:38 +00:00
|
|
|
|
|
2017-06-30 10:46:17 +00:00
|
|
|
|
@implementation SQLLiteralProxy
|
2017-06-30 10:39:24 +00:00
|
|
|
|
- (unichar) characterAtIndex: (NSUInteger)i
|
|
|
|
|
{
|
|
|
|
|
return [content characterAtIndex: i];
|
2017-06-29 11:40:38 +00:00
|
|
|
|
}
|
|
|
|
|
- (void) dealloc
|
|
|
|
|
{
|
|
|
|
|
[content release];
|
|
|
|
|
[super dealloc];
|
|
|
|
|
}
|
2017-06-30 12:43:45 +00:00
|
|
|
|
- (void) getCharacters: (unichar*)buffer
|
|
|
|
|
{
|
|
|
|
|
[content getCharacters: buffer];
|
|
|
|
|
}
|
|
|
|
|
- (void) getCharacters: (unichar*)buffer
|
|
|
|
|
range: (NSRange)aRange
|
|
|
|
|
{
|
|
|
|
|
return [content getCharacters: buffer range: aRange];
|
|
|
|
|
}
|
|
|
|
|
- (NSUInteger) hash
|
|
|
|
|
{
|
|
|
|
|
return [content hash];
|
|
|
|
|
}
|
|
|
|
|
- (BOOL) isEqual: (id)other
|
|
|
|
|
{
|
|
|
|
|
return [content isEqual: other];
|
|
|
|
|
}
|
2017-06-30 10:39:24 +00:00
|
|
|
|
- (NSUInteger) length
|
|
|
|
|
{
|
|
|
|
|
return [content length];
|
|
|
|
|
}
|
2017-06-30 12:43:45 +00:00
|
|
|
|
- (const char *) UTF8String
|
|
|
|
|
{
|
|
|
|
|
return [content UTF8String];
|
|
|
|
|
}
|
2017-06-29 11:40:38 +00:00
|
|
|
|
@end
|
|
|
|
|
|
2017-03-06 17:23:37 +00:00
|
|
|
|
@implementation SQLRecordKeys
|
2015-04-28 11:47:23 +00:00
|
|
|
|
|
|
|
|
|
- (NSUInteger) count
|
|
|
|
|
{
|
|
|
|
|
return count;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) dealloc
|
|
|
|
|
{
|
|
|
|
|
if (nil != order) [order release];
|
|
|
|
|
if (nil != map) [map release];
|
|
|
|
|
if (nil != low) [low release];
|
|
|
|
|
[super dealloc];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSUInteger) indexForKey: (NSString*)key
|
|
|
|
|
{
|
|
|
|
|
NSUInteger c;
|
|
|
|
|
|
|
|
|
|
c = (NSUInteger)NSMapGet(map, key);
|
|
|
|
|
if (c > 0)
|
|
|
|
|
{
|
|
|
|
|
return c - 1;
|
|
|
|
|
}
|
|
|
|
|
key = [key lowercaseString];
|
|
|
|
|
c = (NSUInteger)NSMapGet(low, key);
|
|
|
|
|
if (c > 0)
|
|
|
|
|
{
|
|
|
|
|
if (classDebugging > 0)
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"[SQLRecordKeys-indexForKey:] lowercase '%@'", key);
|
|
|
|
|
}
|
|
|
|
|
return c - 1;
|
|
|
|
|
}
|
|
|
|
|
return NSNotFound;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id) initWithKeys: (NSString**)keys count: (NSUInteger)c
|
|
|
|
|
{
|
|
|
|
|
if (nil != (self = [super init]))
|
|
|
|
|
{
|
|
|
|
|
count = c;
|
|
|
|
|
order = [[NSArray alloc] initWithObjects: keys count: c];
|
|
|
|
|
map = NSCreateMapTable(NSObjectMapKeyCallBacks,
|
|
|
|
|
NSIntegerMapValueCallBacks, count);
|
|
|
|
|
low = NSCreateMapTable(NSObjectMapKeyCallBacks,
|
|
|
|
|
NSIntegerMapValueCallBacks, count);
|
|
|
|
|
for (c = 1; c <= count; c++)
|
|
|
|
|
{
|
|
|
|
|
NSString *k = keys[c-1];
|
|
|
|
|
|
|
|
|
|
NSMapInsert(map, (void*)k, (void*)c);
|
|
|
|
|
k = [k lowercaseString];
|
|
|
|
|
NSMapInsert(low, (void*)k, (void*)c);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSArray*) order
|
|
|
|
|
{
|
|
|
|
|
return order;
|
|
|
|
|
}
|
2015-07-16 10:13:12 +00:00
|
|
|
|
|
|
|
|
|
- (NSUInteger) sizeInBytesExcluding: (NSHashTable*)exclude
|
|
|
|
|
{
|
|
|
|
|
NSUInteger size = [super sizeInBytesExcluding: exclude];
|
|
|
|
|
|
|
|
|
|
if (size > 0)
|
|
|
|
|
{
|
|
|
|
|
if (0 == bytes)
|
|
|
|
|
{
|
|
|
|
|
bytes = size;
|
|
|
|
|
bytes += [order sizeInBytesExcluding: exclude];
|
|
|
|
|
bytes += [map sizeInBytesExcluding: exclude];
|
|
|
|
|
bytes += [low sizeInBytesExcluding: exclude];
|
|
|
|
|
}
|
|
|
|
|
size = bytes;
|
|
|
|
|
}
|
|
|
|
|
return size;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-28 11:47:23 +00:00
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
|
2009-11-18 11:11:29 +00:00
|
|
|
|
@interface _ConcreteSQLRecord : SQLRecord
|
|
|
|
|
{
|
2015-04-28 11:47:23 +00:00
|
|
|
|
SQLRecordKeys *keys;
|
|
|
|
|
NSUInteger count; // Must be last
|
2009-11-18 11:11:29 +00:00
|
|
|
|
}
|
|
|
|
|
@end
|
|
|
|
|
|
2008-02-21 16:23:23 +00:00
|
|
|
|
@interface CacheQuery : NSObject
|
|
|
|
|
{
|
|
|
|
|
@public
|
2017-06-30 10:39:24 +00:00
|
|
|
|
SQLLiteral *query;
|
2008-03-03 14:10:54 +00:00
|
|
|
|
id recordType;
|
|
|
|
|
id listType;
|
2008-02-21 16:23:23 +00:00
|
|
|
|
unsigned lifetime;
|
|
|
|
|
}
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation CacheQuery
|
|
|
|
|
- (void) dealloc
|
|
|
|
|
{
|
2009-11-18 11:11:29 +00:00
|
|
|
|
[query release];
|
2008-02-21 16:23:23 +00:00
|
|
|
|
[super dealloc];
|
|
|
|
|
}
|
|
|
|
|
@end
|
|
|
|
|
|
2008-03-03 14:10:54 +00:00
|
|
|
|
static Class aClass = 0;
|
2007-03-08 17:12:55 +00:00
|
|
|
|
static Class rClass = 0;
|
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
@implementation SQLRecord
|
|
|
|
|
+ (id) allocWithZone: (NSZone*)aZone
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"Illegal attempt to allocate an SQLRecord");
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
|
2004-08-22 09:34:18 +00:00
|
|
|
|
+ (void) initialize
|
|
|
|
|
{
|
2005-11-14 20:37:33 +00:00
|
|
|
|
GSTickerTimeNow();
|
2016-04-27 07:55:26 +00:00
|
|
|
|
if (nil == null)
|
2004-08-22 09:34:18 +00:00
|
|
|
|
{
|
|
|
|
|
null = [NSNull new];
|
2016-05-06 12:59:19 +00:00
|
|
|
|
}
|
|
|
|
|
if (Nil == aClass)
|
|
|
|
|
{
|
2008-03-03 14:10:54 +00:00
|
|
|
|
aClass = [NSMutableArray class];
|
2016-05-06 12:59:19 +00:00
|
|
|
|
}
|
|
|
|
|
if (Nil == rClass)
|
|
|
|
|
{
|
2007-03-15 12:38:44 +00:00
|
|
|
|
rClass = [_ConcreteSQLRecord class];
|
2004-08-22 09:34:18 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2007-03-08 17:12:55 +00:00
|
|
|
|
+ (id) newWithValues: (id*)v keys: (NSString**)k count: (unsigned int)c
|
|
|
|
|
{
|
|
|
|
|
return [rClass newWithValues: v keys: k count: c];
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-28 11:47:23 +00:00
|
|
|
|
+ (id) newWithValues: (id*)v keys: (SQLRecordKeys*)k
|
|
|
|
|
{
|
|
|
|
|
return [rClass newWithValues: v keys: k];
|
|
|
|
|
}
|
|
|
|
|
|
2007-03-08 17:12:55 +00:00
|
|
|
|
- (NSArray*) allKeys
|
|
|
|
|
{
|
2015-01-06 10:56:39 +00:00
|
|
|
|
NSUInteger count = [self count];
|
2007-03-08 17:12:55 +00:00
|
|
|
|
|
2015-01-06 10:56:39 +00:00
|
|
|
|
if (count > 0)
|
2007-03-08 17:12:55 +00:00
|
|
|
|
{
|
2015-01-06 10:56:39 +00:00
|
|
|
|
id buf[count];
|
|
|
|
|
|
|
|
|
|
while (count-- > 0)
|
|
|
|
|
{
|
|
|
|
|
buf[count] = [self keyAtIndex: count];
|
|
|
|
|
}
|
|
|
|
|
return [NSArray arrayWithObjects: buf count: count];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return [NSArray array];
|
2007-03-08 17:12:55 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id) copyWithZone: (NSZone*)z
|
|
|
|
|
{
|
2009-11-18 11:11:29 +00:00
|
|
|
|
return [self retain];
|
2007-03-08 17:12:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-01-06 16:03:51 +00:00
|
|
|
|
- (NSUInteger) count
|
2007-03-08 17:12:55 +00:00
|
|
|
|
{
|
2007-04-01 08:03:21 +00:00
|
|
|
|
SUBCLASS_RESPONSIBILITY
|
2007-03-08 17:12:55 +00:00
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSMutableDictionary*) dictionary
|
|
|
|
|
{
|
2015-01-06 10:56:39 +00:00
|
|
|
|
NSUInteger count = [self count];
|
2007-03-08 17:12:55 +00:00
|
|
|
|
|
2015-01-06 10:56:39 +00:00
|
|
|
|
if (count > 0)
|
|
|
|
|
{
|
|
|
|
|
id keys[count];
|
|
|
|
|
id vals[count];
|
|
|
|
|
|
|
|
|
|
[self getKeys: keys];
|
|
|
|
|
[self getObjects: vals];
|
|
|
|
|
return [NSMutableDictionary dictionaryWithObjects: vals
|
|
|
|
|
forKeys: keys
|
|
|
|
|
count: count];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return [NSMutableDictionary dictionary];
|
|
|
|
|
}
|
2007-03-08 17:12:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) getKeys: (id*)buf
|
|
|
|
|
{
|
2015-04-28 11:47:23 +00:00
|
|
|
|
NSUInteger i = [self count];
|
2007-03-08 17:12:55 +00:00
|
|
|
|
|
|
|
|
|
while (i-- > 0)
|
|
|
|
|
{
|
|
|
|
|
buf[i] = [self keyAtIndex: i];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) getObjects: (id*)buf
|
|
|
|
|
{
|
2015-04-28 11:47:23 +00:00
|
|
|
|
NSUInteger i = [self count];
|
2007-03-08 17:12:55 +00:00
|
|
|
|
|
|
|
|
|
while (i-- > 0)
|
|
|
|
|
{
|
|
|
|
|
buf[i] = [self objectAtIndex: i];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id) init
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"Illegal attempt to -init an SQLRecord");
|
2009-11-18 11:11:29 +00:00
|
|
|
|
[self release];
|
|
|
|
|
return nil;
|
2007-03-08 17:12:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-01-06 16:03:51 +00:00
|
|
|
|
- (NSString*) keyAtIndex: (NSUInteger)index
|
2007-03-08 17:12:55 +00:00
|
|
|
|
{
|
2007-04-01 08:03:21 +00:00
|
|
|
|
SUBCLASS_RESPONSIBILITY
|
|
|
|
|
return nil;
|
2007-03-08 17:12:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-04-28 11:47:23 +00:00
|
|
|
|
- (SQLRecordKeys*) keys
|
|
|
|
|
{
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
|
2012-01-06 15:39:11 +00:00
|
|
|
|
- (id) objectAtIndex: (NSUInteger)index
|
2007-03-08 17:12:55 +00:00
|
|
|
|
{
|
2007-04-01 08:03:21 +00:00
|
|
|
|
SUBCLASS_RESPONSIBILITY
|
|
|
|
|
return nil;
|
2007-03-08 17:12:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id) objectForKey: (NSString*)key
|
|
|
|
|
{
|
2015-01-06 10:56:39 +00:00
|
|
|
|
NSUInteger count = [self count];
|
2007-03-08 17:12:55 +00:00
|
|
|
|
|
2015-01-06 10:56:39 +00:00
|
|
|
|
if (count > 0)
|
2007-03-08 17:12:55 +00:00
|
|
|
|
{
|
2015-01-06 10:56:39 +00:00
|
|
|
|
NSUInteger pos;
|
|
|
|
|
id keys[count];
|
|
|
|
|
|
|
|
|
|
[self getKeys: keys];
|
2007-03-08 17:12:55 +00:00
|
|
|
|
for (pos = 0; pos < count; pos++)
|
2015-01-06 10:56:39 +00:00
|
|
|
|
{
|
|
|
|
|
if ([key isEqualToString: keys[pos]] == YES)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (pos == count)
|
|
|
|
|
{
|
|
|
|
|
for (pos = 0; pos < count; pos++)
|
|
|
|
|
{
|
|
|
|
|
if ([key caseInsensitiveCompare: keys[pos]] == NSOrderedSame)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2007-03-08 17:12:55 +00:00
|
|
|
|
|
2015-01-06 10:56:39 +00:00
|
|
|
|
if (pos != count)
|
|
|
|
|
{
|
|
|
|
|
return [self objectAtIndex: pos];
|
|
|
|
|
}
|
2007-03-08 17:12:55 +00:00
|
|
|
|
}
|
2015-01-06 10:56:39 +00:00
|
|
|
|
return nil;
|
2007-03-08 17:12:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-01-06 15:39:11 +00:00
|
|
|
|
- (void) replaceObjectAtIndex: (NSUInteger)index withObject: (id)anObject
|
2007-03-08 17:12:55 +00:00
|
|
|
|
{
|
2007-04-01 08:03:21 +00:00
|
|
|
|
SUBCLASS_RESPONSIBILITY
|
2007-03-08 17:12:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) setObject: (id)anObject forKey: (NSString*)aKey
|
|
|
|
|
{
|
2015-01-06 10:56:39 +00:00
|
|
|
|
NSUInteger count = [self count];
|
2007-03-08 17:12:55 +00:00
|
|
|
|
|
2015-01-06 10:56:39 +00:00
|
|
|
|
if (count > 0)
|
2007-03-08 17:12:55 +00:00
|
|
|
|
{
|
2015-01-06 10:56:39 +00:00
|
|
|
|
NSUInteger pos;
|
|
|
|
|
id keys[count];
|
|
|
|
|
|
|
|
|
|
if (anObject == nil)
|
2007-03-08 17:12:55 +00:00
|
|
|
|
{
|
2015-01-06 10:56:39 +00:00
|
|
|
|
anObject = null;
|
|
|
|
|
}
|
|
|
|
|
[self getKeys: keys];
|
2007-03-08 17:12:55 +00:00
|
|
|
|
for (pos = 0; pos < count; pos++)
|
2015-01-06 10:56:39 +00:00
|
|
|
|
{
|
|
|
|
|
if ([aKey isEqualToString: keys[pos]] == YES)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (pos == count)
|
|
|
|
|
{
|
|
|
|
|
for (pos = 0; pos < count; pos++)
|
|
|
|
|
{
|
|
|
|
|
if ([aKey caseInsensitiveCompare: keys[pos]] == NSOrderedSame)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2007-03-08 17:12:55 +00:00
|
|
|
|
|
2015-01-06 10:56:39 +00:00
|
|
|
|
if (pos == count)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"Bad key (%@) in -setObject:forKey:", aKey];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[self replaceObjectAtIndex: pos withObject: anObject];
|
|
|
|
|
}
|
2007-03-08 17:12:55 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2015-01-06 10:56:39 +00:00
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"Bad key (%@) in -setObject:forKey:", aKey];
|
2007-03-08 17:12:55 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-05 12:48:53 +00:00
|
|
|
|
- (NSUInteger) sizeInBytes: (NSMutableSet*)exclude
|
2007-03-08 17:12:55 +00:00
|
|
|
|
{
|
2013-09-05 12:48:53 +00:00
|
|
|
|
NSUInteger size = [super sizeInBytes: exclude];
|
2015-01-06 10:56:39 +00:00
|
|
|
|
NSUInteger count = [self count];
|
2007-04-01 07:03:43 +00:00
|
|
|
|
|
2015-01-06 10:56:39 +00:00
|
|
|
|
if (size > 0 && count > 0)
|
2007-03-08 17:12:55 +00:00
|
|
|
|
{
|
2013-09-05 12:48:53 +00:00
|
|
|
|
NSUInteger pos;
|
|
|
|
|
id vals[count];
|
2007-03-08 17:12:55 +00:00
|
|
|
|
|
|
|
|
|
[self getObjects: vals];
|
|
|
|
|
for (pos = 0; pos < count; pos++)
|
|
|
|
|
{
|
|
|
|
|
size += [vals[pos] sizeInBytes: exclude];
|
|
|
|
|
}
|
|
|
|
|
}
|
2007-04-01 07:03:43 +00:00
|
|
|
|
return size;
|
2007-03-08 17:12:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation SQLRecord (KVC)
|
|
|
|
|
- (void) setValue: (id)aValue forKey: (NSString*)aKey
|
|
|
|
|
{
|
|
|
|
|
[self setObject: aValue forKey: aKey];
|
|
|
|
|
}
|
|
|
|
|
- (id) valueForKey: (NSString*)aKey
|
|
|
|
|
{
|
|
|
|
|
id v = [self objectForKey: aKey];
|
|
|
|
|
|
|
|
|
|
if (v == nil)
|
|
|
|
|
{
|
|
|
|
|
v = [super valueForKey: aKey];
|
|
|
|
|
}
|
|
|
|
|
return v;
|
|
|
|
|
}
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
|
2007-03-15 12:38:44 +00:00
|
|
|
|
@implementation _ConcreteSQLRecord
|
2007-03-08 17:12:55 +00:00
|
|
|
|
|
2015-04-28 11:47:23 +00:00
|
|
|
|
+ (id) newWithValues: (id*)v keys: (SQLRecordKeys*)k
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2015-04-28 11:47:23 +00:00
|
|
|
|
id *ptr;
|
2007-03-15 12:38:44 +00:00
|
|
|
|
_ConcreteSQLRecord *r;
|
2015-04-28 11:47:23 +00:00
|
|
|
|
NSUInteger c;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
2015-04-28 11:47:23 +00:00
|
|
|
|
c = [k count];
|
2007-03-15 12:38:44 +00:00
|
|
|
|
r = (_ConcreteSQLRecord*)NSAllocateObject(self,
|
2015-04-28 11:47:23 +00:00
|
|
|
|
c*sizeof(id), NSDefaultMallocZone());
|
2004-04-26 15:13:27 +00:00
|
|
|
|
r->count = c;
|
2015-04-28 11:47:23 +00:00
|
|
|
|
r->keys = [k retain];
|
|
|
|
|
ptr = (id*)(((void*)&(r->count)) + sizeof(r->count));
|
|
|
|
|
while (c-- > 0)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2015-04-28 11:47:23 +00:00
|
|
|
|
if (nil == v[c])
|
2004-08-22 09:34:18 +00:00
|
|
|
|
{
|
2015-04-28 11:47:23 +00:00
|
|
|
|
ptr[c] = [null retain];
|
2004-08-22 09:34:18 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2015-04-28 11:47:23 +00:00
|
|
|
|
ptr[c] = [v[c] retain];
|
2004-08-22 09:34:18 +00:00
|
|
|
|
}
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-28 11:47:23 +00:00
|
|
|
|
+ (id) newWithValues: (id*)v keys: (NSString**)k count: (unsigned int)c
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2015-04-28 11:47:23 +00:00
|
|
|
|
SQLRecordKeys *o;
|
|
|
|
|
_ConcreteSQLRecord *r;
|
|
|
|
|
|
|
|
|
|
o = [[SQLRecordKeys alloc] initWithKeys: k count: c];
|
|
|
|
|
r = [self newWithValues: v keys: o];
|
|
|
|
|
[o release];
|
|
|
|
|
return r;
|
|
|
|
|
}
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
2015-04-28 11:47:23 +00:00
|
|
|
|
- (NSArray*) allKeys
|
|
|
|
|
{
|
|
|
|
|
return [keys order];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id) copyWithZone: (NSZone*)z
|
|
|
|
|
{
|
2009-11-18 11:11:29 +00:00
|
|
|
|
return [self retain];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-01-06 16:03:51 +00:00
|
|
|
|
- (NSUInteger) count
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
return count;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) dealloc
|
|
|
|
|
{
|
|
|
|
|
id *ptr;
|
2015-04-28 11:47:23 +00:00
|
|
|
|
NSUInteger pos;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
2015-04-28 11:47:23 +00:00
|
|
|
|
[keys release];
|
|
|
|
|
ptr = (id*)(((void*)&count) + sizeof(count));
|
2004-04-26 15:13:27 +00:00
|
|
|
|
for (pos = 0; pos < count; pos++)
|
|
|
|
|
{
|
2009-11-18 11:11:29 +00:00
|
|
|
|
[ptr[pos] release]; ptr[pos] = nil;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
2005-08-03 05:39:29 +00:00
|
|
|
|
[super dealloc];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2004-08-22 09:34:18 +00:00
|
|
|
|
- (NSMutableDictionary*) dictionary
|
|
|
|
|
{
|
|
|
|
|
NSMutableDictionary *d;
|
2015-04-28 11:47:23 +00:00
|
|
|
|
NSUInteger pos;
|
|
|
|
|
NSArray *k = [keys order];
|
2004-08-22 09:34:18 +00:00
|
|
|
|
id *ptr;
|
|
|
|
|
|
2015-04-28 11:47:23 +00:00
|
|
|
|
ptr = (id*)(((void*)&count) + sizeof(count));
|
2004-08-22 09:34:18 +00:00
|
|
|
|
d = [NSMutableDictionary dictionaryWithCapacity: count];
|
|
|
|
|
for (pos = 0; pos < count; pos++)
|
|
|
|
|
{
|
2015-04-28 11:47:23 +00:00
|
|
|
|
[d setObject: ptr[pos]
|
|
|
|
|
forKey: [[k objectAtIndex: pos] lowercaseString]];
|
2004-08-22 09:34:18 +00:00
|
|
|
|
}
|
|
|
|
|
return d;
|
|
|
|
|
}
|
|
|
|
|
|
2007-03-08 17:12:55 +00:00
|
|
|
|
- (void) getKeys: (id*)buf
|
|
|
|
|
{
|
2015-04-28 11:47:23 +00:00
|
|
|
|
[[keys order] getObjects: buf];
|
2007-03-08 17:12:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2005-09-30 13:12:27 +00:00
|
|
|
|
- (void) getObjects: (id*)buf
|
|
|
|
|
{
|
|
|
|
|
id *ptr;
|
2015-04-28 11:47:23 +00:00
|
|
|
|
NSUInteger pos;
|
2005-09-30 13:12:27 +00:00
|
|
|
|
|
2015-04-28 11:47:23 +00:00
|
|
|
|
ptr = (id*)(((void*)&count) + sizeof(count));
|
2005-09-30 13:12:27 +00:00
|
|
|
|
for (pos = 0; pos < count; pos++)
|
|
|
|
|
{
|
|
|
|
|
buf[pos] = ptr[pos];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
- (id) init
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"Illegal attempt to -init an SQLRecord");
|
2009-11-18 11:11:29 +00:00
|
|
|
|
[self release];
|
|
|
|
|
return nil;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-01-06 16:03:51 +00:00
|
|
|
|
- (NSString*) keyAtIndex: (NSUInteger)pos
|
2007-03-08 17:12:55 +00:00
|
|
|
|
{
|
2015-04-28 11:47:23 +00:00
|
|
|
|
return [[keys order] objectAtIndex: pos];
|
|
|
|
|
}
|
2007-03-08 17:12:55 +00:00
|
|
|
|
|
2015-04-28 11:47:23 +00:00
|
|
|
|
- (SQLRecordKeys*) keys
|
|
|
|
|
{
|
|
|
|
|
return keys;
|
2007-03-08 17:12:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-01-06 15:39:11 +00:00
|
|
|
|
- (id) objectAtIndex: (NSUInteger)pos
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
id *ptr;
|
|
|
|
|
|
|
|
|
|
if (pos >= count)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSRangeException
|
|
|
|
|
format: @"Array index too large"];
|
|
|
|
|
}
|
2015-04-28 11:47:23 +00:00
|
|
|
|
ptr = (id*)(((void*)&count) + sizeof(count));
|
2004-04-26 15:13:27 +00:00
|
|
|
|
return ptr[pos];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id) objectForKey: (NSString*)key
|
|
|
|
|
{
|
2015-04-28 11:47:23 +00:00
|
|
|
|
NSUInteger pos = [keys indexForKey: key];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
2015-04-28 11:47:23 +00:00
|
|
|
|
if (NSNotFound == pos)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2015-04-28 11:47:23 +00:00
|
|
|
|
return nil;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
2015-04-28 11:47:23 +00:00
|
|
|
|
else
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2015-04-28 11:47:23 +00:00
|
|
|
|
id *ptr;
|
|
|
|
|
|
|
|
|
|
ptr = (id*)(((void*)&count) + sizeof(count));
|
|
|
|
|
return ptr[pos];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2005-09-20 12:57:48 +00:00
|
|
|
|
|
2012-01-06 15:39:11 +00:00
|
|
|
|
- (void) replaceObjectAtIndex: (NSUInteger)index withObject: (id)anObject
|
2007-03-08 17:12:55 +00:00
|
|
|
|
{
|
|
|
|
|
id *ptr;
|
|
|
|
|
|
|
|
|
|
if (index >= count)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSRangeException
|
|
|
|
|
format: @"Array index too large"];
|
|
|
|
|
}
|
|
|
|
|
if (anObject == nil)
|
|
|
|
|
{
|
|
|
|
|
anObject = null;
|
|
|
|
|
}
|
2015-04-28 11:47:23 +00:00
|
|
|
|
ptr = (id*)(((void*)&count) + sizeof(count));
|
2007-03-08 17:12:55 +00:00
|
|
|
|
ptr += index;
|
2009-11-18 11:11:29 +00:00
|
|
|
|
[anObject retain];
|
|
|
|
|
[*ptr release];
|
|
|
|
|
*ptr = anObject;
|
2007-03-08 17:12:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2005-09-20 12:57:48 +00:00
|
|
|
|
- (void) setObject: (id)anObject forKey: (NSString*)aKey
|
|
|
|
|
{
|
|
|
|
|
id *ptr;
|
2015-04-28 11:47:23 +00:00
|
|
|
|
NSUInteger pos;
|
2005-09-20 12:57:48 +00:00
|
|
|
|
|
|
|
|
|
if (anObject == nil)
|
|
|
|
|
{
|
|
|
|
|
anObject = null;
|
|
|
|
|
}
|
2015-04-28 11:47:23 +00:00
|
|
|
|
ptr = (id*)(((void*)&count) + sizeof(count));
|
|
|
|
|
pos = [keys indexForKey: aKey];
|
|
|
|
|
if (NSNotFound == pos)
|
2005-09-20 12:57:48 +00:00
|
|
|
|
{
|
2015-04-28 11:47:23 +00:00
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"Bad key (%@) in -setObject:forKey:", aKey];
|
2005-09-20 12:57:48 +00:00
|
|
|
|
}
|
2015-04-28 11:47:23 +00:00
|
|
|
|
else
|
2005-09-20 12:57:48 +00:00
|
|
|
|
{
|
2015-04-28 11:47:23 +00:00
|
|
|
|
[anObject retain];
|
|
|
|
|
[ptr[pos] release];
|
|
|
|
|
ptr[pos] = anObject;
|
2005-09-20 12:57:48 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2005-09-22 08:42:37 +00:00
|
|
|
|
|
2013-09-05 12:48:53 +00:00
|
|
|
|
- (NSUInteger) sizeInBytes: (NSMutableSet*)exclude
|
2005-09-22 08:42:37 +00:00
|
|
|
|
{
|
|
|
|
|
if ([exclude member: self] != nil)
|
|
|
|
|
{
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2013-09-05 12:48:53 +00:00
|
|
|
|
NSUInteger size = [super sizeInBytes: exclude];
|
|
|
|
|
NSUInteger pos;
|
2005-09-22 08:42:37 +00:00
|
|
|
|
id *ptr;
|
|
|
|
|
|
2015-04-28 11:47:23 +00:00
|
|
|
|
ptr = (id*)(((void*)&count) + sizeof(count));
|
2005-09-22 08:42:37 +00:00
|
|
|
|
for (pos = 0; pos < count; pos++)
|
|
|
|
|
{
|
|
|
|
|
size += [ptr[pos] sizeInBytes: exclude];
|
|
|
|
|
}
|
|
|
|
|
return size;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-16 10:13:12 +00:00
|
|
|
|
- (NSUInteger) sizeInBytesExcluding: (NSHashTable*)exclude
|
|
|
|
|
{
|
|
|
|
|
static NSUInteger (*imp)(id,SEL,id) = 0;
|
|
|
|
|
NSUInteger size;
|
|
|
|
|
|
|
|
|
|
/* We use the NSObject implementation to get the memory used,
|
|
|
|
|
* and then add in the fields within the record.
|
|
|
|
|
*/
|
|
|
|
|
if (0 == imp)
|
|
|
|
|
{
|
|
|
|
|
imp = (NSUInteger(*)(id,SEL,id))
|
|
|
|
|
[NSObject instanceMethodForSelector: _cmd];
|
|
|
|
|
}
|
|
|
|
|
size = (*imp)(self, _cmd, exclude);
|
|
|
|
|
if (size > 0)
|
|
|
|
|
{
|
|
|
|
|
NSUInteger pos;
|
|
|
|
|
id *ptr;
|
|
|
|
|
|
|
|
|
|
size += [keys sizeInBytesExcluding: exclude];
|
|
|
|
|
size += sizeof(void*) * count;
|
|
|
|
|
ptr = (id*)(((void*)&count) + sizeof(count));
|
|
|
|
|
for (pos = 0; pos < count; pos++)
|
|
|
|
|
{
|
|
|
|
|
size += [ptr[pos] sizeInBytesExcluding: exclude];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return size;
|
|
|
|
|
}
|
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Exception raised when an error with the remote database server occurs.
|
|
|
|
|
*/
|
|
|
|
|
NSString *SQLException = @"SQLException";
|
|
|
|
|
/**
|
|
|
|
|
* Exception for when a connection to the server is lost.
|
|
|
|
|
*/
|
|
|
|
|
NSString *SQLConnectionException = @"SQLConnectionException";
|
|
|
|
|
/**
|
|
|
|
|
* Exception for when a query is supposed to return data and doesn't.
|
|
|
|
|
*/
|
|
|
|
|
NSString *SQLEmptyException = @"SQLEmptyException";
|
|
|
|
|
/**
|
|
|
|
|
* Exception for when an insert/update would break the uniqueness of a
|
|
|
|
|
* field or index.
|
|
|
|
|
*/
|
|
|
|
|
NSString *SQLUniqueException = @"SQLUniqueException";
|
|
|
|
|
|
|
|
|
|
@implementation SQLClient (Logging)
|
|
|
|
|
|
|
|
|
|
+ (unsigned int) debugging
|
|
|
|
|
{
|
|
|
|
|
return classDebugging;
|
|
|
|
|
}
|
|
|
|
|
|
2004-05-07 08:16:16 +00:00
|
|
|
|
+ (NSTimeInterval) durationLogging
|
|
|
|
|
{
|
|
|
|
|
return classDuration;
|
|
|
|
|
}
|
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
+ (void) setDebugging: (unsigned int)level
|
|
|
|
|
{
|
|
|
|
|
classDebugging = level;
|
|
|
|
|
}
|
|
|
|
|
|
2004-05-07 08:16:16 +00:00
|
|
|
|
+ (void) setDurationLogging: (NSTimeInterval)threshold
|
|
|
|
|
{
|
|
|
|
|
classDuration = threshold;
|
|
|
|
|
}
|
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
- (void) debug: (NSString*)fmt, ...
|
|
|
|
|
{
|
|
|
|
|
va_list ap;
|
|
|
|
|
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
|
NSLogv(fmt, ap);
|
|
|
|
|
va_end(ap);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (unsigned int) debugging
|
|
|
|
|
{
|
2004-05-07 08:16:16 +00:00
|
|
|
|
return _debugging;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSTimeInterval) durationLogging
|
|
|
|
|
{
|
|
|
|
|
return _duration;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) setDebugging: (unsigned int)level
|
|
|
|
|
{
|
2004-05-07 08:16:16 +00:00
|
|
|
|
_debugging = level;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) setDurationLogging: (NSTimeInterval)threshold
|
|
|
|
|
{
|
|
|
|
|
_duration = threshold;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
2015-06-09 17:31:09 +00:00
|
|
|
|
/* Containers for all instances.
|
|
|
|
|
* Access to and update of these containers is protected by classLock.
|
|
|
|
|
* Most other operations involving an instance are protected by a lock
|
|
|
|
|
* in that instance.
|
|
|
|
|
* To avoid deadlocks, we always obtain the instance lock *before* we
|
|
|
|
|
* obtain the class lock, so we don't get the situation where one thread
|
|
|
|
|
* has locked the instance and another has locked the class lock, and
|
|
|
|
|
* each thread then tries to get the other lock.
|
2004-04-26 15:13:27 +00:00
|
|
|
|
*/
|
2014-06-19 21:26:25 +00:00
|
|
|
|
static NSHashTable *clientsHash = 0;
|
2005-09-27 06:35:05 +00:00
|
|
|
|
static NSMapTable *clientsMap = 0;
|
2014-06-19 21:26:25 +00:00
|
|
|
|
static NSRecursiveLock *clientsLock = nil;
|
2015-06-09 17:31:09 +00:00
|
|
|
|
|
|
|
|
|
/* Protect changes to the cache used for queries by any individual client.
|
|
|
|
|
*/
|
|
|
|
|
static NSRecursiveLock *cacheLock = nil;
|
|
|
|
|
|
2004-09-17 14:54:56 +00:00
|
|
|
|
static NSString *beginString = @"begin";
|
2004-04-26 15:13:27 +00:00
|
|
|
|
static NSArray *beginStatement = nil;
|
2004-09-17 14:54:56 +00:00
|
|
|
|
static NSString *commitString = @"commit";
|
2004-04-26 15:13:27 +00:00
|
|
|
|
static NSArray *commitStatement = nil;
|
2004-09-17 14:54:56 +00:00
|
|
|
|
static NSString *rollbackString = @"rollback";
|
2004-04-26 15:13:27 +00:00
|
|
|
|
static NSArray *rollbackStatement = nil;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@interface SQLClient (Private)
|
2014-10-07 09:15:16 +00:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Internal method to handle configuration using the notification object.
|
|
|
|
|
* This object may be either a configuration front end or a user defaults
|
|
|
|
|
* object ... so we have to be careful that we work with both.
|
|
|
|
|
*/
|
2004-04-26 15:13:27 +00:00
|
|
|
|
- (void) _configure: (NSNotification*)n;
|
2014-10-07 09:15:16 +00:00
|
|
|
|
|
|
|
|
|
/** Internal method to make the client instance lock available to
|
|
|
|
|
* an associated SQLTransaction
|
|
|
|
|
*/
|
|
|
|
|
- (NSRecursiveLock*) _lock;
|
|
|
|
|
|
|
|
|
|
/** Internal method to populate the cache with the result of a query.
|
|
|
|
|
*/
|
2008-02-21 16:23:23 +00:00
|
|
|
|
- (void) _populateCache: (CacheQuery*)a;
|
2014-10-07 09:15:16 +00:00
|
|
|
|
|
|
|
|
|
/** Internal method called to record the 'main' thread in which automated
|
|
|
|
|
* cache updates are to be performed.
|
|
|
|
|
*/
|
2008-02-21 16:23:23 +00:00
|
|
|
|
- (void) _recordMainThread;
|
2014-10-07 09:15:16 +00:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Called at one second intervals to ensure that our current timestamp
|
|
|
|
|
* is reasonably accurate.
|
|
|
|
|
*/
|
2005-09-27 06:35:05 +00:00
|
|
|
|
+ (void) _tick: (NSTimer*)t;
|
2014-10-07 09:15:16 +00:00
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
@end
|
|
|
|
|
|
2008-02-21 16:23:23 +00:00
|
|
|
|
@interface SQLClient (GSCacheDelegate)
|
|
|
|
|
- (BOOL) shouldKeepItem: (id)anObject
|
|
|
|
|
withKey: (id)aKey
|
|
|
|
|
lifetime: (unsigned)lifetime
|
|
|
|
|
after: (unsigned)delay;
|
|
|
|
|
@end
|
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
@implementation SQLClient
|
|
|
|
|
|
2017-08-27 11:41:22 +00:00
|
|
|
|
static NSTimeInterval abandonAfter = 0.0;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
static unsigned int maxConnections = 8;
|
2014-10-13 10:47:06 +00:00
|
|
|
|
static int poolConnections = 0;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
|
|
|
|
+ (NSArray*) allClients
|
|
|
|
|
{
|
2014-06-19 21:26:25 +00:00
|
|
|
|
NSMutableArray *a;
|
|
|
|
|
NSHashEnumerator e;
|
|
|
|
|
id o;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
2014-06-19 21:26:25 +00:00
|
|
|
|
[clientsLock lock];
|
|
|
|
|
a = [NSMutableArray arrayWithCapacity: NSCountHashTable(clientsHash)];
|
|
|
|
|
e = NSEnumerateHashTable(clientsHash);
|
|
|
|
|
while (nil != (o = (id)NSNextHashEnumeratorItem(&e)))
|
|
|
|
|
{
|
|
|
|
|
[a addObject: o];
|
|
|
|
|
}
|
|
|
|
|
NSEndHashTableEnumeration(&e);
|
|
|
|
|
[clientsLock unlock];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
return a;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
+ (SQLClient*) clientWithConfiguration: (NSDictionary*)config
|
|
|
|
|
name: (NSString*)reference
|
|
|
|
|
{
|
|
|
|
|
SQLClient *o;
|
|
|
|
|
|
2004-10-07 09:30:14 +00:00
|
|
|
|
if ([reference isKindOfClass: NSStringClass] == NO)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2015-06-25 08:08:47 +00:00
|
|
|
|
if (nil == config)
|
2004-06-29 17:11:10 +00:00
|
|
|
|
{
|
2015-06-25 08:08:47 +00:00
|
|
|
|
config = (NSDictionary*)[NSUserDefaults standardUserDefaults];
|
2004-06-29 17:11:10 +00:00
|
|
|
|
}
|
2015-06-25 08:08:47 +00:00
|
|
|
|
reference = [config objectForKey: @"SQLClientName"];
|
2004-10-07 09:30:14 +00:00
|
|
|
|
if ([reference isKindOfClass: NSStringClass] == NO)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
reference = @"Database";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
o = [self existingClient: reference];
|
2015-01-06 10:56:39 +00:00
|
|
|
|
if (nil == o)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2015-01-06 10:56:39 +00:00
|
|
|
|
o = [[[SQLClient alloc] initWithConfiguration: config name: reference]
|
|
|
|
|
autorelease];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
2020-04-08 12:24:39 +00:00
|
|
|
|
else if ([config isKindOfClass: [NSUserDefaults class]] == NO)
|
|
|
|
|
{
|
|
|
|
|
/* If the configuration object is not the user defaults
|
|
|
|
|
* make sure to update the existing connnection's configuration.
|
|
|
|
|
*/
|
|
|
|
|
NSNotification *n = [NSNotification
|
|
|
|
|
notificationWithName: NSUserDefaultsDidChangeNotification
|
|
|
|
|
object: config
|
|
|
|
|
userInfo: nil];
|
|
|
|
|
[o _configure: n];
|
|
|
|
|
}
|
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
return o;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
+ (SQLClient*) existingClient: (NSString*)reference
|
|
|
|
|
{
|
|
|
|
|
SQLClient *existing;
|
|
|
|
|
|
2004-10-07 09:30:14 +00:00
|
|
|
|
if ([reference isKindOfClass: NSStringClass] == NO)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
reference = [[NSUserDefaults standardUserDefaults] stringForKey:
|
|
|
|
|
@"SQLClientName"];
|
|
|
|
|
if (reference == nil)
|
|
|
|
|
{
|
|
|
|
|
reference = @"Database";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-06-19 21:26:25 +00:00
|
|
|
|
[clientsLock lock];
|
2005-09-27 06:35:05 +00:00
|
|
|
|
existing = (SQLClient*)NSMapGet(clientsMap, reference);
|
2009-11-18 11:11:29 +00:00
|
|
|
|
[[existing retain] autorelease];
|
2014-06-19 21:26:25 +00:00
|
|
|
|
[clientsLock unlock];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
return existing;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
+ (void) initialize
|
|
|
|
|
{
|
2014-06-20 05:15:24 +00:00
|
|
|
|
if (Nil == SQLClientClass && [SQLClient class] == self)
|
|
|
|
|
{
|
|
|
|
|
static id modes[1];
|
|
|
|
|
|
2017-03-06 17:23:37 +00:00
|
|
|
|
if (Nil == LitStringClass)
|
|
|
|
|
{
|
|
|
|
|
/* Find the literal string class used by the foundation library.
|
2020-02-27 14:55:59 +00:00
|
|
|
|
* We may have two varieties.
|
2017-03-06 17:23:37 +00:00
|
|
|
|
*/
|
2020-02-27 14:55:59 +00:00
|
|
|
|
LitStringClass = object_getClass(@"test string");
|
|
|
|
|
TinyStringClass = object_getClass(@"test");
|
2017-03-06 17:23:37 +00:00
|
|
|
|
|
2020-02-14 17:04:24 +00:00
|
|
|
|
SQLStringClass = [SQLString class];
|
2017-06-29 08:32:09 +00:00
|
|
|
|
SQLStringSize = class_getInstanceSize(SQLStringClass);
|
2020-02-14 17:04:24 +00:00
|
|
|
|
LitProxyClass = [SQLLiteralProxy class];
|
2017-03-06 17:23:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-04-27 07:55:26 +00:00
|
|
|
|
if (nil == null)
|
|
|
|
|
{
|
|
|
|
|
null = [NSNull new];
|
|
|
|
|
}
|
2014-06-20 05:15:24 +00:00
|
|
|
|
SQLClientClass = self;
|
|
|
|
|
modes[0] = NSDefaultRunLoopMode;
|
|
|
|
|
queryModes = [[NSArray alloc] initWithObjects: modes count: 1];
|
|
|
|
|
GSTickerTimeNow();
|
2015-05-04 12:15:47 +00:00
|
|
|
|
[SQLRecord class]; // Force initialisatio
|
2014-06-20 05:15:24 +00:00
|
|
|
|
if (0 == clientsHash)
|
|
|
|
|
{
|
2015-05-04 12:15:47 +00:00
|
|
|
|
cacheLock = [NSRecursiveLock new];
|
2014-06-20 05:15:24 +00:00
|
|
|
|
clientsHash = NSCreateHashTable(NSNonOwnedPointerHashCallBacks, 0);
|
|
|
|
|
clientsMap = NSCreateMapTable(NSObjectMapKeyCallBacks,
|
|
|
|
|
NSNonRetainedObjectMapValueCallBacks, 0);
|
|
|
|
|
clientsLock = [NSRecursiveLock new];
|
|
|
|
|
beginStatement = [[NSArray arrayWithObject: beginString] retain];
|
|
|
|
|
commitStatement = [[NSArray arrayWithObject: commitString] retain];
|
|
|
|
|
rollbackStatement
|
|
|
|
|
= [[NSArray arrayWithObject: rollbackString] retain];
|
|
|
|
|
NSStringClass = [NSString class];
|
2015-07-23 14:59:03 +00:00
|
|
|
|
NSDateClass = [NSDate class];
|
2014-06-20 05:15:24 +00:00
|
|
|
|
NSArrayClass = [NSArray class];
|
|
|
|
|
NSSetClass = [NSSet class];
|
|
|
|
|
[NSTimer scheduledTimerWithTimeInterval: 1.0
|
|
|
|
|
target: self
|
|
|
|
|
selector: @selector(_tick:)
|
|
|
|
|
userInfo: 0
|
|
|
|
|
repeats: YES];
|
|
|
|
|
}
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
+ (unsigned int) maxConnections
|
|
|
|
|
{
|
|
|
|
|
return maxConnections;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
+ (void) purgeConnections: (NSDate*)since
|
|
|
|
|
{
|
2014-06-19 21:26:25 +00:00
|
|
|
|
NSHashEnumerator e;
|
2015-06-09 16:47:36 +00:00
|
|
|
|
NSMutableArray *a = nil;
|
2004-10-07 09:30:14 +00:00
|
|
|
|
SQLClient *o;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
unsigned int connectionCount = 0;
|
2015-06-09 16:47:36 +00:00
|
|
|
|
NSTimeInterval t;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
2015-06-09 16:47:36 +00:00
|
|
|
|
t = (nil == since) ? 0.0 : [since timeIntervalSinceReferenceDate];
|
|
|
|
|
|
|
|
|
|
/* Find clients we may want to disconnect.
|
|
|
|
|
*/
|
2014-06-19 21:26:25 +00:00
|
|
|
|
[clientsLock lock];
|
|
|
|
|
e = NSEnumerateHashTable(clientsHash);
|
|
|
|
|
while (nil != (o = (SQLClient*)NSNextHashEnumeratorItem(&e)))
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2015-06-10 08:05:59 +00:00
|
|
|
|
if (YES == o->connected)
|
|
|
|
|
{
|
|
|
|
|
connectionCount++;
|
|
|
|
|
if (since != nil)
|
2015-06-09 16:47:36 +00:00
|
|
|
|
{
|
2015-06-10 08:05:59 +00:00
|
|
|
|
NSTimeInterval when = o->_lastOperation;
|
|
|
|
|
|
|
|
|
|
if (when < o->_lastStart)
|
|
|
|
|
{
|
|
|
|
|
when = o->_lastStart;
|
|
|
|
|
}
|
|
|
|
|
if (when < t)
|
2015-06-09 16:47:36 +00:00
|
|
|
|
{
|
2015-06-10 08:05:59 +00:00
|
|
|
|
if (nil == a)
|
|
|
|
|
{
|
|
|
|
|
a = [NSMutableArray array];
|
|
|
|
|
}
|
|
|
|
|
[a addObject: o];
|
2015-06-09 16:47:36 +00:00
|
|
|
|
}
|
2015-06-10 08:05:59 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
2014-06-19 21:26:25 +00:00
|
|
|
|
NSEndHashTableEnumeration(&e);
|
|
|
|
|
[clientsLock unlock];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
2015-06-09 16:47:36 +00:00
|
|
|
|
/* Disconnect any clients idle too long
|
|
|
|
|
*/
|
|
|
|
|
while ([a count] > 0)
|
|
|
|
|
{
|
|
|
|
|
o = [a lastObject];
|
|
|
|
|
if ([o->lock tryLock])
|
|
|
|
|
{
|
2015-06-10 08:05:59 +00:00
|
|
|
|
if (YES == o->connected)
|
2015-06-09 16:47:36 +00:00
|
|
|
|
{
|
2015-06-10 08:05:59 +00:00
|
|
|
|
NSTimeInterval when = o->_lastOperation;
|
|
|
|
|
|
|
|
|
|
if (when < o->_lastStart)
|
2015-06-09 16:47:36 +00:00
|
|
|
|
{
|
2015-06-10 08:05:59 +00:00
|
|
|
|
when = o->_lastStart;
|
2015-06-09 16:47:36 +00:00
|
|
|
|
}
|
2015-06-10 08:05:59 +00:00
|
|
|
|
if (when < t)
|
2015-06-09 16:47:36 +00:00
|
|
|
|
{
|
2015-06-10 08:05:59 +00:00
|
|
|
|
NS_DURING
|
|
|
|
|
{
|
|
|
|
|
[o disconnect];
|
|
|
|
|
}
|
|
|
|
|
NS_HANDLER
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"Problem disconnecting: %@", localException);
|
|
|
|
|
}
|
|
|
|
|
NS_ENDHANDLER
|
2015-06-09 16:47:36 +00:00
|
|
|
|
}
|
2015-06-10 08:05:59 +00:00
|
|
|
|
}
|
|
|
|
|
if (NO == o->connected)
|
|
|
|
|
{
|
|
|
|
|
connectionCount--;
|
2015-06-09 16:47:36 +00:00
|
|
|
|
}
|
|
|
|
|
[o->lock unlock];
|
|
|
|
|
}
|
|
|
|
|
[a removeLastObject];
|
|
|
|
|
}
|
|
|
|
|
|
2014-10-13 10:47:06 +00:00
|
|
|
|
while (connectionCount >= (maxConnections + poolConnections))
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
SQLClient *other = nil;
|
2004-10-07 09:30:14 +00:00
|
|
|
|
NSTimeInterval oldest = 0.0;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
|
|
|
|
connectionCount = 0;
|
2014-06-19 21:26:25 +00:00
|
|
|
|
[clientsLock lock];
|
|
|
|
|
e = NSEnumerateHashTable(clientsHash);
|
|
|
|
|
while (nil != (o = (SQLClient*)NSNextHashEnumeratorItem(&e)))
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2014-09-10 11:49:40 +00:00
|
|
|
|
if (YES == o->connected)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2004-10-07 09:30:14 +00:00
|
|
|
|
NSTimeInterval when = o->_lastOperation;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
2015-06-09 16:47:36 +00:00
|
|
|
|
if (when < o->_lastStart)
|
|
|
|
|
{
|
|
|
|
|
when = o->_lastStart;
|
|
|
|
|
}
|
2004-04-26 15:13:27 +00:00
|
|
|
|
connectionCount++;
|
2004-10-07 09:30:14 +00:00
|
|
|
|
if (oldest == 0.0 || when < oldest)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
oldest = when;
|
2015-06-09 16:47:36 +00:00
|
|
|
|
ASSIGN(other, o);
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-06-19 21:26:25 +00:00
|
|
|
|
NSEndHashTableEnumeration(&e);
|
|
|
|
|
[clientsLock unlock];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
connectionCount--;
|
|
|
|
|
if ([other debugging] > 0)
|
|
|
|
|
{
|
|
|
|
|
[other debug:
|
2014-10-13 10:47:06 +00:00
|
|
|
|
@"Force disconnect of '%@' because max connections (%u) reached",
|
2004-04-26 15:13:27 +00:00
|
|
|
|
other, maxConnections];
|
|
|
|
|
}
|
2015-06-09 16:47:36 +00:00
|
|
|
|
[AUTORELEASE(other) disconnect];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-27 11:41:22 +00:00
|
|
|
|
+ (void) setAbandonFailedConnectionsAfter: (NSTimeInterval)delay
|
|
|
|
|
{
|
|
|
|
|
abandonAfter = delay;
|
|
|
|
|
}
|
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
+ (void) setMaxConnections: (unsigned int)c
|
|
|
|
|
{
|
|
|
|
|
if (c > 0)
|
|
|
|
|
{
|
|
|
|
|
maxConnections = c;
|
|
|
|
|
[self purgeConnections: nil];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) begin
|
|
|
|
|
{
|
|
|
|
|
[lock lock];
|
2004-05-07 08:16:16 +00:00
|
|
|
|
if (_inTransaction == NO)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
NS_DURING
|
|
|
|
|
{
|
2004-05-07 08:16:16 +00:00
|
|
|
|
[self simpleExecute: beginStatement];
|
2019-03-07 15:20:43 +00:00
|
|
|
|
_inTransaction = YES;
|
2011-09-16 07:25:33 +00:00
|
|
|
|
/* NB. We leave the lock locked ... until a matching -commit
|
|
|
|
|
* or -rollback is called. This prevents other threads from
|
2014-05-07 16:36:06 +00:00
|
|
|
|
* interfering with this transaction.
|
2011-09-16 07:25:33 +00:00
|
|
|
|
*/
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
NS_HANDLER
|
|
|
|
|
{
|
2014-05-13 10:26:48 +00:00
|
|
|
|
[lock unlock];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
[localException raise];
|
|
|
|
|
}
|
|
|
|
|
NS_ENDHANDLER
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[lock unlock];
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"begin used inside transaction"];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-05 14:25:13 +00:00
|
|
|
|
- (SQLLiteral*) buildQuery: (NSString*)stmt, ...
|
2007-03-08 17:12:55 +00:00
|
|
|
|
{
|
|
|
|
|
va_list ap;
|
|
|
|
|
NSString *sql = nil;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* First check validity and concatenate parts of the query.
|
|
|
|
|
*/
|
|
|
|
|
va_start (ap, stmt);
|
2015-02-26 16:14:51 +00:00
|
|
|
|
sql = [[self prepare: stmt args: ap] objectAtIndex: 0];
|
2007-03-08 17:12:55 +00:00
|
|
|
|
va_end (ap);
|
|
|
|
|
|
2017-07-05 14:25:13 +00:00
|
|
|
|
if ([sql length] < 1000)
|
|
|
|
|
{
|
|
|
|
|
return SQLClientMakeLiteral(sql);
|
|
|
|
|
}
|
|
|
|
|
return SQLClientProxyLiteral(sql);
|
2007-03-08 17:12:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-05 14:25:13 +00:00
|
|
|
|
- (SQLLiteral*) buildQuery: (NSString*)stmt with: (NSDictionary*)values
|
2007-03-08 17:12:55 +00:00
|
|
|
|
{
|
|
|
|
|
NSString *sql = nil;
|
|
|
|
|
|
2015-06-29 11:59:18 +00:00
|
|
|
|
sql = [[self prepare: stmt with: values] objectAtIndex: 0];
|
2007-03-08 17:12:55 +00:00
|
|
|
|
|
2017-07-05 14:25:13 +00:00
|
|
|
|
if ([sql length] < 1000)
|
|
|
|
|
{
|
|
|
|
|
return SQLClientMakeLiteral(sql);
|
|
|
|
|
}
|
|
|
|
|
return SQLClientProxyLiteral(sql);
|
2007-03-08 17:12:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
- (NSString*) clientName
|
|
|
|
|
{
|
2015-06-26 12:06:13 +00:00
|
|
|
|
NSString *s;
|
|
|
|
|
|
|
|
|
|
[lock lock];
|
|
|
|
|
if (nil == _client)
|
|
|
|
|
{
|
|
|
|
|
_client = [[[NSProcessInfo processInfo] globallyUniqueString] retain];
|
|
|
|
|
}
|
|
|
|
|
s = [_client retain];
|
|
|
|
|
[lock unlock];
|
|
|
|
|
return [s autorelease];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) commit
|
|
|
|
|
{
|
|
|
|
|
[lock lock];
|
2004-05-07 08:16:16 +00:00
|
|
|
|
if (_inTransaction == NO)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
[lock unlock];
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"commit used outside transaction"];
|
|
|
|
|
}
|
2011-09-16 07:25:33 +00:00
|
|
|
|
|
|
|
|
|
/* Since we are in a transaction we must be doubly locked right now,
|
|
|
|
|
* so we unlock once, and we still have the lock (which was locked
|
|
|
|
|
* in the earlier call to the -begin method).
|
|
|
|
|
*/
|
|
|
|
|
[lock unlock];
|
|
|
|
|
_inTransaction = NO;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
NS_DURING
|
|
|
|
|
{
|
2004-05-07 08:16:16 +00:00
|
|
|
|
[self simpleExecute: commitStatement];
|
2007-07-07 07:23:40 +00:00
|
|
|
|
[_statements removeAllObjects];
|
2004-09-17 14:54:56 +00:00
|
|
|
|
[lock unlock]; // Locked by -begin
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
NS_HANDLER
|
|
|
|
|
{
|
2007-07-07 07:23:40 +00:00
|
|
|
|
[_statements removeAllObjects];
|
2004-09-17 14:54:56 +00:00
|
|
|
|
[lock unlock]; // Locked by -begin
|
2004-04-26 15:13:27 +00:00
|
|
|
|
[localException raise];
|
|
|
|
|
}
|
|
|
|
|
NS_ENDHANDLER
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (BOOL) connect
|
|
|
|
|
{
|
2017-08-31 10:38:53 +00:00
|
|
|
|
if (NO == [self tryConnect] && abandonAfter > 0.0)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2017-08-27 11:41:22 +00:00
|
|
|
|
NSTimeInterval end;
|
|
|
|
|
|
2017-08-31 10:38:53 +00:00
|
|
|
|
end = [NSDate timeIntervalSinceReferenceDate] + abandonAfter;
|
2017-08-31 10:05:08 +00:00
|
|
|
|
while (NO == connected && [NSDate timeIntervalSinceReferenceDate] < end)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2017-08-29 20:35:59 +00:00
|
|
|
|
[self tryConnect];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return connected;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (BOOL) connected
|
|
|
|
|
{
|
|
|
|
|
return connected;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSString*) database
|
|
|
|
|
{
|
2004-05-07 08:16:16 +00:00
|
|
|
|
return _database;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) dealloc
|
|
|
|
|
{
|
|
|
|
|
NSNotificationCenter *nc;
|
|
|
|
|
|
2014-06-19 21:26:25 +00:00
|
|
|
|
[clientsLock lock];
|
|
|
|
|
NSHashRemove(clientsHash, (void*)self);
|
2014-07-04 13:11:47 +00:00
|
|
|
|
if (_name != nil
|
|
|
|
|
&& (SQLClient*)NSMapGet(clientsMap, (void*)_name) == self)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2005-09-27 06:35:05 +00:00
|
|
|
|
NSMapRemove(clientsMap, (void*)_name);
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
2014-06-19 21:26:25 +00:00
|
|
|
|
[clientsLock unlock];
|
2006-09-10 13:37:23 +00:00
|
|
|
|
nc = [NSNotificationCenter defaultCenter];
|
|
|
|
|
[nc removeObserver: self];
|
2014-09-10 11:49:40 +00:00
|
|
|
|
if (YES == connected) [self disconnect];
|
2009-11-18 11:11:29 +00:00
|
|
|
|
[lock release]; lock = nil;
|
|
|
|
|
[_client release]; _client = nil;
|
|
|
|
|
[_database release]; _database = nil;
|
|
|
|
|
[_password release]; _password = nil;
|
|
|
|
|
[_user release]; _user = nil;
|
|
|
|
|
[_name release]; _name = nil;
|
|
|
|
|
[_statements release]; _statements = nil;
|
|
|
|
|
[_cache release]; _cache = nil;
|
|
|
|
|
[_cacheThread release]; _cacheThread = nil;
|
2012-10-22 15:57:56 +00:00
|
|
|
|
if (0 != _observers)
|
|
|
|
|
{
|
|
|
|
|
NSNotificationCenter *nc;
|
|
|
|
|
NSMapEnumerator e;
|
|
|
|
|
NSMutableSet *n;
|
|
|
|
|
id o;
|
|
|
|
|
|
|
|
|
|
nc = [NSNotificationCenter defaultCenter];
|
|
|
|
|
e = NSEnumerateMapTable(_observers);
|
|
|
|
|
while (NSNextMapEnumeratorPair(&e, (void**)&o, (void**)&n) != 0)
|
|
|
|
|
{
|
|
|
|
|
NSEnumerator *ne = [n objectEnumerator];
|
|
|
|
|
NSString *name;
|
|
|
|
|
|
|
|
|
|
while (nil != (name = [ne nextObject]))
|
|
|
|
|
{
|
|
|
|
|
[nc removeObserver: o name: name object: nil];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
NSEndMapTableEnumeration(&e);
|
|
|
|
|
NSFreeMapTable(_observers);
|
|
|
|
|
_observers = 0;
|
|
|
|
|
}
|
|
|
|
|
[_names release]; _names = 0;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
[super dealloc];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSString*) description
|
|
|
|
|
{
|
2009-11-18 11:11:29 +00:00
|
|
|
|
NSMutableString *s = [[NSMutableString new] autorelease];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
2008-02-21 16:23:23 +00:00
|
|
|
|
[lock lock];
|
2012-11-29 11:40:04 +00:00
|
|
|
|
NS_DURING
|
2006-03-29 06:46:37 +00:00
|
|
|
|
{
|
2012-11-29 11:40:04 +00:00
|
|
|
|
[s appendFormat: @"Database - %@\n", [self clientName]];
|
|
|
|
|
[s appendFormat: @" Name - %@\n", [self name]];
|
|
|
|
|
[s appendFormat: @" DBase - %@\n", [self database]];
|
|
|
|
|
[s appendFormat: @" DB User - %@\n", [self user]];
|
|
|
|
|
[s appendFormat: @" Password - %@\n",
|
|
|
|
|
[self password] == nil ? @"unknown" : @"known"];
|
|
|
|
|
[s appendFormat: @" Connected - %@\n", connected ? @"yes" : @"no"];
|
|
|
|
|
[s appendFormat: @" Transaction - %@\n",
|
|
|
|
|
_inTransaction ? @"yes" : @"no"];
|
2006-03-29 06:46:37 +00:00
|
|
|
|
}
|
2012-11-29 11:40:04 +00:00
|
|
|
|
NS_HANDLER
|
2006-03-29 06:46:37 +00:00
|
|
|
|
{
|
2012-11-29 11:40:04 +00:00
|
|
|
|
[lock unlock];
|
|
|
|
|
[localException raise];
|
2006-03-29 06:46:37 +00:00
|
|
|
|
}
|
2012-11-29 11:40:04 +00:00
|
|
|
|
NS_ENDHANDLER
|
2008-02-21 16:23:23 +00:00
|
|
|
|
[lock unlock];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
return s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) disconnect
|
|
|
|
|
{
|
2014-05-13 11:01:50 +00:00
|
|
|
|
if (YES == connected)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2007-08-01 15:40:12 +00:00
|
|
|
|
NSNotificationCenter *nc;
|
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
[lock lock];
|
2013-01-31 09:04:35 +00:00
|
|
|
|
if (YES == _inTransaction)
|
|
|
|
|
{
|
|
|
|
|
/* If we are inside a transaction we must be doubly locked,
|
|
|
|
|
* so we do an unlock corresponding to the -begin before we
|
|
|
|
|
* disconnect (the disconnect implicitly rolls back the
|
|
|
|
|
* transaction).
|
|
|
|
|
*/
|
|
|
|
|
_inTransaction = NO;
|
|
|
|
|
[lock unlock];
|
|
|
|
|
}
|
2014-05-13 11:01:50 +00:00
|
|
|
|
if (YES == connected)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
NS_DURING
|
|
|
|
|
{
|
|
|
|
|
[self backendDisconnect];
|
|
|
|
|
}
|
|
|
|
|
NS_HANDLER
|
|
|
|
|
{
|
|
|
|
|
[lock unlock];
|
|
|
|
|
[localException raise];
|
|
|
|
|
}
|
|
|
|
|
NS_ENDHANDLER
|
|
|
|
|
}
|
|
|
|
|
[lock unlock];
|
2007-08-01 15:40:12 +00:00
|
|
|
|
nc = [NSNotificationCenter defaultCenter];
|
|
|
|
|
[nc postNotificationName: SQLClientDidDisconnectNotification
|
|
|
|
|
object: self];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-10-19 18:41:36 +00:00
|
|
|
|
- (NSInteger) execute: (NSString*)stmt, ...
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
NSArray *info;
|
|
|
|
|
va_list ap;
|
|
|
|
|
|
|
|
|
|
va_start (ap, stmt);
|
2015-02-26 16:14:51 +00:00
|
|
|
|
info = [self prepare: stmt args: ap];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
va_end (ap);
|
2012-10-19 18:41:36 +00:00
|
|
|
|
return [self simpleExecute: info];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-10-19 18:41:36 +00:00
|
|
|
|
- (NSInteger) execute: (NSString*)stmt with: (NSDictionary*)values
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
NSArray *info;
|
|
|
|
|
|
2015-06-29 11:59:18 +00:00
|
|
|
|
info = [self prepare: stmt with: values];
|
2012-10-19 18:41:36 +00:00
|
|
|
|
return [self simpleExecute: info];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id) init
|
|
|
|
|
{
|
|
|
|
|
return [self initWithConfiguration: nil name: nil];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id) initWithConfiguration: (NSDictionary*)config
|
|
|
|
|
{
|
|
|
|
|
return [self initWithConfiguration: config name: nil];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id) initWithConfiguration: (NSDictionary*)config
|
|
|
|
|
name: (NSString*)reference
|
2014-06-19 21:26:25 +00:00
|
|
|
|
{
|
2014-06-20 15:39:25 +00:00
|
|
|
|
return [self initWithConfiguration: config name: reference pool: nil];
|
2014-06-19 21:26:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (id) initWithConfiguration: (NSDictionary*)config
|
|
|
|
|
name: (NSString*)reference
|
2014-06-20 15:39:25 +00:00
|
|
|
|
pool: (SQLClientPool*)pool
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
NSNotification *n;
|
2005-07-07 21:11:04 +00:00
|
|
|
|
NSDictionary *conf = config;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
id existing;
|
|
|
|
|
|
2004-06-29 17:11:10 +00:00
|
|
|
|
if (conf == nil)
|
|
|
|
|
{
|
2005-07-07 21:11:04 +00:00
|
|
|
|
// Pretend the defaults object is a dictionary.
|
|
|
|
|
conf = (NSDictionary*)[NSUserDefaults standardUserDefaults];
|
2004-06-29 17:11:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
2004-10-07 09:30:14 +00:00
|
|
|
|
if ([reference isKindOfClass: NSStringClass] == NO)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2004-06-29 17:11:10 +00:00
|
|
|
|
reference = [conf objectForKey: @"SQLClientName"];
|
2004-10-07 09:30:14 +00:00
|
|
|
|
if ([reference isKindOfClass: NSStringClass] == NO)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2015-06-25 08:08:47 +00:00
|
|
|
|
reference = @"Database";
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-06-19 21:26:25 +00:00
|
|
|
|
[clientsLock lock];
|
2014-06-20 15:39:25 +00:00
|
|
|
|
_pool = pool;
|
2014-07-04 13:31:30 +00:00
|
|
|
|
if (nil != _pool)
|
2014-06-19 21:26:25 +00:00
|
|
|
|
{
|
2014-07-04 13:31:30 +00:00
|
|
|
|
existing = nil; // Pool, object ... can't already exist
|
2014-06-19 21:26:25 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2014-06-20 15:39:25 +00:00
|
|
|
|
existing = (SQLClient*)NSMapGet(clientsMap, reference);
|
2014-06-19 21:26:25 +00:00
|
|
|
|
}
|
2014-05-17 09:47:58 +00:00
|
|
|
|
if (nil == existing)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2014-05-07 16:36:06 +00:00
|
|
|
|
lock = [NSRecursiveLock new]; // Ensure thread-safety.
|
2004-04-26 15:13:27 +00:00
|
|
|
|
[self setDebugging: [[self class] debugging]];
|
2004-05-07 08:16:16 +00:00
|
|
|
|
[self setDurationLogging: [[self class] durationLogging]];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
[self setName: reference]; // Set name and store in cache.
|
2004-09-17 14:54:56 +00:00
|
|
|
|
_statements = [NSMutableArray new];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
2004-06-29 17:11:10 +00:00
|
|
|
|
if ([conf isKindOfClass: [NSUserDefaults class]] == YES)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
NSNotificationCenter *nc;
|
|
|
|
|
|
|
|
|
|
nc = [NSNotificationCenter defaultCenter];
|
|
|
|
|
[nc addObserver: self
|
|
|
|
|
selector: @selector(_configure:)
|
|
|
|
|
name: NSUserDefaultsDidChangeNotification
|
2004-06-29 17:11:10 +00:00
|
|
|
|
object: conf];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
2004-06-29 17:11:10 +00:00
|
|
|
|
n = [NSNotification
|
|
|
|
|
notificationWithName: NSUserDefaultsDidChangeNotification
|
|
|
|
|
object: conf
|
|
|
|
|
userInfo: nil];
|
|
|
|
|
|
2014-06-19 21:26:25 +00:00
|
|
|
|
NSHashInsert(clientsHash, (void*)self);
|
2004-04-26 15:13:27 +00:00
|
|
|
|
[self _configure: n]; // Actually set up the configuration.
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2009-11-18 11:11:29 +00:00
|
|
|
|
[self release];
|
|
|
|
|
self = [existing retain];
|
2020-04-06 07:04:08 +00:00
|
|
|
|
|
|
|
|
|
if ([conf isKindOfClass: [NSUserDefaults class]] == NO)
|
|
|
|
|
{
|
|
|
|
|
/* If the configuration object is not the user defaults
|
|
|
|
|
* make sure to update the existing connnection's configuration.
|
|
|
|
|
*/
|
|
|
|
|
n = [NSNotification
|
|
|
|
|
notificationWithName: NSUserDefaultsDidChangeNotification
|
|
|
|
|
object: conf
|
|
|
|
|
userInfo: nil];
|
|
|
|
|
[self _configure: n];
|
|
|
|
|
}
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
2014-06-19 21:26:25 +00:00
|
|
|
|
[clientsLock unlock];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
2012-06-17 14:57:48 +00:00
|
|
|
|
- (NSUInteger) hash
|
|
|
|
|
{
|
|
|
|
|
return [[self database] hash] + [[self user] hash];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (BOOL) isEqual: (id)other
|
|
|
|
|
{
|
|
|
|
|
if (self == other)
|
|
|
|
|
{
|
|
|
|
|
return YES;
|
|
|
|
|
}
|
|
|
|
|
if ([self class] != [other class])
|
|
|
|
|
{
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
if (NO == [[self database] isEqual: [other database]])
|
|
|
|
|
{
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
if (NO == [[self user] isEqual: [other user]])
|
|
|
|
|
{
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
return YES;
|
|
|
|
|
}
|
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
- (BOOL) isInTransaction
|
|
|
|
|
{
|
2004-05-07 08:16:16 +00:00
|
|
|
|
return _inTransaction;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-05-29 09:33:03 +00:00
|
|
|
|
- (NSDate*) lastConnect
|
|
|
|
|
{
|
|
|
|
|
if (_lastConnect > 0.0)
|
|
|
|
|
{
|
|
|
|
|
return [NSDate dateWithTimeIntervalSinceReferenceDate: _lastConnect];
|
|
|
|
|
}
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
- (NSDate*) lastOperation
|
|
|
|
|
{
|
2008-11-12 05:52:38 +00:00
|
|
|
|
if (_lastOperation > 0.0 && _connectFails == 0)
|
2004-10-07 09:30:14 +00:00
|
|
|
|
{
|
|
|
|
|
return [NSDate dateWithTimeIntervalSinceReferenceDate: _lastOperation];
|
|
|
|
|
}
|
|
|
|
|
return nil;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-04-01 11:32:49 +00:00
|
|
|
|
- (BOOL) lockBeforeDate: (NSDate*)limit
|
|
|
|
|
{
|
|
|
|
|
if (nil == limit)
|
|
|
|
|
{
|
|
|
|
|
return [lock tryLock];
|
|
|
|
|
}
|
|
|
|
|
return [lock lockBeforeDate: limit];
|
|
|
|
|
}
|
|
|
|
|
|
2014-06-20 05:15:24 +00:00
|
|
|
|
- (SQLClient*) longestIdle: (SQLClient*)other
|
|
|
|
|
{
|
|
|
|
|
NSTimeInterval t0;
|
|
|
|
|
NSTimeInterval t1;
|
|
|
|
|
|
|
|
|
|
t0 = _lastOperation;
|
|
|
|
|
if (t0 < _lastStart)
|
|
|
|
|
{
|
|
|
|
|
t0 = _lastStart;
|
|
|
|
|
}
|
|
|
|
|
if (NO == connected || 0 != _connectFails)
|
|
|
|
|
{
|
|
|
|
|
t0 = 0.0;
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-27 06:02:43 +00:00
|
|
|
|
if (NO == [other isKindOfClass: SQLClientClass] || YES == [other isProxy])
|
2014-06-20 05:15:24 +00:00
|
|
|
|
{
|
|
|
|
|
t1 = 0.0;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
t1 = other->_lastOperation;
|
|
|
|
|
if (t1 < other->_lastStart)
|
|
|
|
|
{
|
|
|
|
|
t1 = other->_lastStart;
|
|
|
|
|
}
|
2015-05-27 06:02:43 +00:00
|
|
|
|
if (NO == other->connected || 0 != other->_connectFails)
|
2014-06-20 05:15:24 +00:00
|
|
|
|
{
|
|
|
|
|
t1 = 0.0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-28 08:39:10 +00:00
|
|
|
|
if (t1 > 0.0 && (t1 <= t0 || 0.0 == t0))
|
2014-06-20 05:15:24 +00:00
|
|
|
|
{
|
2015-05-27 06:02:43 +00:00
|
|
|
|
return other;
|
2014-06-20 05:15:24 +00:00
|
|
|
|
}
|
2015-05-28 08:39:10 +00:00
|
|
|
|
if (t0 > 0.0 && (t0 <= t1 || 0.0 == t1))
|
2014-06-20 05:15:24 +00:00
|
|
|
|
{
|
2015-05-27 06:02:43 +00:00
|
|
|
|
return self;
|
2014-06-20 05:15:24 +00:00
|
|
|
|
}
|
2015-05-27 06:02:43 +00:00
|
|
|
|
return nil;
|
2014-06-20 05:15:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
- (NSString*) name
|
|
|
|
|
{
|
2004-05-07 08:16:16 +00:00
|
|
|
|
return _name;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSString*) password
|
|
|
|
|
{
|
2004-05-07 08:16:16 +00:00
|
|
|
|
return _password;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-06-29 11:40:38 +00:00
|
|
|
|
- (NSMutableArray*) prepare: (NSString*)stmt, ...
|
|
|
|
|
{
|
|
|
|
|
va_list ap;
|
|
|
|
|
NSMutableArray *result;
|
|
|
|
|
|
|
|
|
|
va_start (ap, stmt);
|
|
|
|
|
result = [self prepare: stmt args: ap];
|
|
|
|
|
va_end (ap);
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-26 16:14:51 +00:00
|
|
|
|
- (NSMutableArray*) prepare: (NSString*)stmt args: (va_list)args
|
|
|
|
|
{
|
|
|
|
|
NSMutableArray *ma = [NSMutableArray arrayWithCapacity: 2];
|
|
|
|
|
NSString *tmp = va_arg(args, NSString*);
|
|
|
|
|
NSAutoreleasePool *arp = [NSAutoreleasePool new];
|
|
|
|
|
|
|
|
|
|
if (tmp != nil)
|
|
|
|
|
{
|
|
|
|
|
NSMutableString *s = [NSMutableString stringWithCapacity: 1024];
|
2017-06-29 08:32:09 +00:00
|
|
|
|
NSString *warn = nil;
|
2017-07-03 14:39:28 +00:00
|
|
|
|
unsigned index = 0;
|
2015-02-26 16:14:51 +00:00
|
|
|
|
|
|
|
|
|
[s appendString: stmt];
|
|
|
|
|
/*
|
|
|
|
|
* Append any values from the nil terminated varargs
|
|
|
|
|
*/
|
2017-06-29 08:32:09 +00:00
|
|
|
|
while (tmp != nil)
|
2017-03-06 17:23:37 +00:00
|
|
|
|
{
|
2017-07-03 14:39:28 +00:00
|
|
|
|
index++;
|
2017-06-29 08:32:09 +00:00
|
|
|
|
if ([tmp isKindOfClass: [NSData class]] == YES)
|
2017-03-06 17:23:37 +00:00
|
|
|
|
{
|
2017-06-29 08:32:09 +00:00
|
|
|
|
[ma addObject: tmp];
|
|
|
|
|
tmp = @"'?'''?'"; // Marker.
|
2017-03-06 17:23:37 +00:00
|
|
|
|
}
|
2017-06-29 08:32:09 +00:00
|
|
|
|
else if ([tmp isKindOfClass: NSStringClass] == NO)
|
2017-03-06 17:23:37 +00:00
|
|
|
|
{
|
2017-07-03 14:05:39 +00:00
|
|
|
|
tmp = [self quote: tmp];
|
2017-06-29 08:32:09 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Class c = object_getClass(tmp);
|
|
|
|
|
|
2017-07-03 14:05:39 +00:00
|
|
|
|
if (c == LitProxyClass)
|
|
|
|
|
{
|
|
|
|
|
tmp = ((SQLLiteralProxy*)tmp)->content;
|
|
|
|
|
}
|
2020-02-27 14:55:59 +00:00
|
|
|
|
else if (c != LitStringClass
|
|
|
|
|
&& c != TinyStringClass
|
|
|
|
|
&& c != SQLStringClass)
|
2017-03-06 17:23:37 +00:00
|
|
|
|
{
|
2017-06-29 08:32:09 +00:00
|
|
|
|
if (nil == warn)
|
2017-03-06 17:23:37 +00:00
|
|
|
|
{
|
2017-07-03 14:39:28 +00:00
|
|
|
|
warn = [NSString stringWithFormat:
|
2020-02-27 14:55:59 +00:00
|
|
|
|
@"\"%@\" (argument %u, %@)",
|
|
|
|
|
tmp, index, NSStringFromClass(c)];
|
2017-03-06 17:23:37 +00:00
|
|
|
|
}
|
2017-06-29 08:32:09 +00:00
|
|
|
|
if (YES == autoquote)
|
2017-03-06 17:23:37 +00:00
|
|
|
|
{
|
2017-06-29 08:32:09 +00:00
|
|
|
|
tmp = [self quote: tmp];
|
2017-03-06 17:23:37 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-06-29 08:32:09 +00:00
|
|
|
|
[s appendString: tmp];
|
|
|
|
|
tmp = va_arg(args, NSString*);
|
2017-03-06 17:23:37 +00:00
|
|
|
|
}
|
2015-02-26 16:14:51 +00:00
|
|
|
|
stmt = s;
|
2017-06-29 08:32:09 +00:00
|
|
|
|
if (nil != warn && YES == autoquoteWarning)
|
|
|
|
|
{
|
|
|
|
|
if (YES == autoquote)
|
|
|
|
|
{
|
2017-07-03 14:39:28 +00:00
|
|
|
|
NSLog(@"SQLClient autoquote performed for %@ in \"%@\"",
|
2017-06-29 08:32:09 +00:00
|
|
|
|
warn, stmt);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2017-07-03 14:39:28 +00:00
|
|
|
|
NSLog(@"SQLClient autoquote proposed for %@ in \"%@\"",
|
2017-06-29 08:32:09 +00:00
|
|
|
|
warn, stmt);
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-02-26 16:14:51 +00:00
|
|
|
|
}
|
2019-03-07 12:06:34 +00:00
|
|
|
|
[ma insertObject: SQLClientMakeLiteral(stmt) atIndex: 0];
|
2015-02-26 16:14:51 +00:00
|
|
|
|
[arp release];
|
|
|
|
|
return ma;
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-29 11:59:18 +00:00
|
|
|
|
- (NSMutableArray*) prepare: (NSString*)stmt with: (NSDictionary*)values
|
|
|
|
|
{
|
|
|
|
|
unsigned int l = [stmt length];
|
|
|
|
|
NSRange r;
|
|
|
|
|
NSMutableArray *ma = [NSMutableArray arrayWithCapacity: 2];
|
|
|
|
|
NSAutoreleasePool *arp = [NSAutoreleasePool new];
|
|
|
|
|
|
|
|
|
|
if (l < 2)
|
|
|
|
|
{
|
2019-03-07 11:05:25 +00:00
|
|
|
|
[ma addObject: SQLClientMakeLiteral(stmt)]; // Can't contain {...}
|
2015-06-29 11:59:18 +00:00
|
|
|
|
}
|
|
|
|
|
else if ((r = [stmt rangeOfString: @"{"]).length == 0)
|
|
|
|
|
{
|
2019-03-07 11:05:25 +00:00
|
|
|
|
[ma addObject: SQLClientMakeLiteral(stmt)]; // No '{' markup
|
2015-06-29 11:59:18 +00:00
|
|
|
|
}
|
|
|
|
|
else if (l - r.location < 2)
|
|
|
|
|
{
|
2019-03-07 11:05:25 +00:00
|
|
|
|
[ma addObject: SQLClientMakeLiteral(stmt)]; // Can't contain {...}
|
2015-06-29 11:59:18 +00:00
|
|
|
|
}
|
|
|
|
|
else if ([stmt rangeOfString: @"}" options: NSLiteralSearch
|
|
|
|
|
range: NSMakeRange(r.location, l - r.location)].length == 0
|
|
|
|
|
&& [stmt rangeOfString: @"{{" options: NSLiteralSearch
|
|
|
|
|
range: NSMakeRange(0, l)].length == 0)
|
|
|
|
|
{
|
2019-03-07 11:05:25 +00:00
|
|
|
|
[ma addObject: SQLClientMakeLiteral(stmt)]; // No '}' or '{{'
|
2015-06-29 11:59:18 +00:00
|
|
|
|
}
|
|
|
|
|
else if (r.length == 0)
|
|
|
|
|
{
|
2019-03-07 11:05:25 +00:00
|
|
|
|
[ma addObject: SQLClientMakeLiteral(stmt)]; // Nothing to do.
|
2015-06-29 11:59:18 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
NSMutableString *mtext = [[stmt mutableCopy] autorelease];
|
2017-06-29 08:32:09 +00:00
|
|
|
|
NSString *warn = nil;
|
2015-06-29 11:59:18 +00:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Replace {FieldName} with the value of the field
|
|
|
|
|
*/
|
|
|
|
|
while (r.length > 0)
|
|
|
|
|
{
|
|
|
|
|
unsigned pos = r.location;
|
|
|
|
|
unsigned nxt;
|
|
|
|
|
unsigned vLength;
|
|
|
|
|
NSArray *a;
|
|
|
|
|
NSRange s;
|
2017-07-03 14:39:28 +00:00
|
|
|
|
NSString *k;
|
2015-06-29 11:59:18 +00:00
|
|
|
|
NSString *v;
|
|
|
|
|
NSString *alt;
|
|
|
|
|
id o;
|
|
|
|
|
unsigned i;
|
|
|
|
|
|
|
|
|
|
r.length = l - pos;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* If the length of the string from the '{' onwards is less than two,
|
|
|
|
|
* there is nothing to do and we can end processing.
|
|
|
|
|
*/
|
|
|
|
|
if (r.length < 2)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ([mtext characterAtIndex: r.location + 1] == '{')
|
|
|
|
|
{
|
|
|
|
|
// Got '{{' ... remove one of them.
|
|
|
|
|
r.length = 1;
|
|
|
|
|
[mtext replaceCharactersInRange: r withString: @""];
|
|
|
|
|
l--;
|
|
|
|
|
r.location++;
|
|
|
|
|
r.length = l - r.location;
|
|
|
|
|
r = [mtext rangeOfString: @"{"
|
|
|
|
|
options: NSLiteralSearch
|
|
|
|
|
range: r];
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
r = [mtext rangeOfString: @"}"
|
|
|
|
|
options: NSLiteralSearch
|
|
|
|
|
range: r];
|
|
|
|
|
if (r.length == 0)
|
|
|
|
|
{
|
|
|
|
|
break; // No closing bracket
|
|
|
|
|
}
|
|
|
|
|
nxt = NSMaxRange(r);
|
|
|
|
|
r = NSMakeRange(pos, nxt - pos);
|
|
|
|
|
s.location = r.location + 1;
|
|
|
|
|
s.length = r.length - 2;
|
2017-07-03 14:39:28 +00:00
|
|
|
|
k = v = [mtext substringWithRange: s];
|
2015-06-29 11:59:18 +00:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* If the value contains a '?', it is actually in two parts,
|
|
|
|
|
* the first part is the field name, and the second part is
|
|
|
|
|
* an alternative text to be used if the value from the
|
|
|
|
|
* dictionary is empty.
|
|
|
|
|
*/
|
|
|
|
|
s = [v rangeOfString: @"?"];
|
|
|
|
|
if (s.length == 0)
|
|
|
|
|
{
|
|
|
|
|
alt = @""; // No alternative value.
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
alt = [v substringFromIndex: NSMaxRange(s)];
|
|
|
|
|
v = [v substringToIndex: s.location];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* If the value we are substituting contains dots, we split it apart.
|
|
|
|
|
* We use the value to make a reference into the dictionary we are
|
|
|
|
|
* given.
|
|
|
|
|
*/
|
|
|
|
|
a = [v componentsSeparatedByString: @"."];
|
|
|
|
|
o = values;
|
|
|
|
|
for (i = 0; i < [a count]; i++)
|
|
|
|
|
{
|
|
|
|
|
NSString *k = [a objectAtIndex: i];
|
|
|
|
|
|
|
|
|
|
if ([k length] > 0)
|
|
|
|
|
{
|
|
|
|
|
o = [(NSDictionary*)o objectForKey: k];
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-04-16 15:45:25 +00:00
|
|
|
|
if (o == values || o == nil)
|
2015-06-29 11:59:18 +00:00
|
|
|
|
{
|
|
|
|
|
v = nil; // Mo match found.
|
|
|
|
|
}
|
2017-06-29 08:32:09 +00:00
|
|
|
|
else
|
2017-03-06 17:23:37 +00:00
|
|
|
|
{
|
|
|
|
|
if ([o isKindOfClass: [NSData class]] == YES)
|
|
|
|
|
{
|
|
|
|
|
[ma addObject: o];
|
|
|
|
|
v = @"'?'''?'";
|
|
|
|
|
}
|
2017-06-29 08:32:09 +00:00
|
|
|
|
else if ([o isKindOfClass: NSStringClass] == NO)
|
2017-03-06 17:23:37 +00:00
|
|
|
|
{
|
2017-07-03 14:05:39 +00:00
|
|
|
|
v = [self quote: o];
|
2017-03-06 17:23:37 +00:00
|
|
|
|
}
|
2017-06-29 08:32:09 +00:00
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Class c = object_getClass(o);
|
|
|
|
|
|
|
|
|
|
v = o;
|
2017-07-03 14:05:39 +00:00
|
|
|
|
if (c == LitProxyClass)
|
|
|
|
|
{
|
|
|
|
|
v = ((SQLLiteralProxy*)o)->content;
|
|
|
|
|
}
|
2020-02-27 14:55:59 +00:00
|
|
|
|
else if (c != LitStringClass
|
|
|
|
|
&& c != TinyStringClass
|
|
|
|
|
&& c != SQLStringClass)
|
2017-06-29 08:32:09 +00:00
|
|
|
|
{
|
|
|
|
|
if (nil == warn)
|
|
|
|
|
{
|
2017-07-03 14:39:28 +00:00
|
|
|
|
warn = [NSString stringWithFormat:
|
2020-02-27 14:55:59 +00:00
|
|
|
|
@"\"%@\" (value for \"%@\", %@)",
|
|
|
|
|
o, k, NSStringFromClass(c)];
|
2017-06-29 08:32:09 +00:00
|
|
|
|
}
|
|
|
|
|
if (YES == autoquote)
|
|
|
|
|
{
|
|
|
|
|
v = [self quote: o];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-03-06 17:23:37 +00:00
|
|
|
|
}
|
2015-06-29 11:59:18 +00:00
|
|
|
|
|
|
|
|
|
if ([v length] == 0)
|
|
|
|
|
{
|
|
|
|
|
v = alt;
|
|
|
|
|
if (v == nil)
|
|
|
|
|
{
|
|
|
|
|
v = @"";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
vLength = [v length];
|
|
|
|
|
|
|
|
|
|
[mtext replaceCharactersInRange: r withString: v];
|
|
|
|
|
l += vLength; // Add length of string inserted
|
|
|
|
|
l -= r.length; // Remove length of string replaced
|
|
|
|
|
r.location += vLength;
|
|
|
|
|
|
|
|
|
|
if (r.location >= l)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
r.length = l - r.location;
|
|
|
|
|
r = [mtext rangeOfString: @"{"
|
|
|
|
|
options: NSLiteralSearch
|
|
|
|
|
range: r];
|
|
|
|
|
}
|
2019-03-09 09:39:34 +00:00
|
|
|
|
[ma insertObject: SQLClientProxyLiteral(mtext) atIndex: 0];
|
2017-06-29 08:32:09 +00:00
|
|
|
|
if (nil != warn && YES == autoquoteWarning)
|
|
|
|
|
{
|
|
|
|
|
if (YES == autoquote)
|
|
|
|
|
{
|
2017-07-03 14:39:28 +00:00
|
|
|
|
NSLog(@"SQLClient autoquote performed for %@ in \"%@\"",
|
2017-06-29 08:32:09 +00:00
|
|
|
|
warn, mtext);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2017-07-03 14:39:28 +00:00
|
|
|
|
NSLog(@"SQLClient autoquote proposed for %@ in \"%@\"",
|
2017-06-29 08:32:09 +00:00
|
|
|
|
warn, mtext);
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-06-29 11:59:18 +00:00
|
|
|
|
}
|
|
|
|
|
[arp release];
|
|
|
|
|
return ma;
|
|
|
|
|
}
|
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
- (NSMutableArray*) query: (NSString*)stmt, ...
|
|
|
|
|
{
|
|
|
|
|
va_list ap;
|
|
|
|
|
NSMutableArray *result = nil;
|
2017-06-30 10:39:24 +00:00
|
|
|
|
SQLLiteral *query;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* First check validity and concatenate parts of the query.
|
|
|
|
|
*/
|
|
|
|
|
va_start (ap, stmt);
|
2017-06-30 10:39:24 +00:00
|
|
|
|
query = [[self prepare: stmt args: ap] objectAtIndex: 0];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
va_end (ap);
|
|
|
|
|
|
2017-06-30 10:39:24 +00:00
|
|
|
|
result = [self simpleQuery: query];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSMutableArray*) query: (NSString*)stmt with: (NSDictionary*)values
|
|
|
|
|
{
|
|
|
|
|
NSMutableArray *result = nil;
|
2017-06-30 10:39:24 +00:00
|
|
|
|
SQLLiteral *query;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
2017-06-30 10:39:24 +00:00
|
|
|
|
query = [[self prepare: stmt with: values] objectAtIndex: 0];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
2017-06-30 10:39:24 +00:00
|
|
|
|
result = [self simpleQuery: query];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-30 10:39:24 +00:00
|
|
|
|
- (SQLLiteral*) quote: (id)obj
|
2004-10-08 09:29:00 +00:00
|
|
|
|
{
|
2017-07-03 20:09:07 +00:00
|
|
|
|
/* For a nil object, we return NULL.
|
2004-04-26 15:13:27 +00:00
|
|
|
|
*/
|
2005-09-28 06:20:43 +00:00
|
|
|
|
if (obj == nil || obj == null)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2017-06-30 10:39:24 +00:00
|
|
|
|
return (SQLLiteral*)@"NULL";
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
2004-10-07 09:30:14 +00:00
|
|
|
|
else if ([obj isKindOfClass: NSStringClass] == NO)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2017-07-03 20:09:07 +00:00
|
|
|
|
/* For a data object, we don't quote ... the other parts of the code
|
2004-10-07 09:30:14 +00:00
|
|
|
|
* need to know they have an NSData object and pass it on unchanged
|
|
|
|
|
* to the -backendExecute: method.
|
|
|
|
|
*/
|
|
|
|
|
if ([obj isKindOfClass: [NSData class]] == YES)
|
|
|
|
|
{
|
|
|
|
|
return obj;
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-04 11:13:16 +00:00
|
|
|
|
SQLLiteral *tmp = [obj quoteForSQLClient: self];
|
|
|
|
|
if (nil == tmp)
|
2017-07-03 20:09:07 +00:00
|
|
|
|
{
|
2017-07-04 11:13:16 +00:00
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"Attempt to SQL quote instance of unsupported class: %@",
|
|
|
|
|
obj];
|
2017-07-03 20:09:07 +00:00
|
|
|
|
}
|
2017-07-04 11:13:16 +00:00
|
|
|
|
return tmp;
|
2004-10-07 09:30:14 +00:00
|
|
|
|
}
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
|
|
|
|
/* Get a string description of the object. */
|
2006-05-25 11:34:03 +00:00
|
|
|
|
obj = [self quoteString: obj];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
|
|
|
|
return obj;
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-30 10:39:24 +00:00
|
|
|
|
- (SQLLiteral*) quotef: (NSString*)fmt, ...
|
2004-10-08 09:29:00 +00:00
|
|
|
|
{
|
2006-05-25 11:34:03 +00:00
|
|
|
|
va_list ap;
|
|
|
|
|
NSString *str;
|
2017-06-30 10:39:24 +00:00
|
|
|
|
SQLLiteral *quoted;
|
2004-10-08 09:29:00 +00:00
|
|
|
|
|
|
|
|
|
va_start(ap, fmt);
|
2006-05-25 11:34:03 +00:00
|
|
|
|
str = [[NSString allocWithZone: NSDefaultMallocZone()]
|
2004-10-08 09:29:00 +00:00
|
|
|
|
initWithFormat: fmt arguments: ap];
|
|
|
|
|
va_end(ap);
|
|
|
|
|
|
2006-05-25 11:34:03 +00:00
|
|
|
|
quoted = [self quoteString: str];
|
2009-11-18 11:11:29 +00:00
|
|
|
|
[str release];
|
2006-05-25 11:34:03 +00:00
|
|
|
|
return quoted;
|
2004-10-08 09:29:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-06-30 12:43:45 +00:00
|
|
|
|
- (SQLLiteral*) quoteArray: (NSArray *)a
|
2017-06-30 10:39:24 +00:00
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSGenericException
|
|
|
|
|
format: @"%@ not supported for this database", NSStringFromSelector(_cmd)];
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-11 17:16:14 +00:00
|
|
|
|
- (NSMutableString*) quoteArray: (NSArray *)a
|
|
|
|
|
toString: (NSMutableString *)s
|
|
|
|
|
quotingStrings: (BOOL)q
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSGenericException
|
|
|
|
|
format: @"%@ not supported for this database", NSStringFromSelector(_cmd)];
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-30 10:39:24 +00:00
|
|
|
|
- (SQLLiteral*) quoteBigInteger: (int64_t)i
|
2008-11-12 05:52:38 +00:00
|
|
|
|
{
|
2017-03-06 17:23:37 +00:00
|
|
|
|
char buf[32];
|
|
|
|
|
unsigned len;
|
2017-06-30 10:39:24 +00:00
|
|
|
|
SQLLiteral *s;
|
2017-03-06 17:23:37 +00:00
|
|
|
|
|
|
|
|
|
len = sprintf(buf, "%"PRId64, i);
|
2017-06-29 10:30:33 +00:00
|
|
|
|
s = SQLClientNewLiteral(buf, len);
|
2017-03-06 17:23:37 +00:00
|
|
|
|
return [s autorelease];
|
2008-11-12 05:52:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-11-22 10:57:19 +00:00
|
|
|
|
- (SQLLiteral*) quoteBoolean: (int)i
|
|
|
|
|
{
|
|
|
|
|
return (SQLLiteral*)(i ? @"true" : @"false");
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-30 10:39:24 +00:00
|
|
|
|
- (SQLLiteral*) quoteCString: (const char *)s
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2006-05-25 11:34:03 +00:00
|
|
|
|
NSString *str;
|
2017-06-30 10:39:24 +00:00
|
|
|
|
SQLLiteral *quoted;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
2006-05-25 11:34:03 +00:00
|
|
|
|
if (s == 0)
|
|
|
|
|
{
|
|
|
|
|
s = "";
|
|
|
|
|
}
|
|
|
|
|
str = [[NSString alloc] initWithCString: s];
|
|
|
|
|
quoted = [self quoteString: str];
|
2009-11-18 11:11:29 +00:00
|
|
|
|
[str release];
|
2006-05-25 11:34:03 +00:00
|
|
|
|
return quoted;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-06-30 10:39:24 +00:00
|
|
|
|
- (SQLLiteral*) quoteChar: (char)c
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2006-05-25 11:34:03 +00:00
|
|
|
|
NSString *str;
|
2017-06-30 10:39:24 +00:00
|
|
|
|
SQLLiteral *quoted;
|
2006-05-25 11:34:03 +00:00
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
if (c == 0)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"Attempt to quote a nul character in -quoteChar:"];
|
|
|
|
|
}
|
2006-05-25 11:34:03 +00:00
|
|
|
|
str = [[NSString alloc] initWithFormat: @"%c", c];
|
|
|
|
|
quoted = [self quoteString: str];
|
2009-11-18 11:11:29 +00:00
|
|
|
|
[str release];
|
2006-05-25 11:34:03 +00:00
|
|
|
|
return quoted;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-11-23 13:46:01 +00:00
|
|
|
|
- (SQLLiteral*) quoteFloat: (double)f
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2017-03-06 17:23:37 +00:00
|
|
|
|
char buf[32];
|
|
|
|
|
unsigned len;
|
2017-06-30 10:39:24 +00:00
|
|
|
|
SQLLiteral *s;
|
2017-03-06 17:23:37 +00:00
|
|
|
|
|
2018-11-23 13:46:01 +00:00
|
|
|
|
len = sprintf(buf, "%.17g", f);
|
2017-06-29 10:30:33 +00:00
|
|
|
|
s = SQLClientNewLiteral(buf, len);
|
2017-03-06 17:23:37 +00:00
|
|
|
|
return [s autorelease];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-06-30 10:39:24 +00:00
|
|
|
|
- (SQLLiteral*) quoteInteger: (int)i
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2017-03-06 17:23:37 +00:00
|
|
|
|
char buf[32];
|
|
|
|
|
unsigned len;
|
2017-06-30 10:39:24 +00:00
|
|
|
|
SQLLiteral *s;
|
2017-03-06 17:23:37 +00:00
|
|
|
|
|
|
|
|
|
len = sprintf(buf, "%i", i);
|
2017-06-29 10:30:33 +00:00
|
|
|
|
s = SQLClientNewLiteral(buf, len);
|
2017-03-06 17:23:37 +00:00
|
|
|
|
return [s autorelease];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-06-30 10:39:24 +00:00
|
|
|
|
- (SQLLiteral*) quoteName: (NSString *)s
|
2017-06-29 12:53:11 +00:00
|
|
|
|
{
|
|
|
|
|
NSData *d = [s dataUsingEncoding: NSUTF8StringEncoding];
|
|
|
|
|
const char *src = (const char*)[d bytes];
|
|
|
|
|
char *dst;
|
|
|
|
|
unsigned len = [d length];
|
|
|
|
|
unsigned count = 2;
|
|
|
|
|
unsigned i;
|
|
|
|
|
SQLString *q;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < len; i++)
|
|
|
|
|
{
|
|
|
|
|
char c = src[i];
|
|
|
|
|
|
|
|
|
|
if ('\"' == c)
|
|
|
|
|
{
|
|
|
|
|
count++; // A quote needs to be doubled
|
|
|
|
|
}
|
|
|
|
|
if ('\0' != c)
|
|
|
|
|
{
|
|
|
|
|
count++; // A nul needs to be ignored
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
q = NSAllocateObject(SQLStringClass, count + 1, NSDefaultMallocZone());
|
2020-02-14 17:04:24 +00:00
|
|
|
|
dst = ((char*)(void*)q) + SQLStringSize;
|
|
|
|
|
q->utf8Bytes = (uint8_t*)dst;
|
|
|
|
|
q->byteLen = count;
|
2017-06-29 12:53:11 +00:00
|
|
|
|
*dst++ = '\"';
|
|
|
|
|
for (i = 0; i < len; i++)
|
|
|
|
|
{
|
|
|
|
|
char c = src[i];
|
|
|
|
|
|
|
|
|
|
if ('\"' == c)
|
|
|
|
|
{
|
|
|
|
|
*dst++ = '\"';
|
|
|
|
|
}
|
|
|
|
|
if ('\0' != c)
|
|
|
|
|
{
|
|
|
|
|
*dst++ = c;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
*dst++ = '\"';
|
|
|
|
|
*dst = '\0';
|
2020-02-14 17:04:24 +00:00
|
|
|
|
q->charLen = lengthUTF8(q->utf8Bytes, q->byteLen, &q->ascii, &q->latin1);
|
2017-06-29 12:53:11 +00:00
|
|
|
|
return [q autorelease];
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-30 10:39:24 +00:00
|
|
|
|
- (SQLLiteral*) quoteSet: (id)obj
|
2017-06-29 13:41:36 +00:00
|
|
|
|
{
|
|
|
|
|
NSEnumerator *enumerator = [obj objectEnumerator];
|
2017-07-04 11:13:16 +00:00
|
|
|
|
NSMutableString *ms = [NSMutableString stringWithCapacity: 100];
|
2017-06-29 13:41:36 +00:00
|
|
|
|
id value = [enumerator nextObject];
|
|
|
|
|
|
|
|
|
|
[ms appendString: @"("];
|
|
|
|
|
if (value != nil)
|
|
|
|
|
{
|
|
|
|
|
[ms appendString: [self quote: value]];
|
|
|
|
|
}
|
|
|
|
|
while ((value = [enumerator nextObject]) != nil)
|
|
|
|
|
{
|
|
|
|
|
[ms appendString: @","];
|
|
|
|
|
[ms appendString: [self quote: value]];
|
|
|
|
|
}
|
|
|
|
|
[ms appendString: @")"];
|
2017-06-30 10:39:24 +00:00
|
|
|
|
return SQLClientMakeLiteral(ms);
|
2017-06-29 13:41:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-06-30 10:39:24 +00:00
|
|
|
|
- (SQLLiteral*) quoteString: (NSString *)s
|
2006-05-25 11:34:03 +00:00
|
|
|
|
{
|
2017-03-06 17:23:37 +00:00
|
|
|
|
NSData *d = [s dataUsingEncoding: NSUTF8StringEncoding];
|
|
|
|
|
const char *src = (const char*)[d bytes];
|
|
|
|
|
char *dst;
|
|
|
|
|
unsigned len = [d length];
|
|
|
|
|
unsigned count = 2;
|
|
|
|
|
unsigned i;
|
|
|
|
|
SQLString *q;
|
2006-05-25 11:34:03 +00:00
|
|
|
|
|
2017-03-06 17:23:37 +00:00
|
|
|
|
for (i = 0; i < len; i++)
|
2006-05-25 11:34:03 +00:00
|
|
|
|
{
|
2017-03-06 17:23:37 +00:00
|
|
|
|
char c = src[i];
|
2006-05-25 11:34:03 +00:00
|
|
|
|
|
2017-03-06 17:23:37 +00:00
|
|
|
|
if ('\'' == c)
|
|
|
|
|
{
|
|
|
|
|
count++; // A quote needs to be doubled
|
|
|
|
|
}
|
|
|
|
|
if ('\0' != c)
|
|
|
|
|
{
|
|
|
|
|
count++; // A nul needs to be ignored
|
|
|
|
|
}
|
2006-05-25 11:34:03 +00:00
|
|
|
|
}
|
2017-03-06 17:23:37 +00:00
|
|
|
|
q = NSAllocateObject(SQLStringClass, count + 1, NSDefaultMallocZone());
|
2020-02-14 17:04:24 +00:00
|
|
|
|
dst = ((char*)(void*)q) + SQLStringSize;
|
|
|
|
|
q->utf8Bytes = (uint8_t*)dst;
|
|
|
|
|
q->byteLen = count;
|
2017-03-06 17:23:37 +00:00
|
|
|
|
*dst++ = '\'';
|
|
|
|
|
for (i = 0; i < len; i++)
|
2006-05-25 11:34:03 +00:00
|
|
|
|
{
|
2017-03-06 17:23:37 +00:00
|
|
|
|
char c = src[i];
|
2006-05-25 11:34:03 +00:00
|
|
|
|
|
2017-03-06 17:23:37 +00:00
|
|
|
|
if ('\'' == c)
|
2006-05-25 11:34:03 +00:00
|
|
|
|
{
|
2017-03-06 17:23:37 +00:00
|
|
|
|
*dst++ = '\'';
|
2006-05-25 11:34:03 +00:00
|
|
|
|
}
|
2017-03-06 17:23:37 +00:00
|
|
|
|
if ('\0' != c)
|
|
|
|
|
{
|
|
|
|
|
*dst++ = c;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
*dst++ = '\'';
|
|
|
|
|
*dst = '\0';
|
2020-02-14 17:04:24 +00:00
|
|
|
|
q->charLen = lengthUTF8(q->utf8Bytes, q->byteLen, &q->ascii, &q->latin1);
|
2017-03-06 17:23:37 +00:00
|
|
|
|
return [q autorelease];
|
2006-05-25 11:34:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-01-06 15:39:11 +00:00
|
|
|
|
- (oneway void) release
|
2006-09-10 13:37:23 +00:00
|
|
|
|
{
|
2020-02-27 14:55:59 +00:00
|
|
|
|
/* We lock the table while checking, to prevent
|
|
|
|
|
* another thread from grabbing this object while we are
|
|
|
|
|
* checking it.
|
|
|
|
|
* If we are going to deallocate the object, we first remove
|
|
|
|
|
* it from the table so that no other thread will find it
|
|
|
|
|
* and try to use it while it is being deallocated.
|
2006-09-10 13:37:23 +00:00
|
|
|
|
*/
|
2014-06-19 21:26:25 +00:00
|
|
|
|
[clientsLock lock];
|
2019-05-03 15:45:54 +00:00
|
|
|
|
if (nil != _pool && [self retainCount] == 1)
|
|
|
|
|
{
|
2020-02-27 14:55:59 +00:00
|
|
|
|
/* If this is the only reference to a client associated with
|
2019-05-03 15:45:54 +00:00
|
|
|
|
* a connection pool we put this client back to the pool.
|
|
|
|
|
*
|
|
|
|
|
* wl 2019-05-01: The original implementation was calling this code
|
|
|
|
|
* when NSDecrementExtraRefCountWasZero returns YES, but
|
|
|
|
|
* this does not work with newer versions of the GNUstep
|
|
|
|
|
* Objective-C runtime nor with recent versions of Apple's
|
|
|
|
|
* Objective-C runtime, which both don't handle resurrection
|
|
|
|
|
* gracefully for objects whose retain count has become zero.
|
|
|
|
|
*/
|
|
|
|
|
[_pool _swallowClient: self explicit: NO];
|
|
|
|
|
}
|
|
|
|
|
else
|
2006-09-10 13:37:23 +00:00
|
|
|
|
{
|
2019-05-03 15:45:54 +00:00
|
|
|
|
[super release];
|
2006-09-10 13:37:23 +00:00
|
|
|
|
}
|
2020-02-27 14:55:59 +00:00
|
|
|
|
[clientsLock unlock];
|
2006-09-10 13:37:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-06-25 13:38:41 +00:00
|
|
|
|
- (SQLClientPool*) pool
|
|
|
|
|
{
|
|
|
|
|
return _pool;
|
|
|
|
|
}
|
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
- (void) rollback
|
|
|
|
|
{
|
|
|
|
|
[lock lock];
|
2011-09-16 07:25:33 +00:00
|
|
|
|
if (NO == _inTransaction)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2011-09-16 07:25:33 +00:00
|
|
|
|
[lock unlock]; // Not in a transaction ... nothing to do.
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Since we are in a transaction we must be doubly locked right now,
|
|
|
|
|
* so we unlock once, and we still have the lock (which was locked
|
|
|
|
|
* in the earlier call to the -begin method).
|
|
|
|
|
*/
|
|
|
|
|
[lock unlock];
|
|
|
|
|
_inTransaction = NO;
|
|
|
|
|
NS_DURING
|
|
|
|
|
{
|
2017-08-29 20:35:59 +00:00
|
|
|
|
/* If the connection was lost, the transaction has implicitly
|
|
|
|
|
* rolled back anyway, so we should skip the rollback statement.
|
|
|
|
|
*/
|
|
|
|
|
if (YES == [self connected])
|
|
|
|
|
{
|
|
|
|
|
[self simpleExecute: rollbackStatement];
|
|
|
|
|
}
|
2007-07-07 07:23:40 +00:00
|
|
|
|
[_statements removeAllObjects];
|
2011-09-16 07:25:33 +00:00
|
|
|
|
[lock unlock]; // Locked by -begin
|
|
|
|
|
}
|
|
|
|
|
NS_HANDLER
|
|
|
|
|
{
|
|
|
|
|
[_statements removeAllObjects];
|
|
|
|
|
[lock unlock]; // Locked by -begin
|
|
|
|
|
[localException raise];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
2011-09-16 07:25:33 +00:00
|
|
|
|
NS_ENDHANDLER
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-06-26 12:06:13 +00:00
|
|
|
|
- (void) setClientName: (NSString*)s
|
|
|
|
|
{
|
|
|
|
|
[lock lock];
|
|
|
|
|
ASSIGNCOPY(_client, s);
|
|
|
|
|
[lock unlock];
|
|
|
|
|
}
|
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
- (void) setDatabase: (NSString*)s
|
|
|
|
|
{
|
2014-05-13 11:01:50 +00:00
|
|
|
|
[lock lock];
|
|
|
|
|
NS_DURING
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2014-05-13 11:01:50 +00:00
|
|
|
|
if ([s isEqual: _database] == NO)
|
|
|
|
|
{
|
2014-09-10 11:49:40 +00:00
|
|
|
|
if (YES == connected)
|
2014-05-13 11:01:50 +00:00
|
|
|
|
{
|
|
|
|
|
[self disconnect];
|
|
|
|
|
}
|
|
|
|
|
s = [s copy];
|
|
|
|
|
[_database release];
|
|
|
|
|
_database = s;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
NS_HANDLER
|
|
|
|
|
{
|
|
|
|
|
[lock unlock];
|
|
|
|
|
[localException raise];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
2014-05-13 11:01:50 +00:00
|
|
|
|
NS_ENDHANDLER
|
|
|
|
|
[lock unlock];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) setName: (NSString*)s
|
|
|
|
|
{
|
2014-05-13 11:01:50 +00:00
|
|
|
|
[lock lock];
|
|
|
|
|
NS_DURING
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2014-05-13 11:01:50 +00:00
|
|
|
|
if ([s isEqual: _name] == NO)
|
|
|
|
|
{
|
2014-06-19 21:26:25 +00:00
|
|
|
|
[clientsLock lock];
|
2014-06-20 15:39:25 +00:00
|
|
|
|
if (nil == _pool)
|
2014-05-13 11:01:50 +00:00
|
|
|
|
{
|
2014-06-19 21:26:25 +00:00
|
|
|
|
if (NSMapGet(clientsMap, s) != 0)
|
2014-05-13 11:01:50 +00:00
|
|
|
|
{
|
2014-06-19 21:26:25 +00:00
|
|
|
|
[clientsLock unlock];
|
|
|
|
|
[lock unlock];
|
|
|
|
|
if ([self debugging] > 0)
|
|
|
|
|
{
|
|
|
|
|
[self
|
|
|
|
|
debug: @"Error attempt to re-use client name %@", s];
|
|
|
|
|
}
|
|
|
|
|
NS_VOIDRETURN;
|
2014-05-13 11:01:50 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2014-09-10 11:49:40 +00:00
|
|
|
|
if (YES == connected)
|
2014-05-13 11:01:50 +00:00
|
|
|
|
{
|
|
|
|
|
[self disconnect];
|
|
|
|
|
}
|
2014-07-04 13:11:47 +00:00
|
|
|
|
if (_name != nil
|
|
|
|
|
&& (SQLClient*)NSMapGet(clientsMap, (void*)_name) == self)
|
2014-05-13 11:01:50 +00:00
|
|
|
|
{
|
|
|
|
|
NSMapRemove(clientsMap, (void*)_name);
|
|
|
|
|
}
|
|
|
|
|
s = [s copy];
|
|
|
|
|
[_name release];
|
|
|
|
|
_name = s;
|
2015-06-26 12:06:13 +00:00
|
|
|
|
if (nil == _client)
|
|
|
|
|
{
|
|
|
|
|
_client
|
|
|
|
|
= [[[NSProcessInfo processInfo] globallyUniqueString] retain];
|
|
|
|
|
}
|
2014-06-20 15:39:25 +00:00
|
|
|
|
if (nil == _pool && _name != nil)
|
2014-06-19 21:26:25 +00:00
|
|
|
|
{
|
|
|
|
|
NSMapInsert(clientsMap, (void*)_name, (void*)self);
|
|
|
|
|
}
|
|
|
|
|
[clientsLock unlock];
|
2005-08-03 07:03:28 +00:00
|
|
|
|
}
|
2014-05-13 11:01:50 +00:00
|
|
|
|
}
|
|
|
|
|
NS_HANDLER
|
|
|
|
|
{
|
2004-04-26 15:13:27 +00:00
|
|
|
|
[lock unlock];
|
2014-05-13 11:01:50 +00:00
|
|
|
|
[localException raise];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
2014-05-13 11:01:50 +00:00
|
|
|
|
NS_ENDHANDLER
|
|
|
|
|
[lock unlock];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) setPassword: (NSString*)s
|
|
|
|
|
{
|
2014-05-13 11:01:50 +00:00
|
|
|
|
[lock lock];
|
|
|
|
|
NS_DURING
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2014-05-13 11:01:50 +00:00
|
|
|
|
if ([s isEqual: _password] == NO)
|
|
|
|
|
{
|
2014-09-10 11:49:40 +00:00
|
|
|
|
if (YES == connected)
|
2014-05-13 11:01:50 +00:00
|
|
|
|
{
|
|
|
|
|
[self disconnect];
|
|
|
|
|
}
|
|
|
|
|
s = [s copy];
|
|
|
|
|
[_password release];
|
|
|
|
|
_password = s;
|
|
|
|
|
}
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
2014-05-13 11:01:50 +00:00
|
|
|
|
NS_HANDLER
|
|
|
|
|
{
|
|
|
|
|
[lock unlock];
|
|
|
|
|
[localException raise];
|
|
|
|
|
}
|
|
|
|
|
NS_ENDHANDLER
|
|
|
|
|
[lock unlock];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-04-10 15:03:55 +00:00
|
|
|
|
- (void) setShouldTrim: (BOOL)aFlag
|
|
|
|
|
{
|
|
|
|
|
_shouldTrim = (YES == aFlag) ? YES : NO;
|
|
|
|
|
}
|
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
- (void) setUser: (NSString*)s
|
|
|
|
|
{
|
2014-05-13 11:01:50 +00:00
|
|
|
|
[lock lock];
|
|
|
|
|
NS_DURING
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2014-09-10 11:36:20 +00:00
|
|
|
|
if ([s isEqual: _user] == NO)
|
2014-05-13 11:01:50 +00:00
|
|
|
|
{
|
2014-09-10 11:49:40 +00:00
|
|
|
|
if (YES == connected)
|
2014-05-13 11:01:50 +00:00
|
|
|
|
{
|
|
|
|
|
[self disconnect];
|
|
|
|
|
}
|
|
|
|
|
s = [s copy];
|
|
|
|
|
[_user release];
|
|
|
|
|
_user = s;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
NS_HANDLER
|
|
|
|
|
{
|
|
|
|
|
[lock unlock];
|
|
|
|
|
[localException raise];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
2014-05-13 11:01:50 +00:00
|
|
|
|
NS_ENDHANDLER
|
|
|
|
|
[lock unlock];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-04-28 08:37:59 +00:00
|
|
|
|
- (NSInteger) simpleExecute: (id)info
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2012-10-19 18:41:36 +00:00
|
|
|
|
NSInteger result;
|
2013-01-31 09:04:35 +00:00
|
|
|
|
NSString *debug = nil;
|
2017-08-27 15:13:10 +00:00
|
|
|
|
BOOL done = NO;
|
2017-08-27 17:06:29 +00:00
|
|
|
|
BOOL isCommit = NO;
|
|
|
|
|
BOOL isRollback = NO;
|
|
|
|
|
NSString *statement;
|
2004-09-17 14:54:56 +00:00
|
|
|
|
|
2017-04-28 08:37:59 +00:00
|
|
|
|
if ([info isKindOfClass: NSArrayClass] == NO)
|
|
|
|
|
{
|
|
|
|
|
if ([info isKindOfClass: NSStringClass] == NO)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"[%@ -simpleExecute: %@ (class %@)]",
|
|
|
|
|
NSStringFromClass([self class]),
|
|
|
|
|
info,
|
|
|
|
|
NSStringFromClass([info class])];
|
|
|
|
|
}
|
|
|
|
|
info = [NSMutableArray arrayWithObject: info];
|
|
|
|
|
}
|
|
|
|
|
|
2004-10-07 09:30:14 +00:00
|
|
|
|
[lock lock];
|
2017-08-27 17:06:29 +00:00
|
|
|
|
|
|
|
|
|
statement = [info objectAtIndex: 0];
|
2020-03-21 17:15:27 +00:00
|
|
|
|
|
|
|
|
|
/* Ensure we have a working connection.
|
|
|
|
|
*/
|
|
|
|
|
if ([self connect] == NO)
|
|
|
|
|
{
|
|
|
|
|
[lock unlock];
|
|
|
|
|
[NSException raise: SQLConnectionException
|
|
|
|
|
format: @"Unable to connect to '%@' to run statement %@",
|
|
|
|
|
[self name], statement];
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-27 17:06:29 +00:00
|
|
|
|
if ([statement isEqualToString: commitString])
|
|
|
|
|
{
|
|
|
|
|
isCommit = YES;
|
|
|
|
|
}
|
|
|
|
|
if ([statement isEqualToString: rollbackString])
|
|
|
|
|
{
|
|
|
|
|
isRollback = YES;
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-27 15:13:10 +00:00
|
|
|
|
while (NO == done)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2017-08-27 17:06:29 +00:00
|
|
|
|
debug = nil;
|
2017-08-27 15:13:10 +00:00
|
|
|
|
done = YES;
|
|
|
|
|
NS_DURING
|
|
|
|
|
{
|
2020-03-21 17:15:27 +00:00
|
|
|
|
_lastStart = GSTickerTimeNow();
|
2017-08-27 15:13:10 +00:00
|
|
|
|
result = [self backendExecute: info];
|
|
|
|
|
_lastOperation = GSTickerTimeNow();
|
|
|
|
|
[_statements addObject: statement];
|
|
|
|
|
if (_duration >= 0)
|
|
|
|
|
{
|
|
|
|
|
NSTimeInterval d;
|
|
|
|
|
|
|
|
|
|
d = _lastOperation - _lastStart;
|
|
|
|
|
if (d >= _duration)
|
|
|
|
|
{
|
|
|
|
|
if (isCommit || isRollback)
|
|
|
|
|
{
|
2017-08-27 15:37:21 +00:00
|
|
|
|
NSEnumerator *e = [_statements objectEnumerator];
|
|
|
|
|
NSMutableString *m;
|
2017-08-27 15:13:10 +00:00
|
|
|
|
|
|
|
|
|
if (isCommit)
|
|
|
|
|
{
|
|
|
|
|
m = [NSMutableString stringWithFormat:
|
|
|
|
|
@"Duration %g for transaction commit ...\n", d];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
m = [NSMutableString stringWithFormat:
|
|
|
|
|
@"Duration %g for transaction rollback ...\n", d];
|
|
|
|
|
}
|
|
|
|
|
while ((statement = [e nextObject]) != nil)
|
|
|
|
|
{
|
|
|
|
|
[m appendFormat: @" %@;\n", statement];
|
|
|
|
|
}
|
|
|
|
|
debug = m;
|
|
|
|
|
}
|
|
|
|
|
else if ([self debugging] > 1)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* For higher debug levels, we log data objects as well
|
|
|
|
|
* as the query string, otherwise we omit them.
|
|
|
|
|
*/
|
|
|
|
|
debug = [NSString stringWithFormat:
|
|
|
|
|
@"Duration %g for statement %@", d, info];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
debug = [NSString stringWithFormat:
|
|
|
|
|
@"Duration %g for statement %@", d, statement];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (_inTransaction == NO)
|
|
|
|
|
{
|
|
|
|
|
[_statements removeAllObjects];
|
|
|
|
|
}
|
2012-11-29 11:40:04 +00:00
|
|
|
|
}
|
2017-08-27 15:13:10 +00:00
|
|
|
|
NS_HANDLER
|
2012-11-29 11:40:04 +00:00
|
|
|
|
{
|
2017-08-29 20:35:59 +00:00
|
|
|
|
result = -1;
|
|
|
|
|
if (NO == _inTransaction)
|
2017-08-27 15:13:10 +00:00
|
|
|
|
{
|
|
|
|
|
[_statements removeAllObjects];
|
2017-08-29 20:35:59 +00:00
|
|
|
|
if ([[localException name] isEqual: SQLConnectionException])
|
2017-08-27 15:13:10 +00:00
|
|
|
|
{
|
2017-08-29 20:35:59 +00:00
|
|
|
|
/* A connection failure while not in a transaction ...
|
|
|
|
|
* we can and should retry.
|
|
|
|
|
*/
|
|
|
|
|
done = NO;
|
|
|
|
|
if (nil != debug)
|
2017-08-27 15:13:10 +00:00
|
|
|
|
{
|
2017-08-29 20:35:59 +00:00
|
|
|
|
NSLog(@"Will retry after: %@", localException);
|
2017-08-27 15:13:10 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-08-29 20:35:59 +00:00
|
|
|
|
}
|
|
|
|
|
if (done == YES)
|
|
|
|
|
{
|
|
|
|
|
[lock unlock];
|
|
|
|
|
[localException raise];
|
2017-08-27 15:13:10 +00:00
|
|
|
|
}
|
2012-11-29 11:40:04 +00:00
|
|
|
|
}
|
2017-08-27 15:13:10 +00:00
|
|
|
|
NS_ENDHANDLER
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
[lock unlock];
|
2013-01-31 09:04:35 +00:00
|
|
|
|
if (nil != debug)
|
|
|
|
|
{
|
|
|
|
|
[self debug: @"%@", debug];
|
|
|
|
|
}
|
2012-10-19 18:41:36 +00:00
|
|
|
|
return result;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-05 09:41:27 +00:00
|
|
|
|
- (NSMutableArray*) simpleQuery: (SQLLitArg*)stmt
|
2007-03-08 17:12:55 +00:00
|
|
|
|
{
|
2008-03-03 14:10:54 +00:00
|
|
|
|
return [self simpleQuery: stmt recordType: rClass listType: aClass];
|
2007-03-08 17:12:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-05 09:41:27 +00:00
|
|
|
|
- (NSMutableArray*) simpleQuery: (SQLLitArg*)stmt
|
2008-03-03 14:10:54 +00:00
|
|
|
|
recordType: (id)rtype
|
|
|
|
|
listType: (id)ltype
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
NSMutableArray *result = nil;
|
2013-01-31 09:04:35 +00:00
|
|
|
|
NSString *debug = nil;
|
2017-08-27 15:13:10 +00:00
|
|
|
|
BOOL done = NO;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
2008-03-03 14:10:54 +00:00
|
|
|
|
if (rtype == 0) rtype = rClass;
|
|
|
|
|
if (ltype == 0) ltype = aClass;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
[lock lock];
|
2020-03-21 17:15:27 +00:00
|
|
|
|
if ([self connect] == NO)
|
|
|
|
|
{
|
|
|
|
|
[lock unlock];
|
|
|
|
|
[NSException raise: SQLConnectionException
|
|
|
|
|
format: @"Unable to connect to '%@' to run query %@",
|
|
|
|
|
[self name], stmt];
|
|
|
|
|
}
|
2017-08-27 15:13:10 +00:00
|
|
|
|
while (NO == done)
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2017-08-27 15:13:10 +00:00
|
|
|
|
done = YES;
|
|
|
|
|
NS_DURING
|
|
|
|
|
{
|
|
|
|
|
_lastStart = GSTickerTimeNow();
|
2019-03-09 06:56:15 +00:00
|
|
|
|
result = [self backendQuery: stmt recordType: rtype listType: ltype];
|
2017-08-27 15:13:10 +00:00
|
|
|
|
_lastOperation = GSTickerTimeNow();
|
|
|
|
|
if (_duration >= 0)
|
|
|
|
|
{
|
|
|
|
|
NSTimeInterval d;
|
2004-05-07 08:16:16 +00:00
|
|
|
|
|
2017-08-27 15:13:10 +00:00
|
|
|
|
d = _lastOperation - _lastStart;
|
|
|
|
|
if (d >= _duration)
|
|
|
|
|
{
|
|
|
|
|
debug = [NSString stringWithFormat:
|
2019-03-09 06:56:15 +00:00
|
|
|
|
@"Duration %g for query %@", d, stmt];
|
2017-08-27 15:13:10 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
NS_HANDLER
|
|
|
|
|
{
|
|
|
|
|
if (NO == _inTransaction)
|
|
|
|
|
{
|
2017-08-28 07:23:19 +00:00
|
|
|
|
if ([[localException name] isEqual: SQLConnectionException])
|
2017-08-27 15:13:10 +00:00
|
|
|
|
{
|
|
|
|
|
/* A connection failure while not in a transaction ...
|
|
|
|
|
* we can and should retry.
|
|
|
|
|
*/
|
|
|
|
|
done = NO;
|
|
|
|
|
if (nil != debug)
|
|
|
|
|
{
|
2017-08-27 15:37:21 +00:00
|
|
|
|
NSLog(@"Will retry after: %@", localException);
|
2017-08-27 15:13:10 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (YES == done)
|
|
|
|
|
{
|
|
|
|
|
[lock unlock];
|
|
|
|
|
[localException raise];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
NS_ENDHANDLER
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
[lock unlock];
|
2013-01-31 09:04:35 +00:00
|
|
|
|
if (nil != debug)
|
|
|
|
|
{
|
|
|
|
|
[self debug: @"%@", debug];
|
|
|
|
|
}
|
2004-04-26 15:13:27 +00:00
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-29 20:35:59 +00:00
|
|
|
|
- (BOOL) tryConnect
|
|
|
|
|
{
|
|
|
|
|
if (NO == connected)
|
|
|
|
|
{
|
|
|
|
|
[lock lock];
|
|
|
|
|
if (NO == connected)
|
|
|
|
|
{
|
|
|
|
|
NS_DURING
|
|
|
|
|
{
|
2020-03-21 17:15:27 +00:00
|
|
|
|
NSTimeInterval _lastListen = 0.0;
|
|
|
|
|
|
2017-08-29 20:35:59 +00:00
|
|
|
|
if (_connectFails > 1)
|
|
|
|
|
{
|
|
|
|
|
NSTimeInterval delay;
|
|
|
|
|
NSTimeInterval elapsed;
|
|
|
|
|
|
|
|
|
|
/* If we have repeated connection failures, we enforce a
|
|
|
|
|
* delay of up to 30 seconds between connection attempts
|
|
|
|
|
* to avoid overloading the system with too frequent
|
|
|
|
|
* connection attempts.
|
|
|
|
|
*/
|
|
|
|
|
delay = (_connectFails < 30) ? _connectFails : 30;
|
|
|
|
|
elapsed = GSTickerTimeNow() - _lastOperation;
|
|
|
|
|
if (elapsed < delay)
|
|
|
|
|
{
|
|
|
|
|
[NSThread sleepForTimeInterval: delay - elapsed];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_lastStart = GSTickerTimeNow();
|
|
|
|
|
if (YES == [self backendConnect])
|
|
|
|
|
{
|
2019-03-11 10:37:49 +00:00
|
|
|
|
/* On establishing a new connection, we must restore any
|
2017-08-29 20:35:59 +00:00
|
|
|
|
* listen instructions in the backend.
|
|
|
|
|
*/
|
|
|
|
|
if (nil != _names)
|
|
|
|
|
{
|
|
|
|
|
NSEnumerator *e;
|
|
|
|
|
NSString *n;
|
|
|
|
|
|
2020-03-21 17:15:27 +00:00
|
|
|
|
_lastListen = GSTickerTimeNow();
|
2017-08-29 20:35:59 +00:00
|
|
|
|
e = [_names objectEnumerator];
|
|
|
|
|
while (nil != (n = [e nextObject]))
|
|
|
|
|
{
|
|
|
|
|
[self backendListen: [self quoteName: n]];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_lastConnect = GSTickerTimeNow();
|
|
|
|
|
_connectFails = 0;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
_lastOperation = GSTickerTimeNow();
|
|
|
|
|
_connectFails++;
|
|
|
|
|
}
|
2020-03-21 17:15:27 +00:00
|
|
|
|
|
|
|
|
|
if (_duration >= 0)
|
|
|
|
|
{
|
|
|
|
|
NSTimeInterval d;
|
2020-03-21 17:27:18 +00:00
|
|
|
|
NSString *s;
|
|
|
|
|
|
|
|
|
|
if (0 == _connectFails)
|
|
|
|
|
{
|
|
|
|
|
s = @"success";
|
|
|
|
|
d = _lastConnect - _lastStart;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
s = @"failure";
|
|
|
|
|
d = _lastOperation - _lastStart;
|
|
|
|
|
}
|
2020-03-21 17:15:27 +00:00
|
|
|
|
|
|
|
|
|
if (d >= _duration)
|
|
|
|
|
{
|
|
|
|
|
if (_lastListen > 0.0)
|
|
|
|
|
{
|
|
|
|
|
[self debug: @"Duration %g for connection (%@)"
|
|
|
|
|
@", of which %g adding observers.",
|
2020-03-21 17:27:18 +00:00
|
|
|
|
d, s, _lastOperation - _lastListen];
|
2020-03-21 17:15:27 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[self debug: @"Duration %g for connection (%@).",
|
2020-03-21 17:27:18 +00:00
|
|
|
|
d, s];
|
2020-03-21 17:15:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-08-29 20:35:59 +00:00
|
|
|
|
}
|
|
|
|
|
NS_HANDLER
|
|
|
|
|
{
|
|
|
|
|
_lastOperation = GSTickerTimeNow();
|
|
|
|
|
_connectFails++;
|
|
|
|
|
[lock unlock];
|
|
|
|
|
[localException raise];
|
|
|
|
|
}
|
|
|
|
|
NS_ENDHANDLER
|
|
|
|
|
}
|
|
|
|
|
[lock unlock];
|
|
|
|
|
if (YES == connected)
|
|
|
|
|
{
|
|
|
|
|
NSNotificationCenter *nc;
|
|
|
|
|
|
|
|
|
|
nc = [NSNotificationCenter defaultCenter];
|
|
|
|
|
[nc postNotificationName: SQLClientDidConnectNotification
|
|
|
|
|
object: self];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return connected;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-01 11:32:49 +00:00
|
|
|
|
- (void) unlock
|
|
|
|
|
{
|
|
|
|
|
[lock unlock];
|
|
|
|
|
}
|
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
- (NSString*) user
|
|
|
|
|
{
|
2004-05-07 08:16:16 +00:00
|
|
|
|
return _user;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation SQLClient (Subclass)
|
|
|
|
|
|
|
|
|
|
- (BOOL) backendConnect
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"Called -%@ without backend bundle loaded",
|
|
|
|
|
NSStringFromSelector(_cmd)];
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) backendDisconnect
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"Called -%@ without backend bundle loaded",
|
|
|
|
|
NSStringFromSelector(_cmd)];
|
|
|
|
|
}
|
|
|
|
|
|
2012-10-19 18:41:36 +00:00
|
|
|
|
- (NSInteger) backendExecute: (NSArray*)info
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"Called -%@ without backend bundle loaded",
|
|
|
|
|
NSStringFromSelector(_cmd)];
|
2012-10-19 18:41:36 +00:00
|
|
|
|
return -1;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-10-22 15:57:56 +00:00
|
|
|
|
- (void) backendListen: (NSString*)name
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) backendNotify: (NSString*)name payload: (NSString*)more
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"Called -%@ without backend bundle implementation",
|
|
|
|
|
NSStringFromSelector(_cmd)];
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2008-03-03 14:10:54 +00:00
|
|
|
|
- (NSMutableArray*) backendQuery: (NSString*)stmt
|
|
|
|
|
recordType: (id)rtype
|
|
|
|
|
listType: (id)ltype
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"Called -%@ without backend bundle loaded",
|
|
|
|
|
NSStringFromSelector(_cmd)];
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
|
2012-10-22 15:57:56 +00:00
|
|
|
|
- (void) backendUnlisten: (NSString*)name
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
- (unsigned) copyEscapedBLOB: (NSData*)blob into: (void*)buf
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"Called -%@ without backend bundle loaded",
|
|
|
|
|
NSStringFromSelector(_cmd)];
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (const void*) insertBLOBs: (NSArray*)blobs
|
|
|
|
|
intoStatement: (const void*)statement
|
|
|
|
|
length: (unsigned)sLength
|
|
|
|
|
withMarker: (const void*)marker
|
|
|
|
|
length: (unsigned)mLength
|
|
|
|
|
giving: (unsigned*)result
|
|
|
|
|
{
|
|
|
|
|
unsigned count = [blobs count];
|
|
|
|
|
unsigned length = sLength;
|
|
|
|
|
|
|
|
|
|
if (count > 1)
|
|
|
|
|
{
|
|
|
|
|
unsigned i;
|
|
|
|
|
unsigned char *buf;
|
|
|
|
|
unsigned char *ptr;
|
|
|
|
|
const unsigned char *from = (const unsigned char*)statement;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Calculate length of buffer needed.
|
|
|
|
|
*/
|
|
|
|
|
for (i = 1; i < count; i++)
|
|
|
|
|
{
|
|
|
|
|
length += [self lengthOfEscapedBLOB: [blobs objectAtIndex: i]];
|
|
|
|
|
length -= mLength;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
buf = NSZoneMalloc(NSDefaultMallocZone(), length + 1);
|
|
|
|
|
[NSData dataWithBytesNoCopy: buf length: length + 1]; // autoreleased
|
|
|
|
|
ptr = buf;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Merge quoted data objects into statement.
|
|
|
|
|
*/
|
|
|
|
|
i = 1;
|
|
|
|
|
from = (unsigned char*)statement;
|
|
|
|
|
while (*from != 0)
|
|
|
|
|
{
|
|
|
|
|
if (*from == *(unsigned char*)marker
|
|
|
|
|
&& memcmp(from, marker, mLength) == 0)
|
|
|
|
|
{
|
|
|
|
|
NSData *d = [blobs objectAtIndex: i++];
|
|
|
|
|
|
|
|
|
|
from += mLength;
|
|
|
|
|
ptr += [self copyEscapedBLOB: d into: ptr];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
*ptr++ = *from++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
*ptr = '\0';
|
|
|
|
|
statement = buf;
|
|
|
|
|
}
|
|
|
|
|
*result = length;
|
|
|
|
|
return statement;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (unsigned) lengthOfEscapedBLOB: (NSData*)blob
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"Called -%@ without backend bundle loaded",
|
|
|
|
|
NSStringFromSelector(_cmd)];
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation SQLClient (Private)
|
|
|
|
|
|
|
|
|
|
- (void) _configure: (NSNotification*)n
|
|
|
|
|
{
|
2014-05-17 09:47:58 +00:00
|
|
|
|
NSDictionary *o;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
NSDictionary *d;
|
|
|
|
|
NSString *s;
|
|
|
|
|
Class c;
|
|
|
|
|
|
2014-05-17 09:47:58 +00:00
|
|
|
|
[lock lock];
|
|
|
|
|
NS_DURING
|
2004-04-26 15:13:27 +00:00
|
|
|
|
{
|
2014-05-17 09:47:58 +00:00
|
|
|
|
o = [n object];
|
|
|
|
|
/*
|
|
|
|
|
* get dictionary containing config info for this client by name.
|
|
|
|
|
*/
|
|
|
|
|
d = [o objectForKey: @"SQLClientReferences"];
|
|
|
|
|
if ([d isKindOfClass: [NSDictionary class]] == NO)
|
|
|
|
|
{
|
|
|
|
|
[self debug: @"Unable to find SQLClientReferences config dictionary"];
|
|
|
|
|
d = nil;
|
|
|
|
|
}
|
|
|
|
|
d = [d objectForKey: _name];
|
|
|
|
|
if ([d isKindOfClass: [NSDictionary class]] == NO)
|
|
|
|
|
{
|
|
|
|
|
[self debug: @"Unable to find config for client '%@'", _name];
|
|
|
|
|
d = nil;
|
|
|
|
|
}
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
2014-05-17 09:47:58 +00:00
|
|
|
|
s = [d objectForKey: @"ServerType"];
|
|
|
|
|
if ([s isKindOfClass: NSStringClass] == NO)
|
|
|
|
|
{
|
|
|
|
|
s = @"Postgres";
|
|
|
|
|
}
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
2014-05-17 09:47:58 +00:00
|
|
|
|
c = NSClassFromString([@"SQLClient" stringByAppendingString: s]);
|
2005-02-19 04:20:13 +00:00
|
|
|
|
if (c == nil)
|
2014-05-17 09:47:58 +00:00
|
|
|
|
{
|
|
|
|
|
NSString *path;
|
|
|
|
|
NSBundle *bundle;
|
|
|
|
|
NSArray *paths;
|
|
|
|
|
NSMutableArray *tried;
|
|
|
|
|
unsigned count;
|
|
|
|
|
|
|
|
|
|
paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
|
|
|
|
|
NSAllDomainsMask, YES);
|
|
|
|
|
count = [paths count];
|
|
|
|
|
tried = [NSMutableArray arrayWithCapacity: count];
|
|
|
|
|
while (count-- > 0)
|
|
|
|
|
{
|
|
|
|
|
path = [paths objectAtIndex: count];
|
|
|
|
|
path = [path stringByAppendingPathComponent: @"Bundles"];
|
2014-07-17 08:51:33 +00:00
|
|
|
|
path = [path stringByAppendingPathComponent:
|
|
|
|
|
@"SQLClient"SOVERSION""];
|
2014-05-17 09:47:58 +00:00
|
|
|
|
path = [path stringByAppendingPathComponent: s];
|
|
|
|
|
path = [path stringByAppendingPathExtension: @"bundle"];
|
|
|
|
|
bundle = [NSBundle bundleWithPath: path];
|
|
|
|
|
if (bundle != nil)
|
|
|
|
|
{
|
|
|
|
|
[tried addObject: path];
|
|
|
|
|
if ((c = [bundle principalClass]) != nil)
|
|
|
|
|
{
|
|
|
|
|
break; // Found it.
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/* Try alternative version with more libraries linked in.
|
|
|
|
|
* In some systems and situations the dynamic linker needs
|
|
|
|
|
* to haved the SQLClient, gnustep-base, and objc libraries
|
|
|
|
|
* explicitly linked into the bundle, but in others it
|
|
|
|
|
* requires them to not be linked. To handle that, we create
|
|
|
|
|
* two versions of each bundle, the seond version has _libs
|
|
|
|
|
* appended to the bundle name, and has the extra libraries linked.
|
|
|
|
|
*/
|
|
|
|
|
path = [path stringByDeletingPathExtension];
|
|
|
|
|
path = [path stringByAppendingString: @"_libs"];
|
|
|
|
|
path = [path stringByAppendingPathExtension: @"bundle"];
|
|
|
|
|
bundle = [NSBundle bundleWithPath: path];
|
|
|
|
|
if (bundle != nil)
|
|
|
|
|
{
|
|
|
|
|
[tried addObject: path];
|
|
|
|
|
if ((c = [bundle principalClass]) != nil)
|
|
|
|
|
{
|
|
|
|
|
break; // Found it.
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (c == nil)
|
|
|
|
|
{
|
|
|
|
|
if ([tried count] == 0)
|
|
|
|
|
{
|
|
|
|
|
[self debug: @"unable to load bundle for '%@' server type"
|
|
|
|
|
@" ... failed to locate bundle in %@", s, paths];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2014-05-19 09:31:02 +00:00
|
|
|
|
[self debug: @"unable to load backend class for '%@' server"
|
|
|
|
|
@" type ... dynamic library load failed in %@", s, tried];
|
2014-05-17 09:47:58 +00:00
|
|
|
|
}
|
2014-05-19 09:31:02 +00:00
|
|
|
|
[lock unlock];
|
|
|
|
|
NS_VOIDRETURN;
|
2014-05-17 09:47:58 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (c != [self class])
|
|
|
|
|
{
|
2014-09-10 11:49:40 +00:00
|
|
|
|
if (YES == connected)
|
|
|
|
|
{
|
|
|
|
|
[self disconnect];
|
|
|
|
|
}
|
2005-08-03 05:39:29 +00:00
|
|
|
|
#ifdef GNUSTEP
|
2014-05-17 09:47:58 +00:00
|
|
|
|
GSDebugAllocationRemove(object_getClass(self), self);
|
2005-08-03 05:39:29 +00:00
|
|
|
|
#endif
|
2014-05-17 09:47:58 +00:00
|
|
|
|
object_setClass(self, c);
|
2005-08-03 05:39:29 +00:00
|
|
|
|
#ifdef GNUSTEP
|
2014-05-17 09:47:58 +00:00
|
|
|
|
GSDebugAllocationAdd(object_getClass(self), self);
|
2005-08-03 05:39:29 +00:00
|
|
|
|
#endif
|
2014-05-17 09:47:58 +00:00
|
|
|
|
}
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
2014-05-17 09:47:58 +00:00
|
|
|
|
s = [d objectForKey: @"Database"];
|
2004-10-07 09:30:14 +00:00
|
|
|
|
if ([s isKindOfClass: NSStringClass] == NO)
|
2014-05-17 09:47:58 +00:00
|
|
|
|
{
|
|
|
|
|
s = [o objectForKey: @"Database"];
|
|
|
|
|
if ([s isKindOfClass: NSStringClass] == NO)
|
|
|
|
|
{
|
|
|
|
|
s = nil;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
[self setDatabase: s];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
2014-05-17 09:47:58 +00:00
|
|
|
|
s = [d objectForKey: @"User"];
|
2004-10-07 09:30:14 +00:00
|
|
|
|
if ([s isKindOfClass: NSStringClass] == NO)
|
2014-05-17 09:47:58 +00:00
|
|
|
|
{
|
|
|
|
|
s = [o objectForKey: @"User"];
|
|
|
|
|
if ([s isKindOfClass: NSStringClass] == NO)
|
|
|
|
|
{
|
|
|
|
|
s = @"";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
[self setUser: s];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
2014-05-17 09:47:58 +00:00
|
|
|
|
s = [d objectForKey: @"Password"];
|
2004-10-07 09:30:14 +00:00
|
|
|
|
if ([s isKindOfClass: NSStringClass] == NO)
|
2014-05-17 09:47:58 +00:00
|
|
|
|
{
|
|
|
|
|
s = [o objectForKey: @"Password"];
|
|
|
|
|
if ([s isKindOfClass: NSStringClass] == NO)
|
|
|
|
|
{
|
|
|
|
|
s = @"";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
[self setPassword: s];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
2014-05-17 09:47:58 +00:00
|
|
|
|
NS_HANDLER
|
|
|
|
|
{
|
|
|
|
|
[lock unlock];
|
|
|
|
|
[localException raise];
|
|
|
|
|
}
|
|
|
|
|
NS_ENDHANDLER
|
|
|
|
|
[lock unlock];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-10-07 09:15:16 +00:00
|
|
|
|
- (NSRecursiveLock*) _lock
|
|
|
|
|
{
|
|
|
|
|
return lock;
|
|
|
|
|
}
|
|
|
|
|
|
2008-02-21 16:23:23 +00:00
|
|
|
|
- (void) _populateCache: (CacheQuery*)a
|
|
|
|
|
{
|
|
|
|
|
GSCache *cache;
|
|
|
|
|
id result;
|
|
|
|
|
|
2015-05-29 09:33:03 +00:00
|
|
|
|
result = [self simpleQuery: a->query
|
|
|
|
|
recordType: a->recordType
|
|
|
|
|
listType: a->listType];
|
2008-02-21 16:23:23 +00:00
|
|
|
|
cache = [self cache];
|
|
|
|
|
[cache setObject: result
|
|
|
|
|
forKey: a->query
|
|
|
|
|
lifetime: a->lifetime];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) _recordMainThread
|
|
|
|
|
{
|
|
|
|
|
mainThread = [NSThread currentThread];
|
|
|
|
|
}
|
|
|
|
|
|
2005-09-27 06:35:05 +00:00
|
|
|
|
+ (void) _tick: (NSTimer*)t
|
2005-09-26 11:22:35 +00:00
|
|
|
|
{
|
2005-11-15 14:16:37 +00:00
|
|
|
|
(void) GSTickerTimeNow();
|
2005-09-26 11:22:35 +00:00
|
|
|
|
}
|
2004-04-26 15:13:27 +00:00
|
|
|
|
@end
|
|
|
|
|
|
2008-02-21 16:23:23 +00:00
|
|
|
|
@implementation SQLClient (GSCacheDelegate)
|
|
|
|
|
- (BOOL) shouldKeepItem: (id)anObject
|
|
|
|
|
withKey: (id)aKey
|
|
|
|
|
lifetime: (unsigned)lifetime
|
|
|
|
|
after: (unsigned)delay
|
|
|
|
|
{
|
|
|
|
|
CacheQuery *a;
|
2008-03-03 14:10:54 +00:00
|
|
|
|
NSDictionary *d;
|
2008-02-21 16:23:23 +00:00
|
|
|
|
|
|
|
|
|
a = [CacheQuery new];
|
2009-11-18 11:11:29 +00:00
|
|
|
|
aKey = [aKey copy];
|
|
|
|
|
[a->query release];
|
|
|
|
|
a->query = aKey;
|
2008-03-03 14:10:54 +00:00
|
|
|
|
d = [[NSThread currentThread] threadDictionary];
|
|
|
|
|
a->recordType = [d objectForKey: @"SQLClientRecordType"];
|
|
|
|
|
a->listType = [d objectForKey: @"SQLClientListType"];
|
2008-02-21 16:23:23 +00:00
|
|
|
|
a->lifetime = lifetime;
|
2009-11-18 11:11:29 +00:00
|
|
|
|
[a autorelease];
|
2008-02-21 16:23:23 +00:00
|
|
|
|
if (_cacheThread == nil)
|
|
|
|
|
{
|
|
|
|
|
[self _populateCache: a];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* We schedule an asynchronous update if the item is not too old,
|
|
|
|
|
* otherwise (more than lifetime seconds past its expiry) we wait
|
|
|
|
|
* for the update to complete.
|
|
|
|
|
*/
|
|
|
|
|
[self performSelectorOnMainThread: @selector(_populateCache:)
|
|
|
|
|
withObject: a
|
|
|
|
|
waitUntilDone: (delay > lifetime) ? YES : NO
|
|
|
|
|
modes: queryModes];
|
|
|
|
|
}
|
|
|
|
|
return YES; // Always keep items ...
|
|
|
|
|
}
|
|
|
|
|
@end
|
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@implementation SQLClient(Convenience)
|
|
|
|
|
|
2014-12-11 17:26:32 +00:00
|
|
|
|
+ (NSMutableArray*) columns: (NSMutableArray*)records
|
2009-09-16 08:59:59 +00:00
|
|
|
|
{
|
|
|
|
|
SQLRecord *r = [records lastObject];
|
|
|
|
|
unsigned rowCount = [records count];
|
|
|
|
|
unsigned colCount = [r count];
|
|
|
|
|
NSMutableArray *m;
|
|
|
|
|
|
|
|
|
|
if (rowCount == 0 || colCount == 0)
|
|
|
|
|
{
|
|
|
|
|
m = [NSMutableArray array];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
NSMutableArray *cols[colCount];
|
|
|
|
|
unsigned i;
|
|
|
|
|
|
|
|
|
|
m = [NSMutableArray arrayWithCapacity: colCount];
|
|
|
|
|
for (i = 0; i < colCount; i++)
|
|
|
|
|
{
|
|
|
|
|
cols[i] = [[NSMutableArray alloc] initWithCapacity: rowCount];
|
|
|
|
|
[m addObject: cols[i]];
|
|
|
|
|
[cols[i] release];
|
|
|
|
|
}
|
|
|
|
|
for (i = 0; i < rowCount; i++)
|
|
|
|
|
{
|
|
|
|
|
unsigned j;
|
|
|
|
|
|
|
|
|
|
r = [records objectAtIndex: i];
|
|
|
|
|
for (j = 0; j < colCount; j++)
|
|
|
|
|
{
|
|
|
|
|
[cols[j] addObject: [r objectAtIndex: j]];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return m;
|
|
|
|
|
}
|
|
|
|
|
|
2014-12-11 17:26:32 +00:00
|
|
|
|
+ (void) singletons: (NSMutableArray*)records
|
|
|
|
|
{
|
|
|
|
|
unsigned c = [records count];
|
|
|
|
|
|
|
|
|
|
while (c-- > 0)
|
|
|
|
|
{
|
|
|
|
|
[records replaceObjectAtIndex: c
|
|
|
|
|
withObject: [[records objectAtIndex: c] lastObject]];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (SQLTransaction*) batch: (BOOL)stopOnFailure
|
|
|
|
|
{
|
2015-06-27 15:28:03 +00:00
|
|
|
|
return [SQLTransaction _transactionUsing: self
|
|
|
|
|
batch: YES
|
|
|
|
|
stop: stopOnFailure];
|
2014-12-11 17:26:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSMutableArray*) columns: (NSMutableArray*)records
|
|
|
|
|
{
|
|
|
|
|
return [SQLClient columns: records];
|
|
|
|
|
}
|
|
|
|
|
|
2004-04-26 15:13:27 +00:00
|
|
|
|
- (SQLRecord*) queryRecord: (NSString*)stmt, ...
|
|
|
|
|
{
|
|
|
|
|
va_list ap;
|
|
|
|
|
NSArray *result = nil;
|
|
|
|
|
SQLRecord *record;
|
2017-06-30 10:39:24 +00:00
|
|
|
|
SQLLiteral *query;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
|
|
|
|
va_start (ap, stmt);
|
2017-06-30 10:39:24 +00:00
|
|
|
|
query = [[self prepare: stmt args: ap] objectAtIndex: 0];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
va_end (ap);
|
|
|
|
|
|
2017-06-30 10:39:24 +00:00
|
|
|
|
result = [self simpleQuery: query];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
|
|
|
|
if ([result count] > 1)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
2017-06-30 10:39:24 +00:00
|
|
|
|
format: @"Query returns more than one record -\n%@\n", query];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
record = [result lastObject];
|
|
|
|
|
if (record == nil)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: SQLEmptyException
|
2017-06-30 10:39:24 +00:00
|
|
|
|
format: @"Query returns no data -\n%@\n", query];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
return record;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSString*) queryString: (NSString*)stmt, ...
|
|
|
|
|
{
|
|
|
|
|
va_list ap;
|
|
|
|
|
NSArray *result = nil;
|
|
|
|
|
SQLRecord *record;
|
2017-06-30 10:39:24 +00:00
|
|
|
|
SQLLiteral *query;
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
|
|
|
|
va_start (ap, stmt);
|
2017-06-30 10:39:24 +00:00
|
|
|
|
query = [[self prepare: stmt args: ap] objectAtIndex: 0];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
va_end (ap);
|
|
|
|
|
|
2017-06-30 10:39:24 +00:00
|
|
|
|
result = [self simpleQuery: query];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
|
|
|
|
|
if ([result count] > 1)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
2017-06-30 10:39:24 +00:00
|
|
|
|
format: @"Query returns more than one record -\n%@\n", query];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
record = [result lastObject];
|
|
|
|
|
if (record == nil)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: SQLEmptyException
|
2017-06-30 10:39:24 +00:00
|
|
|
|
format: @"Query returns no data -\n%@\n", query];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
if ([record count] > 1)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
2017-06-30 10:39:24 +00:00
|
|
|
|
format: @"Query returns multiple fields -\n%@\n", query];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
return [[record lastObject] description];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) singletons: (NSMutableArray*)records
|
|
|
|
|
{
|
2014-12-11 17:26:32 +00:00
|
|
|
|
[SQLClient singletons: records];
|
2004-04-26 15:13:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2004-07-26 08:56:26 +00:00
|
|
|
|
- (SQLTransaction*) transaction
|
|
|
|
|
{
|
2015-06-27 15:28:03 +00:00
|
|
|
|
return [SQLTransaction _transactionUsing: self batch: NO stop: NO];
|
2004-07-26 08:56:26 +00:00
|
|
|
|
}
|
2015-06-26 12:06:13 +00:00
|
|
|
|
|
2004-07-26 08:56:26 +00:00
|
|
|
|
@end
|
|
|
|
|
|
2005-03-02 10:00:24 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@interface SQLClientCacheInfo : NSObject
|
|
|
|
|
{
|
|
|
|
|
@public
|
|
|
|
|
NSString *query;
|
|
|
|
|
NSMutableArray *result;
|
|
|
|
|
NSTimeInterval expires;
|
|
|
|
|
}
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation SQLClientCacheInfo
|
|
|
|
|
- (void) dealloc
|
|
|
|
|
{
|
2009-11-18 11:11:29 +00:00
|
|
|
|
[query release]; query = nil;
|
|
|
|
|
[result release]; result = nil;
|
2005-03-02 10:00:24 +00:00
|
|
|
|
[super dealloc];
|
|
|
|
|
}
|
2009-11-18 11:11:29 +00:00
|
|
|
|
- (NSUInteger) hash
|
2005-03-02 10:00:24 +00:00
|
|
|
|
{
|
|
|
|
|
return [query hash];
|
|
|
|
|
}
|
2005-07-07 21:11:04 +00:00
|
|
|
|
- (BOOL) isEqual: (id)other
|
2005-03-02 10:00:24 +00:00
|
|
|
|
{
|
2005-07-07 21:11:04 +00:00
|
|
|
|
return [query isEqual: ((SQLClientCacheInfo*)other)->query];
|
2005-03-02 10:00:24 +00:00
|
|
|
|
}
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation SQLClient (Caching)
|
2005-09-22 08:42:37 +00:00
|
|
|
|
|
2005-11-14 20:37:33 +00:00
|
|
|
|
- (GSCache*) cache
|
2005-09-22 08:42:37 +00:00
|
|
|
|
{
|
2008-02-21 16:23:23 +00:00
|
|
|
|
GSCache *c;
|
|
|
|
|
|
2015-05-04 12:15:47 +00:00
|
|
|
|
/* NB we use a different lock to protect the cache from the lock
|
|
|
|
|
* used to protect the database query. That allows multiple
|
|
|
|
|
* connections (a connection pool) to share the same cache.
|
|
|
|
|
*/
|
|
|
|
|
[cacheLock lock];
|
2014-05-13 11:01:50 +00:00
|
|
|
|
if (nil == _cache)
|
2005-09-22 08:42:37 +00:00
|
|
|
|
{
|
2005-11-14 20:37:33 +00:00
|
|
|
|
_cache = [GSCache new];
|
2017-03-06 17:23:37 +00:00
|
|
|
|
[_cache setName: [self clientName]];
|
2008-02-21 16:23:23 +00:00
|
|
|
|
if (_cacheThread != nil)
|
|
|
|
|
{
|
|
|
|
|
[_cache setDelegate: self];
|
|
|
|
|
}
|
2005-09-22 08:42:37 +00:00
|
|
|
|
}
|
2009-11-18 11:11:29 +00:00
|
|
|
|
c = [_cache retain];
|
2015-05-04 12:15:47 +00:00
|
|
|
|
[cacheLock unlock];
|
2009-11-18 11:11:29 +00:00
|
|
|
|
return [c autorelease];
|
2005-09-22 08:42:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-02-26 16:14:51 +00:00
|
|
|
|
- (NSMutableArray*) cacheCheckSimpleQuery: (NSString*)stmt
|
|
|
|
|
{
|
|
|
|
|
NSMutableArray *result = [[self cache] objectForKey: stmt];
|
|
|
|
|
|
|
|
|
|
if (result != nil)
|
|
|
|
|
{
|
|
|
|
|
result = [[result mutableCopy] autorelease];
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2005-03-02 10:00:24 +00:00
|
|
|
|
- (NSMutableArray*) cache: (int)seconds
|
|
|
|
|
query: (NSString*)stmt,...
|
|
|
|
|
{
|
2017-06-30 10:39:24 +00:00
|
|
|
|
va_list ap;
|
|
|
|
|
SQLLiteral *query;
|
2005-03-02 10:00:24 +00:00
|
|
|
|
|
|
|
|
|
va_start (ap, stmt);
|
2017-06-30 10:39:24 +00:00
|
|
|
|
query = [[self prepare: stmt args: ap] objectAtIndex: 0];
|
2005-03-02 10:00:24 +00:00
|
|
|
|
va_end (ap);
|
|
|
|
|
|
2017-06-30 10:39:24 +00:00
|
|
|
|
return [self cache: seconds simpleQuery: query];
|
2005-03-02 10:00:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSMutableArray*) cache: (int)seconds
|
|
|
|
|
query: (NSString*)stmt
|
|
|
|
|
with: (NSDictionary*)values
|
|
|
|
|
{
|
2017-06-30 10:39:24 +00:00
|
|
|
|
SQLLiteral *query;
|
|
|
|
|
|
|
|
|
|
query = [[self prepare: stmt with: values] objectAtIndex: 0];
|
|
|
|
|
return [self cache: seconds simpleQuery: query];
|
2005-03-02 10:00:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
2007-03-08 17:12:55 +00:00
|
|
|
|
- (NSMutableArray*) cache: (int)seconds
|
2017-07-05 09:41:27 +00:00
|
|
|
|
simpleQuery: (SQLLitArg*)stmt
|
2007-03-08 17:12:55 +00:00
|
|
|
|
{
|
2008-03-03 14:10:54 +00:00
|
|
|
|
return [self cache: seconds
|
|
|
|
|
simpleQuery: stmt
|
|
|
|
|
recordType: nil
|
|
|
|
|
listType: nil];
|
2007-03-08 17:12:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSMutableArray*) cache: (int)seconds
|
2017-07-05 09:41:27 +00:00
|
|
|
|
simpleQuery: (SQLLitArg*)stmt
|
2008-03-03 14:10:54 +00:00
|
|
|
|
recordType: (id)rtype
|
|
|
|
|
listType: (id)ltype
|
2005-03-02 10:00:24 +00:00
|
|
|
|
{
|
2008-02-21 16:23:23 +00:00
|
|
|
|
NSMutableArray *result;
|
2008-03-03 14:10:54 +00:00
|
|
|
|
NSMutableDictionary *md;
|
2008-02-21 16:23:23 +00:00
|
|
|
|
GSCache *c;
|
|
|
|
|
id toCache;
|
2019-07-29 11:33:42 +00:00
|
|
|
|
BOOL cacheHit;
|
2005-03-02 10:00:24 +00:00
|
|
|
|
|
2008-03-03 14:10:54 +00:00
|
|
|
|
if (rtype == 0) rtype = rClass;
|
|
|
|
|
if (ltype == 0) ltype = aClass;
|
2008-02-21 16:23:23 +00:00
|
|
|
|
|
2008-03-03 14:10:54 +00:00
|
|
|
|
md = [[NSThread currentThread] threadDictionary];
|
|
|
|
|
[md setObject: rtype forKey: @"SQLClientRecordType"];
|
|
|
|
|
[md setObject: ltype forKey: @"SQLClientListType"];
|
2014-06-20 05:15:24 +00:00
|
|
|
|
_lastStart = GSTickerTimeNow();
|
2008-02-21 16:23:23 +00:00
|
|
|
|
c = [self cache];
|
|
|
|
|
toCache = nil;
|
|
|
|
|
|
|
|
|
|
if (seconds < 0)
|
|
|
|
|
{
|
|
|
|
|
seconds = -seconds;
|
|
|
|
|
result = nil;
|
|
|
|
|
}
|
|
|
|
|
else
|
2005-03-02 10:00:24 +00:00
|
|
|
|
{
|
2008-02-21 16:23:23 +00:00
|
|
|
|
result = [c objectForKey: stmt];
|
|
|
|
|
}
|
2005-03-02 10:00:24 +00:00
|
|
|
|
|
2008-02-21 16:23:23 +00:00
|
|
|
|
if (result == nil)
|
|
|
|
|
{
|
|
|
|
|
CacheQuery *a;
|
|
|
|
|
|
2019-07-29 11:33:42 +00:00
|
|
|
|
cacheHit = NO;
|
2008-02-21 16:23:23 +00:00
|
|
|
|
a = [CacheQuery new];
|
2009-11-18 11:11:29 +00:00
|
|
|
|
a->query = [stmt copy];
|
2008-03-03 14:10:54 +00:00
|
|
|
|
a->recordType = rtype;
|
|
|
|
|
a->listType = ltype;
|
2008-02-21 16:23:23 +00:00
|
|
|
|
a->lifetime = seconds;
|
2009-11-18 11:11:29 +00:00
|
|
|
|
[a autorelease];
|
2008-02-21 16:23:23 +00:00
|
|
|
|
|
|
|
|
|
if (_cacheThread == nil)
|
|
|
|
|
{
|
|
|
|
|
[self _populateCache: a];
|
2005-03-02 10:00:24 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2016-10-18 08:32:05 +00:00
|
|
|
|
/* Not really an asynchronous query because we wait until it's
|
2008-02-21 16:23:23 +00:00
|
|
|
|
* done in order to have a result we can return.
|
|
|
|
|
*/
|
|
|
|
|
[self performSelectorOnMainThread: @selector(_populateCache:)
|
|
|
|
|
withObject: a
|
|
|
|
|
waitUntilDone: YES
|
|
|
|
|
modes: queryModes];
|
|
|
|
|
}
|
|
|
|
|
result = [c objectForKey: stmt];
|
2019-07-29 11:33:42 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
cacheHit = YES;
|
2008-02-21 16:23:23 +00:00
|
|
|
|
}
|
2005-09-27 14:07:04 +00:00
|
|
|
|
|
2008-02-21 16:23:23 +00:00
|
|
|
|
if (seconds == 0)
|
|
|
|
|
{
|
|
|
|
|
// We have been told to remove the existing cached item.
|
|
|
|
|
[c setObject: nil forKey: stmt lifetime: seconds];
|
|
|
|
|
toCache = nil;
|
|
|
|
|
}
|
2005-09-22 08:42:37 +00:00
|
|
|
|
|
2008-02-21 16:23:23 +00:00
|
|
|
|
if (toCache != nil)
|
|
|
|
|
{
|
|
|
|
|
// We have a newly retrieved object ... cache it.
|
|
|
|
|
[c setObject: toCache forKey: stmt lifetime: seconds];
|
2005-03-02 10:00:24 +00:00
|
|
|
|
}
|
2008-02-21 16:23:23 +00:00
|
|
|
|
|
|
|
|
|
if (result != nil)
|
2005-03-02 10:00:24 +00:00
|
|
|
|
{
|
2008-02-21 16:23:23 +00:00
|
|
|
|
/*
|
|
|
|
|
* Return an autoreleased copy ... not the original cached data.
|
|
|
|
|
*/
|
2008-03-03 14:10:54 +00:00
|
|
|
|
result = [[result mutableCopy] autorelease];
|
2005-03-02 10:00:24 +00:00
|
|
|
|
}
|
2019-07-29 11:33:42 +00:00
|
|
|
|
|
|
|
|
|
_lastOperation = GSTickerTimeNow();
|
|
|
|
|
if (_duration >= 0)
|
|
|
|
|
{
|
|
|
|
|
NSTimeInterval d;
|
|
|
|
|
|
|
|
|
|
d = _lastOperation - _lastStart;
|
|
|
|
|
if (d >= _duration)
|
|
|
|
|
{
|
|
|
|
|
[self debug: @"Duration %g for cache-%@ query %@",
|
|
|
|
|
d, (YES == cacheHit) ? @"hit" : @"miss", stmt];
|
|
|
|
|
}
|
|
|
|
|
}
|
2005-03-02 10:00:24 +00:00
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2005-11-14 20:37:33 +00:00
|
|
|
|
- (void) setCache: (GSCache*)aCache
|
2005-09-27 06:35:05 +00:00
|
|
|
|
{
|
2015-05-04 12:15:47 +00:00
|
|
|
|
/* NB we use a different lock to protect the cache from the lock
|
|
|
|
|
* used to protect the database query. That allows multiple
|
|
|
|
|
* connections (a connection pool) to share the same cache.
|
|
|
|
|
*/
|
|
|
|
|
[cacheLock lock];
|
2012-11-29 11:40:04 +00:00
|
|
|
|
NS_DURING
|
2008-02-21 16:23:23 +00:00
|
|
|
|
{
|
2012-11-29 11:40:04 +00:00
|
|
|
|
if (_cacheThread != nil)
|
|
|
|
|
{
|
|
|
|
|
[_cache setDelegate: nil];
|
|
|
|
|
}
|
|
|
|
|
[aCache retain];
|
|
|
|
|
[_cache release];
|
|
|
|
|
_cache = aCache;
|
|
|
|
|
if (_cacheThread != nil)
|
|
|
|
|
{
|
|
|
|
|
[_cache setDelegate: self];
|
|
|
|
|
}
|
2008-02-21 16:23:23 +00:00
|
|
|
|
}
|
2012-11-29 11:40:04 +00:00
|
|
|
|
NS_HANDLER
|
2008-02-21 16:23:23 +00:00
|
|
|
|
{
|
2015-05-04 12:15:47 +00:00
|
|
|
|
[cacheLock unlock];
|
2012-11-29 11:40:04 +00:00
|
|
|
|
[localException raise];
|
2008-02-21 16:23:23 +00:00
|
|
|
|
}
|
2012-11-29 11:40:04 +00:00
|
|
|
|
NS_ENDHANDLER
|
2015-05-04 12:15:47 +00:00
|
|
|
|
[cacheLock unlock];
|
2008-02-21 16:23:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) setCacheThread: (NSThread*)aThread
|
|
|
|
|
{
|
|
|
|
|
if (mainThread == nil)
|
|
|
|
|
{
|
|
|
|
|
[self performSelectorOnMainThread: @selector(_recordMainThread)
|
|
|
|
|
withObject: nil
|
|
|
|
|
waitUntilDone: NO
|
|
|
|
|
modes: queryModes];
|
|
|
|
|
}
|
|
|
|
|
if (aThread != nil && aThread != mainThread)
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"SQLClient: only the main thread is usable as cache thread");
|
|
|
|
|
aThread = mainThread;
|
|
|
|
|
}
|
|
|
|
|
[lock lock];
|
2012-11-29 11:40:04 +00:00
|
|
|
|
NS_DURING
|
2008-02-21 16:23:23 +00:00
|
|
|
|
{
|
2012-11-29 11:40:04 +00:00
|
|
|
|
if (_cacheThread != nil)
|
|
|
|
|
{
|
|
|
|
|
[_cache setDelegate: nil];
|
|
|
|
|
}
|
|
|
|
|
[aThread retain];
|
|
|
|
|
[_cacheThread release];
|
|
|
|
|
_cacheThread = aThread;
|
|
|
|
|
if (_cacheThread != nil)
|
|
|
|
|
{
|
|
|
|
|
[_cache setDelegate: self];
|
|
|
|
|
}
|
2008-02-21 16:23:23 +00:00
|
|
|
|
}
|
2012-11-29 11:40:04 +00:00
|
|
|
|
NS_HANDLER
|
2008-02-21 16:23:23 +00:00
|
|
|
|
{
|
2012-11-29 11:40:04 +00:00
|
|
|
|
[lock unlock];
|
|
|
|
|
[localException raise];
|
2008-02-21 16:23:23 +00:00
|
|
|
|
}
|
2012-11-29 11:40:04 +00:00
|
|
|
|
NS_ENDHANDLER
|
2008-02-21 16:23:23 +00:00
|
|
|
|
[lock unlock];
|
2005-09-27 06:35:05 +00:00
|
|
|
|
}
|
2005-03-02 10:00:24 +00:00
|
|
|
|
@end
|
|
|
|
|
|
2004-07-26 08:56:26 +00:00
|
|
|
|
@implementation SQLTransaction
|
2005-09-28 06:35:03 +00:00
|
|
|
|
|
2015-06-26 12:06:13 +00:00
|
|
|
|
+ (SQLTransaction*) _transactionUsing: (id)clientOrPool
|
2015-06-27 15:28:03 +00:00
|
|
|
|
batch: (BOOL)isBatched
|
|
|
|
|
stop: (BOOL)stopOnFailure
|
2015-06-26 12:06:13 +00:00
|
|
|
|
{
|
|
|
|
|
SQLTransaction *transaction;
|
|
|
|
|
|
|
|
|
|
transaction = (SQLTransaction*)NSAllocateObject(self, 0,
|
|
|
|
|
NSDefaultMallocZone());
|
|
|
|
|
|
2015-06-29 12:07:00 +00:00
|
|
|
|
transaction->_owner = [clientOrPool retain];
|
2015-06-26 12:06:13 +00:00
|
|
|
|
transaction->_info = [NSMutableArray new];
|
2015-06-27 15:28:03 +00:00
|
|
|
|
transaction->_batch = isBatched;
|
|
|
|
|
transaction->_stop = stopOnFailure;
|
2018-06-29 08:22:13 +00:00
|
|
|
|
transaction->_lock = [NSRecursiveLock new];
|
2015-06-26 12:06:13 +00:00
|
|
|
|
return [transaction autorelease];
|
|
|
|
|
}
|
|
|
|
|
|
2007-07-09 17:08:22 +00:00
|
|
|
|
- (void) _addSQL: (NSMutableString*)sql andArgs: (NSMutableArray*)args
|
|
|
|
|
{
|
|
|
|
|
unsigned count = [_info count];
|
|
|
|
|
unsigned index;
|
|
|
|
|
|
|
|
|
|
for (index = 0; index < count; index++)
|
|
|
|
|
{
|
|
|
|
|
id o = [_info objectAtIndex: index];
|
|
|
|
|
|
|
|
|
|
if ([o isKindOfClass: NSArrayClass] == YES)
|
|
|
|
|
{
|
|
|
|
|
unsigned c = [(NSArray*)o count];
|
|
|
|
|
|
|
|
|
|
if (c > 0)
|
|
|
|
|
{
|
|
|
|
|
unsigned i;
|
|
|
|
|
|
|
|
|
|
[sql appendString: [(NSArray*)o objectAtIndex: 0]];
|
|
|
|
|
[sql appendString: @";"];
|
|
|
|
|
for (i = 1; i < c; i++)
|
|
|
|
|
{
|
|
|
|
|
[args addObject: [(NSArray*)o objectAtIndex: i]];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[(SQLTransaction*)o _addSQL: sql andArgs: args];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-12 09:50:50 +00:00
|
|
|
|
- (void) addPrepared: (NSArray*)statement
|
2009-09-08 09:59:05 +00:00
|
|
|
|
{
|
2018-06-29 08:22:13 +00:00
|
|
|
|
[_lock lock];
|
2009-09-08 09:59:05 +00:00
|
|
|
|
[_info addObject: statement];
|
|
|
|
|
_count++;
|
2018-06-29 08:22:13 +00:00
|
|
|
|
[_lock unlock];
|
2009-09-08 09:59:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
2007-07-09 17:08:22 +00:00
|
|
|
|
- (void) _countLength: (unsigned*)length andArgs: (unsigned*)args
|
|
|
|
|
{
|
|
|
|
|
unsigned count = [_info count];
|
|
|
|
|
unsigned index;
|
|
|
|
|
|
|
|
|
|
for (index = 0; index < count; index++)
|
|
|
|
|
{
|
|
|
|
|
id o = [_info objectAtIndex: index];
|
|
|
|
|
|
|
|
|
|
if ([o isKindOfClass: NSArrayClass] == YES)
|
|
|
|
|
{
|
|
|
|
|
unsigned c = [(NSArray*)o count];
|
|
|
|
|
|
|
|
|
|
if (c > 0)
|
|
|
|
|
{
|
|
|
|
|
length += [[(NSArray*)o objectAtIndex: 0] length] + 1;
|
|
|
|
|
args += c - 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[(SQLTransaction*)o _countLength: length andArgs: args];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2006-06-23 19:36:52 +00:00
|
|
|
|
|
2009-09-08 08:05:31 +00:00
|
|
|
|
- (void) add: (NSString*)stmt,...
|
|
|
|
|
{
|
2014-08-09 14:02:11 +00:00
|
|
|
|
va_list ap;
|
|
|
|
|
NSMutableArray *p;
|
2009-09-08 08:05:31 +00:00
|
|
|
|
|
|
|
|
|
va_start (ap, stmt);
|
2015-06-29 12:07:00 +00:00
|
|
|
|
p = [_owner prepare: stmt args: ap];
|
2009-09-08 08:05:31 +00:00
|
|
|
|
va_end (ap);
|
2019-02-19 15:36:59 +00:00
|
|
|
|
[self addPrepared: p];
|
2009-09-08 08:05:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) add: (NSString*)stmt with: (NSDictionary*)values
|
|
|
|
|
{
|
2014-08-09 14:02:11 +00:00
|
|
|
|
NSMutableArray *p;
|
|
|
|
|
|
2015-06-29 12:07:00 +00:00
|
|
|
|
p = [_owner prepare: stmt with: values];
|
2019-02-19 15:36:59 +00:00
|
|
|
|
[self addPrepared: p];
|
2009-09-08 08:05:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) append: (SQLTransaction*)other
|
|
|
|
|
{
|
2018-06-29 08:22:13 +00:00
|
|
|
|
if (nil == other)
|
2009-09-08 08:05:31 +00:00
|
|
|
|
{
|
2018-06-29 08:22:13 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
NSAssert(other != self, NSInvalidArgumentException);
|
|
|
|
|
NSAssert([other isKindOfClass: [SQLTransaction class]],
|
|
|
|
|
NSInvalidArgumentException);
|
|
|
|
|
[other lock];
|
|
|
|
|
[_lock lock];
|
|
|
|
|
NS_DURING
|
|
|
|
|
{
|
|
|
|
|
if (other->_count > 0)
|
2014-08-09 14:02:11 +00:00
|
|
|
|
{
|
2019-02-19 13:55:31 +00:00
|
|
|
|
SQLTransaction *t;
|
|
|
|
|
|
2018-06-29 08:22:13 +00:00
|
|
|
|
/* Owners must the the same client, or the same pool, or members
|
|
|
|
|
* of the same pool or a client and the pool it belongs to.
|
2014-08-09 14:02:11 +00:00
|
|
|
|
*/
|
2018-06-29 08:22:13 +00:00
|
|
|
|
if (NO == [_owner isEqual: other->_owner]
|
|
|
|
|
&& NO == [[_owner pool] isEqual: [other->_owner pool]])
|
2014-08-09 14:02:11 +00:00
|
|
|
|
{
|
2018-06-29 08:22:13 +00:00
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
2018-07-27 08:21:10 +00:00
|
|
|
|
format: @"[%@-%@] database owner mismatch",
|
2018-06-29 08:22:13 +00:00
|
|
|
|
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-19 13:55:31 +00:00
|
|
|
|
t = [other copy];
|
2018-06-29 08:22:13 +00:00
|
|
|
|
|
2019-02-19 13:55:31 +00:00
|
|
|
|
[_info addObject: t];
|
|
|
|
|
_count += t->_count;
|
|
|
|
|
[t release];
|
2014-08-09 14:02:11 +00:00
|
|
|
|
}
|
2009-09-08 08:05:31 +00:00
|
|
|
|
}
|
2018-06-29 08:22:13 +00:00
|
|
|
|
NS_HANDLER
|
|
|
|
|
{
|
|
|
|
|
[_lock unlock];
|
|
|
|
|
[other unlock];
|
|
|
|
|
[localException raise];
|
|
|
|
|
}
|
|
|
|
|
NS_ENDHANDLER
|
|
|
|
|
[_lock unlock];
|
|
|
|
|
[other unlock];
|
2009-09-08 08:05:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id) copyWithZone: (NSZone*)z
|
|
|
|
|
{
|
|
|
|
|
SQLTransaction *c;
|
|
|
|
|
|
2018-06-29 08:22:13 +00:00
|
|
|
|
[_lock lock];
|
2009-09-08 08:05:31 +00:00
|
|
|
|
c = (SQLTransaction*)NSCopyObject(self, 0, z);
|
2015-06-29 12:07:00 +00:00
|
|
|
|
c->_owner = [c->_owner retain];
|
2009-09-08 08:05:31 +00:00
|
|
|
|
c->_info = [c->_info mutableCopy];
|
2018-06-29 08:22:13 +00:00
|
|
|
|
c->_lock = [NSRecursiveLock new];
|
|
|
|
|
[_lock unlock];
|
2009-09-08 08:05:31 +00:00
|
|
|
|
return c;
|
|
|
|
|
}
|
|
|
|
|
|
2012-01-06 16:03:51 +00:00
|
|
|
|
- (NSUInteger) count
|
2009-09-08 08:05:31 +00:00
|
|
|
|
{
|
2018-06-29 08:22:13 +00:00
|
|
|
|
NSUInteger count;
|
|
|
|
|
|
|
|
|
|
[_lock lock];
|
|
|
|
|
count = [_info count];
|
|
|
|
|
[_lock unlock];
|
|
|
|
|
return count;
|
2009-09-08 08:05:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-06-26 12:06:13 +00:00
|
|
|
|
- (id) db
|
2009-09-08 08:05:31 +00:00
|
|
|
|
{
|
2015-06-29 12:07:00 +00:00
|
|
|
|
return _owner;
|
2009-09-08 08:05:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) dealloc
|
|
|
|
|
{
|
2015-06-29 12:07:00 +00:00
|
|
|
|
[_owner release]; _owner = nil;
|
2009-11-18 11:11:29 +00:00
|
|
|
|
[_info release]; _info = nil;
|
2018-06-29 08:22:13 +00:00
|
|
|
|
[_lock release]; _lock = nil;
|
2009-09-08 08:05:31 +00:00
|
|
|
|
[super dealloc];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSString*) description
|
|
|
|
|
{
|
2018-06-29 08:22:13 +00:00
|
|
|
|
NSString *str;
|
|
|
|
|
|
|
|
|
|
[_lock lock];
|
|
|
|
|
str = [NSString stringWithFormat: @"%@ with SQL '%@' for %@",
|
2009-09-08 08:05:31 +00:00
|
|
|
|
[super description],
|
2015-06-29 12:07:00 +00:00
|
|
|
|
(_count == 0 ? (id)@"" : (id)_info), _owner];
|
2018-06-29 08:22:13 +00:00
|
|
|
|
[_lock unlock];
|
|
|
|
|
return str;
|
2009-09-08 08:05:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
2004-07-26 08:56:26 +00:00
|
|
|
|
- (void) execute
|
|
|
|
|
{
|
2018-06-29 08:22:13 +00:00
|
|
|
|
[_lock lock];
|
2004-07-26 08:56:26 +00:00
|
|
|
|
if (_count > 0)
|
|
|
|
|
{
|
|
|
|
|
NS_DURING
|
2018-06-29 08:22:13 +00:00
|
|
|
|
{
|
|
|
|
|
NSMutableArray *info = nil;
|
|
|
|
|
SQLClientPool *pool = nil;
|
|
|
|
|
SQLClient *db;
|
|
|
|
|
NSRecursiveLock *dbLock;
|
|
|
|
|
BOOL wrap;
|
2007-07-09 17:08:22 +00:00
|
|
|
|
|
2018-06-29 08:22:13 +00:00
|
|
|
|
if ([_owner isKindOfClass: [SQLClientPool class]])
|
2007-07-09 17:08:22 +00:00
|
|
|
|
{
|
2018-06-29 08:22:13 +00:00
|
|
|
|
pool = (SQLClientPool*)_owner;
|
|
|
|
|
db = [pool provideClient];
|
2007-07-09 17:08:22 +00:00
|
|
|
|
}
|
2018-06-29 08:22:13 +00:00
|
|
|
|
else
|
2007-07-09 17:08:22 +00:00
|
|
|
|
{
|
2018-06-29 08:22:13 +00:00
|
|
|
|
db = _owner;
|
2007-07-09 17:08:22 +00:00
|
|
|
|
}
|
2018-06-29 08:22:13 +00:00
|
|
|
|
|
|
|
|
|
dbLock = [db _lock];
|
|
|
|
|
[dbLock lock];
|
|
|
|
|
wrap = [db isInTransaction] ? NO : YES;
|
|
|
|
|
NS_DURING
|
2015-06-26 12:06:13 +00:00
|
|
|
|
{
|
2018-06-29 08:22:13 +00:00
|
|
|
|
NSMutableString *sql;
|
|
|
|
|
unsigned sqlSize = 0;
|
|
|
|
|
unsigned argCount = 0;
|
2014-10-07 07:09:38 +00:00
|
|
|
|
|
2018-06-29 08:22:13 +00:00
|
|
|
|
[self _countLength: &sqlSize andArgs: &argCount];
|
|
|
|
|
|
|
|
|
|
/* Allocate and initialise the transaction statement.
|
|
|
|
|
*/
|
|
|
|
|
info = [[NSMutableArray alloc] initWithCapacity: argCount + 1];
|
|
|
|
|
sql = [[NSMutableString alloc] initWithCapacity: sqlSize + 13];
|
2019-03-09 09:39:34 +00:00
|
|
|
|
[info addObject: SQLClientProxyLiteral(sql)];
|
2018-06-29 08:22:13 +00:00
|
|
|
|
[sql release];
|
|
|
|
|
if (YES == wrap)
|
2014-10-02 20:30:22 +00:00
|
|
|
|
{
|
2018-06-29 08:22:13 +00:00
|
|
|
|
[sql appendString: @"begin;"];
|
2014-10-02 20:30:22 +00:00
|
|
|
|
}
|
2018-06-29 08:22:13 +00:00
|
|
|
|
|
|
|
|
|
[self _addSQL: sql andArgs: info];
|
|
|
|
|
|
|
|
|
|
if (YES == wrap)
|
|
|
|
|
{
|
|
|
|
|
[sql appendString: @"commit;"];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[db simpleExecute: info];
|
|
|
|
|
[info release]; info = nil;
|
|
|
|
|
[dbLock unlock];
|
|
|
|
|
if (nil != pool)
|
2014-10-02 20:30:22 +00:00
|
|
|
|
{
|
2018-06-29 08:22:13 +00:00
|
|
|
|
[pool swallowClient: db];
|
2014-10-02 20:30:22 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-06-29 08:22:13 +00:00
|
|
|
|
NS_HANDLER
|
2015-06-26 12:06:13 +00:00
|
|
|
|
{
|
2018-06-29 08:22:13 +00:00
|
|
|
|
NSException *e = localException;
|
|
|
|
|
|
|
|
|
|
[info release];
|
2019-03-11 09:37:02 +00:00
|
|
|
|
if (YES == wrap
|
|
|
|
|
&& NO == [[e name] isEqual: SQLConnectionException])
|
|
|
|
|
{
|
2018-06-29 08:22:13 +00:00
|
|
|
|
NS_DURING
|
|
|
|
|
{
|
|
|
|
|
[db simpleExecute: rollbackStatement];
|
|
|
|
|
}
|
|
|
|
|
NS_HANDLER
|
|
|
|
|
{
|
|
|
|
|
[db disconnect];
|
|
|
|
|
NSLog(@"Disconnected due to failed rollback after %@", e);
|
|
|
|
|
}
|
|
|
|
|
NS_ENDHANDLER
|
|
|
|
|
}
|
|
|
|
|
[dbLock unlock];
|
|
|
|
|
if (nil != pool)
|
|
|
|
|
{
|
|
|
|
|
[pool swallowClient: db];
|
|
|
|
|
}
|
|
|
|
|
[e raise];
|
2015-06-26 12:06:13 +00:00
|
|
|
|
}
|
2018-06-29 08:22:13 +00:00
|
|
|
|
NS_ENDHANDLER
|
|
|
|
|
}
|
|
|
|
|
NS_HANDLER
|
|
|
|
|
{
|
|
|
|
|
[_lock unlock];
|
|
|
|
|
[localException raise];
|
|
|
|
|
}
|
2004-07-26 08:56:26 +00:00
|
|
|
|
NS_ENDHANDLER
|
2018-06-29 08:22:13 +00:00
|
|
|
|
if (YES == _reset)
|
|
|
|
|
{
|
|
|
|
|
[self reset];
|
|
|
|
|
}
|
2004-07-26 08:56:26 +00:00
|
|
|
|
}
|
2018-06-29 08:22:13 +00:00
|
|
|
|
[_lock unlock];
|
2004-07-26 08:56:26 +00:00
|
|
|
|
}
|
2007-07-09 17:08:22 +00:00
|
|
|
|
|
|
|
|
|
- (unsigned) executeBatch
|
2009-09-08 08:05:31 +00:00
|
|
|
|
{
|
2009-09-08 08:17:09 +00:00
|
|
|
|
return [self executeBatchReturningFailures: nil logExceptions: NO];
|
2009-09-08 08:05:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (unsigned) executeBatchReturningFailures: (SQLTransaction*)failures
|
2009-09-08 08:17:09 +00:00
|
|
|
|
logExceptions: (BOOL)log
|
2007-07-09 17:08:22 +00:00
|
|
|
|
{
|
|
|
|
|
unsigned executed = 0;
|
|
|
|
|
|
2018-06-29 08:22:13 +00:00
|
|
|
|
[_lock lock];
|
2007-07-09 17:08:22 +00:00
|
|
|
|
if (_count > 0)
|
|
|
|
|
{
|
|
|
|
|
NS_DURING
|
|
|
|
|
{
|
2018-06-29 08:22:13 +00:00
|
|
|
|
NSRecursiveLock *dbLock;
|
|
|
|
|
SQLClientPool *pool = nil;
|
|
|
|
|
SQLClient *db;
|
|
|
|
|
|
|
|
|
|
if ([_owner isKindOfClass: [SQLClientPool class]])
|
|
|
|
|
{
|
|
|
|
|
pool = (SQLClientPool*)_owner;
|
|
|
|
|
db = [pool provideClient];
|
|
|
|
|
}
|
|
|
|
|
else
|
2007-07-09 17:08:22 +00:00
|
|
|
|
{
|
2018-06-29 08:22:13 +00:00
|
|
|
|
db = _owner;
|
|
|
|
|
}
|
2007-07-09 17:08:22 +00:00
|
|
|
|
|
2018-06-29 08:22:13 +00:00
|
|
|
|
dbLock = [db _lock];
|
|
|
|
|
[dbLock lock];
|
|
|
|
|
NS_DURING
|
|
|
|
|
{
|
|
|
|
|
[self execute];
|
|
|
|
|
executed = _count;
|
|
|
|
|
}
|
|
|
|
|
NS_HANDLER
|
|
|
|
|
{
|
|
|
|
|
if (log == YES || [db debugging] > 0)
|
2007-07-09 17:08:22 +00:00
|
|
|
|
{
|
2018-06-29 08:22:13 +00:00
|
|
|
|
[db debug: @"Initial failure executing batch %@: %@",
|
|
|
|
|
self, localException];
|
|
|
|
|
}
|
|
|
|
|
if (_batch == YES)
|
|
|
|
|
{
|
|
|
|
|
SQLTransaction *wrapper = nil;
|
2019-03-11 09:37:02 +00:00
|
|
|
|
NSUInteger count = [_info count];
|
|
|
|
|
NSUInteger i;
|
2018-06-29 08:22:13 +00:00
|
|
|
|
|
|
|
|
|
for (i = 0; i < count; i++)
|
2007-07-09 17:08:22 +00:00
|
|
|
|
{
|
2018-06-29 08:22:13 +00:00
|
|
|
|
BOOL success = NO;
|
|
|
|
|
id o = [_info objectAtIndex: i];
|
|
|
|
|
|
|
|
|
|
if ([o isKindOfClass: NSArrayClass] == YES)
|
|
|
|
|
{
|
|
|
|
|
NS_DURING
|
|
|
|
|
{
|
|
|
|
|
/* Wrap the statement inside a transaction so
|
|
|
|
|
* its context will still be that of a statement
|
|
|
|
|
* in a transaction rather than a standalone
|
|
|
|
|
* statement. This might be important if the
|
|
|
|
|
* statement is actually a call to a stored
|
|
|
|
|
* procedure whose code must all be executed
|
|
|
|
|
* with the visibility rules of a single
|
|
|
|
|
* transaction.
|
|
|
|
|
*/
|
|
|
|
|
if (wrapper == nil)
|
|
|
|
|
{
|
|
|
|
|
wrapper = [db transaction];
|
|
|
|
|
}
|
|
|
|
|
[wrapper reset];
|
|
|
|
|
[wrapper addPrepared: o];
|
|
|
|
|
[wrapper execute];
|
|
|
|
|
executed++;
|
|
|
|
|
success = YES;
|
|
|
|
|
}
|
|
|
|
|
NS_HANDLER
|
|
|
|
|
{
|
|
|
|
|
if (failures != nil)
|
|
|
|
|
{
|
|
|
|
|
[failures addPrepared: o];
|
|
|
|
|
}
|
|
|
|
|
if (log == YES || [db debugging] > 0)
|
|
|
|
|
{
|
|
|
|
|
[db debug:
|
|
|
|
|
@"Failure of %d executing batch %@: %@",
|
|
|
|
|
i, self, localException];
|
|
|
|
|
}
|
|
|
|
|
success = NO;
|
|
|
|
|
}
|
|
|
|
|
NS_ENDHANDLER
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
unsigned result;
|
|
|
|
|
|
|
|
|
|
result = [(SQLTransaction*)o
|
|
|
|
|
executeBatchReturningFailures: failures
|
|
|
|
|
logExceptions: log];
|
|
|
|
|
executed += result;
|
|
|
|
|
if (result == [(SQLTransaction*)o totalCount])
|
|
|
|
|
{
|
|
|
|
|
success = YES;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (success == NO && _stop == YES)
|
|
|
|
|
{
|
|
|
|
|
/* We are configured to stop after a failure,
|
|
|
|
|
* so we need to add all the subsequent statements
|
|
|
|
|
* or transactions to the list of those which have
|
|
|
|
|
* not been done.
|
|
|
|
|
*/
|
|
|
|
|
i++;
|
|
|
|
|
while (i < count)
|
|
|
|
|
{
|
|
|
|
|
id o = [_info objectAtIndex: i++];
|
|
|
|
|
|
|
|
|
|
if ([o isKindOfClass: NSArrayClass] == YES)
|
|
|
|
|
{
|
|
|
|
|
[failures addPrepared: o];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[failures append: (SQLTransaction*)o];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
2007-07-09 17:08:22 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-06-29 08:22:13 +00:00
|
|
|
|
NS_ENDHANDLER
|
|
|
|
|
[dbLock unlock];
|
|
|
|
|
if (nil != pool)
|
|
|
|
|
{
|
|
|
|
|
[pool swallowClient: db];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
NS_HANDLER
|
|
|
|
|
{
|
|
|
|
|
[_lock unlock];
|
|
|
|
|
[localException raise];
|
2007-07-09 17:08:22 +00:00
|
|
|
|
}
|
|
|
|
|
NS_ENDHANDLER
|
2018-06-29 08:22:13 +00:00
|
|
|
|
if (YES == _reset)
|
2015-06-26 12:06:13 +00:00
|
|
|
|
{
|
2018-06-29 08:22:13 +00:00
|
|
|
|
[self reset];
|
2015-06-26 12:06:13 +00:00
|
|
|
|
}
|
2007-07-09 17:08:22 +00:00
|
|
|
|
}
|
2018-06-29 08:22:13 +00:00
|
|
|
|
[_lock unlock];
|
2007-07-09 17:08:22 +00:00
|
|
|
|
return executed;
|
|
|
|
|
}
|
|
|
|
|
|
2009-09-08 08:05:31 +00:00
|
|
|
|
- (void) insertTransaction: (SQLTransaction*)trn atIndex: (unsigned)index
|
|
|
|
|
{
|
2018-06-29 08:22:13 +00:00
|
|
|
|
[_lock lock];
|
2009-09-08 08:05:31 +00:00
|
|
|
|
if (index > [_info count])
|
|
|
|
|
{
|
2018-06-29 08:22:13 +00:00
|
|
|
|
[_lock unlock];
|
2009-09-08 08:05:31 +00:00
|
|
|
|
[NSException raise: NSRangeException
|
|
|
|
|
format: @"[%@-%@] index too large",
|
|
|
|
|
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
|
|
|
|
|
}
|
|
|
|
|
if (trn == nil || trn->_count == 0)
|
|
|
|
|
{
|
2018-06-29 08:22:13 +00:00
|
|
|
|
[_lock unlock];
|
2009-09-08 08:05:31 +00:00
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"[%@-%@] attempt to insert nil/empty transaction",
|
|
|
|
|
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
|
|
|
|
|
}
|
2017-12-05 14:26:36 +00:00
|
|
|
|
if (NO == [_owner isEqual: trn->_owner]
|
|
|
|
|
&& NO == [[_owner pool] isEqual: [trn->_owner pool]])
|
2009-09-08 08:05:31 +00:00
|
|
|
|
{
|
2018-06-29 08:22:13 +00:00
|
|
|
|
[_lock unlock];
|
2009-09-08 08:05:31 +00:00
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
2018-07-27 08:21:10 +00:00
|
|
|
|
format: @"[%@-%@] database owner mismatch",
|
2009-09-08 08:05:31 +00:00
|
|
|
|
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
|
|
|
|
|
}
|
|
|
|
|
trn = [trn copy];
|
2019-03-07 13:08:31 +00:00
|
|
|
|
[_info insertObject: trn atIndex: index];
|
2009-09-08 08:05:31 +00:00
|
|
|
|
_count += trn->_count;
|
2009-11-18 11:11:29 +00:00
|
|
|
|
[trn release];
|
2018-06-29 08:22:13 +00:00
|
|
|
|
[_lock unlock];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) lock
|
|
|
|
|
{
|
|
|
|
|
[_lock lock];
|
2009-09-08 08:05:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-06-29 12:07:00 +00:00
|
|
|
|
- (id) owner
|
|
|
|
|
{
|
|
|
|
|
return _owner;
|
|
|
|
|
}
|
|
|
|
|
|
2009-09-08 08:05:31 +00:00
|
|
|
|
- (void) removeTransactionAtIndex: (unsigned)index
|
|
|
|
|
{
|
|
|
|
|
id o;
|
|
|
|
|
|
2018-06-29 08:22:13 +00:00
|
|
|
|
[_lock lock];
|
2009-09-08 08:05:31 +00:00
|
|
|
|
if (index >= [_info count])
|
|
|
|
|
{
|
2018-06-29 08:22:13 +00:00
|
|
|
|
[_lock unlock];
|
2009-09-08 08:05:31 +00:00
|
|
|
|
[NSException raise: NSRangeException
|
|
|
|
|
format: @"[%@-%@] index too large",
|
|
|
|
|
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
|
|
|
|
|
}
|
|
|
|
|
o = [_info objectAtIndex: index];
|
|
|
|
|
if ([o isKindOfClass: NSArrayClass] == YES)
|
|
|
|
|
{
|
|
|
|
|
_count--;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
_count -= [(SQLTransaction*)o totalCount];
|
|
|
|
|
}
|
|
|
|
|
[_info removeObjectAtIndex: index];
|
2018-06-29 08:22:13 +00:00
|
|
|
|
[_lock unlock];
|
2009-09-08 08:05:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
2004-07-26 08:56:26 +00:00
|
|
|
|
- (void) reset
|
|
|
|
|
{
|
2018-06-29 08:22:13 +00:00
|
|
|
|
[_lock lock];
|
2004-07-26 08:56:26 +00:00
|
|
|
|
[_info removeAllObjects];
|
|
|
|
|
_count = 0;
|
2018-06-29 08:22:13 +00:00
|
|
|
|
[_lock unlock];
|
2004-07-26 08:56:26 +00:00
|
|
|
|
}
|
2009-09-08 08:05:31 +00:00
|
|
|
|
|
2018-06-29 08:22:13 +00:00
|
|
|
|
- (BOOL) setResetOnExecute: (BOOL)aFlag
|
|
|
|
|
{
|
|
|
|
|
BOOL old;
|
|
|
|
|
|
|
|
|
|
[_lock lock];
|
|
|
|
|
old = _reset;
|
|
|
|
|
_reset = (aFlag ? YES : NO);
|
|
|
|
|
[_lock unlock];
|
2014-08-09 14:02:11 +00:00
|
|
|
|
return old;
|
|
|
|
|
}
|
|
|
|
|
|
2009-09-08 08:05:31 +00:00
|
|
|
|
- (unsigned) totalCount
|
|
|
|
|
{
|
|
|
|
|
return _count;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (SQLTransaction*) transactionAtIndex: (unsigned)index
|
|
|
|
|
{
|
|
|
|
|
id o;
|
|
|
|
|
|
2018-06-29 08:22:13 +00:00
|
|
|
|
[_lock lock];
|
2009-09-08 08:05:31 +00:00
|
|
|
|
if (index >= [_info count])
|
|
|
|
|
{
|
2018-06-29 08:22:13 +00:00
|
|
|
|
[_lock unlock];
|
2009-09-08 08:05:31 +00:00
|
|
|
|
[NSException raise: NSRangeException
|
|
|
|
|
format: @"[%@-%@] index too large",
|
|
|
|
|
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
|
|
|
|
|
}
|
|
|
|
|
o = [_info objectAtIndex: index];
|
|
|
|
|
if ([o isKindOfClass: NSArrayClass] == YES)
|
|
|
|
|
{
|
2015-06-29 12:07:00 +00:00
|
|
|
|
SQLTransaction *t = [[self owner] transaction];
|
2009-09-08 08:05:31 +00:00
|
|
|
|
|
2015-04-12 09:50:50 +00:00
|
|
|
|
[t addPrepared: o];
|
2018-06-29 08:22:13 +00:00
|
|
|
|
[_lock unlock];
|
2009-09-08 08:05:31 +00:00
|
|
|
|
return t;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
o = [o copy];
|
2018-06-29 08:22:13 +00:00
|
|
|
|
[_lock unlock];
|
2009-11-18 11:11:29 +00:00
|
|
|
|
return [o autorelease];
|
2009-09-08 08:05:31 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-06-29 08:22:13 +00:00
|
|
|
|
|
|
|
|
|
- (void) unlock
|
|
|
|
|
{
|
|
|
|
|
[_lock unlock];
|
|
|
|
|
}
|
2004-04-26 15:13:27 +00:00
|
|
|
|
@end
|
|
|
|
|
|
2012-10-22 15:57:56 +00:00
|
|
|
|
|
|
|
|
|
@implementation SQLClient (Notifications)
|
|
|
|
|
|
|
|
|
|
static NSString *
|
|
|
|
|
validName(NSString *name)
|
|
|
|
|
{
|
|
|
|
|
const char *ptr;
|
|
|
|
|
|
|
|
|
|
if (NO == [name isKindOfClass: [NSString class]])
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"Notification name must be a string"];
|
|
|
|
|
}
|
|
|
|
|
ptr = [name UTF8String];
|
|
|
|
|
if (!isalpha(*ptr))
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"Notification name must begin with letter"];
|
|
|
|
|
}
|
|
|
|
|
ptr++;
|
|
|
|
|
while (0 != *ptr)
|
|
|
|
|
{
|
|
|
|
|
if (!isdigit(*ptr) && !isalpha(*ptr) && *ptr != '_')
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"Notification name must contain only letters,"
|
|
|
|
|
@" digits, and underscores"];
|
|
|
|
|
}
|
|
|
|
|
ptr++;
|
|
|
|
|
}
|
2016-06-23 16:57:13 +00:00
|
|
|
|
return name;
|
2012-10-22 15:57:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) addObserver: (id)anObserver
|
|
|
|
|
selector: (SEL)aSelector
|
|
|
|
|
name: (NSString*)name
|
|
|
|
|
{
|
2016-06-21 15:34:42 +00:00
|
|
|
|
if (nil == anObserver)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"Attempt to add nil observer to SQL client"];
|
|
|
|
|
}
|
2016-10-18 08:32:05 +00:00
|
|
|
|
if (nil != _pool)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"Attempt to use pool client as observer"];
|
|
|
|
|
}
|
2012-10-22 15:57:56 +00:00
|
|
|
|
name = validName(name);
|
|
|
|
|
[lock lock];
|
2012-11-29 11:40:04 +00:00
|
|
|
|
NS_DURING
|
2012-10-22 15:57:56 +00:00
|
|
|
|
{
|
2017-09-19 10:28:05 +00:00
|
|
|
|
NSMutableSet *set;
|
|
|
|
|
|
2012-11-29 11:40:04 +00:00
|
|
|
|
if (nil == _observers)
|
2012-10-22 15:57:56 +00:00
|
|
|
|
{
|
2012-11-29 11:40:04 +00:00
|
|
|
|
_observers = NSCreateMapTable(NSNonRetainedObjectMapKeyCallBacks,
|
|
|
|
|
NSObjectMapValueCallBacks, 0);
|
|
|
|
|
_names = [NSCountedSet new];
|
2012-10-22 15:57:56 +00:00
|
|
|
|
}
|
2012-11-29 11:40:04 +00:00
|
|
|
|
set = (NSMutableSet*)NSMapGet(_observers, (void*)anObserver);
|
|
|
|
|
if (nil == set)
|
|
|
|
|
{
|
|
|
|
|
set = [NSMutableSet new];
|
|
|
|
|
NSMapInsert(_observers, anObserver, set);
|
|
|
|
|
[set release];
|
|
|
|
|
}
|
|
|
|
|
if (nil == [set member: name])
|
|
|
|
|
{
|
|
|
|
|
[set addObject: name];
|
2017-09-19 10:28:05 +00:00
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver: anObserver
|
|
|
|
|
selector: aSelector
|
|
|
|
|
name: name
|
|
|
|
|
object: self];
|
2012-11-29 11:40:04 +00:00
|
|
|
|
[_names addObject: name];
|
2017-09-19 10:28:05 +00:00
|
|
|
|
if (YES == connected && 1 == [_names countForObject: name])
|
2012-11-29 11:40:04 +00:00
|
|
|
|
{
|
2017-06-29 12:53:11 +00:00
|
|
|
|
[self backendListen: [self quoteName: name]];
|
2012-11-29 11:40:04 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2012-10-22 15:57:56 +00:00
|
|
|
|
}
|
2012-11-29 11:40:04 +00:00
|
|
|
|
NS_HANDLER
|
|
|
|
|
{
|
|
|
|
|
[lock unlock];
|
|
|
|
|
[localException raise];
|
|
|
|
|
}
|
|
|
|
|
NS_ENDHANDLER
|
2012-10-22 15:57:56 +00:00
|
|
|
|
[lock unlock];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) postNotificationName: (NSString*)name payload: (NSString*)more
|
|
|
|
|
{
|
|
|
|
|
name = validName(name);
|
|
|
|
|
if (nil != more)
|
|
|
|
|
{
|
|
|
|
|
if (NO == [more isKindOfClass: [NSString class]])
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"Notification payload is not a string"];
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-05-16 10:03:51 +00:00
|
|
|
|
[lock lock];
|
2020-03-21 17:15:27 +00:00
|
|
|
|
if ([self connect] == NO)
|
|
|
|
|
{
|
|
|
|
|
[lock unlock];
|
|
|
|
|
[NSException raise: SQLConnectionException
|
|
|
|
|
format: @"Unable to connect to '%@' to notify %@ with %@",
|
|
|
|
|
[self name], name, more];
|
|
|
|
|
}
|
2014-05-16 10:03:51 +00:00
|
|
|
|
NS_DURING
|
|
|
|
|
{
|
2017-06-29 12:53:11 +00:00
|
|
|
|
[self backendNotify: [self quoteName: name]
|
2016-06-23 16:57:13 +00:00
|
|
|
|
payload: more];
|
2014-05-16 10:03:51 +00:00
|
|
|
|
}
|
|
|
|
|
NS_HANDLER
|
|
|
|
|
{
|
|
|
|
|
[lock unlock];
|
|
|
|
|
[localException raise];
|
|
|
|
|
}
|
|
|
|
|
NS_ENDHANDLER
|
|
|
|
|
[lock unlock];
|
2012-10-22 15:57:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) removeObserver: (id)anObserver name: (NSString*)name
|
|
|
|
|
{
|
|
|
|
|
if (nil != name)
|
|
|
|
|
{
|
|
|
|
|
name = validName(name);
|
|
|
|
|
}
|
|
|
|
|
[lock lock];
|
2012-11-29 11:40:04 +00:00
|
|
|
|
NS_DURING
|
2012-10-22 15:57:56 +00:00
|
|
|
|
{
|
2012-11-29 11:40:04 +00:00
|
|
|
|
if (_observers != nil)
|
2012-10-22 15:57:56 +00:00
|
|
|
|
{
|
2012-11-29 11:40:04 +00:00
|
|
|
|
NSNotificationCenter *nc;
|
2016-06-21 15:34:42 +00:00
|
|
|
|
NSEnumerator *oe = nil;
|
2012-11-29 11:40:04 +00:00
|
|
|
|
|
|
|
|
|
nc = [NSNotificationCenter defaultCenter];
|
2016-06-21 15:34:42 +00:00
|
|
|
|
if (nil == anObserver)
|
2012-10-22 15:57:56 +00:00
|
|
|
|
{
|
2016-06-21 15:34:42 +00:00
|
|
|
|
oe = [NSAllMapTableKeys(_observers) objectEnumerator];
|
|
|
|
|
anObserver = [oe nextObject];
|
2012-11-29 11:40:04 +00:00
|
|
|
|
}
|
2016-06-21 15:34:42 +00:00
|
|
|
|
while (anObserver != nil)
|
2012-11-29 11:40:04 +00:00
|
|
|
|
{
|
2016-06-21 15:34:42 +00:00
|
|
|
|
NSMutableSet *set;
|
2017-09-19 10:28:05 +00:00
|
|
|
|
NSEnumerator *nameEnumerator = nil;
|
2016-06-21 15:34:42 +00:00
|
|
|
|
|
|
|
|
|
set = (NSMutableSet*)NSMapGet(_observers, (void*)anObserver);
|
|
|
|
|
if (nil == name)
|
|
|
|
|
{
|
2017-09-19 10:28:05 +00:00
|
|
|
|
/* Remove all names for this observer.
|
|
|
|
|
*/
|
|
|
|
|
nameEnumerator = [[set allObjects] objectEnumerator];
|
2016-06-21 15:34:42 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2017-09-19 10:28:05 +00:00
|
|
|
|
nameEnumerator
|
|
|
|
|
= [[NSArray arrayWithObject: name] objectEnumerator];
|
2016-06-21 15:34:42 +00:00
|
|
|
|
}
|
2017-09-19 10:28:05 +00:00
|
|
|
|
while (nil != (name = [nameEnumerator nextObject]))
|
2012-10-22 15:57:56 +00:00
|
|
|
|
{
|
2016-06-21 15:34:42 +00:00
|
|
|
|
if (nil != [set member: name])
|
2012-11-29 11:40:04 +00:00
|
|
|
|
{
|
2017-09-19 10:28:05 +00:00
|
|
|
|
[[name retain] autorelease];
|
|
|
|
|
[set removeObject: name];
|
2016-06-21 15:34:42 +00:00
|
|
|
|
[nc removeObserver: anObserver
|
|
|
|
|
name: name
|
|
|
|
|
object: self];
|
|
|
|
|
[_names removeObject: name];
|
2016-06-23 15:06:05 +00:00
|
|
|
|
if (YES == connected
|
|
|
|
|
&& 0 == [_names countForObject: name])
|
2016-06-21 15:34:42 +00:00
|
|
|
|
{
|
2017-06-29 12:53:11 +00:00
|
|
|
|
[self backendUnlisten: [self quoteName: name]];
|
2016-06-21 15:34:42 +00:00
|
|
|
|
}
|
2012-11-29 11:40:04 +00:00
|
|
|
|
}
|
2012-10-22 15:57:56 +00:00
|
|
|
|
}
|
2016-06-21 15:34:42 +00:00
|
|
|
|
if ([set count] == 0)
|
|
|
|
|
{
|
|
|
|
|
NSMapRemove(_observers, (void*)anObserver);
|
|
|
|
|
}
|
|
|
|
|
anObserver = [oe nextObject];
|
2014-04-12 07:24:31 +00:00
|
|
|
|
}
|
2012-10-22 15:57:56 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2012-11-29 11:40:04 +00:00
|
|
|
|
NS_HANDLER
|
|
|
|
|
{
|
|
|
|
|
[lock unlock];
|
|
|
|
|
[localException raise];
|
|
|
|
|
}
|
|
|
|
|
NS_ENDHANDLER
|
2012-10-22 15:57:56 +00:00
|
|
|
|
[lock unlock];
|
|
|
|
|
}
|
|
|
|
|
@end
|
2013-03-04 14:47:29 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@implementation SQLDictionaryBuilder
|
|
|
|
|
- (void) addObject: (id)anObject
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id) alloc
|
|
|
|
|
{
|
|
|
|
|
return [self retain];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSMutableDictionary*) content
|
|
|
|
|
{
|
|
|
|
|
return content;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) dealloc
|
|
|
|
|
{
|
|
|
|
|
[content release];
|
|
|
|
|
[super dealloc];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id) initWithCapacity: (NSUInteger)capacity
|
|
|
|
|
{
|
2015-01-06 10:56:39 +00:00
|
|
|
|
if (nil != (self = [super init]))
|
|
|
|
|
{
|
|
|
|
|
DESTROY(content);
|
|
|
|
|
content = [[NSMutableDictionary alloc] initWithCapacity: capacity];
|
|
|
|
|
}
|
2013-03-04 14:47:29 +00:00
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-21 09:56:09 +00:00
|
|
|
|
- (id) mutableCopyWithZone: (NSZone*)aZone
|
|
|
|
|
{
|
|
|
|
|
return [content mutableCopyWithZone: aZone];
|
|
|
|
|
}
|
|
|
|
|
|
2013-03-06 22:46:15 +00:00
|
|
|
|
- (id) newWithValues: (id*)values
|
|
|
|
|
keys: (NSString**)keys
|
|
|
|
|
count: (unsigned int)count
|
2013-03-04 14:47:29 +00:00
|
|
|
|
{
|
|
|
|
|
if (count != 2)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"Query did not return key/value pairs"];
|
|
|
|
|
}
|
|
|
|
|
[content setObject: values[1] forKey: values[0]];
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
@end
|
|
|
|
|
|
2014-02-15 07:16:26 +00:00
|
|
|
|
@implementation SQLSetBuilder
|
|
|
|
|
- (NSUInteger) added
|
|
|
|
|
{
|
|
|
|
|
return added;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) addObject: (id)anObject
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id) alloc
|
|
|
|
|
{
|
|
|
|
|
return [self retain];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSCountedSet*) content
|
|
|
|
|
{
|
|
|
|
|
return content;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) dealloc
|
|
|
|
|
{
|
|
|
|
|
[content release];
|
|
|
|
|
[super dealloc];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id) initWithCapacity: (NSUInteger)capacity
|
|
|
|
|
{
|
2015-01-06 10:56:39 +00:00
|
|
|
|
if (nil != (self = [super init]))
|
|
|
|
|
{
|
|
|
|
|
DESTROY(content);
|
|
|
|
|
content = [[NSCountedSet alloc] initWithCapacity: capacity];
|
|
|
|
|
added = 0;
|
|
|
|
|
}
|
2014-02-15 07:16:26 +00:00
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-21 09:56:09 +00:00
|
|
|
|
- (id) mutableCopyWithZone: (NSZone*)aZone
|
|
|
|
|
{
|
|
|
|
|
return [content mutableCopyWithZone: aZone];
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-15 07:16:26 +00:00
|
|
|
|
- (id) newWithValues: (id*)values
|
|
|
|
|
keys: (NSString**)keys
|
|
|
|
|
count: (unsigned int)count
|
|
|
|
|
{
|
|
|
|
|
if (count != 1)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"Query did not return a single value"];
|
|
|
|
|
}
|
|
|
|
|
added++;
|
|
|
|
|
[content addObject: values[0]];
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
@end
|
|
|
|
|
|
2013-03-04 14:47:29 +00:00
|
|
|
|
@implementation SQLSingletonBuilder
|
2013-03-06 22:46:15 +00:00
|
|
|
|
- (id) newWithValues: (id*)values
|
|
|
|
|
keys: (NSString**)keys
|
|
|
|
|
count: (unsigned int)count
|
2013-03-04 14:47:29 +00:00
|
|
|
|
{
|
|
|
|
|
/* Instead of creating an object to hold the supplied record,
|
|
|
|
|
* we use the field from the record as the value to be used.
|
|
|
|
|
*/
|
|
|
|
|
if (count != 1)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"Query did not return singleton values"];
|
|
|
|
|
}
|
|
|
|
|
return [values[0] retain];
|
|
|
|
|
}
|
|
|
|
|
@end
|
|
|
|
|
|
2014-10-13 10:47:06 +00:00
|
|
|
|
@implementation SQLClientPool (Adjust)
|
|
|
|
|
|
|
|
|
|
+ (void) _adjustPoolConnections: (int)n
|
|
|
|
|
{
|
|
|
|
|
unsigned err = 0;
|
|
|
|
|
|
|
|
|
|
[clientsLock lock];
|
|
|
|
|
poolConnections += n;
|
|
|
|
|
if (poolConnections < 0)
|
|
|
|
|
{
|
|
|
|
|
err -= poolConnections;
|
|
|
|
|
poolConnections = 0;
|
|
|
|
|
}
|
|
|
|
|
[clientsLock unlock];
|
|
|
|
|
NSAssert(0 == err, NSInvalidArgumentException);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
2016-04-27 07:55:26 +00:00
|
|
|
|
@implementation NSObject (SQLClient)
|
2017-07-04 11:13:16 +00:00
|
|
|
|
|
2016-04-27 07:55:26 +00:00
|
|
|
|
- (BOOL) isNull
|
|
|
|
|
{
|
|
|
|
|
if (nil == null)
|
|
|
|
|
{
|
|
|
|
|
null = [NSNull new];
|
|
|
|
|
}
|
|
|
|
|
return (self == null ? YES : NO);
|
|
|
|
|
}
|
2017-07-04 11:13:16 +00:00
|
|
|
|
|
2017-07-10 09:04:32 +00:00
|
|
|
|
- (SQLLiteral*) quoteBigInteger
|
|
|
|
|
{
|
|
|
|
|
int64_t v = (int64_t)[[self description] longLongValue];
|
|
|
|
|
|
|
|
|
|
return quoteBigInteger(v);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (SQLLiteral*) quoteBigNatural
|
|
|
|
|
{
|
|
|
|
|
int64_t v = (int64_t)[[self description] longLongValue];
|
|
|
|
|
|
|
|
|
|
if (v < 0)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"Object (%@) is not a natural number", self];
|
|
|
|
|
}
|
|
|
|
|
return quoteBigInteger(v);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (SQLLiteral*) quoteBigPositive
|
|
|
|
|
{
|
|
|
|
|
int64_t v = (int64_t)[[self description] longLongValue];
|
|
|
|
|
|
|
|
|
|
if (v <= 0)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"Object (%@) is not a positive number", self];
|
|
|
|
|
}
|
|
|
|
|
return quoteBigInteger(v);
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-22 10:57:19 +00:00
|
|
|
|
- (SQLLiteral*) quoteBoolean
|
|
|
|
|
{
|
|
|
|
|
return (SQLLiteral*)([[self description] boolValue] ? @"true" : @"false");
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-04 11:13:16 +00:00
|
|
|
|
- (SQLLiteral*) quoteForSQLClient: (SQLClient*)db
|
|
|
|
|
{
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-10 09:04:32 +00:00
|
|
|
|
- (SQLLiteral*) quoteInteger
|
|
|
|
|
{
|
|
|
|
|
int64_t v = (int64_t)[[self description] longLongValue];
|
|
|
|
|
|
2017-11-29 10:05:19 +00:00
|
|
|
|
if (v > (int64_t)2147483647LL || v < (int64_t)-2147483648LL)
|
2017-07-10 09:04:32 +00:00
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"Object (%@) is not a 32bit number", self];
|
|
|
|
|
}
|
|
|
|
|
return quoteBigInteger(v);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (SQLLiteral*) quoteNatural
|
|
|
|
|
{
|
|
|
|
|
int64_t v = (int64_t)[[self description] longLongValue];
|
|
|
|
|
|
2017-11-29 10:05:19 +00:00
|
|
|
|
if (v > (int64_t)2147483647LL || v < (int64_t)-2147483648LL)
|
2017-07-10 09:04:32 +00:00
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"Object (%@) is not a 32bit number", self];
|
|
|
|
|
}
|
|
|
|
|
if (v < 0)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"Object (%@) is not a natural number", self];
|
|
|
|
|
}
|
|
|
|
|
return quoteBigInteger(v);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (SQLLiteral*) quotePositive
|
|
|
|
|
{
|
|
|
|
|
int64_t v = (int64_t)[[self description] longLongValue];
|
|
|
|
|
|
2017-11-29 10:05:19 +00:00
|
|
|
|
if (v > (int64_t)2147483647LL || v < (int64_t)-2147483648LL)
|
2017-07-10 09:04:32 +00:00
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"Object (%@) is not a 32bit number", self];
|
|
|
|
|
}
|
|
|
|
|
if (v <= 0)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"Object (%@) is not a positive number", self];
|
|
|
|
|
}
|
|
|
|
|
return quoteBigInteger(v);
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-27 07:55:26 +00:00
|
|
|
|
@end
|
|
|
|
|
|
2017-06-30 10:39:24 +00:00
|
|
|
|
@implementation SQLClient (Quote)
|
|
|
|
|
|
|
|
|
|
+ (BOOL) autoquote
|
|
|
|
|
{
|
|
|
|
|
return autoquote;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
+ (BOOL) autoquoteWarning
|
|
|
|
|
{
|
|
|
|
|
return autoquoteWarning;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
+ (void) setAutoquote: (BOOL)aFlag
|
|
|
|
|
{
|
|
|
|
|
autoquote = (aFlag ? YES : NO);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
+ (void) setAutoquoteWarning: (BOOL)aFlag
|
|
|
|
|
{
|
|
|
|
|
autoquoteWarning = (aFlag ? YES : NO);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@end
|
2017-07-04 11:13:16 +00:00
|
|
|
|
|
|
|
|
|
@implementation NSArray (Quote)
|
|
|
|
|
- (SQLLiteral*) quoteForSQLClient: (SQLClient*)db
|
|
|
|
|
{
|
|
|
|
|
return [db quoteSet: self];
|
|
|
|
|
}
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation NSDate (Quote)
|
|
|
|
|
- (SQLLiteral*) quoteForSQLClient: (SQLClient*)db
|
|
|
|
|
{
|
|
|
|
|
return SQLClientMakeLiteral([self descriptionWithCalendarFormat:
|
|
|
|
|
@"'%Y-%m-%d %H:%M:%S.%F %z'" timeZone: nil locale: nil]);
|
|
|
|
|
}
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation NSNull (Quote)
|
|
|
|
|
- (SQLLiteral*) quoteForSQLClient: (SQLClient*)db
|
|
|
|
|
{
|
|
|
|
|
return (SQLLiteral*)@"NULL";
|
|
|
|
|
}
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation NSNumber (Quote)
|
2017-07-10 09:04:32 +00:00
|
|
|
|
- (SQLLiteral*) quoteBigInteger
|
|
|
|
|
{
|
|
|
|
|
int64_t v = (int64_t)[self longLongValue];
|
|
|
|
|
|
|
|
|
|
return quoteBigInteger(v);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (SQLLiteral*) quoteBigNatural
|
|
|
|
|
{
|
|
|
|
|
int64_t v = (int64_t)[self longLongValue];
|
|
|
|
|
|
|
|
|
|
if (v < 0)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"Object (%@) is not a natural number", self];
|
|
|
|
|
}
|
|
|
|
|
return quoteBigInteger(v);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (SQLLiteral*) quoteBigPositive
|
|
|
|
|
{
|
|
|
|
|
int64_t v = (int64_t)[self longLongValue];
|
|
|
|
|
|
|
|
|
|
if (v <= 0)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"Object (%@) is not a positive number", self];
|
|
|
|
|
}
|
|
|
|
|
return quoteBigInteger(v);
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-04 11:13:16 +00:00
|
|
|
|
- (SQLLiteral*) quoteForSQLClient: (SQLClient*)db
|
|
|
|
|
{
|
|
|
|
|
return SQLClientMakeLiteral([self description]);
|
|
|
|
|
}
|
2017-07-10 09:04:32 +00:00
|
|
|
|
|
|
|
|
|
- (SQLLiteral*) quoteInteger
|
|
|
|
|
{
|
|
|
|
|
int64_t v = (int64_t)[self longLongValue];
|
|
|
|
|
|
2017-11-29 10:05:19 +00:00
|
|
|
|
if (v > (int64_t)2147483647LL || v < (int64_t)-2147483648LL)
|
2017-07-10 09:04:32 +00:00
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"Object (%@) is not a 32bit number", self];
|
|
|
|
|
}
|
|
|
|
|
return quoteBigInteger(v);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (SQLLiteral*) quoteNatural
|
|
|
|
|
{
|
|
|
|
|
int64_t v = (int64_t)[self longLongValue];
|
|
|
|
|
|
2017-11-29 10:05:19 +00:00
|
|
|
|
if (v > (int64_t)2147483647LL || v < (int64_t)-2147483648LL)
|
2017-07-10 09:04:32 +00:00
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"Object (%@) is not a 32bit number", self];
|
|
|
|
|
}
|
|
|
|
|
if (v < 0)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"Object (%@) is not a natural number", self];
|
|
|
|
|
}
|
|
|
|
|
return quoteBigInteger(v);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (SQLLiteral*) quotePositive
|
|
|
|
|
{
|
|
|
|
|
int64_t v = (int64_t)[self longLongValue];
|
|
|
|
|
|
2017-11-29 10:05:19 +00:00
|
|
|
|
if (v > (int64_t)2147483647LL || v < (int64_t)-2147483648LL)
|
2017-07-10 09:04:32 +00:00
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"Object (%@) is not a 32bit number", self];
|
|
|
|
|
}
|
|
|
|
|
if (v <= 0)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInternalInconsistencyException
|
|
|
|
|
format: @"Object (%@) is not a positive number", self];
|
|
|
|
|
}
|
|
|
|
|
return quoteBigInteger(v);
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-04 11:13:16 +00:00
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation NSSet (Quote)
|
|
|
|
|
- (SQLLiteral*) quoteForSQLClient: (SQLClient*)db
|
|
|
|
|
{
|
|
|
|
|
return [db quoteSet: self];
|
|
|
|
|
}
|
|
|
|
|
@end
|
|
|
|
|
|
2020-02-14 17:04:24 +00:00
|
|
|
|
|
|
|
|
|
/* Count the number of bytes that make up this UTF-8 code point.
|
|
|
|
|
* This to keep in mind:
|
|
|
|
|
* This macro doesn't return anything larger than '4'
|
|
|
|
|
* Legal UTF-8 cannot be larger than 4 bytes long (0x10FFFF)
|
|
|
|
|
* It will return 0 for anything illegal
|
|
|
|
|
*/
|
|
|
|
|
#define UTF8_BYTE_COUNT(c) \
|
|
|
|
|
(((c) < 0xf8) ? 1 + ((c) >= 0xc0) + ((c) >= 0xe0) + ((c) >= 0xf0) : 0)
|
|
|
|
|
|
|
|
|
|
/* Sequentially extracts characters from UTF-8 string
|
|
|
|
|
* p = pointer to the utf-8 data
|
|
|
|
|
* l = length (bytes) of the utf-8 data
|
|
|
|
|
* o = pointer to current offset within the data
|
|
|
|
|
* n = pointer to either zero or the next pre-read part of a surrogate pair.
|
|
|
|
|
* The condition for having read the entire string is that the offset (*o)
|
|
|
|
|
* is the number of bytes in the string, and the unichar pointed to by *n
|
|
|
|
|
* is zero (meaning there is no second part of a surrogate pair remaining).
|
|
|
|
|
*/
|
|
|
|
|
static inline unichar
|
|
|
|
|
nextUTF8(const uint8_t *p, unsigned l, unsigned *o, unichar *n)
|
|
|
|
|
{
|
|
|
|
|
unsigned i;
|
|
|
|
|
|
|
|
|
|
/* If we still have the second part of a surrogate pair, return it.
|
|
|
|
|
*/
|
|
|
|
|
if (*n > 0)
|
|
|
|
|
{
|
|
|
|
|
unichar u = *n;
|
|
|
|
|
|
|
|
|
|
*n = 0;
|
|
|
|
|
return u;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((i = *o) < l)
|
|
|
|
|
{
|
|
|
|
|
uint8_t c = p[i];
|
|
|
|
|
uint32_t u = c;
|
|
|
|
|
|
|
|
|
|
if (c > 0x7f)
|
|
|
|
|
{
|
|
|
|
|
int j, sle = 0;
|
|
|
|
|
|
|
|
|
|
/* calculated the expected sequence length */
|
|
|
|
|
sle = UTF8_BYTE_COUNT(c);
|
|
|
|
|
|
|
|
|
|
/* legal ? */
|
|
|
|
|
if (sle < 2)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"bad multibyte character length"];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sle + i > l)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"multibyte character extends beyond data"];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* get the codepoint */
|
|
|
|
|
for (j = 1; j < sle; j++)
|
|
|
|
|
{
|
|
|
|
|
uint8_t b = p[i + j];
|
|
|
|
|
|
|
|
|
|
if (b < 0x80 || b >= 0xc0)
|
|
|
|
|
break;
|
|
|
|
|
u = (u << 6) | (b & 0x3f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (j < sle)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"bad data in multibyte character"];
|
|
|
|
|
}
|
|
|
|
|
u = u & ~(0xffffffff << ((5 * sle) + 1));
|
|
|
|
|
i += sle;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* We discard invalid codepoints here.
|
|
|
|
|
*/
|
|
|
|
|
if (u > 0x10ffff)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"invalid unicode codepoint"];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
i++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Add codepoint as either a single unichar for BMP
|
|
|
|
|
* or as a pair of surrogates for codepoints over 16 bits.
|
|
|
|
|
*/
|
|
|
|
|
if (u >= 0x10000)
|
|
|
|
|
{
|
|
|
|
|
unichar ul, uh;
|
|
|
|
|
|
|
|
|
|
u -= 0x10000;
|
|
|
|
|
ul = u & 0x3ff;
|
|
|
|
|
uh = (u >> 10) & 0x3ff;
|
|
|
|
|
|
|
|
|
|
*n = ul + 0xdc00; // record second part of pair
|
|
|
|
|
u = uh + 0xd800; // return first part.
|
|
|
|
|
}
|
|
|
|
|
*o = i; // Return new index
|
|
|
|
|
return (unichar)u;
|
|
|
|
|
}
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"no more data in UTF-8 string"];
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@implementation SQLString
|
|
|
|
|
|
|
|
|
|
- (const char*) UTF8String
|
|
|
|
|
{
|
|
|
|
|
return (const char*)utf8Bytes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (unichar) characterAtIndex: (NSUInteger)index
|
|
|
|
|
{
|
|
|
|
|
NSUInteger l = 0;
|
|
|
|
|
unichar u;
|
|
|
|
|
unichar n = 0;
|
|
|
|
|
unsigned i = 0;
|
|
|
|
|
|
|
|
|
|
while (i < byteLen || n > 0)
|
|
|
|
|
{
|
|
|
|
|
u = nextUTF8(utf8Bytes, byteLen, &i, &n);
|
|
|
|
|
if (l++ == index)
|
|
|
|
|
{
|
|
|
|
|
return u;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[NSException raise: NSInvalidArgumentException
|
|
|
|
|
format: @"-characterAtIndex: index out of range"];
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (BOOL) canBeConvertedToEncoding: (NSStringEncoding)encoding
|
|
|
|
|
{
|
|
|
|
|
if (NSUTF8StringEncoding == encoding
|
|
|
|
|
|| NSUnicodeStringEncoding == encoding
|
|
|
|
|
|| (NSISOLatin1StringEncoding == encoding && YES == latin1)
|
|
|
|
|
|| (isByteCoding(encoding) && YES == ascii))
|
|
|
|
|
{
|
|
|
|
|
return YES;
|
|
|
|
|
}
|
|
|
|
|
NS_DURING
|
|
|
|
|
{
|
|
|
|
|
id d = [self dataUsingEncoding: encoding allowLossyConversion: NO];
|
|
|
|
|
|
|
|
|
|
NS_VALRETURN(d != nil ? YES : NO);
|
|
|
|
|
}
|
|
|
|
|
NS_HANDLER
|
|
|
|
|
{
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
NS_ENDHANDLER
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSData*) dataUsingEncoding: (NSStringEncoding)encoding
|
|
|
|
|
allowLossyConversion: (BOOL)flag
|
|
|
|
|
{
|
|
|
|
|
if (0 == byteLen)
|
|
|
|
|
{
|
|
|
|
|
return [NSData data];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (NSUTF8StringEncoding == encoding
|
|
|
|
|
|| (YES == ascii && YES == isByteCoding(encoding)))
|
|
|
|
|
{
|
|
|
|
|
/* We can just copy the data unmodified
|
|
|
|
|
*/
|
|
|
|
|
return [NSData dataWithBytes: (void*)utf8Bytes
|
|
|
|
|
length: byteLen];
|
|
|
|
|
}
|
|
|
|
|
else if (YES == latin1 && NSISOLatin1StringEncoding == encoding)
|
|
|
|
|
{
|
|
|
|
|
unichar n = 0;
|
|
|
|
|
unsigned i = 0;
|
|
|
|
|
NSUInteger index = 0;
|
|
|
|
|
uint8_t *buf = malloc(charLen);
|
|
|
|
|
|
|
|
|
|
while (index < charLen && (i < byteLen || n > 0))
|
|
|
|
|
{
|
|
|
|
|
buf[index++] = nextUTF8(utf8Bytes, byteLen, &i, &n);
|
|
|
|
|
}
|
|
|
|
|
return [NSData dataWithBytesNoCopy: buf
|
|
|
|
|
length: charLen
|
|
|
|
|
freeWhenDone: YES];
|
|
|
|
|
}
|
|
|
|
|
else if (NSUnicodeStringEncoding == encoding)
|
|
|
|
|
{
|
|
|
|
|
unichar n = 0;
|
|
|
|
|
unsigned i = 0;
|
|
|
|
|
NSUInteger index = 0;
|
|
|
|
|
unichar *buf = malloc(charLen * sizeof(unichar));
|
|
|
|
|
|
|
|
|
|
while (index < charLen && (i < byteLen || n > 0))
|
|
|
|
|
{
|
|
|
|
|
buf[index++] = nextUTF8(utf8Bytes, byteLen, &i, &n);
|
|
|
|
|
}
|
|
|
|
|
return [NSData dataWithBytesNoCopy: buf
|
|
|
|
|
length: charLen * sizeof(unichar)
|
|
|
|
|
freeWhenDone: YES];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [super dataUsingEncoding: encoding allowLossyConversion: flag];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) getCharacters: (unichar*)buffer
|
|
|
|
|
range: (NSRange)aRange
|
|
|
|
|
{
|
|
|
|
|
unichar n = 0;
|
|
|
|
|
unsigned i = 0;
|
|
|
|
|
NSUInteger max = NSMaxRange(aRange);
|
|
|
|
|
NSUInteger index = 0;
|
|
|
|
|
|
|
|
|
|
if (NSNotFound == aRange.location)
|
|
|
|
|
[NSException raise: NSRangeException format:
|
|
|
|
|
@"in %s, range { %"PRIuPTR", %"PRIuPTR" } extends beyond string",
|
|
|
|
|
GSNameFromSelector(_cmd), aRange.location, aRange.length];
|
|
|
|
|
|
|
|
|
|
while (index < aRange.location && (i < byteLen || n > 0))
|
|
|
|
|
{
|
|
|
|
|
nextUTF8(utf8Bytes, byteLen, &i, &n);
|
|
|
|
|
index++;
|
|
|
|
|
}
|
|
|
|
|
if (index == aRange.location)
|
|
|
|
|
{
|
|
|
|
|
while (index < max && (i < byteLen || n > 0))
|
|
|
|
|
{
|
|
|
|
|
*buffer++ = nextUTF8(utf8Bytes, byteLen, &i, &n);
|
|
|
|
|
index++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (index != max)
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSRangeException format:
|
|
|
|
|
@"in %s, range { %"PRIuPTR", %"PRIuPTR" } extends beyond string",
|
|
|
|
|
GSNameFromSelector(_cmd), aRange.location, aRange.length];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (BOOL) getCString: (char*)buffer
|
|
|
|
|
maxLength: (NSUInteger)maxLength
|
|
|
|
|
encoding: (NSStringEncoding)encoding
|
|
|
|
|
{
|
|
|
|
|
const uint8_t *ptr = utf8Bytes;
|
|
|
|
|
int length = byteLen;
|
|
|
|
|
int index;
|
|
|
|
|
|
|
|
|
|
if (0 == maxLength || 0 == buffer)
|
|
|
|
|
{
|
|
|
|
|
return NO; // Can't fit in here
|
|
|
|
|
}
|
|
|
|
|
if (NSUTF8StringEncoding == encoding
|
|
|
|
|
|| (YES == ascii && isByteCoding(encoding)))
|
|
|
|
|
{
|
|
|
|
|
BOOL result = (length < maxLength) ? YES : NO;
|
|
|
|
|
|
|
|
|
|
/* We can just copy directly.
|
|
|
|
|
*/
|
|
|
|
|
if (maxLength <= length)
|
|
|
|
|
{
|
|
|
|
|
length = maxLength - 1;
|
|
|
|
|
}
|
|
|
|
|
for (index = 0; index < length; index++)
|
|
|
|
|
{
|
|
|
|
|
buffer[index] = (char)ptr[index];
|
|
|
|
|
}
|
|
|
|
|
/* Step back before any multibyte sequence
|
|
|
|
|
*/
|
|
|
|
|
while (index > 0 && (ptr[index - 1] & 0x80))
|
|
|
|
|
{
|
|
|
|
|
index--;
|
|
|
|
|
}
|
|
|
|
|
buffer[index] = '\0';
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
return [super getCString: buffer maxLength: maxLength encoding: encoding];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Must match the implementation in NSString
|
|
|
|
|
* To avoid allocating memory, we build the hash incrementally.
|
|
|
|
|
*/
|
|
|
|
|
- (NSUInteger) hash
|
|
|
|
|
{
|
|
|
|
|
if (NO == hasHash)
|
|
|
|
|
{
|
|
|
|
|
hash = [super hash];
|
|
|
|
|
hasHash = YES;
|
|
|
|
|
}
|
|
|
|
|
return hash;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id) initWithBytes: (const void*)bytes
|
|
|
|
|
length: (NSUInteger)length
|
|
|
|
|
encoding: (NSStringEncoding)encoding
|
|
|
|
|
{
|
|
|
|
|
RELEASE(self);
|
|
|
|
|
[NSException raise: NSGenericException
|
|
|
|
|
format: @"Attempt to init an SQL string"];
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id) initWithBytesNoCopy: (void*)bytes
|
|
|
|
|
length: (NSUInteger)length
|
|
|
|
|
encoding: (NSStringEncoding)encoding
|
|
|
|
|
freeWhenDone: (BOOL)flag
|
|
|
|
|
{
|
|
|
|
|
RELEASE(self);
|
|
|
|
|
[NSException raise: NSGenericException
|
|
|
|
|
format: @"Attempt to init an SQL string"];
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (int) intValue
|
|
|
|
|
{
|
|
|
|
|
return strtol((const char*)utf8Bytes, 0, 10);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSInteger) integerValue
|
|
|
|
|
{
|
|
|
|
|
return (NSInteger)strtoll((const char*)utf8Bytes, 0, 10);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSUInteger) length
|
|
|
|
|
{
|
|
|
|
|
return charLen;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (long long) longLongValue
|
|
|
|
|
{
|
|
|
|
|
return strtoll((const char*)utf8Bytes, 0, 10);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSRange) rangeOfCharacterFromSet: (NSCharacterSet*)aSet
|
|
|
|
|
options: (NSUInteger)mask
|
|
|
|
|
range: (NSRange)aRange
|
|
|
|
|
{
|
|
|
|
|
NSUInteger index;
|
|
|
|
|
NSUInteger start;
|
|
|
|
|
NSUInteger stop;
|
|
|
|
|
NSRange range;
|
|
|
|
|
|
|
|
|
|
GS_RANGE_CHECK(aRange, charLen);
|
|
|
|
|
|
|
|
|
|
start = aRange.location;
|
|
|
|
|
stop = NSMaxRange(aRange);
|
|
|
|
|
|
|
|
|
|
range.location = NSNotFound;
|
|
|
|
|
range.length = 0;
|
|
|
|
|
|
|
|
|
|
if (stop > start)
|
|
|
|
|
{
|
|
|
|
|
BOOL (*mImp)(id, SEL, unichar);
|
|
|
|
|
unichar n = 0;
|
|
|
|
|
unsigned i = 0;
|
|
|
|
|
|
|
|
|
|
mImp = (BOOL(*)(id,SEL,unichar))
|
|
|
|
|
[aSet methodForSelector: @selector(characterIsMember:)];
|
|
|
|
|
|
|
|
|
|
for (index = 0; index < start; index++)
|
|
|
|
|
{
|
|
|
|
|
nextUTF8(utf8Bytes, byteLen, &i, &n);
|
|
|
|
|
}
|
|
|
|
|
if ((mask & NSBackwardsSearch) == NSBackwardsSearch)
|
|
|
|
|
{
|
|
|
|
|
unichar buf[stop - start];
|
|
|
|
|
NSUInteger pos = 0;
|
|
|
|
|
|
|
|
|
|
for (pos = 0; pos + start < stop; pos++)
|
|
|
|
|
{
|
|
|
|
|
buf[pos] = nextUTF8(utf8Bytes, byteLen, &i, &n);
|
|
|
|
|
}
|
|
|
|
|
index = stop;
|
|
|
|
|
while (index-- > start)
|
|
|
|
|
{
|
|
|
|
|
if ((*mImp)(aSet, @selector(characterIsMember:), buf[--pos]))
|
|
|
|
|
{
|
|
|
|
|
range = NSMakeRange(index, 1);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
while (index < stop)
|
|
|
|
|
{
|
|
|
|
|
unichar letter;
|
|
|
|
|
|
|
|
|
|
letter = nextUTF8(utf8Bytes, byteLen, &i, &n);
|
|
|
|
|
if ((*mImp)(aSet, @selector(characterIsMember:), letter))
|
|
|
|
|
{
|
|
|
|
|
range = NSMakeRange(index, 1);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
index++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return range;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id) copyWithZone: (NSZone*)z
|
|
|
|
|
{
|
|
|
|
|
return RETAIN(self);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSZone*) zone
|
|
|
|
|
{
|
|
|
|
|
return NSDefaultMallocZone();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSStringEncoding) fastestEncoding
|
|
|
|
|
{
|
|
|
|
|
return NSUTF8StringEncoding;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSStringEncoding) smallestEncoding
|
|
|
|
|
{
|
|
|
|
|
return NSUTF8StringEncoding;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSUInteger) sizeOfContentExcluding: (NSHashTable*)exclude
|
|
|
|
|
{
|
|
|
|
|
return byteLen + 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@end
|