diff options
author | rowanbeentje <rowan@beent.je> | 2010-01-17 16:24:13 +0000 |
---|---|---|
committer | rowanbeentje <rowan@beent.je> | 2010-01-17 16:24:13 +0000 |
commit | ab9b7d9dad3dcd3a2675d33684729637e5509d42 (patch) | |
tree | b1655480f033fdcbbd12caed945fcead636042dc | |
parent | fc6348bb6cadfedba1fd19615f2af83d8e8098ae (diff) | |
download | sequelpro-ab9b7d9dad3dcd3a2675d33684729637e5509d42.tar.gz sequelpro-ab9b7d9dad3dcd3a2675d33684729637e5509d42.tar.bz2 sequelpro-ab9b7d9dad3dcd3a2675d33684729637e5509d42.zip |
Add a new SPDataStorage class, and use it in TableContent and CustomQuery:
- SPDataStorage is a class designed for a 2D array of fixed-width data storage, replacing the current method of nested NSMutableArrays. NSFastEnumerator compatible.
- Overall memory overhead for table storage in memory reduced by 1.2-1.4x - this almost gains back the large memory jump seen for 64 bit
- Some operations (adding data, retrieving a single cell's data) are faster than nested NSMutableArrays; some operations (requesting a row as an NSArray) are slightly slower as the data needs to be converted, but overall result is a slight speed gain.
(- Could be used in future to store SQL results in C datatypes, avoiding very high NSObject overhead for numbers and short strings)
-rw-r--r-- | Source/CMCopyTable.h | 3 | ||||
-rw-r--r-- | Source/CMCopyTable.m | 4 | ||||
-rw-r--r-- | Source/CustomQuery.h | 8 | ||||
-rw-r--r-- | Source/CustomQuery.m | 64 | ||||
-rw-r--r-- | Source/SPDataStorage.h | 101 | ||||
-rw-r--r-- | Source/SPDataStorage.m | 424 | ||||
-rw-r--r-- | Source/TableContent.h | 5 | ||||
-rw-r--r-- | Source/TableContent.m | 118 | ||||
-rw-r--r-- | sequel-pro.xcodeproj/project.pbxproj | 6 |
9 files changed, 627 insertions, 106 deletions
diff --git a/Source/CMCopyTable.h b/Source/CMCopyTable.h index aede3540..95dd0c4b 100644 --- a/Source/CMCopyTable.h +++ b/Source/CMCopyTable.h @@ -36,7 +36,6 @@ @interface CMCopyTable : SPTableView { id tableInstance; // the table content view instance - id tableData; // the actual table data source id mySQLConnection; // current MySQL connection NSArray* columnDefinitions; // array of NSDictionary containing info about columns NSString* selectedTable; // the name of the current selected table @@ -103,7 +102,7 @@ /* * Set all necessary data from the table content view. */ -- (void)setTableInstance:(id)anInstance withTableData:(id)theTableData withColumns:(NSArray *)columnDefs withTableName:(NSString *)aTableName withConnection:(id)aMySqlConnection; +- (void)setTableInstance:(id)anInstance withColumns:(NSArray *)columnDefs withTableName:(NSString *)aTableName withConnection:(id)aMySqlConnection; @end diff --git a/Source/CMCopyTable.m b/Source/CMCopyTable.m index 6fd83cee..621fc08f 100644 --- a/Source/CMCopyTable.m +++ b/Source/CMCopyTable.m @@ -232,7 +232,6 @@ NSInteger MENU_EDIT_COPY_AS_SQL = 2002; rowCounter++; for ( c = 0; c < numColumns; c++ ) { - // rowData = [[tableData objectAtIndex:rowIndex] objectAtIndex:[[columnMappings objectAtIndex:c] integerValue]]; col = NSArrayObjectAtIndex(columns, c); rowData = [dataSource tableView:self objectValueForTableColumn:col @@ -417,10 +416,9 @@ NSInteger MENU_EDIT_COPY_AS_SQL = 2002; /* * Init self with data coming from the table content view. Mainly used for copying data properly. */ -- (void)setTableInstance:(id)anInstance withTableData:(id)theTableData withColumns:(NSArray *)columnDefs withTableName:(NSString *)aTableName withConnection:(id)aMySqlConnection +- (void)setTableInstance:(id)anInstance withColumns:(NSArray *)columnDefs withTableName:(NSString *)aTableName withConnection:(id)aMySqlConnection { selectedTable = aTableName; - tableData = theTableData; mySQLConnection = aMySqlConnection; tableInstance = anInstance; diff --git a/Source/CustomQuery.h b/Source/CustomQuery.h index 1c9a32bf..8e2e9fdd 100644 --- a/Source/CustomQuery.h +++ b/Source/CustomQuery.h @@ -49,7 +49,7 @@ #define SP_HISTORY_SAVE_MENUITEM_TAG 300001 #define SP_HISTORY_CLEAR_MENUITEM_TAG 300002 -@class SPQueryFavoriteManager; +@class SPQueryFavoriteManager, SPDataStorage; @interface CustomQuery : NSObject { @@ -128,9 +128,9 @@ WebHistory *helpHistory; NSString *helpHTMLTemplate; - NSMutableArray *fullResult; - pthread_mutex_t fullResultLock; - NSInteger fullResultCount; + SPDataStorage *resultData; + pthread_mutex_t resultDataLock; + NSInteger resultDataCount; NSArray *cqColumnDefinition; NSString *lastExecutedQuery; NSInteger editedRow; diff --git a/Source/CustomQuery.m b/Source/CustomQuery.m index de035270..e35a7b26 100644 --- a/Source/CustomQuery.m +++ b/Source/CustomQuery.m @@ -41,6 +41,7 @@ #import "SPQueryController.h" #import "SPConstants.h" #import "SPEncodingPopupAccessory.h" +#import "SPDataStorage.h" @implementation CustomQuery @@ -779,7 +780,7 @@ [tableDocumentInstance setQueryMode:SPInterfaceQueryMode]; // If no results were returned, redraw the empty table and post notifications before returning. - if ( !fullResultCount ) { + if ( !resultDataCount ) { [customQueryView reloadData]; if (streamingResult) [streamingResult release]; @@ -831,7 +832,7 @@ } // Init copyTable with necessary information for copying selected rows as SQL INSERT - [customQueryView setTableInstance:self withTableData:fullResult withColumns:cqColumnDefinition withTableName:resultTableName withConnection:mySQLConnection]; + [customQueryView setTableInstance:self withColumns:cqColumnDefinition withTableName:resultTableName withConnection:mySQLConnection]; //query finished [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; @@ -864,11 +865,14 @@ BOOL tableViewRedrawn = NO; // Remove all items from the table - fullResultCount = 0; + resultDataCount = 0; [customQueryView performSelectorOnMainThread:@selector(noteNumberOfRowsChanged) withObject:nil waitUntilDone:YES]; - pthread_mutex_lock(&fullResultLock); - [fullResult removeAllObjects]; - pthread_mutex_unlock(&fullResultLock); + pthread_mutex_lock(&resultDataLock); + [resultData removeAllRows]; + pthread_mutex_unlock(&resultDataLock); + + // Set the column count on the data store + [resultData setColumnCount:[theResult numOfFields]]; // Set up an autorelease pool for row processing dataLoadingPool = [[NSAutoreleasePool alloc] init]; @@ -876,10 +880,10 @@ // Loop through the result rows as they become available while (tempRow = [theResult fetchNextRowAsArray]) { - pthread_mutex_lock(&fullResultLock); - NSMutableArrayAddObject(fullResult, [NSMutableArray arrayWithArray:tempRow]); - fullResultCount++; - pthread_mutex_unlock(&fullResultLock); + pthread_mutex_lock(&resultDataLock); + SPDataStorageAddRow(resultData, tempRow); + resultDataCount++; + pthread_mutex_unlock(&resultDataLock); // Update the count of rows processed rowsProcessed++; @@ -1334,7 +1338,7 @@ [fieldIDQueryStr setString:@"WHERE ("]; // --- Build WHERE clause --- - dataRow = [fullResult objectAtIndex:rowIndex]; + dataRow = [resultData rowContentsAtIndex:rowIndex]; // Get the primary key if there is one MCPResult *theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW COLUMNS FROM %@.%@", @@ -1394,7 +1398,7 @@ - (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView { if (aTableView == customQueryView) { - return (fullResult == nil) ? 0 : fullResultCount; + return (resultData == nil) ? 0 : resultDataCount; } else { return 0; @@ -1417,21 +1421,18 @@ // locks must be used to avoid crashes, and indexes higher than the available // rows or columns may be requested. Use gray to show loading in these cases. if (isWorking) { - pthread_mutex_lock(&fullResultLock); - if (rowIndex < fullResultCount) { - NSMutableArray *rowData = NSArrayObjectAtIndex(fullResult, rowIndex); - if (columnIndex < [rowData count]) { - theValue = NSArrayObjectAtIndex(rowData, columnIndex); - } + pthread_mutex_lock(&resultDataLock); + if (rowIndex < resultDataCount && columnIndex < [resultData columnCount]) { + theValue = SPDataStorageObjectAtRowAndColumn(resultData, rowIndex, columnIndex); } - pthread_mutex_unlock(&fullResultLock); + pthread_mutex_unlock(&resultDataLock); if (!theValue) { [cell setTextColor:[NSColor lightGrayColor]]; return; } } else { - theValue = NSArrayObjectAtIndex(NSArrayObjectAtIndex(fullResult, rowIndex), columnIndex); + theValue = SPDataStorageObjectAtRowAndColumn(resultData, rowIndex, columnIndex); } [cell setTextColor:[theValue isNSNull] ? [NSColor lightGrayColor] : [NSColor blackColor]]; @@ -1453,18 +1454,15 @@ // rows or columns may be requested. Return "..." to indicate loading in these // cases. if (isWorking) { - pthread_mutex_lock(&fullResultLock); - if (rowIndex < fullResultCount) { - NSMutableArray *rowData = NSArrayObjectAtIndex(fullResult, rowIndex); - if (columnIndex < [rowData count]) { - theValue = NSArrayObjectAtIndex(rowData, columnIndex); - } + pthread_mutex_lock(&resultDataLock); + if (rowIndex < resultDataCount && columnIndex < [resultData columnCount]) { + theValue = SPDataStorageObjectAtRowAndColumn(resultData, rowIndex, columnIndex); } - pthread_mutex_unlock(&fullResultLock); + pthread_mutex_unlock(&resultDataLock); if (!theValue) return @"..."; } else { - theValue = NSArrayObjectAtIndex(NSArrayObjectAtIndex(fullResult, rowIndex), columnIndex); + theValue = SPDataStorageObjectAtRowAndColumn(resultData, rowIndex, columnIndex); } if ([theValue isKindOfClass:[NSData class]]) @@ -1791,7 +1789,7 @@ // possible exceptions (eg for reloading tables etc.) id theValue; @try{ - theValue = NSArrayObjectAtIndex(NSArrayObjectAtIndex(fullResult, row), [[aTableColumn identifier] integerValue]); + theValue = SPDataStorageObjectAtRowAndColumn(resultData, row, [[aTableColumn identifier] integerValue]); } @catch(id ae) { return nil; @@ -1891,7 +1889,7 @@ && [columnDefinition valueForKey:@"char_length"]) [fieldEditor setTextMaxLength:[[columnDefinition valueForKey:@"char_length"] integerValue]]; - id originalData = [[fullResult objectAtIndex:rowIndex] objectAtIndex:[[aTableColumn identifier] integerValue]]; + id originalData = [resultData cellDataAtRow:rowIndex column:[[aTableColumn identifier] integerValue]]; if ([originalData isNSNull]) originalData = [prefs objectForKey:SPNullValue]; id editData = [[fieldEditor editWithObject:originalData @@ -2976,8 +2974,8 @@ [[helpWebView backForwardList] setCapacity:20]; // init tableView's data source - fullResultCount = 0; - fullResult = [[NSMutableArray alloc] init]; + resultDataCount = 0; + resultData = [[SPDataStorage alloc] init]; editedRow = -1; prefs = [NSUserDefaults standardUserDefaults]; @@ -3083,7 +3081,7 @@ [[NSNotificationCenter defaultCenter] removeObserver:self]; [usedQuery release]; - [fullResult release]; + [resultData release]; [favoritesManager release]; if (helpHTMLTemplate) [helpHTMLTemplate release]; diff --git a/Source/SPDataStorage.h b/Source/SPDataStorage.h new file mode 100644 index 00000000..2baa8d03 --- /dev/null +++ b/Source/SPDataStorage.h @@ -0,0 +1,101 @@ +// +// $Id$ +// +// SPDataStorage.h +// sequel-pro +// +// Created by Rowan Beentje on 10/01/2009. +// Copyright 2009 Rowan Beentje. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// More info at <http://code.google.com/p/sequel-pro/> + +#import <Cocoa/Cocoa.h> + +/* + * This class provides a storage mechanism intended to represent tabular + * data, in a 2D array. Data can be added and retrieved either directly + * or via NSArrays; internally, C arrays are used to provide speed and + * memory improvements. + * This class is essentially mutable. + */ + +#pragma mark - +#pragma mark Class definition + +@interface SPDataStorage : NSObject { + NSUInteger numColumns; + NSUInteger columnPointerByteSize; + NSUInteger numRows, numRowsCapacity; + + id **dataStorage; +} + +/* Retrieving rows and cells */ +- (NSMutableArray *) rowContentsAtIndex:(NSUInteger)index; +- (id) cellDataAtRow:(NSUInteger)rowIndex column:(NSUInteger)columnIndex; + +/* Adding and amending rows and cells */ +- (void) addRowWithContents:(NSArray *)row; +- (void) insertRowContents:(NSArray *)row atIndex:(NSUInteger)index; +- (void) replaceRowAtIndex:(NSUInteger)index withRowContents:(NSArray *)row; +- (void) replaceObjectInRow:(NSUInteger)rowIndex column:(NSUInteger)columnIndex withObject:(id)object; +- (void) removeRowAtIndex:(NSUInteger)index; +- (void) removeRowsInRange:(NSRange)rangeToRemove; +- (void) removeAllRows; + +/* Basic information */ +- (NSUInteger) count; +- (void) setColumnCount:(NSUInteger)columnCount; +- (NSUInteger) columnCount; + +/* Initialisation and teardown */ +#pragma mark - +- (id) init; +- (void) dealloc; + +@end + +#pragma mark - +#pragma mark Cached method calls to remove obj-c messaging overhead in tight loops + +static inline void SPDataStorageAddRow(SPDataStorage* self, NSArray* row) { + typedef void (*SPDSAddRowMethodPtr)(SPDataStorage*, SEL, NSArray*); + static SPDSAddRowMethodPtr SPDSAddRow; + if (!SPDSAddRow) SPDSAddRow = (SPDSAddRowMethodPtr)[self methodForSelector:@selector(addRowWithContents:)]; + SPDSAddRow(self, @selector(addRowWithContents:), row); +} + +static inline void SPDataStorageReplaceRow(SPDataStorage* self, NSUInteger rowIndex, NSArray* row) { + typedef void (*SPDSReplaceRowMethodPtr)(SPDataStorage*, SEL, NSUInteger, NSArray*); + static SPDSReplaceRowMethodPtr SPDSReplaceRow; + if (!SPDSReplaceRow) SPDSReplaceRow = (SPDSReplaceRowMethodPtr)[self methodForSelector:@selector(replaceRowAtIndex:withRowContents:)]; + SPDSReplaceRow(self, @selector(replaceRowAtIndex:withRowContents:), rowIndex, row); +} + +static inline void SPDataStorageReplaceObjectAtRowAndColumn(SPDataStorage* self, NSUInteger rowIndex, NSUInteger colIndex, id newObject) { + typedef void (*SPDSObjectReplaceMethodPtr)(SPDataStorage*, SEL, NSUInteger, NSUInteger, id); + static SPDSObjectReplaceMethodPtr SPDSObjectReplace; + if (!SPDSObjectReplace) SPDSObjectReplace = (SPDSObjectReplaceMethodPtr)[self methodForSelector:@selector(replaceObjectInRow:column:withObject:)]; + SPDSObjectReplace(self, @selector(replaceObjectInRow:column:withObject:), rowIndex, colIndex, newObject); +} + +static inline id SPDataStorageObjectAtRowAndColumn(SPDataStorage* self, NSUInteger rowIndex, NSUInteger colIndex) { + typedef id (*SPDSObjectFetchMethodPtr)(SPDataStorage*, SEL, NSUInteger, NSUInteger); + static SPDSObjectFetchMethodPtr SPDSObjectFetch; + if (!SPDSObjectFetch) SPDSObjectFetch = (SPDSObjectFetchMethodPtr)[self methodForSelector:@selector(cellDataAtRow:column:)]; + return SPDSObjectFetch(self, @selector(cellDataAtRow:column:), rowIndex, colIndex); +} diff --git a/Source/SPDataStorage.m b/Source/SPDataStorage.m new file mode 100644 index 00000000..3db5cdbe --- /dev/null +++ b/Source/SPDataStorage.m @@ -0,0 +1,424 @@ +// +// $Id$ +// +// SPDataStorage.m +// sequel-pro +// +// Created by Rowan Beentje on 10/01/2009. +// Copyright 2009 Rowan Beentje. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// More info at <http://code.google.com/p/sequel-pro/> + +#import "SPDataStorage.h" + +@interface SPDataStorage (PrivateAPI) + +- (void) _ensureCapacityForAdditionalRowCount:(NSUInteger)numExtraRows; +- (void) _increaseCapacity; + +@end + + +@implementation SPDataStorage + +static inline void SPDataStorageEnsureCapacityForAdditionalRowCount(SPDataStorage* self, NSUInteger numExtraRows) { + typedef void (*SPDSEnsureCapacityMethodPtr)(SPDataStorage*, SEL, NSUInteger); + static SPDSEnsureCapacityMethodPtr SPDSEnsureCapacity; + if (!SPDSEnsureCapacity) SPDSEnsureCapacity = (SPDSEnsureCapacityMethodPtr)[self methodForSelector:@selector(_ensureCapacityForAdditionalRowCount:)]; + SPDSEnsureCapacity(self, @selector(_ensureCapacityForAdditionalRowCount:), numExtraRows); +} + +#pragma mark - +#pragma mark Retrieving rows and cells + +/** + * Return a mutable array containing the data for a specified row. + */ +- (NSMutableArray *) rowContentsAtIndex:(NSUInteger)index +{ + + // Throw an exception if the index is out of bounds + if (index >= numRows) [NSException raise:NSRangeException format:@"Requested storage index beyond bounds"]; + + // Construct the NSMutableArray + NSMutableArray *rowArray = [NSMutableArray arrayWithCapacity:numColumns]; + id *row = dataStorage[index]; + NSUInteger i; + for (i = 0; i < numColumns; i++) { + CFArrayAppendValue((CFMutableArrayRef)rowArray, row[i]); + } + + return rowArray; +} + +/** + * Return the data at a specified row and column index. + */ +- (id) cellDataAtRow:(NSUInteger)rowIndex column:(NSUInteger)columnIndex +{ + + // Throw an exception if the row or column index is out of bounds + if (rowIndex >= numRows || columnIndex >= numColumns) [NSException raise:NSRangeException format:@"Requested storage index beyond bounds"]; + + // Return the content + return dataStorage[rowIndex][columnIndex]; +} + +#pragma mark - +#pragma mark Retrieving rows via NSFastEnumeration + +/** + * Implementation of the NSFastEnumeration protocol. + * Note that this currently doesn't implement mutation guards. + */ +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len +{ + + // If the start index is out of bounds, return 0 to indicate end of results + if (state->state >= numRows) return 0; + + // Determine how many objects to return - 128, len, or all items remaining + NSUInteger itemsToReturn = 128; + if (len < 128) itemsToReturn = len; + if (numRows - state->state < itemsToReturn) { + itemsToReturn = numRows - state->state; + } + + // Construct the arrays to return + NSUInteger i, j; + NSMutableArray *rowArray; + id *row; + for (i = 0; i < itemsToReturn; i++) { + row = dataStorage[state->state + i]; + rowArray = [NSMutableArray arrayWithCapacity:numColumns]; + for (j = 0; j < numColumns; j++) { + CFArrayAppendValue((CFMutableArrayRef)rowArray, row[j]); + } + stackbuf[i] = rowArray; + } + + state->state += itemsToReturn; + state->itemsPtr = stackbuf; + state->mutationsPtr = (unsigned long *)&numRows; + return itemsToReturn; +} + +#pragma mark - +#pragma mark Adding and amending rows and cells + +/** + * Add a new row to the end of the storage array, supplying an NSArray + * of objects. Note that the supplied objects are retained as a reference + * rather than copied. + */ +- (void) addRowWithContents:(NSArray *)row +{ + + // Ensure that sufficient capacity is available + SPDataStorageEnsureCapacityForAdditionalRowCount(self, 1); + + // Add an empty row array to the data store + id *newRow = (id *)malloc(columnPointerByteSize); + dataStorage[numRows] = newRow; + numRows++; + + // Copy over references to the array contents, and retain the objects + NSUInteger cellsCopied = 0; + for (id cellData in row) { + if (cellData) newRow[cellsCopied] = (id)CFRetain(cellData); + else newRow[cellsCopied] = nil; + if (++cellsCopied == numColumns) break; + } + + // If an array shorter than the row width was added, pad with nils + if (cellsCopied < numColumns) { + for ( ; cellsCopied <= numColumns; cellsCopied++) newRow[cellsCopied] = nil; + } +} + +/** + * Insert a new row into the storage array at a specified point, pushing + * all later rows the next index. Note that the supplied objects within the + * array are retained as a reference rather than copied. + */ +- (void) insertRowContents:(NSArray *)row atIndex:(NSUInteger)index +{ + + // Throw an exception if the index is out of bounds + if (index > numRows) [NSException raise:NSRangeException format:@"Requested storage index beyond bounds"]; + + // If "inserting" at the end of the array just add a row + if (index == numRows) return SPDataStorageAddRow(self, row); + + // Ensure that sufficient capacity is available to hold all the rows + SPDataStorageEnsureCapacityForAdditionalRowCount(self, 1); + + // Renumber the specified index, and all subsequent indices, to create a gap + for (NSUInteger j = numRows - 1; j >= index; j--) { + dataStorage[j + 1] = dataStorage[j]; + } + + // Add a new instantiated row array to the data store at the specified point + id *newRow = (id *)malloc(columnPointerByteSize); + dataStorage[index] = newRow; + numRows++; + + // Copy over references to the array contents, and retain the objects + NSUInteger cellsCopied = 0; + for (id cellData in row) { + if (cellData) newRow[cellsCopied] = (id)CFRetain(cellData); + else newRow[cellsCopied] = nil; + if (++cellsCopied == numColumns) break; + } + + // If an array shorter than the row width was inserted, pad with nils + if (cellsCopied < numColumns) { + for ( ; cellsCopied <= numColumns; cellsCopied++) newRow[cellsCopied] = nil; + } +} + +/** + * Replace a row with contents of the supplied NSArray. + */ +- (void) replaceRowAtIndex:(NSUInteger)index withRowContents:(NSArray *)row +{ + NSUInteger cellsProcessed = 0; + + // Throw an exception if the index is out of bounds + if (index >= numRows) [NSException raise:NSRangeException format:@"Requested storage index beyond bounds"]; + + id *storageRow = dataStorage[index]; + + // Iterate through the row replacing the objects + for (id cellData in row) { + if (storageRow[cellsProcessed]) CFRelease(storageRow[cellsProcessed]); + if (cellData) storageRow[cellsProcessed] = (id)CFRetain(cellData); + else storageRow[cellsProcessed] = nil; + if (++cellsProcessed == numColumns) break; + } + + // Ensure all cells are correctly updated if an array shorter than the row width was supplied + if (cellsProcessed < numColumns) { + for ( ; cellsProcessed <= numColumns; cellsProcessed++) { + if (storageRow[cellsProcessed]) CFRelease(storageRow[cellsProcessed]); + storageRow[cellsProcessed] = nil; + } + } +} + +/** + * Replace the contents of a single cell with a supplied object. + */ +- (void) replaceObjectInRow:(NSUInteger)rowIndex column:(NSUInteger)columnIndex withObject:(id)object +{ + + // Throw an exception of either index is out of bounds + if (rowIndex >= numRows || columnIndex >= numColumns) [NSException raise:NSRangeException format:@"Requested storage index beyond bounds"]; + + // Release the old object and retain the new one + if (dataStorage[rowIndex][columnIndex]) CFRelease(dataStorage[rowIndex][columnIndex]); + if (object) dataStorage[rowIndex][columnIndex] = (id)CFRetain(object); + else dataStorage[rowIndex][columnIndex] = nil; +} + +/** + * Remove a row, renumbering all elements beyond index. + */ +- (void) removeRowAtIndex:(NSUInteger)index +{ + + // Throw an exception if the index is out of bounds + if (index >= numRows) [NSException raise:NSRangeException format:@"Requested storage index beyond bounds"]; + + // Free the row + NSUInteger j = numColumns; + id *row = dataStorage[index]; + while (j > 0) { + if (row[--j]) CFRelease(row[j]); + } + free(row); + numRows--; + + // Renumber all subsequent indices to fill the gap + for (j = index; j < numRows; j++) { + dataStorage[j] = dataStorage[j + 1]; + } + dataStorage[numRows] = NULL; +} + +/** + * Remove all rows in the specified range, renumbering all elements + * beyond the end of the range. + */ +- (void) removeRowsInRange:(NSRange)rangeToRemove +{ + + // Throw an exception if the range is out of bounds + if (rangeToRemove.location + rangeToRemove.length >= numRows) [NSException raise:NSRangeException format:@"Requested storage index beyond bounds"]; + + // Free rows in the range + NSUInteger i, j = numColumns; + id *row; + for (i = rangeToRemove.location; i < rangeToRemove.location + rangeToRemove.length; i++) { + row = dataStorage[i]; + while (j > 0) { + if (row[--j]) CFRelease(row[j]); + } + free(row); + } + numRows -= rangeToRemove.length; + + // Renumber all subsequent indices to fill the gap + for (i = rangeToRemove.location + rangeToRemove.length - 1; i < numRows; i++) { + dataStorage[i] = dataStorage[i + rangeToRemove.length]; + } + for (i = numRows; i < numRows + rangeToRemove.length; i++) { + dataStorage[i] = NULL; + } +} + +/** + * Remove all rows from the array, and free their associated memory. + */ +- (void) removeAllRows +{ + NSUInteger j; + id *row; + + // Free all the data + while (numRows > 0) { + row = dataStorage[--numRows]; + j = numColumns; + while (j > 0) { + if (row[--j]) CFRelease(row[j]); + } + free(row); + } + + numRows = 0; +} + +#pragma mark - +#pragma mark Basic information + +/** + * Returns the number of rows currently held in data storage. + */ +- (NSUInteger) count +{ + return numRows; +} + +/** + * Set the number of columns represented by the data storage. + */ +- (void) setColumnCount:(NSUInteger)columnCount +{ + columnPointerByteSize = columnCount * sizeof(id); + + // If there are rows present in the storage, and the number of + // columns has changed, amend the existing rows to match. + if (columnCount != numColumns && numRows) { + NSUInteger i = numRows, j; + id *row; + + // If the new column count is higher than the old count, iterate through the existing rows + // and pad with nils + if (columnCount > numColumns) { + while (i > 0) { + row = dataStorage[--i]; + row = (id *)realloc(row, columnPointerByteSize); + j = numColumns; + while (j < columnCount) { + row[j++] = nil; + } + } + + // If the new column count is lower than the old count, iterate through the existing rows + // freeing any extra objects + } else { + while (i > 0) { + row = dataStorage[--i]; + j = numColumns; + while (j > columnCount) { + if (row[--j]) CFRelease(row[j]); + } + row = (id *)realloc(row, columnPointerByteSize); + } + } + } + + // Update the column count + numColumns = columnCount; +} + +/** + * Return the number of columns represented by the data storage. + */ +- (NSUInteger) columnCount +{ + return numColumns; +} + +/** + * Setup and teardown + */ +#pragma mark - + +- (id) init { + if (self = [super init]) { + numColumns = 0; + columnPointerByteSize = 0; + numRows = 0; + + // Initialise the array, initially with space for 100 rows + numRowsCapacity = 100; + dataStorage = (id **)malloc(numRowsCapacity * sizeof(id *)); + } + return self; +} + +- (void) dealloc { + [self removeAllRows]; + free(dataStorage); + [super dealloc]; +} + +@end + +@implementation SPDataStorage (PrivateAPI) + +/** + * Private method to ensure the array always has sufficient capacity + * to store any additional rows required. + */ +- (void) _ensureCapacityForAdditionalRowCount:(NSUInteger)numExtraRows +{ + while (numRows + numExtraRows > numRowsCapacity) [self _increaseCapacity]; +} + +/** + * Private method to increase the storage available for the array; + * currently doubles the capacity as boundaries are reached. + */ +- (void) _increaseCapacity +{ + numRowsCapacity *= 2; + dataStorage = (id **)realloc(dataStorage, numRowsCapacity * sizeof(id *)); +} + +@end diff --git a/Source/TableContent.h b/Source/TableContent.h index c467b865..1b9e4af6 100644 --- a/Source/TableContent.h +++ b/Source/TableContent.h @@ -28,7 +28,7 @@ #import <Cocoa/Cocoa.h> #import <MCPKit/MCPKit.h> -@class CMCopyTable, SPTextAndLinkCell, SPHistoryController, SPTableInfo; +@class CMCopyTable, SPTextAndLinkCell, SPHistoryController, SPTableInfo, SPDataStorage; @interface TableContent : NSObject { @@ -72,7 +72,8 @@ pthread_mutex_t tableValuesLock; NSString *selectedTable, *usedQuery; - NSMutableArray *tableValues, *dataColumns, *keys, *oldRow; + SPDataStorage *tableValues; + NSMutableArray *dataColumns, *keys, *oldRow; NSUInteger tableRowsCount, previousTableRowsCount; NSString *compareType; NSNumber *sortCol; diff --git a/Source/TableContent.m b/Source/TableContent.m index 5f3f7759..eb74accc 100644 --- a/Source/TableContent.m +++ b/Source/TableContent.m @@ -48,6 +48,7 @@ #import "SPContentFilterManager.h" #import "SPNotLoaded.h" #import "SPConstants.h" +#import "SPDataStorage.h" #import "TableDocument.h" @implementation TableContent @@ -62,7 +63,7 @@ isWorking = NO; pthread_mutex_init(&tableValuesLock, NULL); - tableValues = [[NSMutableArray alloc] init]; + tableValues = [[SPDataStorage alloc] init]; tableRowsCount = 0; previousTableRowsCount = 0; dataColumns = [[NSMutableArray alloc] init]; @@ -221,7 +222,7 @@ [tableContentView performSelectorOnMainThread:@selector(displayIfNeeded) withObject:nil waitUntilDone:NO]; // Init copyTable with necessary information for copying selected rows as SQL INSERT - [tableContentView setTableInstance:self withTableData:tableValues withColumns:dataColumns withTableName:selectedTable withConnection:mySQLConnection]; + [tableContentView setTableInstance:self withColumns:dataColumns withTableName:selectedTable withConnection:mySQLConnection]; // Post the notification that the query is finished [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; @@ -523,12 +524,12 @@ */ - (void) clearTableValues { - NSMutableArray *tableValuesTransition; + SPDataStorage *tableValuesTransition; tableValuesTransition = tableValues; pthread_mutex_lock(&tableValuesLock); tableRowsCount = 0; - tableValues = [[NSMutableArray alloc] init]; + tableValues = [[SPDataStorage alloc] init]; pthread_mutex_unlock(&tableValuesLock); [tableValuesTransition release]; } @@ -604,9 +605,11 @@ // Perform and process the query [tableContentView performSelectorOnMainThread:@selector(noteNumberOfRowsChanged) withObject:nil waitUntilDone:YES]; [self setUsedQuery:queryString]; + NSDate *startDate = [NSDate date]; streamingResult = [mySQLConnection streamingQueryString:queryString]; [self processResultIntoDataStorage:streamingResult approximateRowCount:rowsToLoad]; [streamingResult release]; + NSLog(@"New took %f sec", [[NSDate date] timeIntervalSinceDate:startDate]); // If the result is empty, and a late page is selected, reset the page if ([prefs boolForKey:SPLimitResults] && queryStringBeforeLimit && !tableRowsCount && ![mySQLConnection queryCancelled]) { @@ -654,10 +657,12 @@ - (void)processResultIntoDataStorage:(MCPStreamingResult *)theResult approximateRowCount:(NSUInteger)targetRowCount { NSArray *tempRow; - NSMutableArray *newRow; - NSMutableArray *columnBlobStatuses = [[NSMutableArray alloc] init]; NSUInteger i; NSUInteger dataColumnsCount = [dataColumns count]; + BOOL *columnBlobStatuses = malloc(dataColumnsCount * sizeof(BOOL));; + + // Set the column count on the data store + [tableValues setColumnCount:dataColumnsCount]; CGFloat relativeTargetRowCount = 100.0/targetRowCount; NSUInteger nextTableDisplayBoundary = 50; @@ -671,7 +676,7 @@ // Build up an array of which columns are blobs for faster iteration for ( i = 0; i < dataColumnsCount ; i++ ) { - [columnBlobStatuses addObject:[NSNumber numberWithBool:[tableDataInstance columnIsBlobOrText:[NSArrayObjectAtIndex(dataColumns, i) objectForKey:@"name"] ]]]; + columnBlobStatuses[i] = [tableDataInstance columnIsBlobOrText:[NSArrayObjectAtIndex(dataColumns, i) objectForKey:@"name"]]; } // Set up an autorelease pool for row processing @@ -682,17 +687,16 @@ pthread_mutex_lock(&tableValuesLock); if (rowsProcessed < previousTableRowsCount) { - NSMutableArrayReplaceObject(tableValues, rowsProcessed, [NSMutableArray arrayWithArray:tempRow]); + SPDataStorageReplaceRow(tableValues, rowsProcessed, tempRow); } else { - NSMutableArrayAddObject(tableValues, [NSMutableArray arrayWithArray:tempRow]); + SPDataStorageAddRow(tableValues, tempRow); } // Alter the values for hidden blob and text fields if appropriate if ( prefsLoadBlobsAsNeeded ) { - newRow = NSArrayObjectAtIndex(tableValues, rowsProcessed); for ( i = 0 ; i < dataColumnsCount ; i++ ) { - if ( [NSArrayObjectAtIndex(columnBlobStatuses, i) boolValue] ) { - NSMutableArrayReplaceObject(newRow, i, [SPNotLoaded notLoaded]); + if (columnBlobStatuses[i]) { + SPDataStorageReplaceObjectAtRowAndColumn(tableValues, rowsProcessed, i, [SPNotLoaded notLoaded]); } } } @@ -732,7 +736,7 @@ // If the reloaded table is shorter than the previous table, remove the extra values from the storage if (tableRowsCount < [tableValues count]) { pthread_mutex_lock(&tableValuesLock); - [tableValues removeObjectsInRange:NSMakeRange(tableRowsCount, [tableValues count] - tableRowsCount)]; + [tableValues removeRowsInRange:NSMakeRange(tableRowsCount, [tableValues count] - tableRowsCount)]; pthread_mutex_unlock(&tableValuesLock); } @@ -743,8 +747,8 @@ // Clean up the autorelease pool and reset the progress indicator [dataLoadingPool drain]; [dataLoadingIndicator setIndeterminate:YES]; - - [columnBlobStatuses release]; + + free(columnBlobStatuses); } /** @@ -1243,7 +1247,7 @@ [newRow addObject:[column objectForKey:@"default"]]; } } - [tableValues addObject:newRow]; + [tableValues addRowWithContents:newRow]; tableRowsCount++; [tableContentView reloadData]; @@ -1278,8 +1282,8 @@ } //copy row - tempRow = [NSMutableArray arrayWithArray:[tableValues objectAtIndex:[tableContentView selectedRow]]]; - [tableValues insertObject:tempRow atIndex:[tableContentView selectedRow]+1]; + tempRow = [tableValues rowContentsAtIndex:[tableContentView selectedRow]]; + [tableValues insertRowContents:tempRow atIndex:[tableContentView selectedRow]+1]; tableRowsCount++; //if we don't show blobs, read data for this duplicate column from db @@ -1396,7 +1400,7 @@ [tempRow removeAllObjects]; enumerator = [tableColumns objectEnumerator]; while ( (tableColumn = [enumerator nextObject]) ) { - id o = [NSArrayObjectAtIndex(tableValues, i) objectAtIndex:[[tableColumn identifier] integerValue]]; + id o = SPDataStorageObjectAtRowAndColumn(tableValues, i, [[tableColumn identifier] integerValue]); if([o isNSNull]) [tempRow addObject:@"NULL"]; else if ([o isSPNotLoaded]) @@ -1496,7 +1500,7 @@ // Save existing scroll position and details [spHistoryControllerInstance updateHistoryEntries]; - NSString *targetFilterValue = [[tableValues objectAtIndex:[theArrowCell getClickedRow]] objectAtIndex:dataColumnIndex]; + NSString *targetFilterValue = [tableValues cellDataAtRow:[theArrowCell getClickedRow] column:dataColumnIndex]; // If the link is within the current table, apply filter settings manually if ([[refDictionary objectForKey:@"table"] isEqualToString:selectedTable]) { @@ -1697,7 +1701,7 @@ [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance]; // If editing, compare the new row to the old row and if they are identical finish editing without saving. - if (!isEditingNewRow && [oldRow isEqualToArray:[tableValues objectAtIndex:currentlyEditingRow]]) { + if (!isEditingNewRow && [oldRow isEqualToArray:[tableValues rowContentsAtIndex:currentlyEditingRow]]) { isEditingRow = NO; currentlyEditingRow = -1; [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; @@ -1705,9 +1709,10 @@ } NSMutableArray *fieldValues = [[NSMutableArray alloc] init]; + // Get the field values for ( i = 0 ; i < [dataColumns count] ; i++ ) { - rowObject = [NSArrayObjectAtIndex(tableValues, currentlyEditingRow) objectAtIndex:i]; + rowObject = [tableValues cellDataAtRow:currentlyEditingRow column:i]; // Add not loaded placeholders directly for easy comparison when added if (prefsLoadBlobsAsNeeded && !isEditingNewRow && [rowObject isSPNotLoaded]) @@ -1809,7 +1814,7 @@ } else { NSBeep(); } - [tableValues replaceObjectAtIndex:currentlyEditingRow withObject:[NSMutableArray arrayWithArray:oldRow]]; + [tableValues replaceRowAtIndex:currentlyEditingRow withRowContents:oldRow]; isEditingRow = NO; isEditingNewRow = NO; currentlyEditingRow = -1; @@ -1831,7 +1836,7 @@ // Set the insertId for fields with auto_increment for ( i = 0; i < [dataColumns count] ; i++ ) { if ([[NSArrayObjectAtIndex(dataColumns, i) objectForKey:@"autoincrement"] integerValue]) { - [[tableValues objectAtIndex:currentlyEditingRow] replaceObjectAtIndex:i withObject:[[NSNumber numberWithLong:[mySQLConnection insertId]] description]]; + [tableValues replaceObjectInRow:currentlyEditingRow column:i withObject:[[NSNumber numberWithLong:[mySQLConnection insertId]] description]]; } } } @@ -1948,7 +1953,7 @@ // Use the selected row if appropriate if ( row >= 0 ) { - tempValue = [NSArrayObjectAtIndex(tableValues, row) objectAtIndex:[[[tableDataInstance columnWithName:NSArrayObjectAtIndex(keys, i)] objectForKey:@"datacolumnindex"] integerValue]]; + tempValue = [tableValues cellDataAtRow:row column:[[[tableDataInstance columnWithName:NSArrayObjectAtIndex(keys, i)] objectForKey:@"datacolumnindex"] integerValue]]; // Otherwise use the oldRow } else { @@ -2032,7 +2037,6 @@ */ { - NSMutableArray *tempResult = [NSMutableArray array]; NSMutableIndexSet *selectedRows = [NSMutableIndexSet indexSet]; NSString *wherePart; NSInteger i, errors; @@ -2051,12 +2055,11 @@ [tableContentView performSelector:@selector(keyDown:) withObject:[NSEvent keyEventWithType:NSKeyDown location:NSMakePoint(0,0) modifierFlags:0 timestamp:0 windowNumber:[tableWindow windowNumber] context:[NSGraphicsContext currentContext] characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:0x24] afterDelay:0.0]; } else { if ( !isEditingNewRow ) { - [tableValues replaceObjectAtIndex:[tableContentView selectedRow] - withObject:[NSMutableArray arrayWithArray:oldRow]]; + [tableValues replaceRowAtIndex:[tableContentView selectedRow] withRowContents:oldRow]; isEditingRow = NO; } else { tableRowsCount--; - [tableValues removeObjectAtIndex:[tableContentView selectedRow]]; + [tableValues removeRowAtIndex:[tableContentView selectedRow]]; isEditingRow = NO; isEditingNewRow = NO; } @@ -2154,7 +2157,7 @@ while (index != NSNotFound) { - id keyValue = [NSArrayObjectAtIndex(tableValues, index) objectAtIndex:[[[tableDataInstance columnWithName:NSArrayObjectAtIndex(primaryKeyFieldNames,0)] objectForKey:@"datacolumnindex"] integerValue]]; + id keyValue = [tableValues cellDataAtRow:index column:[[[tableDataInstance columnWithName:NSArrayObjectAtIndex(primaryKeyFieldNames,0)] objectForKey:@"datacolumnindex"] integerValue]]; if([keyValue isKindOfClass:[NSData class]]) [deleteQuery appendString:[NSString stringWithFormat:@"X'%@'", [mySQLConnection prepareBinaryData:keyValue]]]; @@ -2199,9 +2202,8 @@ // Build the AND clause of PRIMARY KEYS [deleteQuery appendString:@"("]; for(NSString *primaryKeyFieldName in primaryKeyFieldNames) { - - - id keyValue = [NSArrayObjectAtIndex(tableValues, index) objectAtIndex:[[[tableDataInstance columnWithName:primaryKeyFieldName] objectForKey:@"datacolumnindex"] integerValue]]; + + id keyValue = [tableValues cellDataAtRow:index column:[[[tableDataInstance columnWithName:primaryKeyFieldName] objectForKey:@"datacolumnindex"] integerValue]]; [deleteQuery appendString:[primaryKeyFieldName backtickQuotedString]]; if ([keyValue isKindOfClass:[NSData class]]) { @@ -2273,12 +2275,10 @@ previousTableRowsCount = tableRowsCount; [self loadTableValues]; } else { - for ( i = 0 ; i < tableRowsCount ; i++ ) { - if ( ![selectedRows containsIndex:i] ) - [tempResult addObject:NSArrayObjectAtIndex(tableValues, i)]; + for ( i = tableRowsCount - 1 ; i >= 0 ; i-- ) { + if ([selectedRows containsIndex:i]) [tableValues removeRowAtIndex:i]; } - tableRowsCount = [tempResult count]; - [tableValues setArray:tempResult]; + tableRowsCount = [tableValues count]; [tableContentView reloadData]; } [tableContentView deselectAll:self]; @@ -2576,7 +2576,7 @@ // possible exceptions (eg for reloading tables etc.) id theValue; @try{ - theValue = NSArrayObjectAtIndex(NSArrayObjectAtIndex(tableValues, row), [[aTableColumn identifier] integerValue]); + theValue = [tableValues cellDataAtRow:row column:[[aTableColumn identifier] integerValue]]; } @catch(id ae) { return nil; @@ -2613,17 +2613,14 @@ // cases. if (isWorking) { pthread_mutex_lock(&tableValuesLock); - if (rowIndex < tableRowsCount) { - NSMutableArray *rowData = NSArrayObjectAtIndex(tableValues, rowIndex); - if (columnIndex < [rowData count]) { - theValue = NSArrayObjectAtIndex(rowData, columnIndex); - } + if (rowIndex < tableRowsCount && columnIndex < [tableValues columnCount]) { + theValue = SPDataStorageObjectAtRowAndColumn(tableValues, rowIndex, columnIndex); } pthread_mutex_unlock(&tableValuesLock); if (!theValue) return @"..."; } else { - theValue = NSArrayObjectAtIndex(NSArrayObjectAtIndex(tableValues, rowIndex), columnIndex); + theValue = SPDataStorageObjectAtRowAndColumn(tableValues, rowIndex, columnIndex); } if ([theValue isNSNull]) @@ -2653,11 +2650,8 @@ // rows or columns may be requested. Use gray to indicate loading in these cases. if (isWorking) { pthread_mutex_lock(&tableValuesLock); - if (rowIndex < tableRowsCount) { - NSMutableArray *rowData = NSArrayObjectAtIndex(tableValues, rowIndex); - if (columnIndex < [rowData count]) { - theValue = NSArrayObjectAtIndex(rowData, columnIndex); - } + if (rowIndex < tableRowsCount && columnIndex < [tableValues columnCount]) { + theValue = SPDataStorageObjectAtRowAndColumn(tableValues, rowIndex, columnIndex); } pthread_mutex_unlock(&tableValuesLock); @@ -2666,12 +2660,12 @@ return; } } else { - theValue = NSArrayObjectAtIndex(NSArrayObjectAtIndex(tableValues, rowIndex), columnIndex); + theValue = SPDataStorageObjectAtRowAndColumn(tableValues, rowIndex, columnIndex); } // If user wants to edit 'cell' set text color to black and return to avoid // writing in gray if value was NULL - if ( [aTableView editedColumn] == [[aTableColumn identifier] integerValue] && [aTableView editedRow] == rowIndex) { + if ( [aTableView editedColumn] == columnIndex && [aTableView editedRow] == rowIndex) { [cell setTextColor:[NSColor blackColor]]; return; } @@ -2691,7 +2685,7 @@ // Catch editing events in the row and if the row isn't currently being edited, // start an edit. This allows edits including enum changes to save correctly. if ( !isEditingRow ) { - [oldRow setArray:NSArrayObjectAtIndex(tableValues, rowIndex)]; + [oldRow setArray:[tableValues rowContentsAtIndex:rowIndex]]; isEditingRow = YES; currentlyEditingRow = rowIndex; } @@ -2704,9 +2698,9 @@ if ([anObject isEqualToString:[prefs objectForKey:SPNullValue]] && [[column objectForKey:@"null"] boolValue]) anObject = [NSNull null]; - [NSArrayObjectAtIndex(tableValues, rowIndex) replaceObjectAtIndex:[[aTableColumn identifier] integerValue] withObject:anObject]; + [tableValues replaceObjectInRow:rowIndex column:[[aTableColumn identifier] integerValue] withObject:anObject]; } else { - [NSArrayObjectAtIndex(tableValues, rowIndex) replaceObjectAtIndex:[[aTableColumn identifier] integerValue] withObject:@""]; + [tableValues replaceObjectInRow:rowIndex column:[[aTableColumn identifier] integerValue] withObject:@""]; } } @@ -2855,7 +2849,7 @@ if ([wherePart length] == 0) return NO; // If the selected cell hasn't been loaded, load it. - if ([NSArrayObjectAtIndex(NSArrayObjectAtIndex(tableValues, rowIndex), [[aTableColumn identifier] integerValue]) isSPNotLoaded]) { + if ([[tableValues cellDataAtRow:rowIndex column:[[aTableColumn identifier] integerValue]] isSPNotLoaded]) { // Only get the data for the selected column, not all of them NSString *query = [NSString stringWithFormat:@"SELECT %@ FROM %@ WHERE %@", [[[aTableColumn headerCell] stringValue] backtickQuotedString], [selectedTable backtickQuotedString], wherePart]; @@ -2868,7 +2862,7 @@ } NSArray *tempRow = [tempResult fetchRowAsArray]; - [[tableValues objectAtIndex:rowIndex] replaceObjectAtIndex:[[tableContentView tableColumns] indexOfObject:aTableColumn] withObject:[tempRow objectAtIndex:0]]; + [tableValues replaceObjectInRow:rowIndex column:[[tableContentView tableColumns] indexOfObject:aTableColumn] withObject:[tempRow objectAtIndex:0]]; [tableContentView reloadData]; } @@ -2881,7 +2875,7 @@ [fieldEditor setTextMaxLength:[[[aTableColumn dataCellForRow:rowIndex] formatter] textLimit]]; - id cellValue = [[tableValues objectAtIndex:rowIndex] objectAtIndex:[[aTableColumn identifier] integerValue]]; + id cellValue = [tableValues cellDataAtRow:rowIndex column:[[aTableColumn identifier] integerValue]]; if ([cellValue isNSNull]) cellValue = [NSString stringWithString:[prefs objectForKey:SPNullValue]]; id editData = [[fieldEditor editWithObject:cellValue @@ -2893,7 +2887,7 @@ if (editData) { if (!isEditingRow) { - [oldRow setArray:[tableValues objectAtIndex:rowIndex]]; + [oldRow setArray:[tableValues rowContentsAtIndex:rowIndex]]; isEditingRow = YES; currentlyEditingRow = rowIndex; } @@ -2905,7 +2899,7 @@ [editData release]; editData = [[NSNull null] retain]; } - [[tableValues objectAtIndex:rowIndex] replaceObjectAtIndex:[[aTableColumn identifier] integerValue] withObject:[[editData copy] autorelease]]; + [tableValues replaceObjectInRow:rowIndex column:[[aTableColumn identifier] integerValue] withObject:[[editData copy] autorelease]]; } [fieldEditor release]; @@ -3079,12 +3073,12 @@ [control abortEditing]; if ( isEditingRow && !isEditingNewRow ) { isEditingRow = NO; - [tableValues replaceObjectAtIndex:row withObject:[NSMutableArray arrayWithArray:oldRow]]; + [tableValues replaceRowAtIndex:row withRowContents:oldRow]; } else if ( isEditingNewRow ) { isEditingRow = NO; isEditingNewRow = NO; tableRowsCount--; - [tableValues removeObjectAtIndex:row]; + [tableValues removeRowAtIndex:row]; [tableContentView reloadData]; } currentlyEditingRow = -1; diff --git a/sequel-pro.xcodeproj/project.pbxproj b/sequel-pro.xcodeproj/project.pbxproj index d76b6728..ebedb972 100644 --- a/sequel-pro.xcodeproj/project.pbxproj +++ b/sequel-pro.xcodeproj/project.pbxproj @@ -138,6 +138,7 @@ 584F5F8F0F50ACD800036517 /* table-view-small.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 584F5F8E0F50ACD800036517 /* table-view-small.tiff */; }; 586F457B0FDB269E00B428D7 /* RegexKitLite.m in Sources */ = {isa = PBXBuildFile; fileRef = 296DC8AB0F909194002A3258 /* RegexKitLite.m */; }; 586F457E0FDB280100B428D7 /* libicucore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 296DC8BE0F9091DF002A3258 /* libicucore.dylib */; }; + 5870868410FA3E9C00D58E1C /* SPDataStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 5870868310FA3E9C00D58E1C /* SPDataStorage.m */; }; 5885940F0F7AEE6000ED0E67 /* sparkle-public-key.pem in Resources */ = {isa = PBXBuildFile; fileRef = 5885940E0F7AEE6000ED0E67 /* sparkle-public-key.pem */; }; 588B2CC80FE5641E00EC5FC0 /* ssh-connected.png in Resources */ = {isa = PBXBuildFile; fileRef = 588B2CC50FE5641E00EC5FC0 /* ssh-connected.png */; }; 588B2CC90FE5641E00EC5FC0 /* ssh-connecting.png in Resources */ = {isa = PBXBuildFile; fileRef = 588B2CC60FE5641E00EC5FC0 /* ssh-connecting.png */; }; @@ -520,6 +521,8 @@ 584192A0101E57BB0089807F /* NSMutableArray-MultipleSort.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSMutableArray-MultipleSort.m"; sourceTree = "<group>"; }; 584F5F8E0F50ACD800036517 /* table-view-small.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "table-view-small.tiff"; sourceTree = "<group>"; }; 586F432A0FD74CFC00B428D7 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/SSHQuestionDialog.xib; sourceTree = "<group>"; }; + 5870868210FA3E9C00D58E1C /* SPDataStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPDataStorage.h; sourceTree = "<group>"; }; + 5870868310FA3E9C00D58E1C /* SPDataStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDataStorage.m; sourceTree = "<group>"; }; 588593F30F7AEC9500ED0E67 /* package-application.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = "package-application.sh"; sourceTree = "<group>"; }; 5885940E0F7AEE6000ED0E67 /* sparkle-public-key.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "sparkle-public-key.pem"; sourceTree = "<group>"; }; 588B2CC50FE5641E00EC5FC0 /* ssh-connected.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ssh-connected.png"; sourceTree = "<group>"; }; @@ -1396,6 +1399,8 @@ BC2C16D30FEBEDF10003993B /* SPDataAdditions.m */, 582A01E7107C0C170027D42B /* SPNotLoaded.h */, 582A01E8107C0C170027D42B /* SPNotLoaded.m */, + 5870868210FA3E9C00D58E1C /* SPDataStorage.h */, + 5870868310FA3E9C00D58E1C /* SPDataStorage.m */, ); name = "Category Additions"; sourceTree = "<group>"; @@ -1784,6 +1789,7 @@ 1792C26110AE1A2D00ABE758 /* SPConnectionDelegate.m in Sources */, 17CC97F310B4ABE90034CD7A /* SPAboutController.m in Sources */, 58C34F5310B86CAE00D37E14 /* NSNotificationAdditions.m in Sources */, + 5870868410FA3E9C00D58E1C /* SPDataStorage.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; |