1/* 2 MySQL4Channel.m 3 4 Copyright (C) 2003-2005 SKYRIX Software AG 5 6 Author: Helge Hess (helge.hess@skyrix.com) 7 8 This file is part of the MySQL4 Adaptor Library 9 10 This library is free software; you can redistribute it and/or 11 modify it under the terms of the GNU Library General Public 12 License as published by the Free Software Foundation; either 13 version 2 of the License, or (at your option) any later version. 14 15 This library is distributed in the hope that it will be useful, 16 but WITHOUT ANY WARRANTY; without even the implied warranty of 17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 Library General Public License for more details. 19 20 You should have received a copy of the GNU Library General Public 21 License along with this library; see the file COPYING.LIB. 22 If not, write to the Free Software Foundation, 23 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 24*/ 25 26#include <ctype.h> 27#include <string.h> 28#include <strings.h> 29#include "MySQL4Channel.h" 30#include "MySQL4Adaptor.h" 31#include "MySQL4Exception.h" 32#include "NSString+MySQL4.h" 33#include "MySQL4Values.h" 34#include "EOAttribute+MySQL4.h" 35#include "common.h" 36#include <mysql/mysql.h> 37 38#ifndef MIN 39# define MIN(x, y) ((x > y) ? y : x) 40#endif 41 42#define MAX_CHAR_BUF 16384 43 44@implementation MySQL4Channel 45 46static EONull *null = nil; 47static NSString *encoding = nil; 48 49+ (void)initialize { 50 if (null == NULL) null = [[EONull null] retain]; 51 encoding = [[NSUserDefaults standardUserDefaults] stringForKey: @"MySQL4Encoding"]; 52 if (!encoding) { 53 encoding = [NSString stringWithString: @"utf8"]; 54 }; 55 [encoding retain]; 56} 57 58- (id)initWithAdaptorContext:(EOAdaptorContext*)_adaptorContext { 59 if ((self = [super initWithAdaptorContext:_adaptorContext])) { 60 [self setDebugEnabled:[[NSUserDefaults standardUserDefaults] 61 boolForKey:@"MySQL4DebugEnabled"]]; 62 63 self->_attributesForTableName = 64 [[NSMutableDictionary alloc] initWithCapacity:16]; 65 self->_primaryKeysNamesForTableName = 66 [[NSMutableDictionary alloc] initWithCapacity:16]; 67 } 68 return self; 69} 70 71- (void)_adaptorWillFinalize:(id)_adaptor { 72} 73 74- (void)dealloc { 75 if ([self isOpen]) 76 [self closeChannel]; 77 [self->_attributesForTableName release]; 78 [self->_primaryKeysNamesForTableName release]; 79 [super dealloc]; 80} 81 82/* NSCopying methods */ 83 84- (id)copyWithZone:(NSZone *)zone { 85 return [self retain]; 86} 87 88// debugging 89 90- (void)setDebugEnabled:(BOOL)_flag { 91 self->isDebuggingEnabled = _flag; 92} 93- (BOOL)isDebugEnabled { 94 return self->isDebuggingEnabled; 95} 96 97- (void)receivedMessage:(NSString *)_message { 98 NSLog(@"%@: message %@.", _message); 99} 100 101/* open/close */ 102 103static int openConnectionCount = 0; 104 105- (BOOL)isOpen { 106 return (self->_connection != NULL) ? YES : NO; 107} 108 109- (int)maxOpenConnectionCount { 110 static int MaxOpenConnectionCount = -1; 111 112 if (MaxOpenConnectionCount != -1) 113 return MaxOpenConnectionCount; 114 115 MaxOpenConnectionCount = 116 [[NSUserDefaults standardUserDefaults] 117 integerForKey:@"MySQL4MaxOpenConnectionCount"]; 118 if (MaxOpenConnectionCount == 0) 119 MaxOpenConnectionCount = 150; 120 return MaxOpenConnectionCount; 121} 122 123- (BOOL)openChannel { 124 const char *cDBName; 125 MySQL4Adaptor *adaptor; 126 NSString *host, *socket, *s; 127 BOOL reconnect; 128 void *rc; 129 130 if (self->_connection != NULL) { 131 NSLog(@"%s: Connection already open !!!", __PRETTY_FUNCTION__); 132 return NO; 133 } 134 135 adaptor = (MySQL4Adaptor *)[adaptorContext adaptor]; 136 137 if (![super openChannel]) 138 return NO; 139 140 if (openConnectionCount > [self maxOpenConnectionCount]) { 141 [MySQL4CouldNotOpenChannelException 142 raise:@"NoMoreConnections" 143 format:@"cannot open a additional connection !"]; 144 return NO; 145 } 146 147 cDBName = [[adaptor databaseName] UTF8String]; 148 149 if ((self->_connection = mysql_init(NULL)) == NULL) { 150 NSLog(@"ERROR(%s): could not allocate MySQL4 connection!"); 151 return NO; 152 } 153 154 // TODO: could change options using mysql_options() 155 156 host = [adaptor serverName]; 157 if ([host hasPrefix:@"/"]) { /* treat hostname as Unix socket path */ 158 socket = host; 159 host = nil; 160 } 161 else 162 socket = nil; 163 164 reconnect = YES; 165 mysql_options(self->_connection, MYSQL_OPT_RECONNECT, &reconnect); 166 167 rc = mysql_real_connect(self->_connection, 168 [host UTF8String], 169 [[adaptor loginName] UTF8String], 170 [[adaptor loginPassword] UTF8String], 171 cDBName, 172 [[adaptor port] intValue], 173 [socket cString], 174 0); 175 if (rc == NULL) { 176 NSLog(@"ERROR: could not open MySQL4 connection to database '%@': %s", 177 [adaptor databaseName], mysql_error(self->_connection)); 178 mysql_close(self->_connection); 179 self->_connection = NULL; 180 return NO; 181 } 182 183 s = [NSString stringWithFormat: @"SET CHARACTER SET %@", encoding]; 184 185 if (mysql_query(self->_connection, [s UTF8String]) != 0) { 186 NSLog(@"WARNING(%s): could not put MySQL4 connection into UTF-8 mode: %s", 187 __PRETTY_FUNCTION__, mysql_error(self->_connection)); 188#if 0 189 mysql_close(self->_connection); 190 self->_connection = NULL; 191 return NO; 192#endif 193 } 194 195 if (isDebuggingEnabled) 196 { 197 NSLog(@"MySQL4 connection established 0x%p", self->_connection); 198 NSLog(@"---------- %s: %@ opens channel count[%d]", __PRETTY_FUNCTION__, 199 self, openConnectionCount); 200 } 201 202 openConnectionCount++; 203 204#if LIB_FOUNDATION_BOEHM_GC 205 [GarbageCollector registerForFinalizationObserver:self 206 selector:@selector(_adaptorWillFinalize:) 207 object:[[self adaptorContext] adaptor]]; 208#endif 209 210 if (isDebuggingEnabled) { 211 NSLog(@"MySQL4 channel 0x%p opened (connection=0x%p,%s)", 212 self, self->_connection, cDBName); 213 } 214 return YES; 215} 216 217- (void)primaryCloseChannel { 218 if ([self isFetchInProgress]) 219 [self cancelFetch]; 220 221 if (self->_connection != NULL) { 222 mysql_close(self->_connection); 223 224 if (isDebuggingEnabled) 225 NSLog(@"---------- %s: %@ close channel count[%d]", __PRETTY_FUNCTION__, 226 self, openConnectionCount); 227 228 openConnectionCount--; 229 230 if (isDebuggingEnabled) { 231 fprintf(stderr, 232 "MySQL4 connection dropped 0x%p (channel=0x%p)\n", 233 self->_connection, self); 234 } 235 self->_connection = NULL; 236 } 237} 238 239- (void)closeChannel { 240 [super closeChannel]; 241 [self primaryCloseChannel]; 242} 243 244/* fetching rows */ 245 246- (void)cancelFetch { 247 self->fields = NULL; /* apparently we do not need to free those */ 248 249 if (self->results != NULL) { 250 mysql_free_result(self->results); 251 self->results = NULL; 252 } 253 [super cancelFetch]; 254} 255 256- (MYSQL_FIELD *)_fetchFields { 257 if (self->results == NULL) 258 return NULL; 259 260 if (self->fields != NULL) 261 return self->fields; 262 263 self->fields = mysql_fetch_fields(self->results); 264 self->fieldCount = mysql_num_fields(self->results); 265 return self->fields; 266} 267 268- (NSArray *)describeResults:(BOOL)_beautifyNames { 269 // TODO: make exception-less method 270 MYSQL_FIELD *mfields; 271 int cnt; 272 NSMutableArray *result = nil; 273 NSMutableDictionary *usedNames = nil; 274 NSNumber *yesObj; 275 276 yesObj = [NSNumber numberWithBool:YES]; 277 278 if (![self isFetchInProgress]) { 279 [MySQL4Exception raise:@"NoFetchInProgress" 280 format:@"No fetch in progress (channel=%@)", self]; 281 return nil; 282 } 283 284 if ((mfields = [self _fetchFields]) == NULL) { 285 [MySQL4Exception raise:@"NoFieldInfo" 286 format:@"Failed to fetch field info (channel=%@)", self]; 287 return nil; 288 } 289 290 result = [[NSMutableArray alloc] initWithCapacity:fieldCount]; 291 usedNames = [[NSMutableDictionary alloc] initWithCapacity:fieldCount]; 292 293 for (cnt = 0; cnt < fieldCount; cnt++) { 294 EOAttribute *attribute = nil; 295 NSString *columnName = nil; 296 NSString *attrName = nil; 297 298 columnName = [NSString stringWithUTF8String:mfields[cnt].name]; 299 attrName = _beautifyNames 300 ? [columnName _mySQL4ModelMakeInstanceVarName] 301 : columnName; 302 303 if ([[usedNames objectForKey:attrName] boolValue]) { 304 int cnt2 = 0; 305 char buf[64]; 306 NSString *newAttrName = nil; 307 308 for (cnt2 = 2; cnt2 < 100; cnt2++) { 309 NSString *s; 310 sprintf(buf, "%i", cnt2); 311 312 // TODO: unicode 313 s = [[NSString alloc] initWithCString:buf]; 314 newAttrName = [attrName stringByAppendingString:s]; 315 [s release]; 316 317 if (![[usedNames objectForKey:newAttrName] boolValue]) { 318 attrName = newAttrName; 319 break; 320 } 321 } 322 } 323 [usedNames setObject:yesObj forKey:attrName]; 324 325 attribute = [[EOAttribute alloc] init]; 326 [attribute setName:attrName]; 327 [attribute setColumnName:columnName]; 328 329 [attribute setAllowsNull: 330 (mfields[cnt].flags & NOT_NULL_FLAG) ? NO : YES]; 331 332 /* 333 We also know whether a field: 334 is primary 335 is unique 336 is auto-increment 337 is zero-fill 338 is unsigned 339 */ 340 switch (mfields[cnt].type) { 341 case FIELD_TYPE_STRING: 342 [attribute setExternalType:@"CHAR"]; 343 [attribute setValueClassName:@"NSString"]; 344 // TODO: length etc 345 break; 346 case FIELD_TYPE_VAR_STRING: 347 [attribute setExternalType:@"VARCHAR"]; 348 [attribute setValueClassName:@"NSString"]; 349 // TODO: length etc 350 break; 351 352 case FIELD_TYPE_TINY: 353 if ((mfields[cnt].flags & UNSIGNED_FLAG)) { 354 [attribute setExternalType:@"TINY UNSIGNED"]; 355 [attribute setValueClassName:@"NSNumber"]; 356 [attribute setValueType:@"C"]; 357 } 358 else { 359 [attribute setExternalType:@"TINY"]; 360 [attribute setValueClassName:@"NSNumber"]; 361 [attribute setValueType:@"c"]; 362 } 363 break; 364 case FIELD_TYPE_SHORT: 365 if ((mfields[cnt].flags & UNSIGNED_FLAG)) { 366 [attribute setExternalType:@"SHORT UNSIGNED"]; 367 [attribute setValueClassName:@"NSNumber"]; 368 [attribute setValueType:@"S"]; 369 } 370 else { 371 [attribute setExternalType:@"SHORT"]; 372 [attribute setValueClassName:@"NSNumber"]; 373 [attribute setValueType:@"s"]; 374 } 375 break; 376 case FIELD_TYPE_LONG: 377 if ((mfields[cnt].flags & UNSIGNED_FLAG)) { 378 [attribute setExternalType:@"LONG UNSIGNED"]; 379 [attribute setValueClassName:@"NSNumber"]; 380 [attribute setValueType:@"L"]; 381 } 382 else { 383 [attribute setExternalType:@"LONG"]; 384 [attribute setValueClassName:@"NSNumber"]; 385 [attribute setValueType:@"l"]; 386 } 387 break; 388 case FIELD_TYPE_INT24: 389 if ((mfields[cnt].flags & UNSIGNED_FLAG)) { 390 [attribute setExternalType:@"INT UNSIGNED"]; 391 [attribute setValueClassName:@"NSNumber"]; 392 [attribute setValueType:@"I"]; 393 } 394 else { 395 [attribute setExternalType:@"INT"]; 396 [attribute setValueClassName:@"NSNumber"]; 397 [attribute setValueType:@"i"]; // bumped 398 } 399 break; 400 case FIELD_TYPE_LONGLONG: 401 if ((mfields[cnt].flags & UNSIGNED_FLAG)) { 402 [attribute setExternalType:@"LONGLONG UNSIGNED"]; 403 [attribute setValueClassName:@"NSNumber"]; 404 [attribute setValueType:@"Q"]; 405 } 406 else { 407 [attribute setExternalType:@"LONGLONG"]; 408 [attribute setValueClassName:@"NSNumber"]; 409 [attribute setValueType:@"q"]; 410 } 411 break; 412 case FIELD_TYPE_DECIMAL: 413 [attribute setExternalType:@"DECIMAL"]; 414 [attribute setValueClassName:@"NSNumber"]; 415 [attribute setValueType:@"f"]; // TODO: need NSDecimalNumber here ... 416 break; 417 case FIELD_TYPE_FLOAT: 418 [attribute setExternalType:@"FLOAT"]; 419 [attribute setValueClassName:@"NSNumber"]; 420 [attribute setValueType:@"f"]; 421 break; 422 case FIELD_TYPE_DOUBLE: 423 [attribute setExternalType:@"DOUBLE"]; 424 [attribute setValueClassName:@"NSNumber"]; 425 [attribute setValueType:@"d"]; 426 break; 427 428 case FIELD_TYPE_TIMESTAMP: 429 [attribute setExternalType:@"TIMESTAMP"]; 430 [attribute setValueClassName:@"NSCalendarDate"]; 431 break; 432 case FIELD_TYPE_DATE: 433 [attribute setExternalType:@"DATE"]; 434 [attribute setValueClassName:@"NSCalendarDate"]; 435 break; 436 case FIELD_TYPE_DATETIME: 437 [attribute setExternalType:@"DATETIME"]; 438 [attribute setValueClassName:@"NSCalendarDate"]; 439 break; 440 441 case FIELD_TYPE_BLOB: 442 case FIELD_TYPE_TINY_BLOB: 443 case FIELD_TYPE_MEDIUM_BLOB: 444 case FIELD_TYPE_LONG_BLOB: 445 // TODO: length etc 446 if (mfields[cnt].flags & BINARY_FLAG) { 447 [attribute setExternalType:@"BLOB"]; 448 [attribute setValueClassName:@"NSData"]; 449 } 450 else { 451 [attribute setExternalType:@"TEXT"]; 452 [attribute setValueClassName:@"NSString"]; 453 } 454 break; 455 456 case FIELD_TYPE_NULL: // TODO: whats that? 457 case FIELD_TYPE_TIME: 458 case FIELD_TYPE_YEAR: 459 case FIELD_TYPE_SET: 460 case FIELD_TYPE_ENUM: 461 default: 462 NSLog(@"ERROR(%s): unexpected MySQL4 type at column %i: %@", 463 __PRETTY_FUNCTION__, cnt, attribute); 464 break; 465 } 466 467 [result addObject:attribute]; 468 [attribute release]; 469 } 470 471 [usedNames release]; 472 usedNames = nil; 473 474 return [result autorelease]; 475} 476- (NSArray *)describeResults { 477 return [self describeResults:NO]; 478} 479 480- (NSMutableDictionary *)primaryFetchAttributes:(NSArray *)_attributes 481 withZone:(NSZone *)_zone 482{ 483 /* 484 Note: we expect that the attributes match the generated SQL. This is 485 because auto-generated SQL can contain SQL table prefixes (like 486 alias.column-name which cannot be detected using the attributes 487 schema) 488 */ 489 // TODO: add a primaryFetchAttributesX method? 490 MYSQL_ROW rawRow; 491 NSMutableDictionary *row = nil; 492 unsigned attrCount = [_attributes count]; 493 unsigned cnt; 494 unsigned long *lengths; 495 496 if (self->results == NULL) { 497 NSLog(@"ERROR(%s): no fetch in progress?", __PRETTY_FUNCTION__); 498 [self cancelFetch]; 499 return nil; 500 } 501 502 /* raw fetch */ 503 504 if ((rawRow = mysql_fetch_row(self->results)) == NULL) { 505 // TODO: might need to close channel on connect exceptions 506 unsigned int merrno; 507 508 if ((merrno = mysql_errno(self->_connection)) != 0) { 509 const char *error; 510 511 error = mysql_error(self->_connection); 512 [MySQL4Exception raise:@"FetchFailed" 513 format:@"%@",[NSString stringWithUTF8String:error]]; 514 return nil; 515 } 516 517 /* regular end of result set */ 518 [self cancelFetch]; 519 return nil; 520 } 521 522 /* ensure field info */ 523 524 if ([self _fetchFields] == NULL) { 525 [self cancelFetch]; 526 [MySQL4Exception raise:@"FetchFailed" 527 format:@"could not fetch field info!"]; 528 return nil; 529 } 530 531 if ((lengths = mysql_fetch_lengths(self->results)) == NULL) { 532 [self cancelFetch]; 533 [MySQL4Exception raise:@"FetchFailed" 534 format:@"could not fetch field lengths!"]; 535 return nil; 536 } 537 538 /* build row */ 539 540 row = [NSMutableDictionary dictionaryWithCapacity:attrCount]; 541 542 for (cnt = 0; cnt < attrCount; cnt++) { 543 EOAttribute *attribute; 544 NSString *attrName; 545 id value = nil; 546 MYSQL_FIELD mfield; 547 548 attribute = [_attributes objectAtIndex:cnt]; 549 attrName = [attribute name]; 550 mfield = ((MYSQL_FIELD *)self->fields)[cnt]; 551 552 if (rawRow[cnt] == NULL) { 553 value = [null retain]; 554 } 555 else { 556 Class valueClass; 557 558 valueClass = NSClassFromString([attribute valueClassName]); 559 if (valueClass == Nil) { 560 NSLog(@"ERROR(%s): %@: got no value class for column:\n" 561 @" attribute=%@\n type=%@", 562 __PRETTY_FUNCTION__, self, 563 attrName, [attribute externalType]); 564 value = null; 565 continue; 566 } 567 568 value = [[valueClass alloc] initWithMySQL4Field:&mfield 569 value:rawRow[cnt] length:lengths[cnt]]; 570 571 if (value == nil) { 572 NSLog(@"ERROR(%s): %@: got no value for column: attribute=%@\n valueClass=%@\n type=%@", 573 __PRETTY_FUNCTION__, self, 574 attrName, NSStringFromClass(valueClass), 575 [attribute externalType]); 576 continue; 577 } 578 } 579 if (value != nil) { 580 [row setObject:value forKey:attrName]; 581 [value release]; 582 } 583 } 584 585 return row; 586} 587 588/* sending SQL to server */ 589 590- (NSException *)evaluateExpressionX:(NSString *)_expression { 591 NSMutableString *sql; 592 BOOL result; 593 const char *s; 594 int rc; 595 596 *(&result) = YES; 597 598 if (_expression == nil) { 599 return [NSException exceptionWithName:@"InvalidArgumentException" 600 reason: 601 @"parameter for evaluateExpression: must not be null" 602 userInfo:nil]; 603 } 604 605 sql = [[_expression mutableCopy] autorelease]; 606 [sql appendString:@";"]; 607 608 /* ask delegate */ 609 610 if (delegateRespondsTo.willEvaluateExpression) { 611 EODelegateResponse response; 612 613 response = [delegate adaptorChannel:self willEvaluateExpression:sql]; 614 615 if (response == EODelegateRejects) { 616 return [NSException exceptionWithName:@"EODelegateRejects" 617 reason:@"delegate rejected insert" 618 userInfo:nil]; 619 } 620 if (response == EODelegateOverrides) 621 return nil; 622 } 623 624 /* check some preconditions */ 625 626 if (![self isOpen]) { 627 return [MySQL4Exception exceptionWithName:@"ChannelNotOpenException" 628 reason:@"MySQL4 connection is not open" 629 userInfo:nil]; 630 } 631 if (self->results != NULL) { 632 return [MySQL4Exception exceptionWithName:@"CommandInProgressException" 633 reason:@"an evaluation is in progress" 634 userInfo:nil]; 635 return NO; 636 } 637 638 if ([self isFetchInProgress]) { 639 NSLog(@"WARNING: a fetch is still in progress: %@", self); 640 [self cancelFetch]; 641 } 642 643 if (isDebuggingEnabled) 644 NSLog(@"%@ SQL: %@", self, sql); 645 646 /* reset environment */ 647 648 self->isFetchInProgress = NO; 649 650 /* start query */ 651 652 s = [sql UTF8String]; 653 if ((rc = mysql_real_query(self->_connection, s, strlen(s))) != 0) { 654 // TODO: might need to close channel on connect exceptions 655 const char *error; 656 657 error = mysql_error(self->_connection); 658 if (isDebuggingEnabled) 659 NSLog(@"%@ ERROR: %s", self, error); 660 661 return [MySQL4Exception exceptionWithName:@"ExecutionFailed" 662 reason:[NSString stringWithUTF8String:error] 663 userInfo:nil]; 664 } 665 666 /* fetch */ 667 668 if ((self->results = mysql_use_result(self->_connection)) != NULL) { 669 if (isDebuggingEnabled) 670 NSLog(@"%@ query has results, entering fetch-mode.", self); 671 self->isFetchInProgress = YES; 672 } 673 else { 674 /* error _OR_ statement without result-set */ 675 unsigned int merrno; 676 677 if ((merrno = mysql_errno(self->_connection)) != 0) { 678 const char *error; 679 680 error = mysql_error(self->_connection); 681 if (isDebuggingEnabled) 682 NSLog(@"%@ cannot use result: '%s'", self, error); 683 684 return [MySQL4Exception exceptionWithName:@"FetchFailed" 685 reason:[NSString stringWithUTF8String:error] 686 userInfo:nil]; 687 } 688 689 if (isDebuggingEnabled) 690 NSLog(@"%@ query has no results.", self); 691 } 692 693 if (delegateRespondsTo.didEvaluateExpression) 694 [delegate adaptorChannel:self didEvaluateExpression:sql]; 695 696 return nil /* everything is OK */; 697} 698- (BOOL)evaluateExpression:(NSString *)_sql { 699 NSException *e; 700 NSString *n; 701 702 if ((e = [self evaluateExpressionX:_sql]) == nil) 703 return YES; 704 705 /* for compatibility with non-X methods, translate some errors to a bool */ 706 n = [e name]; 707 if ([n isEqualToString:@"EOEvaluationError"]) 708 return NO; 709 if ([n isEqualToString:@"EODelegateRejects"]) 710 return NO; 711 712 NSLog(@"ERROR eval '%@': %@", _sql, e); 713 714 [e raise]; 715 return NO; 716} 717 718/* description */ 719 720- (NSString *)description { 721 NSMutableString *ms; 722 723 ms = [NSMutableString stringWithCapacity:64]; 724 [ms appendFormat:@"<%@[0x%p] connection=0x%p", 725 NSStringFromClass([self class]), self, self->_connection]; 726 [ms appendString:@">"]; 727 return ms; 728} 729 730/* PrimaryKeyGeneration */ 731 732- (NSDictionary *)primaryKeyForNewRowWithEntity:(EOEntity *)_entity { 733 NSException *error; 734 NSArray *pkeys; 735 MySQL4Adaptor *adaptor; 736 NSString *seqName, *seq; 737 NSArray *seqs; 738 NSDictionary *pkey; 739 unsigned i, count; 740 id key; 741 742 pkeys = [_entity primaryKeyAttributeNames]; 743 adaptor = (id)[[self adaptorContext] adaptor]; 744 seqName = [adaptor primaryKeySequenceName]; 745 pkey = nil; 746 seq = nil; 747 748 if ([seqName length] > 0) { 749 // TODO: if we do this, we also need to make the 'id' configurable ... 750 seq = [@"UPDATE " stringByAppendingString:seqName]; 751 seq = [seq stringByAppendingString:@" SET id=LAST_INSERT_ID(id+1)"]; 752 seqs = [NSArray arrayWithObjects: 753 seq, @"SELECT_LAST_INSERT_ID()", nil]; 754 } 755 else 756 seqs = [[adaptor newKeyExpression] componentsSeparatedByString:@";"]; 757 758 if ((count = [seqs count]) == 0) { 759 NSLog(@"ERROR(%@): got no primary key expressions %@: %@", 760 self, seqName, _entity); 761 return nil; 762 } 763 764 for (i = 0; i < count - 1; i++) { 765 if ((error = [self evaluateExpressionX:[seqs objectAtIndex:i]]) != nil) { 766 NSLog(@"ERROR(%@): could not prepare next pkey value %@: %@", 767 self, [seqs objectAtIndex:i], error); 768 return nil; 769 } 770 } 771 772 seq = [seqs lastObject]; 773 if ((error = [self evaluateExpressionX:seq]) != nil) { 774 NSLog(@"ERROR(%@): could not select next pkey value from sequence %@: %@", 775 self, seqName, error); 776 return nil; 777 } 778 779 if (![self isFetchInProgress]) { 780 NSLog(@"ERROR(%@): primary key expression returned no result: '%@'", 781 self, seq); 782 return nil; 783 } 784 785 // TODO: this is kinda slow 786 key = [self describeResults]; 787 pkey = [self fetchAttributes:key withZone:NULL]; 788 789 [self cancelFetch]; 790 791 if (pkey != nil) { 792 pkey = [[pkey allValues] lastObject]; 793 pkey = [NSDictionary dictionaryWithObject:pkey 794 forKey:[pkeys objectAtIndex:0]]; 795 } 796 797 return pkey; 798} 799 800@end /* MySQL4Channel */ 801 802void __link_MySQL4Channel() { 803 // used to force linking of object file 804 __link_MySQL4Channel(); 805} 806