aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrowanbeentje <rowan@beent.je>2009-10-10 00:06:52 +0000
committerrowanbeentje <rowan@beent.je>2009-10-10 00:06:52 +0000
commit0371f943f790d4c20ba5947a90db6cbe41669710 (patch)
tree53834dfe68b609773a0c9b0f0ccb79b3114a44af
parenta65889bc725c192813f568f606071268693fce5d (diff)
downloadsequelpro-0371f943f790d4c20ba5947a90db6cbe41669710.tar.gz
sequelpro-0371f943f790d4c20ba5947a90db6cbe41669710.tar.bz2
sequelpro-0371f943f790d4c20ba5947a90db6cbe41669710.zip
Improve handling of NULL and "(not loaded)" placeholders:
- Rewrite TableContent and CustomQuery to store NSNull and SPNotLoaded objects in the data arrays where appropriate, rather than providing string conversion on data load. Faster, simpler comparisons and processing code, slightly lower memory usage, and reduces the chance of bugs caused by inadvertantly processing the string values; we can now also distinguish easily between NULL and "NULL" etc, and further paves the ground for image representations of special values. - Fix a bug caused by consistent value reloading when editing BLOB/TEXT columns with deferred loading - if editing a row and revisiting an edited cell, the original value was restored; the original value is now only loaded once. This addresses the rest of Issue #423.
-rw-r--r--Frameworks/MCPKit/MCPFoundationKit/MCPNull.m2
-rw-r--r--Source/CMCopyTable.m24
-rw-r--r--Source/CustomQuery.m30
-rw-r--r--Source/SPNotLoaded.h41
-rw-r--r--Source/SPNotLoaded.m91
-rw-r--r--Source/TableContent.m191
-rw-r--r--sequel-pro.xcodeproj/project.pbxproj6
7 files changed, 245 insertions, 140 deletions
diff --git a/Frameworks/MCPKit/MCPFoundationKit/MCPNull.m b/Frameworks/MCPKit/MCPFoundationKit/MCPNull.m
index 76d9cfb1..4232fb2a 100644
--- a/Frameworks/MCPKit/MCPFoundationKit/MCPNull.m
+++ b/Frameworks/MCPKit/MCPFoundationKit/MCPNull.m
@@ -36,7 +36,7 @@
- (BOOL) isNSNull
{
static id NSNullForComparison;
- if (!NSNullForComparison) NSNullForComparison = [NSNull null];;
+ if (!NSNullForComparison) NSNullForComparison = [NSNull null];
return (self == NSNullForComparison);
}
diff --git a/Source/CMCopyTable.m b/Source/CMCopyTable.m
index f42ff783..23bf7804 100644
--- a/Source/CMCopyTable.m
+++ b/Source/CMCopyTable.m
@@ -29,6 +29,7 @@
#import "SPStringAdditions.h"
#import "TableContent.h"
#import "CustomQuery.h"
+#import "SPNotLoaded.h"
int MENU_EDIT_COPY_WITH_COLUMN = 2001;
int MENU_EDIT_COPY_AS_SQL = 2002;
@@ -131,7 +132,12 @@ int MENU_EDIT_COPY_AS_SQL = 2002;
if ( nil != rowData )
{
- [result appendString:[NSString stringWithFormat:@"%@\t", [rowData description] ] ];
+ if ([rowData isNSNull])
+ [result appendString:[NSString stringWithFormat:@"%@\t", [prefs objectForKey:@"nullValue"]]];
+ else if ([rowData isSPNotLoaded])
+ [result appendString:[NSString stringWithFormat:@"%@\t", NSLocalizedString(@"(not loaded)", @"value shown for hidden blob and text fields")]];
+ else
+ [result appendString:[NSString stringWithFormat:@"%@\t", [rowData description] ] ];
}
else
{
@@ -175,7 +181,6 @@ int MENU_EDIT_COPY_AS_SQL = 2002;
NSUInteger numColumns = [columns count];
NSIndexSet *selectedRows = [self selectedRowIndexes];
- NSString *spNULL = [prefs objectForKey:@"NullValue"];
NSMutableString *value = [NSMutableString stringWithCapacity:10];
NSArray *dbDataRow;
NSMutableArray *columnMappings;
@@ -227,8 +232,8 @@ int MENU_EDIT_COPY_AS_SQL = 2002;
{
rowData = [[tableData objectAtIndex:rowIndex] objectAtIndex:[[columnMappings objectAtIndex:c] intValue]];
- // Check for NULL value - TODO this is not safe!!
- if([[rowData description] isEqualToString:spNULL]){
+ // Check for NULL value
+ if([rowData isNSNull]) {
[value appendString:@"NULL, "];
continue;
}
@@ -243,7 +248,7 @@ int MENU_EDIT_COPY_AS_SQL = 2002;
[mySQLConnection prepareString:[rowData description]]]];
break;
case 2: // blob
- if (![[self delegate] isKindOfClass:[CustomQuery class]] && [prefs boolForKey:@"LoadBlobsAsNeeded"]) {
+ if (![[self delegate] isKindOfClass:[CustomQuery class]] && [rowData isSPNotLoaded]) {
// Abort if there are no indices on this table or if there's no table name given.
if (![[tableInstance argumentForRow:rowIndex] length] || selectedTable == nil)
@@ -253,7 +258,7 @@ int MENU_EDIT_COPY_AS_SQL = 2002;
dbDataRow = [[mySQLConnection queryString:
[NSString stringWithFormat:@"SELECT * FROM %@ WHERE %@",
[selectedTable backtickQuotedString], [tableInstance argumentForRow:rowIndex]]] fetchRowAsArray];
- if([[dbDataRow objectAtIndex:[[columnMappings objectAtIndex:c] intValue]] isKindOfClass:[NSNull class]])
+ if([[dbDataRow objectAtIndex:[[columnMappings objectAtIndex:c] intValue]] isNSNull])
[value appendString:@"NULL, "];
else
[value appendString:[NSString stringWithFormat:@"X'%@', ",
@@ -365,7 +370,12 @@ int MENU_EDIT_COPY_AS_SQL = 2002;
if ( nil != rowData )
{
- [result appendString:[NSString stringWithFormat:@"%@\t", [rowData description] ] ];
+ if ([rowData isNSNull])
+ [result appendString:[NSString stringWithFormat:@"%@\t", [prefs objectForKey:@"nullValue"]]];
+ else if ([rowData isSPNotLoaded])
+ [result appendString:[NSString stringWithFormat:@"%@\t", NSLocalizedString(@"(not loaded)", @"value shown for hidden blob and text fields")]];
+ else
+ [result appendString:[NSString stringWithFormat:@"%@\t", [rowData description] ] ];
}
else
{
diff --git a/Source/CustomQuery.m b/Source/CustomQuery.m
index f04f9fd1..0589ab6f 100644
--- a/Source/CustomQuery.m
+++ b/Source/CustomQuery.m
@@ -686,13 +686,8 @@
- (void)processResultIntoDataStorage:(MCPStreamingResult *)theResult
{
NSArray *tempRow;
- NSMutableArray *newRow;
- int i;
long rowsProcessed = 0;
- long columnsCount = 0;
NSAutoreleasePool *dataLoadingPool;
- id prefsNullValue = [[prefs objectForKey:@"NullValue"] retain];
- Class nullClass = [NSNull class];
// Remove all items from the table
[fullResult removeAllObjects];
@@ -702,16 +697,8 @@
// Loop through the result rows as they become available
while (tempRow = [theResult fetchNextRowAsArray]) {
- if (columnsCount == 0) columnsCount = [tempRow count];
NSMutableArrayAddObject(fullResult, [NSMutableArray arrayWithArray:tempRow]);
- newRow = NSArrayObjectAtIndex(fullResult, rowsProcessed);
-
- // Process the retrieved row
- for ( i = 0; i < columnsCount; i++ ) {
- if ( [NSArrayObjectAtIndex(tempRow, i) isMemberOfClass:nullClass] )
- [newRow replaceObjectAtIndex:i withObject:prefsNullValue];
- }
// Update the count of rows processed
rowsProcessed++;
@@ -725,7 +712,6 @@
// Clean up the autorelease pool
[dataLoadingPool drain];
- [prefsNullValue release];
}
/*
@@ -1182,7 +1168,7 @@
// If there is no primary key, all found fields belonging to the same table are used in the argument
for(field in columnsForFieldTableName) {
id aValue = [dataRow objectAtIndex:[[field objectForKey:@"datacolumnindex"] intValue]];
- if ([aValue isKindOfClass:[NSNull class]] || [[aValue description] isEqualToString:[prefs stringForKey:@"NullValue"]]) {
+ if ([aValue isKindOfClass:[NSNull class]] || [aValue isNSNull]) {
[fieldIDQueryStr appendFormat:@"%@ IS NULL", [[field objectForKey:@"org_name"] backtickQuotedString]];
} else {
[fieldIDQueryStr appendFormat:@"%@=", [[field objectForKey:@"org_name"] backtickQuotedString]];
@@ -1233,11 +1219,8 @@
// For NULL cell's display the user's NULL value placeholder in grey to easily distinguish it from other values
if ([cell respondsToSelector:@selector(setTextColor:)]) {
- // Note that this approach of changing the color of NULL placeholders is dependent on the cell's value matching that
- // of the user's NULL value preference which was set in the result array when it was retrieved (see fetchResultAsArray).
- // Also, as an added measure check that the table column actually allows NULLs to make sure we don't change a cell that
- // happens to have a value matching the NULL placeholder, but the column doesn't allow NULLs.
- [cell setTextColor:([[cell stringValue] isEqualToString:[prefs objectForKey:@"NullValue"]]) ? [NSColor lightGrayColor] : [NSColor blackColor]];
+ id theValue = NSArrayObjectAtIndex(NSArrayObjectAtIndex(fullResult, row), [[aTableColumn identifier] intValue]);
+ [cell setTextColor:[theValue isNSNull] ? [NSColor lightGrayColor] : [NSColor blackColor]];
}
}
@@ -1255,7 +1238,7 @@
if ( [theValue isKindOfClass:[NSData class]] )
return [theValue shortStringRepresentationUsingEncoding:[mySQLConnection encoding]];
- if ( [theValue isMemberOfClass:[NSNull class]] )
+ if ( [theValue isNSNull] )
return [prefs objectForKey:@"NullValue"];
return theValue;
@@ -1659,7 +1642,10 @@
&& [columnDefinition valueForKey:@"char_length"])
[fieldEditor setTextMaxLength:[[columnDefinition valueForKey:@"char_length"] intValue]];
- id editData = [[fieldEditor editWithObject:[[fullResult objectAtIndex:rowIndex] objectAtIndex:[[aTableColumn identifier] intValue]]
+ id originalData = [[fullResult objectAtIndex:rowIndex] objectAtIndex:[[aTableColumn identifier] intValue]];
+ if ([originalData isNSNull]) originalData = [prefs objectForKey:@"nullValue"];
+
+ id editData = [[fieldEditor editWithObject:originalData
fieldName:[columnDefinition objectForKey:@"name"]
usingEncoding:[mySQLConnection encoding]
isObjectBlob:isBlob
diff --git a/Source/SPNotLoaded.h b/Source/SPNotLoaded.h
new file mode 100644
index 00000000..e2026525
--- /dev/null
+++ b/Source/SPNotLoaded.h
@@ -0,0 +1,41 @@
+//
+// $Id$
+//
+// SPNotLoaded.h
+// sequel-pro
+//
+// Created by Rowan Beentje on 07/10/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>
+
+@interface SPNotLoaded : NSObject {
+
+}
+
++ (SPNotLoaded *) notLoaded;
+
+@end
+
+// Also provide a method for testing objects
+@interface NSObject (SPNotLoadedTest)
+
+- (BOOL) isSPNotLoaded;
+
+@end \ No newline at end of file
diff --git a/Source/SPNotLoaded.m b/Source/SPNotLoaded.m
new file mode 100644
index 00000000..f8afda1b
--- /dev/null
+++ b/Source/SPNotLoaded.m
@@ -0,0 +1,91 @@
+//
+// $Id$
+//
+// SPNotLoaded.m
+// sequel-pro
+//
+// Created by Rowan Beentje on 07/10/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 "SPNotLoaded.h"
+
+static SPNotLoaded *notLoaded = nil;
+
+@implementation SPNotLoaded
+
+// Return the singleton object
++ (SPNotLoaded *) notLoaded
+{
+ @synchronized(self) {
+ if (notLoaded == nil) {
+ [[self alloc] init];
+ }
+ }
+ return notLoaded;
+}
+
++ (id) allocWithZone:(NSZone *)zone
+{
+ @synchronized(self) {
+ if (notLoaded == nil) {
+ return [super allocWithZone:zone];
+ }
+ }
+ return notLoaded;
+}
+
+- (id) init
+{
+ Class notLoadedClass = [self class];
+ @synchronized(notLoadedClass) {
+ if (notLoaded == nil) {
+ if (self = [super init]) {
+ notLoaded = self;
+ }
+ }
+ }
+ return notLoaded;
+}
+
+- (id) copyWithZone:(NSZone *)zone { return self; }
+
+- (id) retain { return self; }
+
+- (unsigned) retainCount { return UINT_MAX; }
+
+- (void) release {}
+
+- (id) autorelease { return self; }
+
+@end
+
+
+/**
+ * This Category is intended to allow easy testing of all objects for SPNotLoaded.
+ */
+@implementation NSObject (SPNotLoadedTest)
+
+- (BOOL) isSPNotLoaded
+{
+ static id SPNotLoadedForComparison;
+ if (!SPNotLoadedForComparison) SPNotLoadedForComparison = [SPNotLoaded notLoaded];
+ return (self == SPNotLoadedForComparison);
+}
+
+@end \ No newline at end of file
diff --git a/Source/TableContent.m b/Source/TableContent.m
index 6537f574..1fa9d193 100644
--- a/Source/TableContent.m
+++ b/Source/TableContent.m
@@ -46,6 +46,7 @@
#import "SPTooltip.h"
#import "RegexKitLite.h"
#import "SPContentFilterManager.h"
+#import "SPNotLoaded.h"
@implementation TableContent
@@ -160,7 +161,7 @@
// If no table has been supplied, reset the view to a blank table and disabled elements.
// [tableDataInstance tableEncoding] == nil indicates that an error occured while retrieving table data
- if ( [[[tableDataInstance statusValues] objectForKey:@"Rows"] isKindOfClass:[NSNull class]] || [aTable isEqualToString:@""] || !aTable || [tableDataInstance tableEncoding] == nil)
+ if ( [[[tableDataInstance statusValues] objectForKey:@"Rows"] isNSNull] || [aTable isEqualToString:@""] || !aTable || [tableDataInstance tableEncoding] == nil)
{
// Empty the stored data arrays
[tableValues removeAllObjects];
@@ -851,7 +852,7 @@
for ( i = 0 ; i < [dataColumns count] ; i++ ) {
column = NSArrayObjectAtIndex(dataColumns, i);
if ([column objectForKey:@"default"] == nil || [[column objectForKey:@"default"] isEqualToString:@"NULL"]) {
- [newRow addObject:[prefs stringForKey:@"NullValue"]];
+ [newRow addObject:[NSNull null]];
} else {
[newRow addObject:[column objectForKey:@"default"]];
}
@@ -909,15 +910,9 @@
for ( i = 0 ; i < [queryResult numOfRows] ; i++ ) {
row = [queryResult fetchRowAsDictionary];
if ( [[row objectForKey:@"Extra"] isEqualToString:@"auto_increment"] ) {
- [tempRow replaceObjectAtIndex:i withObject:[prefs stringForKey:@"NullValue"]];
+ [tempRow replaceObjectAtIndex:i withObject:[NSNull null]];
} else if ( [tableDataInstance columnIsBlobOrText:[row objectForKey:@"Field"]] && [prefs boolForKey:@"LoadBlobsAsNeeded"] && dbDataRow) {
- NSString *valueString = nil;
- //if what we read from DB is NULL (NSNull), we replace it with the string NULL
- if([[dbDataRow objectAtIndex:i] isKindOfClass:[NSNull class]])
- valueString = [prefs objectForKey:@"NullValue"];
- else
- valueString = [dbDataRow objectAtIndex:i];
- [tempRow replaceObjectAtIndex:i withObject:valueString];
+ [tempRow replaceObjectAtIndex:i withObject:[dbDataRow objectAtIndex:i]];
}
}
@@ -1005,8 +1000,10 @@
enumerator = [tableColumns objectEnumerator];
while ( (tableColumn = [enumerator nextObject]) ) {
id o = [NSArrayObjectAtIndex(tableValues, i) objectAtIndex:[[tableColumn identifier] intValue]];
- if([o isKindOfClass:[NSNull class]])
+ if([o isNSNull])
[tempRow addObject:@"NULL"];
+ else if ([o isSPNotLoaded])
+ [tempRow addObject:NSLocalizedString(@"(not loaded)", @"value shown for hidden blob and text fields")];
else if([o isKindOfClass:[NSString class]])
[tempRow addObject:[o description]];
else {
@@ -1117,7 +1114,7 @@
NSDictionary *filterSettings = [NSDictionary dictionaryWithObjectsAndKeys:
[refDictionary objectForKey:@"column"], @"filterField",
targetFilterValue, @"filterValue",
- ([targetFilterValue isEqualToString:[prefs objectForKey:@"NullValue"]]?@"IS NULL":nil), @"filterComparison",
+ ([targetFilterValue isNSNull]?@"IS NULL":nil), @"filterComparison",
nil];
[self setFiltersToRestore:filterSettings];
@@ -1317,9 +1314,7 @@
NSAutoreleasePool *dataLoadingPool;
NSProgressIndicator *dataLoadingIndicator = [tableDocumentInstance valueForKey:@"queryProgressBar"];
- id prefsNullValue = [[prefs objectForKey:@"NullValue"] retain];
BOOL prefsLoadBlobsAsNeeded = [prefs boolForKey:@"LoadBlobsAsNeeded"];
- Class nullClass = [NSNull class];
// Build up an array of which columns are blobs for faster iteration
for ( i = 0; i < columnsCount ; i++ ) {
@@ -1337,25 +1332,22 @@
// Loop through the result rows as they become available
while (tempRow = [theResult fetchNextRowAsArray]) {
- NSMutableArrayAddObject(tableValues, [NSMutableArray arrayWithCapacity:columnsCount]);
- newRow = NSArrayObjectAtIndex(tableValues, rowsProcessed);
-
- // Process the retrieved row
- for ( i = 0; i < columnsCount; i++ ) {
- if ( [NSArrayObjectAtIndex(tempRow, i) isMemberOfClass:nullClass] ) {
- NSMutableArrayAddObject(newRow, prefsNullValue);
- } else {
- NSMutableArrayAddObject(newRow, NSArrayObjectAtIndex(tempRow, i));
- }
- }
// Add values for hidden blob and text fields if appropriate
if ( prefsLoadBlobsAsNeeded ) {
+ NSMutableArrayAddObject(tableValues, [NSMutableArray arrayWithCapacity:columnsCount]);
+ newRow = NSArrayObjectAtIndex(tableValues, rowsProcessed);
for ( i = 0 ; i < columnsCount ; i++ ) {
if ( [NSArrayObjectAtIndex(columnBlobStatuses, i) boolValue] ) {
- [newRow replaceObjectAtIndex:i withObject:NSLocalizedString(@"(not loaded)", @"value shown for hidden blob and text fields")];
+ [newRow addObject:[SPNotLoaded notLoaded]];
+ } else {
+ NSMutableArrayAddObject(newRow, NSArrayObjectAtIndex(tempRow, i));
}
}
+
+ // Otherwise just add the new row
+ } else {
+ NSMutableArrayAddObject(tableValues, [NSMutableArray arrayWithArray:tempRow]);
}
// Update the progress bar as necessary, minimising updates
@@ -1382,7 +1374,6 @@
[dataLoadingIndicator setIndeterminate:YES];
[columnBlobStatuses release];
- [prefsNullValue release];
}
@@ -1419,11 +1410,10 @@
for ( i = 0 ; i < [dataColumns count] ; i++ ) {
rowObject = [NSArrayObjectAtIndex(tableValues, currentlyEditingRow) objectAtIndex:i];
- // Add (not loaded) placeholders directly for easy comparsion when added
- if (prefsLoadBlobsAsNeeded && !isEditingNewRow
- && [rowObject isEqualToString:NSLocalizedString(@"(not loaded)", @"value shown for hidden blob and text fields")])
+ // Add not loaded placeholders directly for easy comparison when added
+ if (prefsLoadBlobsAsNeeded && !isEditingNewRow && [rowObject isSPNotLoaded])
{
- [fieldValues addObject:[NSString stringWithString:rowObject]];
+ [fieldValues addObject:[SPNotLoaded notLoaded]];
continue;
// Catch CURRENT_TIMESTAMP automatic updates - if the row is new and the cell value matches
@@ -1435,7 +1425,7 @@
[rowValue setString:@"CURRENT_TIMESTAMP"];
// Convert the object to a string (here we can add special treatment for date-, number- and data-fields)
- } else if ( [[rowObject description] isEqualToString:[prefs stringForKey:@"NullValue"]]
+ } else if ( [rowObject isNSNull]
|| ([rowObject isMemberOfClass:[NSString class]] && [[rowObject description] isEqualToString:@""]) ) {
//NULL when user entered the nullValue string defined in the prefs or when a number field isn't set
@@ -1467,7 +1457,7 @@
[fieldValues addObject:[NSString stringWithString:rowValue]];
}
- // Use INSERT syntax when creating new rows - no need to do (not loaded) checking, as all values have been entered
+ // Use INSERT syntax when creating new rows - no need to do not loaded checking, as all values have been entered
if ( isEditingNewRow ) {
queryString = [NSString stringWithFormat:@"INSERT INTO %@ (%@) VALUES (%@)",
[selectedTable backtickQuotedString], [[tableDataInstance columnNames] componentsJoinedAndBacktickQuoted], [fieldValues componentsJoinedByString:@","]];
@@ -1479,7 +1469,7 @@
for ( i = 0 ; i < [dataColumns count] ; i++ ) {
// If data column loading is deferred and the value is the not loaded string, skip this cell
- if (prefsLoadBlobsAsNeeded && [[fieldValues objectAtIndex:i] isEqualToString:NSLocalizedString(@"(not loaded)", @"value shown for hidden blob and text fields")]) continue;
+ if (prefsLoadBlobsAsNeeded && [[fieldValues objectAtIndex:i] isSPNotLoaded]) continue;
if (firstCellOutput) [queryString appendString:@", "];
else firstCellOutput = YES;
@@ -1653,7 +1643,7 @@
[value setString:[tempValue description]];
}
- if ( [value isEqualToString:[prefs stringForKey:@"NullValue"]] ) {
+ if ( [value isNSNull] ) {
[argument appendString:[NSString stringWithFormat:@"%@ IS NULL", [NSArrayObjectAtIndex(keys, i) backtickQuotedString]]];
} else {
@@ -2100,14 +2090,23 @@
if ([theValue isKindOfClass:[NSData class]])
return [theValue shortStringRepresentationUsingEncoding:[mySQLConnection encoding]];
+ if ([theValue isNSNull])
+ return [prefs objectForKey:@"NullValue"];
+
+ if ([theValue isSPNotLoaded])
+ return NSLocalizedString(@"(not loaded)", @"value shown for hidden blob and text fields");
+
return theValue;
}
/**
- * This function changes the text color of text/blob fields which are not yet loaded to gray
+ * This function changes the text color of text/blob fields which are null or not yet loaded to gray
*/
- (void)tableView:(CMCopyTable *)aTableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn*)aTableColumn row:(int)row
{
+
+ if (![cell respondsToSelector:@selector(setTextColor:)]) return;
+
// 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] intValue] && [aTableView editedRow] == row) {
@@ -2115,46 +2114,15 @@
return;
}
- NSDictionary *column = NSArrayObjectAtIndex(dataColumns, [[aTableColumn identifier] intValue]);
+ id theValue = NSArrayObjectAtIndex(NSArrayObjectAtIndex(tableValues, row), [[aTableColumn identifier] intValue]);
- // For NULL cell's display the user's NULL value placeholder in grey to easily distinguish it from other values
- if ([cell respondsToSelector:@selector(setTextColor:)]) {
-
- // Note that this approach of changing the color of NULL placeholders is dependent on the cell's value matching that
- // of the user's NULL value preference which was set in the result array when it was retrieved (see fetchResultAsArray).
- // Also, as an added measure check that the table column actually allows NULLs to make sure we don't change a cell that
- // happens to have a value matching the NULL placeholder, but the column doesn't allow NULLs.
- [cell setTextColor:([[cell stringValue] isEqualToString:[prefs objectForKey:@"NullValue"]] && [[column objectForKey:@"null"] boolValue]) ? [NSColor lightGrayColor] : [NSColor blackColor]];
- }
+ // For null cells and not loaded cells, display the contents in gray.
+ if ([theValue isNSNull] || [theValue isSPNotLoaded]) {
+ [cell setTextColor:[NSColor lightGrayColor]];
- // Check if loading of text/blob fields is disabled
- // If not, all text fields are loaded and we don't have to make them gray
- if ([prefs boolForKey:@"LoadBlobsAsNeeded"])
- {
- // Make sure that the cell actually responds to setTextColor:
- // In the future, we might use different cells for the table view
- // that don't support this selector
- if ([cell respondsToSelector:@selector(setTextColor:)])
- {
- // Test if the current column is a text or a blob field
- NSString *columnTypeGrouping = [column objectForKey:@"typegrouping"];
-
- if ([columnTypeGrouping isEqualToString:@"textdata"] || [columnTypeGrouping isEqualToString:@"blobdata"]) {
-
- // now check if the field has been loaded already or not
- if ([[cell stringValue] isEqualToString:NSLocalizedString(@"(not loaded)", @"value shown for hidden blob and text fields")])
- {
- // change the text color of the cell to gray
- [cell setTextColor:[NSColor lightGrayColor]];
- }
- else {
- // Change the text color back to black
- // This is necessary because NSTableView reuses
- // the NSCell to draw further rows in the column
- [cell setTextColor:([[cell stringValue] isEqualToString:[prefs objectForKey:@"NullValue"]] && [[column objectForKey:@"null"] boolValue]) ? [NSColor lightGrayColor] : [NSColor blackColor]];
- }
- }
- }
+ // Otherwise, set the color to black - required as NSTableView reuses NSCells.
+ } else {
+ [cell setTextColor:[NSColor blackColor]];
}
}
@@ -2167,12 +2135,19 @@
isEditingRow = YES;
currentlyEditingRow = rowIndex;
}
+
+ NSDictionary *column = NSArrayObjectAtIndex(dataColumns, [[aTableColumn identifier] intValue]);
- if (anObject)
+ if (anObject) {
+
+ // Restore NULLs if necessary
+ if ([anObject isEqualToString:[prefs objectForKey:@"NullValue"]] && [[column objectForKey:@"null"] boolValue])
+ anObject = [NSNull null];
+
[NSArrayObjectAtIndex(tableValues, rowIndex) replaceObjectAtIndex:[[aTableColumn identifier] intValue] withObject:anObject];
- else
+ } else {
[NSArrayObjectAtIndex(tableValues, rowIndex) replaceObjectAtIndex:[[aTableColumn identifier] intValue] withObject:@""];
-
+ }
}
#pragma mark -
@@ -2291,39 +2266,26 @@
*/
- (BOOL)tableView:(NSTableView *)aTableView shouldEditTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
- NSUInteger i;
- // If the preference value for not showing blobs is set, check whether the row contains any blobs.
- if ([prefs boolForKey:@"LoadBlobsAsNeeded"]) {
+ // If the selected cell hasn't been loaded, load it.
+ if ([NSArrayObjectAtIndex(NSArrayObjectAtIndex(tableValues, rowIndex), [[aTableColumn identifier] intValue]) isSPNotLoaded]) {
- // If the table does contain blob or text fields, load the values ready for editing.
- if ([self tableContainsBlobOrTextColumns]) {
- NSString *wherePart = [NSString stringWithString:[self argumentForRow:[tableContentView selectedRow]]];
-
- if ([wherePart length] == 0) return NO;
-
- // 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];
-
- MCPResult *tempResult = [mySQLConnection queryString:query];
-
- if (![tempResult numOfRows]) {
- NSBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil,
- NSLocalizedString(@"Couldn't load the row. Reload the table to be sure that the row exists and use a primary key for your table.", @"message of panel when loading of row failed"));
- return NO;
- }
-
- NSArray *tempRow = [tempResult fetchRowAsArray];
- NSMutableArray *modifiedRow = [NSMutableArray array];
-
- for (i = 0; i < [tempRow count]; i++)
- {
- [modifiedRow addObject:([[tempRow objectAtIndex:i] isMemberOfClass:[NSNull class]]) ? [prefs stringForKey:@"NullValue"] : [tempRow objectAtIndex:i]];
- }
-
- [[tableValues objectAtIndex:rowIndex] replaceObjectAtIndex:[[tableContentView tableColumns] indexOfObject:aTableColumn] withObject:[modifiedRow objectAtIndex:0]];
- [tableContentView reloadData];
+ NSString *wherePart = [NSString stringWithString:[self argumentForRow:[tableContentView selectedRow]]];
+ if ([wherePart length] == 0) return NO;
+
+ // 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];
+
+ MCPResult *tempResult = [mySQLConnection queryString:query];
+ if (![tempResult numOfRows]) {
+ NSBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil,
+ NSLocalizedString(@"Couldn't load the row. Reload the table to be sure that the row exists and use a primary key for your table.", @"message of panel when loading of row failed"));
+ return NO;
}
+
+ NSArray *tempRow = [tempResult fetchRowAsArray];
+ [[tableValues objectAtIndex:rowIndex] replaceObjectAtIndex:[[tableContentView tableColumns] indexOfObject:aTableColumn] withObject:[tempRow objectAtIndex:0]];
+ [tableContentView reloadData];
}
BOOL isBlob = [tableDataInstance columnIsBlobOrText:[[aTableColumn headerCell] stringValue]];
@@ -2335,7 +2297,10 @@
[fieldEditor setTextMaxLength:[[[aTableColumn dataCellForRow:rowIndex] formatter] textLimit]];
- id editData = [[fieldEditor editWithObject:[[tableValues objectAtIndex:rowIndex] objectAtIndex:[[aTableColumn identifier] intValue]]
+ id cellValue = [[tableValues objectAtIndex:rowIndex] objectAtIndex:[[aTableColumn identifier] intValue]];
+ if ([cellValue isNSNull]) cellValue = [NSString stringWithString:[prefs objectForKey:@"nullValue"]];
+
+ id editData = [[fieldEditor editWithObject:cellValue
fieldName:[[aTableColumn headerCell] stringValue]
usingEncoding:[mySQLConnection encoding]
isObjectBlob:isBlob
@@ -2348,8 +2313,14 @@
isEditingRow = YES;
currentlyEditingRow = rowIndex;
}
-
- [[tableValues objectAtIndex:rowIndex] replaceObjectAtIndex:[[aTableColumn identifier] intValue] withObject:[editData copy]];
+
+ if ([editData isEqualToString:[prefs objectForKey:@"NullValue"]]
+ && [[NSArrayObjectAtIndex(dataColumns, [[aTableColumn identifier] intValue]) objectForKey:@"null"] boolValue])
+ {
+ [editData release];
+ editData = [[NSNull null] retain];
+ }
+ [[tableValues objectAtIndex:rowIndex] replaceObjectAtIndex:[[aTableColumn identifier] intValue] withObject:[[editData copy] autorelease]];
}
[fieldEditor release];
diff --git a/sequel-pro.xcodeproj/project.pbxproj b/sequel-pro.xcodeproj/project.pbxproj
index d678b0bb..adf4032a 100644
--- a/sequel-pro.xcodeproj/project.pbxproj
+++ b/sequel-pro.xcodeproj/project.pbxproj
@@ -141,6 +141,7 @@
5822C9B51000DB2400DCC3D6 /* SPConnectionController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5822C9B41000DB2400DCC3D6 /* SPConnectionController.m */; };
5822CAE110011C8000DCC3D6 /* ConnectionView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5822CADF10011C8000DCC3D6 /* ConnectionView.xib */; };
5822D3091061833C00CE2157 /* SPCSVParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 5822D3081061833C00CE2157 /* SPCSVParser.m */; };
+ 582A01E9107C0C170027D42B /* SPNotLoaded.m in Sources */ = {isa = PBXBuildFile; fileRef = 582A01E8107C0C170027D42B /* SPNotLoaded.m */; };
583B77D4103870C800B21F7E /* MCPStreamingResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 583B779810386B0200B21F7E /* MCPStreamingResult.m */; };
5841423F0F97E11000A34B47 /* NoodleLineNumberView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5841423E0F97E11000A34B47 /* NoodleLineNumberView.m */; };
584192A1101E57BB0089807F /* NSMutableArray-MultipleSort.m in Sources */ = {isa = PBXBuildFile; fileRef = 584192A0101E57BB0089807F /* NSMutableArray-MultipleSort.m */; };
@@ -523,6 +524,8 @@
5822CAE010011C8000DCC3D6 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/ConnectionView.xib; sourceTree = "<group>"; };
5822D3071061833C00CE2157 /* SPCSVParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPCSVParser.h; sourceTree = "<group>"; };
5822D3081061833C00CE2157 /* SPCSVParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPCSVParser.m; sourceTree = "<group>"; };
+ 582A01E7107C0C170027D42B /* SPNotLoaded.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPNotLoaded.h; sourceTree = "<group>"; };
+ 582A01E8107C0C170027D42B /* SPNotLoaded.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPNotLoaded.m; sourceTree = "<group>"; };
583B779710386B0200B21F7E /* MCPStreamingResult.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.c.h; fileEncoding = 4; path = MCPStreamingResult.h; sourceTree = "<group>"; };
583B779810386B0200B21F7E /* MCPStreamingResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MCPStreamingResult.m; sourceTree = "<group>"; };
5841423D0F97E11000A34B47 /* NoodleLineNumberView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NoodleLineNumberView.h; sourceTree = "<group>"; };
@@ -1349,6 +1352,8 @@
B57747D80F7A8990003B34F9 /* SPWindowAdditions.m */,
BC2C16D20FEBEDF10003993B /* SPDataAdditions.h */,
BC2C16D30FEBEDF10003993B /* SPDataAdditions.m */,
+ 582A01E7107C0C170027D42B /* SPNotLoaded.h */,
+ 582A01E8107C0C170027D42B /* SPNotLoaded.m */,
);
name = "Category Additions";
sourceTree = "<group>";
@@ -1773,6 +1778,7 @@
5822D3091061833C00CE2157 /* SPCSVParser.m in Sources */,
BC675A141072039C00C5ACD4 /* SPContentFilterManager.m in Sources */,
17292443107AC41000B21980 /* SPXMLExporter.m in Sources */,
+ 582A01E9107C0C170027D42B /* SPNotLoaded.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};