Add preliminary array support

git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/sqlclient/trunk@38400 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
rfm 2015-03-11 17:16:14 +00:00
parent 2479b58670
commit 2250d65fc0
6 changed files with 362 additions and 81 deletions

View file

@ -1,3 +1,12 @@
2015-03-11 Richard Frith-Macdonald <rfm@gnu.org>
* SQLClientPool.m: Fixup for ewxposing prepare method
* SQLClient.h:
* SQLClient.m:
* Postgres.m:
* testPostgres.m:
Add simple array support for char/varchar/text, integer/real,
timestamp, bool and bytea.
2015-03-02 Richard Frith-Macdonald <rfm@gnu.org>
* Postgres.h: Drop support for old versions of postgres which didn't

View file

@ -485,6 +485,221 @@ static unsigned int trim(char *str)
return (str - start);
}
- (char*) parseIntoArray: (NSMutableArray *)a type: (int)t from: (char*)p
{
p++; /* Step past '{' */
while (*p && *p != '}')
{
id v = nil;
/* Ignore leading space before field data.
*/
while (isspace(*p))
{
p++;
}
if ('{' == *p)
{
/* Found a nested array.
*/
v = [[NSMutableArray alloc] initWithCapacity: 10];
p = [self parseIntoArray: v type: t from: p];
}
else if ('\"' == *p)
{
char *start = ++p;
int len = 0;
/* Found something quoted (char, varchar, text, bytea etc)
*/
while (*p != '\0' && *p != '\"')
{
if ('\'' == *p)
{
p++;
}
p++;
len++;
}
if ('\"' == *p)
{
*p++ = '\0';
}
if (len == (p - start + 1))
{
v = [[NSString alloc] initWithUTF8String: start];
}
else
{
char *buf;
char *ptr;
int i;
buf = malloc(len+1);
ptr = start;
i = 0;
while (*ptr != '\0')
{
if ('\\' == *ptr)
{
ptr++;
}
buf[i++] = *ptr++;
}
buf[len] = '\0';
if ('D' == t)
{
/* This is expected to be bytea data
*/
v = [[self dataFromBLOB: buf] retain];
}
else
{
v = [NSString alloc];
v = [v initWithBytesNoCopy: buf
length: len
encoding: NSUTF8StringEncoding
freeWhenDone: YES];
}
}
}
else
{
char *start = p;
char save;
int len;
/* This is an unquoted field ... could be NULL or a boolean,
* or a numeric field, or a timestamp or just a simple string.
*/
while (*p != '\0' && *p != ',' && *p != '}')
{
p++;
}
save = *p;
*p = '\0';
len = trim(start);
if (strcmp(start, "NULL") == 0)
{
v = null;
}
else if ('T' == t)
{
v = [[self dbToDateFromBuffer: start length: len] retain];
}
else if ('D' == t)
{
v = [[self dataFromBLOB: start] retain];
}
else if ('B' == t)
{
if (*start == 't')
v = @"YES";
else
v = @"NO";
}
else
{
v = [[NSString alloc] initWithUTF8String: start];
}
*p = save;
}
if (nil != v)
{
[a addObject: v];
[v release];
}
if (',' == *p)
{
p++;
}
}
if ('}' == *p)
{
p++;
}
return p;
}
- (id) parseField: (char *)p type: (int)t
{
char arrayType = 0;
switch (t)
{
case 1082: // Date
return [self dbToDateFromBuffer: p length: trim(p)];
case 1083: // Time (treat as string)
trim(p);
return [NSString stringWithUTF8String: p];
case 1114: // Timestamp without time zone.
case 1184: // Timestamp with time zone.
return [self dbToDateFromBuffer: p length: trim(p)];
case 16: // BOOL
if (*p == 't')
{
return @"YES";
}
else
{
return @"NO";
}
case 17: // BYTEA
return [self dataFromBLOB: p];
case 18: // "char"
return [NSString stringWithUTF8String: p];
case 20: // INT8
case 21: // INT2
case 23: // INT4
trim(p);
return [NSString stringWithUTF8String: p];
break;
case 1182: // DATE ARRAY
case 1115: // TS without TZ ARRAY
case 1185: // TS with TZ ARRAY
if (0 == arrayType) arrayType = 'T'; // Timestamp
case 1000: // BOOL ARRAY
if (0 == arrayType) arrayType = 'B'; // Boolean
case 1001: // BYTEA ARRAY
if (0 == arrayType) arrayType = 'D'; // Data
case 1005: // INT2 ARRAY
case 1007: // INT4 ARRAY
case 1016: // INT8 ARRAY
case 1021: // FLOAT ARRAY
case 1022: // DOUBLE ARRAY
case 1002: // CHAR ARRAY
case 1009: // TEXT ARRAY
case 1015: // VARCHAR ARRAY
if ('{' == *p)
{
NSMutableArray *a;
a = [NSMutableArray arrayWithCapacity: 10];
p = [self parseIntoArray: a type: arrayType from: p];
if ([self debugging] > 2)
{
NSLog(@"Parsed array is %@", a);
}
return a;
}
case 25: // TEXT
default:
if (YES == _shouldTrim)
{
trim(p);
}
return [NSString stringWithUTF8String: p];
}
}
- (NSMutableArray*) backendQuery: (NSString*)stmt
recordType: (id)rtype
listType: (id)ltype
@ -542,17 +757,17 @@ static unsigned int trim(char *str)
int recordCount = PQntuples(result);
int fieldCount = PQnfields(result);
NSString *keys[fieldCount];
int types[fieldCount];
int modifiers[fieldCount];
int formats[fieldCount];
int ftype[fieldCount];
int fmod[fieldCount];
int fformat[fieldCount];
int i;
for (i = 0; i < fieldCount; i++)
{
keys[i] = [NSString stringWithUTF8String: PQfname(result, i)];
types[i] = PQftype(result, i);
modifiers[i] = PQfmod(result, i);
formats[i] = PQfformat(result, i);
ftype[i] = PQftype(result, i);
fmod[i] = PQfmod(result, i);
fformat[i] = PQfformat(result, i);
}
records = [[ltype alloc] initWithCapacity: recordCount];
@ -574,69 +789,17 @@ static unsigned int trim(char *str)
if ([self debugging] > 1)
{
[self debug: @"%@ type:%d mod:%d size: %d\n",
keys[j], types[j], modifiers[j], size];
keys[j], ftype[j], fmod[j], size];
}
if (formats[j] == 0) // Text
if (fformat[j] == 0) // Text
{
switch (types[j])
{
case 1082: // Date
v = [self dbToDateFromBuffer: p
length: trim(p)];
break;
case 1083: // Time (treat as string)
trim(p);
v = [NSString stringWithUTF8String: p];
break;
case 1114: // Timestamp without time zone.
case 1184: // Timestamp with time zone.
v = [self dbToDateFromBuffer: p
length: trim(p)];
break;
case 16: // BOOL
if (*p == 't')
{
v = @"YES";
}
else
{
v = @"NO";
}
break;
case 17: // BYTEA
v = [self dataFromBLOB: p];
break;
case 18: // "char"
v = [NSString stringWithUTF8String: p];
break;
case 20: // INT8
case 21: // INT2
case 23: // INT4
trim(p);
v = [NSString stringWithUTF8String: p];
break;
case 25: // TEXT
default:
if (YES == _shouldTrim)
{
trim(p);
}
v = [NSString stringWithUTF8String: p];
break;
}
v = [self parseField: p type: ftype[j]];
}
else // Binary
{
NSLog(@"Binary data treated as NSNull "
@"in %@ type:%d mod:%d size:%d\n",
keys[j], types[j], modifiers[j], size);
keys[j], ftype[j], fmod[j], size);
}
}
values[j] = v;
@ -1012,6 +1175,66 @@ static unsigned int trim(char *str)
[super dealloc];
}
- (NSMutableString*) quoteArray: (NSArray *)a
toString: (NSMutableString *)s
quotingStrings: (BOOL)q
{
NSUInteger count;
NSUInteger index;
NSAssert([a isKindOfClass: [NSArray class]], NSInvalidArgumentException);
if (nil == s)
{
s = [NSMutableString stringWithCapacity: 1000];
}
[s appendString: @"ARRAY["];
count = [a count];
for (index = 0; index < count; index++)
{
id o = [a objectAtIndex: index];
if (index > 0)
{
[s appendString: @","];
}
if ([o isKindOfClass: [NSArray class]])
{
[self quoteArray: (NSArray *)o toString: s quotingStrings: q];
}
else if ([o isKindOfClass: [NSString class]])
{
if (YES == q)
{
o = [self quoteString: (NSString*)o];
}
[s appendString: (NSString*)o];
}
else if ([o isKindOfClass: [NSDate class]])
{
[s appendString: [self quote: (NSString*)o]];
[s appendString: @"::timestamp"];
}
else if ([o isKindOfClass: [NSData class]])
{
unsigned len = [self lengthOfEscapedBLOB: o];
uint8_t *buf;
buf = malloc(len+1);
[self copyEscapedBLOB: o into: buf];
buf[len] = '\0';
[s appendFormat: @"%s::bytea", buf];
free(buf);
}
else
{
o = [self quote: (NSString*)o];
[s appendString: (NSString*)o];
}
}
[s appendString: @"]"];
return s;
}
- (NSString*) quoteString: (NSString *)s
{
NSData *d = [s dataUsingEncoding: NSUTF8StringEncoding];

View file

@ -796,6 +796,18 @@ SQLCLIENT_PRIVATE
*/
- (NSString*) quotef: (NSString*)fmt, ...;
/* Produce a quoted string from an array on databases where arrays are
* supported (currently only Postgres).<br />
* If the s argument is not nil, the quoted array is appended to it rather
* than being produced in a new string (this method uses that feature to
* recursively quote nested arrays).<br />
* The q argument determines whether string values found in the array
* are quoted or added literally.
*/
- (NSMutableString*) quoteArray: (NSArray *)a
toString: (NSMutableString *)s
quotingStrings: (BOOL)q;
/**
* Convert a big (64 bit) integer to a string suitable for use in an SQL query.
*/

View file

@ -1567,6 +1567,15 @@ static int poolConnections = 0;
return quoted;
}
- (NSMutableString*) quoteArray: (NSArray *)a
toString: (NSMutableString *)s
quotingStrings: (BOOL)q
{
[NSException raise: NSGenericException
format: @"%@ not supported for this database", NSStringFromSelector(_cmd)];
return nil;
}
- (NSString*) quoteBigInteger: (int64_t)i
{
return [NSString stringWithFormat: @"%"PRId64, i];

View file

@ -516,10 +516,6 @@
@end
@interface SQLClient (Private)
- (NSMutableArray*) _prepare: (NSString*)stmt args: (va_list)args;
@end
@implementation SQLClientPool (ConvenienceMethods)
- (NSString*) buildQuery: (NSString*)stmt, ...
@ -532,7 +528,7 @@
* First check validity and concatenate parts of the query.
*/
va_start (ap, stmt);
sql = [[db _prepare: stmt args: ap] objectAtIndex: 0];
sql = [[db prepare: stmt args: ap] objectAtIndex: 0];
va_end (ap);
[self swallowClient: db];
@ -556,7 +552,7 @@
va_list ap;
va_start (ap, stmt);
stmt = [[db _prepare: stmt args: ap] objectAtIndex: 0];
stmt = [[db prepare: stmt args: ap] objectAtIndex: 0];
va_end (ap);
result = [db cache: seconds simpleQuery: stmt];
[self swallowClient: db];
@ -612,7 +608,7 @@
va_list ap;
va_start (ap, stmt);
info = [db _prepare: stmt args: ap];
info = [db prepare: stmt args: ap];
va_end (ap);
result = [db simpleExecute: info];
[self swallowClient: db];
@ -638,7 +634,7 @@
* First check validity and concatenate parts of the query.
*/
va_start (ap, stmt);
stmt = [[db _prepare: stmt args: ap] objectAtIndex: 0];
stmt = [[db prepare: stmt args: ap] objectAtIndex: 0];
va_end (ap);
result = [db simpleQuery: stmt];
[self swallowClient: db];
@ -663,7 +659,7 @@
va_list ap;
va_start (ap, stmt);
stmt = [[db _prepare: stmt args: ap] objectAtIndex: 0];
stmt = [[db prepare: stmt args: ap] objectAtIndex: 0];
va_end (ap);
result = [db simpleQuery: stmt];
[self swallowClient: db];
@ -690,7 +686,7 @@
va_list ap;
va_start (ap, stmt);
stmt = [[db _prepare: stmt args: ap] objectAtIndex: 0];
stmt = [[db prepare: stmt args: ap] objectAtIndex: 0];
va_end (ap);
result = [db simpleQuery: stmt];
[self swallowClient: db];

View file

@ -120,7 +120,7 @@ main()
@"Delivery TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, "
@"Reference CHAR(128), "
@"Destination CHAR(15) NOT NULL, "
@"Payload CHAR(250) DEFAULT '' NOT NULL"
@"Payload CHAR(250) DEFAULT '' NOT NULL,"
@")",
nil];
[db execute:
@ -155,8 +155,11 @@ main()
}
[db execute: @"INSERT INTO Queue (Consumer, Destination,"
@" ServiceID, Payload) VALUES (",
[db quote: name], @", ", [db quote: destination], @", ", sid, @", ",
@"'helo there'", @")", nil];
[db quote: name], @", ",
[db quote: destination], @", ",
sid, @", ",
@"'helo there'",
@")", nil];
[arp release];
}
NSLog(@"End producing");
@ -252,25 +255,54 @@ main()
@"intval int, "
@"when1 timestamp with time zone, "
@"when2 timestamp, "
@"b bytea"
@"b bytea,"
@"extra1 int[],"
@"extra2 varchar[],"
@"extra3 bytea[],"
@"extra4 boolean[],"
@"extra5 timestamp[]"
@")",
nil];
if (1 != [db execute: @"insert into xxx "
@"(k, char1, boolval, intval, when1, when2, b) "
if (1 != [db execute: @"insert into xxx (k, char1, boolval, intval,"
@" when1, when2, b, extra1, extra2, extra3, extra4, extra5) "
@"values ("
@"'hello', "
@"'{hello', "
@"'X', "
@"TRUE, "
@"1, "
@"CURRENT_TIMESTAMP, "
@"CURRENT_TIMESTAMP, ",
data,
@")",
data, @", ",
[db quoteArray:
[NSArray arrayWithObjects: @"1", @"2", [NSNull null], nil]
toString: nil
quotingStrings: NO], @", ",
[db quoteArray:
[NSArray arrayWithObjects: @"on,e", @"t'wo", @"many", nil]
toString: nil
quotingStrings: YES], @", ",
[db quoteArray:
[NSArray arrayWithObjects: data, nil]
toString: nil
quotingStrings: YES], @", ",
[db quoteArray:
[NSArray arrayWithObjects: @"TRUE", @"FALSE", nil]
toString: nil
quotingStrings: NO], @", ",
[db quoteArray:
[NSArray arrayWithObjects: [NSDate date], nil]
toString: nil
quotingStrings: YES], @")",
nil])
{
NSLog(@"Insert failed to return row count");
}
[db setDebugging: 9];
[db query: @"select * from xxx", nil];
[db setDebugging: 0];
[db execute: @"insert into xxx "
@"(k, char1, boolval, intval, when1, when2, b) "
@"values ("