aboutsummaryrefslogtreecommitdiffstats
path: root/Source
diff options
context:
space:
mode:
Diffstat (limited to 'Source')
-rw-r--r--Source/CustomQuery.h2
-rw-r--r--Source/CustomQuery.m18
-rw-r--r--Source/SPDataCellFormatter.h31
-rw-r--r--Source/SPDataCellFormatter.m57
-rw-r--r--Source/SPSQLParser.h233
-rw-r--r--Source/SPSQLParser.m655
-rw-r--r--Source/SPTableData.h54
-rw-r--r--Source/SPTableData.m509
-rw-r--r--Source/SPTableInfo.h1
-rw-r--r--Source/SPTableInfo.m60
-rw-r--r--Source/TableContent.h7
-rw-r--r--Source/TableContent.m599
-rw-r--r--Source/TableDocument.h7
-rw-r--r--Source/TableDocument.m69
-rw-r--r--Source/TableDump.h4
-rw-r--r--Source/TableDump.m181
-rw-r--r--Source/TableSource.h1
-rw-r--r--Source/TableSource.m106
-rw-r--r--Source/TableStatus.h2
-rw-r--r--Source/TableStatus.m83
-rw-r--r--Source/TablesList.h3
-rw-r--r--Source/TablesList.m38
22 files changed, 2091 insertions, 629 deletions
diff --git a/Source/CustomQuery.h b/Source/CustomQuery.h
index e873d833..61dbc7b8 100644
--- a/Source/CustomQuery.h
+++ b/Source/CustomQuery.h
@@ -31,8 +31,6 @@
@interface CustomQuery : NSObject {
- IBOutlet id tableDumpInstance;
-
IBOutlet id tableWindow;
IBOutlet id queryFavoritesButton;
IBOutlet id queryHistoryButton;
diff --git a/Source/CustomQuery.m b/Source/CustomQuery.m
index e5f48e7b..7936e962 100644
--- a/Source/CustomQuery.m
+++ b/Source/CustomQuery.m
@@ -23,7 +23,7 @@
// Or mail to <lorenz@textor.ch>
#import "CustomQuery.h"
-#import "TableDump.h"
+#import "SPSQLParser.h"
#import "SPGrowlController.h"
@implementation CustomQuery
@@ -45,23 +45,26 @@ sets the tableView columns corresponding to the mysql-result
NSTableColumn *theCol;
CMMCPResult *theResult = nil;
NSArray *queries;
-// NSArray *theTypes;
NSMutableArray *menuItems = [NSMutableArray array];
NSMutableArray *tempResult = [NSMutableArray array];
NSMutableString *errors = [NSMutableString string];
+ SPSQLParser *queryParser;
int i;
- //query started
+ // Notify listeners that a query has started
[[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:self];
- //split queries by ;'s
- queries = [tableDumpInstance splitQueries:[textView string]];
+ // Retrieve the custom query string and split it into separate SQL queries
+ queryParser = [[SPSQLParser alloc] initWithString:[textView string]];
+ queries = [queryParser splitStringByCharacter:';'];
+ [queryParser release];
-//perform queries
+ // Perform the queries in series
for ( i = 0 ; i < [queries count] ; i++ ) {
theResult = [mySQLConnection queryString:[queries objectAtIndex:i]];
if ( ![[mySQLConnection getLastErrorMessage] isEqualToString:@""] ) {
- //query gave error
+
+ // If the query errored, append error to the error log for display at the end
if ( [queries count] > 1 ) {
[errors appendString:[NSString stringWithFormat:NSLocalizedString(@"[ERROR in query %d] %@\n", @"error text when multiple custom query failed"),
i+1,
@@ -70,7 +73,6 @@ sets the tableView columns corresponding to the mysql-result
[errors setString:[mySQLConnection getLastErrorMessage]];
}
}
-// theTypes = [queryResult fetchTypesAsArray];
}
//perform empty query if no query is given
diff --git a/Source/SPDataCellFormatter.h b/Source/SPDataCellFormatter.h
new file mode 100644
index 00000000..aafb1b1c
--- /dev/null
+++ b/Source/SPDataCellFormatter.h
@@ -0,0 +1,31 @@
+//
+// SPDataCell.h
+// sequel-pro
+//
+// Created by Rowan Beentje on 11/02/2009.
+// Copyright 2009 Arboreal. 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 SPDataCellFormatter : NSFormatter {
+
+}
+
+@end
diff --git a/Source/SPDataCellFormatter.m b/Source/SPDataCellFormatter.m
new file mode 100644
index 00000000..c23fccf5
--- /dev/null
+++ b/Source/SPDataCellFormatter.m
@@ -0,0 +1,57 @@
+//
+// SPDataCell.m
+// sequel-pro
+//
+// Created by Rowan Beentje on 11/02/2009.
+// Copyright 2009 Arboreal. 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 "SPDataCellFormatter.h"
+
+
+@implementation SPDataCellFormatter
+
+- (NSString *)stringForObjectValue:(id)anObject
+{
+
+ // Truncate the string for speed purposes if it's very long - improves table scrolling speed.
+ if ([anObject length] > 150) {
+ return ([NSString stringWithFormat:@"%@...", [anObject substringToIndex:147]]);
+ }
+
+ return anObject;
+}
+
+// Always provide the full string when editing
+- (NSString *)editingStringForObjectValue:(id)anObject
+{
+ return anObject;
+}
+
+- (BOOL) getObjectValue:(id*) object forString:(NSString*) string errorDescription:(NSString**) error
+{
+ *object = string;
+ return YES;
+}
+
+- (NSAttributedString *)attributedStringForObjectValue:(id)anObject withDefaultAttributes:(NSDictionary *)attributes
+{
+ return [[[NSAttributedString alloc] initWithString:[self stringForObjectValue:anObject] attributes:attributes] autorelease];
+}
+
+@end
diff --git a/Source/SPSQLParser.h b/Source/SPSQLParser.h
new file mode 100644
index 00000000..14ac6f9d
--- /dev/null
+++ b/Source/SPSQLParser.h
@@ -0,0 +1,233 @@
+//
+// SPSQLParsing.h
+// sequel-pro
+//
+// Created by Rowan Beentje on 18/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 string class intended for SQL parsing. It extends NSMutableString,
+ * with the intention that as a string is parsed the parsed content is removed. This also
+ * allows parsing to occur in "streaming" mode, with parseable content being pulled off the
+ * start of the string as additional content is appended onto the end of the string, eg from
+ * a file.
+ *
+ * While some methods may look similar to NSScanner methods, and others look like they could be
+ * achieved with Regex libraries or other string parsing libraries, this class was written with
+ * the following goals in mind:
+ * - SQL comments, in "/* ... * /", "#" and "--[\s]" form, are ignored automatically while parsing -
+ *but* are left in the strings in question, to allow (for example) MySQL-version specific query
+ support, eg /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT * /
+ * - Support for quoted strings in most commands, allowing strings quoted with ", ', and ` characters -
+ including support for \-escaping of the quote characters within "- and '-terminated strings.
+ * - Optional support for bracket capturing in most commands. This can allow simpler parsing of strings
+ which also contain subqueries, enums, definitions or groups.
+ * - Speed should remain high even on large strings due to specific context awareness (ie no reliance
+ * on complex lookaheads or lookbehinds to achieve the above).
+ *
+ * It is anticipated that characterAtIndex: is currently the parsing weak point, and that in future
+ * this class could be further optimised by working with the underlying object/characters directly.
+ * This class could also be improved by maintaining an internal parsedTo number to allow streaming
+ * processing to occur without repetition.
+ */
+
+@interface SPSQLParser : NSMutableString
+{
+ id string;
+}
+
+
+typedef enum _SPCommentTypes {
+ SPHashComment = 0,
+ SPDoubleDashComment = 1,
+ SPCStyleComment = 2
+} SPCommentType;
+
+
+/*
+ * Removes comments within the current string, trimming "#", "--[/s]", and "/* * /" style strings.
+ */
+- (void) deleteComments;
+
+/*
+ * Removes quotes surrounding the string if present, and un-escapes internal occurrences of the quote character,
+ * before returning the resulting string.
+ * If no quotes surround the current string, return the entire string; if the current string contains several
+ * quoted strings, the first will be returned.
+ */
+- (NSString *) unquotedString;
+
+/*
+ * Removes characters from the string up to the first occurrence of the supplied character.
+ * "inclusively" controls whether the supplied character is also removed.
+ * Quoted strings are automatically ignored when looking for the character.
+ * SQL comments are automatically ignored when looking for the character.
+ * Returns YES if this caused the string to be shortened, or NO if the character was not encountered.
+ */
+- (BOOL) trimToCharacter:(unichar)character inclusively:(BOOL)inclusive;
+
+/*
+ * As trimToCharacter: ..., but allows control over whether characters within quoted strings
+ * are ignored.
+ */
+- (BOOL) trimToCharacter:(unichar)character inclusively:(BOOL)inclusive ignoringQuotedStrings:(BOOL)ignoreQuotedStrings;
+
+/*
+ * Returns an NSString containing characters from the string up to the first occurrence of the supplied character.
+ * "inclusively" controls whether the supplied character is also returned.
+ * Quoted strings are automatically ignored when looking for the character.
+ * SQL comments are automatically ignored when looking for the character.
+ * If the character is not found, nil is returned.
+ */
+- (NSString *) stringToCharacter:(unichar)character inclusively:(BOOL)inclusive;
+
+/*
+ * As stringToCharacter: ..., but allows control over whether characters within quoted strings
+ * are ignored.
+ */
+- (NSString *) stringToCharacter:(unichar)character inclusively:(BOOL)inclusive ignoringQuotedStrings:(BOOL)ignoreQuotedStrings;
+
+/*
+ * Returns an NSString containing characters from the string up to the first occurrence of the supplied
+ * character, also removing them from the string. "trimmingInclusively" controls whether or not the
+ * supplied character is removed from the string on a successful match, while "returningInclusively"
+ * controls whether it is included in the returned string.
+ * Quoted strings are automatically ignored when looking for the characters.
+ * SQL comments are automatically ignored when looking for the characters.
+ * If the character is not found, nil is returned.
+ */
+- (NSString *) trimAndReturnStringToCharacter:(unichar)character trimmingInclusively:(BOOL)inclusiveTrim returningInclusively:(BOOL)inclusiveReturn;
+
+/*
+ * As trimAndReturnStringToCharacter: ..., but allows control over whether characters within quoted
+ * strings are ignored.
+ */
+- (NSString *) trimAndReturnStringToCharacter:(unichar)character trimmingInclusively:(BOOL)inclusiveTrim returningInclusively:(BOOL)inclusiveReturn ignoringQuotedStrings:(BOOL)ignoreQuotedStrings;
+
+/*
+ * Returns characters from the string up to and from the first occurrence of the supplied opening character
+ * to the appropriate occurrence of the supplied closing character. "inclusively" controls whether the supplied
+ * characters should also be returned.
+ * Quoted strings are automatically ignored when looking for the characters.
+ * SQL comments are automatically ignored when looking for the characters.
+ * Returns nil if no valid matching string can be found.
+ */
+- (NSString *) stringFromCharacter:(unichar)fromCharacter toCharacter:(unichar)toCharacter inclusively:(BOOL)inclusive;
+
+/*
+ * As stringFromCharacter: toCharacter: ..., but allows control over whether to skip
+ * over bracket-enclosed characters, as in subqueries, enums, definitions or groups
+ */
+- (NSString *) stringFromCharacter:(unichar)fromCharacter toCharacter:(unichar)toCharacter inclusively:(BOOL)inclusive skippingBrackets:(BOOL)skipBrackets;
+
+/*
+ * As stringFromCharacter: toCharacter: ..., but allows control over whether characters within quoted
+ * strings are ignored.
+ */
+- (NSString *) stringFromCharacter:(unichar)fromCharacter toCharacter:(unichar)toCharacter inclusively:(BOOL)inclusive ignoringQuotedStrings:(BOOL)ignoreQuotedStrings;
+
+/*
+ * As stringFromCharacter: toCharacter: ..., but allows control over both bracketing and quoting.
+ */
+- (NSString *) stringFromCharacter:(unichar)fromCharacter toCharacter:(unichar)toCharacter inclusively:(BOOL)inclusive skippingBrackets:(BOOL)skipBrackets ignoringQuotedStrings:(BOOL)ignoreQuotedStrings;
+
+/*
+ * As stringFromCharacter: toCharacter: ..., but also trims the string up to the "to" character and
+ * up to or including the "from" character, depending on whether "trimmingInclusively" is set.
+ * "returningInclusively" controls whether the supplied characters should also be returned.
+ * Returns nil if no valid matching string can be found.
+ */
+- (NSString *) trimAndReturnStringFromCharacter:(unichar)fromCharacter toCharacter:(unichar)toCharacter trimmingInclusively:(BOOL)inclusiveTrim returningInclusively:(BOOL)inclusiveReturn;
+
+/*
+ * As trimAndReturnStringFromCharacter: toCharacter: ..., but allows control over whether to
+ * skip over bracket-enclosed characters, as in subqueries, enums, definitions or groups.
+ */
+- (NSString *) trimAndReturnStringFromCharacter:(unichar)fromCharacter toCharacter:(unichar)toCharacter trimmingInclusively:(BOOL)inclusiveTrim returningInclusively:(BOOL)inclusiveReturn skippingBrackets:(BOOL)skipBrackets;
+
+/*
+ * As trimAndReturnStringFromCharacter: toCharacter: ..., but allows control over whether characters
+ * within quoted strings are ignored.
+ */
+- (NSString *) trimAndReturnStringFromCharacter:(unichar)fromCharacter toCharacter:(unichar)toCharacter trimmingInclusively:(BOOL)inclusiveTrim returningInclusively:(BOOL)inclusiveReturn ignoringQuotedStrings:(BOOL)ignoreQuotedStrings;
+
+/*
+ * As trimAndReturnStringFromCharacter: toCharacter: ..., but allows control over both bracketing
+ * and quoting.
+ */
+- (NSString *) trimAndReturnStringFromCharacter:(unichar)fromCharacter toCharacter:(unichar)toCharacter trimmingInclusively:(BOOL)inclusiveTrim returningInclusively:(BOOL)inclusiveReturn skippingBrackets:(BOOL)skipBrackets ignoringQuotedStrings:(BOOL)ignoreQuotedStrings;
+
+/*
+ * Split a string on the boundaries formed by the supplied character, returning an array of strings.
+ * Quoted strings are automatically ignored when looking for the characters.
+ * SQL comments are automatically ignored when looking for the characters.
+ * Returns an array with one element containing the entire string if the supplied character is not found.
+ */
+- (NSArray *) splitStringByCharacter:(unichar)character;
+
+/*
+ * As splitStringByCharacter: ..., but allows control over whether to skip over bracket-enclosed
+ * characters, as in subqueries, enums, definitions or groups.
+ */
+- (NSArray *) splitStringByCharacter:(unichar)character skippingBrackets:(BOOL)skipBrackets;
+
+/*
+ * As splitStringByCharacter:, but allows control over whether characters
+ * within quoted strings are ignored.
+ */
+- (NSArray *) splitStringByCharacter:(unichar)character ignoringQuotedStrings:(BOOL)ignoreQuotedStrings;
+
+/*
+ * As splitStringByCharacter: ..., but allows control over both bracketing and quoting.
+ */
+- (NSArray *) splitStringByCharacter:(unichar)character skippingBrackets:(BOOL)skipBrackets ignoringQuotedStrings:(BOOL)ignoreQuotedStrings;
+
+/*
+ * Methods used internally by this class to power the methods above:
+ */
+- (long) firstOccurrenceOfCharacter:(unichar)character ignoringQuotedStrings:(BOOL)ignoreQuotedStrings;
+- (long) firstOccurrenceOfCharacter:(unichar)character afterIndex:(long)startIndex ignoringQuotedStrings:(BOOL)ignoreQuotedStrings;
+- (long) firstOccurrenceOfCharacter:(unichar)character afterIndex:(long)startIndex skippingBrackets:(BOOL)skipBrackets ignoringQuotedStrings:(BOOL)ignoreQuotedStrings ;
+- (long) endIndexOfStringQuotedByCharacter:(unichar)quoteCharacter startingAtIndex:(long)index;
+- (long) endIndexOfCommentOfType:(SPCommentType)commentType startingAtIndex:(long)index;
+
+
+/* Required and primitive methods to allow subclassing class cluster */
+#pragma mark -
+- (id) init;
+- (id) initWithBytes:(const void *)bytes length:(unsigned int)length encoding:(NSStringEncoding)encoding;
+- (id) initWithBytesNoCopy:(void *)bytes length:(unsigned int)length encoding:(NSStringEncoding)encoding freeWhenDone:(BOOL)flag;
+- (id) initWithCapacity:(unsigned int)capacity;
+- (id) initWithCharactersNoCopy:(unichar *)chars length:(unsigned int)length freeWhenDone:(BOOL)flag;
+- (id) initWithContentsOfFile:(id)path;
+- (id) initWithContentsOfFile:(NSString *)path encoding:(NSStringEncoding)enc error:(NSError **)error;
+- (id) initWithCString:(const char *)nullTerminatedCString encoding:(NSStringEncoding)encoding;
+- (id) initWithFormat:(NSString *)format, ...;
+- (id) initWithFormat:(NSString *)format arguments:(va_list)argList;
+- (unsigned int) length;
+- (unichar) characterAtIndex:(unsigned int)index;
+- (id) description;
+- (unsigned int) replaceOccurrencesOfString:(NSString *)target withString:(NSString *)replacement options:(unsigned)opts range:(NSRange)searchRange;
+- (void) setString:(NSString *)string;
+- (void) replaceCharactersInRange:(NSRange)range withString:(NSString *)string;
+- (void) dealloc;
+@end \ No newline at end of file
diff --git a/Source/SPSQLParser.m b/Source/SPSQLParser.m
new file mode 100644
index 00000000..9828f529
--- /dev/null
+++ b/Source/SPSQLParser.m
@@ -0,0 +1,655 @@
+//
+// SPSQLParsing.m
+// sequel-pro
+//
+// Created by Rowan Beentje on 18/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 "SPSQLParser.h"
+
+/*
+ * Please see the header files for a general description of the purpose of this class,
+ * and increased overview detail for the functions below.
+ */
+@implementation SPSQLParser : NSMutableString
+
+
+
+/*
+ * Removes comments within the current string, trimming "#", "--[/s]", and "/* * /" style strings.
+ */
+- (void) deleteComments
+{
+ long currentStringIndex, commentEndIndex, quotedStringEndIndex;
+ unichar currentCharacter;
+ long stringLength = [string length];
+
+ // Walk along the string, processing characters.
+ for (currentStringIndex = 0; currentStringIndex < stringLength; currentStringIndex++) {
+ currentCharacter = [string characterAtIndex:currentStringIndex];
+ switch (currentCharacter) {
+
+ // When quote characters are encountered walk to the end of the quoted string.
+ case '\'':
+ case '"':
+ case '`':
+ quotedStringEndIndex = [self endIndexOfStringQuotedByCharacter:currentCharacter startingAtIndex:currentStringIndex+1];
+ if (quotedStringEndIndex == NSNotFound) {
+ return;
+ }
+ currentStringIndex = quotedStringEndIndex;
+ break;
+
+ // For comments starting "--[\s]", ensure the start syntax is valid before proceeding.
+ case '-':
+ if (stringLength < currentStringIndex + 2) break;
+ if ([string characterAtIndex:currentStringIndex+1] != '-') break;
+ if (![[NSCharacterSet whitespaceAndNewlineCharacterSet] characterIsMember:[string characterAtIndex:currentStringIndex+2]]) break;
+ commentEndIndex = [self endIndexOfCommentOfType:SPDoubleDashComment startingAtIndex:currentStringIndex];
+
+ // Remove the comment
+ [string deleteCharactersInRange:NSMakeRange(currentStringIndex, commentEndIndex - currentStringIndex + 1)];
+ stringLength -= commentEndIndex - currentStringIndex + 1;
+ currentStringIndex--;
+ break;
+
+ case '#':
+ commentEndIndex = [self endIndexOfCommentOfType:SPHashComment startingAtIndex:currentStringIndex];
+
+ // Remove the comment
+ [string deleteCharactersInRange:NSMakeRange(currentStringIndex, commentEndIndex - currentStringIndex + 1)];
+ stringLength -= commentEndIndex - currentStringIndex + 1;
+ currentStringIndex--;
+ break;
+
+ // For comments starting "/*", ensure the start syntax is valid before proceeding.
+ case '/':
+ if (stringLength < currentStringIndex + 1) break;
+ if ([string characterAtIndex:currentStringIndex+1] != '*') break;
+ commentEndIndex = [self endIndexOfCommentOfType:SPCStyleComment startingAtIndex:currentStringIndex];
+
+ // Remove the comment
+ [string deleteCharactersInRange:NSMakeRange(currentStringIndex, commentEndIndex - currentStringIndex + 1)];
+ stringLength -= commentEndIndex - currentStringIndex + 1;
+ currentStringIndex--;
+ break;
+ }
+ }
+}
+
+
+/*
+ * Removes quotes surrounding the string if present, and un-escapes internal occurrences of the quote character before returning.
+ */
+- (NSString *) unquotedString
+{
+ NSMutableString *returnString;
+ long stringEndIndex;
+ unichar quoteCharacter;
+
+ if (![string length]) return nil;
+
+ // If the first character is not a quote character, return the entire string.
+ quoteCharacter = [string characterAtIndex:0];
+ if (quoteCharacter != '`' && quoteCharacter != '"' && quoteCharacter != '\'') {
+ return [NSString stringWithString:string];
+ }
+
+ // Get the end of the string
+ stringEndIndex = [self endIndexOfStringQuotedByCharacter:quoteCharacter startingAtIndex:1];
+ if (stringEndIndex == NSNotFound) {
+ return [NSString stringWithString:string];
+ }
+
+ // Trim the string appropriately
+ returnString = [NSMutableString stringWithString:[string substringWithRange:NSMakeRange(1, stringEndIndex-1)]];
+
+ // Remove escaped characters and escaped strings as appropriate
+ if (quoteCharacter == '`' || quoteCharacter == '"' || quoteCharacter == '\'') {
+ [returnString replaceOccurrencesOfString:[NSString stringWithFormat:@"%C%C", quoteCharacter, quoteCharacter] withString:[NSString stringWithFormat:@"%C", quoteCharacter] options:0 range:NSMakeRange(0, [returnString length])];
+ }
+ if (quoteCharacter == '"') {
+ [returnString replaceOccurrencesOfString:@"\\\"" withString:@"\"" options:0 range:NSMakeRange(0, [returnString length])];
+ [returnString replaceOccurrencesOfString:@"\\\\" withString:@"\\" options:0 range:NSMakeRange(0, [returnString length])];
+ } else if (quoteCharacter == '\'') {
+ [returnString replaceOccurrencesOfString:@"\\'" withString:@"'" options:0 range:NSMakeRange(0, [returnString length])];
+ [returnString replaceOccurrencesOfString:@"\\\\" withString:@"\\" options:0 range:NSMakeRange(0, [returnString length])];
+ }
+
+ return returnString;
+}
+
+
+/*
+ * Removes characters from the string up to the first occurrence of the supplied character.
+ */
+- (BOOL) trimToCharacter:(unichar)character inclusively:(BOOL)inclusive
+{
+ return [self trimToCharacter:character inclusively:inclusive ignoringQuotedStrings:YES];
+}
+
+
+/*
+ * As trimToCharacter: ..., but allows control over whether characters within quoted
+ * strings are ignored.
+ */
+- (BOOL) trimToCharacter:(unichar)character inclusively:(BOOL)inclusive ignoringQuotedStrings:(BOOL)ignoreQuotedStrings
+{
+ long stringIndex;
+
+ // Get the first occurrence of the specified character, returning NO if not found
+ stringIndex = [self firstOccurrenceOfCharacter:character ignoringQuotedStrings:ignoreQuotedStrings];
+ if (stringIndex == NSNotFound) return NO;
+
+ // If it has been found, trim the string appropriately and return YES
+ [string deleteCharactersInRange:NSMakeRange(0, stringIndex + (inclusive?1:0))];
+ return YES;
+}
+
+
+/*
+ * Returns an NSString containing characters from the string up to the first occurrence of the supplied character.
+ */
+- (NSString *) stringToCharacter:(unichar)character inclusively:(BOOL)inclusive
+{
+ return [self stringToCharacter:character inclusively:inclusive ignoringQuotedStrings:YES];
+}
+
+
+/*
+ * As stringToCharacter: ..., but allows control over whether characters within quoted strings
+ * are ignored.
+ */
+- (NSString *) stringToCharacter:(unichar)character inclusively:(BOOL)inclusive ignoringQuotedStrings:(BOOL)ignoreQuotedStrings {
+ long stringIndex;
+
+ // Get the first occurrence of the specified character, returning nil if not found
+ stringIndex = [self firstOccurrenceOfCharacter:character ignoringQuotedStrings:ignoreQuotedStrings];
+ if (stringIndex == NSNotFound) return nil;
+
+ // If it has been found, return the appropriate string range
+ return [string substringWithRange:NSMakeRange(0, stringIndex + (inclusive?1:0))];
+}
+
+
+/*
+ * Returns an NSString containing characters from the string up to the first occurrence of the supplied
+ * character, also removing them from the string.
+ */
+- (NSString *) trimAndReturnStringToCharacter:(unichar)character trimmingInclusively:(BOOL)inclusiveTrim returningInclusively:(BOOL)inclusiveReturn
+{
+ return [self trimAndReturnStringToCharacter:character trimmingInclusively:inclusiveTrim returningInclusively:inclusiveReturn ignoringQuotedStrings:YES];
+}
+
+
+/*
+ * As trimAndReturnStringToCharacter: ..., but allows control over whether characters within quoted
+ * strings are ignored.
+ */
+- (NSString *) trimAndReturnStringToCharacter:(unichar)character trimmingInclusively:(BOOL)inclusiveTrim returningInclusively:(BOOL)inclusiveReturn ignoringQuotedStrings:(BOOL)ignoreQuotedStrings
+{
+ long stringIndex;
+ NSString *resultString;
+
+ // Get the first occurrence of the specified character, returning nil if it could not be found
+ stringIndex = [self firstOccurrenceOfCharacter:character ignoringQuotedStrings:ignoreQuotedStrings];
+ if (stringIndex == NSNotFound) return nil;
+
+ // Select the appropriate string range, truncate the current string, and return the selected string
+ resultString = [NSString stringWithString:[string substringWithRange:NSMakeRange(0, stringIndex + (inclusiveReturn?1:0))]];
+ [string deleteCharactersInRange:NSMakeRange(0, stringIndex + (inclusiveTrim?1:0))];
+ return resultString;
+}
+
+
+/*
+ * Returns characters from the string up to and from the first occurrence of the supplied opening character
+ * to the appropriate occurrence of the supplied closing character. "inclusively" controls whether the supplied
+ * characters should also be returned.
+ */
+- (NSString *) stringFromCharacter:(unichar)fromCharacter toCharacter:(unichar)toCharacter inclusively:(BOOL)inclusive
+{
+ return [self stringFromCharacter:fromCharacter toCharacter:toCharacter inclusively:inclusive skippingBrackets:NO ignoringQuotedStrings:YES];
+}
+
+
+/*
+ * As stringFromCharacter: toCharacter: ..., but allows control over whether to skip
+ * over bracket-enclosed characters, as in subqueries, enums, definitions or groups
+ */
+- (NSString *) stringFromCharacter:(unichar)fromCharacter toCharacter:(unichar)toCharacter inclusively:(BOOL)inclusive skippingBrackets:(BOOL)skipBrackets
+{
+ return [self stringFromCharacter:fromCharacter toCharacter:toCharacter inclusively:inclusive skippingBrackets:skipBrackets ignoringQuotedStrings:YES];
+}
+
+
+/*
+ * As stringFromCharacter: toCharacter: ..., but allows control over whether characters within quoted
+ * strings are ignored.
+ */
+- (NSString *) stringFromCharacter:(unichar)fromCharacter toCharacter:(unichar)toCharacter inclusively:(BOOL)inclusive ignoringQuotedStrings:(BOOL)ignoreQuotedStrings
+{
+ return [self stringFromCharacter:fromCharacter toCharacter:toCharacter inclusively:inclusive skippingBrackets:NO ignoringQuotedStrings:ignoreQuotedStrings];
+}
+
+
+/*
+ * As stringFromCharacter: toCharacter: ..., but allows control over both bracketing and quoting.
+ */
+- (NSString *) stringFromCharacter:(unichar)fromCharacter toCharacter:(unichar)toCharacter inclusively:(BOOL)inclusive skippingBrackets:(BOOL)skipBrackets ignoringQuotedStrings:(BOOL)ignoreQuotedStrings
+{
+ long fromCharacterIndex, toCharacterIndex;
+
+ // Look for the first occurrence of the from: character
+ fromCharacterIndex = [self firstOccurrenceOfCharacter:fromCharacter afterIndex:-1 skippingBrackets:skipBrackets ignoringQuotedStrings:ignoreQuotedStrings];
+ if (fromCharacterIndex == NSNotFound) return nil;
+
+ // Look for the first/balancing occurrence of the to: character
+ toCharacterIndex = [self firstOccurrenceOfCharacter:toCharacter afterIndex:fromCharacterIndex skippingBrackets:skipBrackets ignoringQuotedStrings:ignoreQuotedStrings];
+ if (toCharacterIndex == NSNotFound) return nil;
+
+ // Return the correct part of the string.
+ return [string substringWithRange:NSMakeRange(fromCharacterIndex + (inclusive?0:1), toCharacterIndex + (inclusive?1:-1) - fromCharacterIndex)];
+}
+
+
+/*
+ * As stringFromCharacter: toCharacter: ..., but also trims the string up to the "to" character and
+ * up to or including the "from" character, depending on whether "trimmingInclusively" is set.
+ */
+- (NSString *) trimAndReturnStringFromCharacter:(unichar)fromCharacter toCharacter:(unichar)toCharacter trimmingInclusively:(BOOL)inclusiveTrim returningInclusively:(BOOL)inclusiveReturn
+{
+ return [self trimAndReturnStringFromCharacter:fromCharacter toCharacter:toCharacter trimmingInclusively:inclusiveTrim returningInclusively:inclusiveReturn skippingBrackets:NO ignoringQuotedStrings:YES];
+}
+
+
+/*
+ * As trimAndReturnStringFromCharacter: toCharacter: ..., but allows control over whether to
+ * skip over bracket-enclosed characters, as in subqueries, enums, definitions or groups.
+ */
+- (NSString *) trimAndReturnStringFromCharacter:(unichar)fromCharacter toCharacter:(unichar)toCharacter trimmingInclusively:(BOOL)inclusiveTrim returningInclusively:(BOOL)inclusiveReturn skippingBrackets:(BOOL)skipBrackets
+{
+ return [self trimAndReturnStringFromCharacter:fromCharacter toCharacter:toCharacter trimmingInclusively:inclusiveTrim returningInclusively:inclusiveReturn skippingBrackets:skipBrackets ignoringQuotedStrings:YES];
+}
+
+
+/*
+ * As trimAndReturnStringFromCharacter: toCharacter: ..., but allows control over whether characters
+ * within quoted strings are ignored.
+ */
+- (NSString *) trimAndReturnStringFromCharacter:(unichar)fromCharacter toCharacter:(unichar)toCharacter trimmingInclusively:(BOOL)inclusiveTrim returningInclusively:(BOOL)inclusiveReturn ignoringQuotedStrings:(BOOL)ignoreQuotedStrings
+{
+ return [self trimAndReturnStringFromCharacter:fromCharacter toCharacter:toCharacter trimmingInclusively:inclusiveTrim returningInclusively:inclusiveReturn skippingBrackets:NO ignoringQuotedStrings:ignoreQuotedStrings];
+}
+
+
+/*
+ * As trimAndReturnStringFromCharacter: toCharacter: ..., but allows control over both bracketing
+ * and quoting.
+ */
+- (NSString *) trimAndReturnStringFromCharacter:(unichar)fromCharacter toCharacter:(unichar)toCharacter trimmingInclusively:(BOOL)inclusiveTrim returningInclusively:(BOOL)inclusiveReturn skippingBrackets:(BOOL)skipBrackets ignoringQuotedStrings:(BOOL)ignoreQuotedStrings
+{
+ long fromCharacterIndex, toCharacterIndex;
+ NSString *resultString;
+
+ // Look for the first occurrence of the from: character
+ fromCharacterIndex = [self firstOccurrenceOfCharacter:fromCharacter afterIndex:-1 skippingBrackets:skipBrackets ignoringQuotedStrings:ignoreQuotedStrings];
+ if (fromCharacterIndex == NSNotFound) return nil;
+
+ // Look for the first/balancing occurrence of the to: character
+ toCharacterIndex = [self firstOccurrenceOfCharacter:toCharacter afterIndex:fromCharacterIndex skippingBrackets:skipBrackets ignoringQuotedStrings:ignoreQuotedStrings];
+ if (toCharacterIndex == NSNotFound) return nil;
+
+ // Select the correct part of the string, truncate the current string, and return the selected string.
+ resultString = [string substringWithRange:NSMakeRange(fromCharacterIndex + (inclusiveReturn?0:1), toCharacterIndex + (inclusiveReturn?1:-1) - fromCharacterIndex)];
+ [string deleteCharactersInRange:NSMakeRange(fromCharacterIndex + (inclusiveTrim?0:1), toCharacterIndex + (inclusiveTrim?1:-1) - fromCharacterIndex)];
+ return resultString;
+}
+
+/*
+ * Split a string on the boundaries formed by the supplied character, returning an array of strings.
+ */
+- (NSArray *) splitStringByCharacter:(unichar)character
+{
+ return [self splitStringByCharacter:character skippingBrackets:NO ignoringQuotedStrings:YES];
+}
+
+/*
+ * As splitStringByCharacter: ..., but allows control over whether to skip over bracket-enclosed
+ * characters, as in subqueries, enums, definitions or groups.
+ */
+- (NSArray *) splitStringByCharacter:(unichar)character skippingBrackets:(BOOL)skipBrackets
+{
+ return [self splitStringByCharacter:character skippingBrackets:skipBrackets ignoringQuotedStrings:YES];
+}
+
+
+/*
+ * As splitStringByCharacter:, but allows control over whether characters
+ * within quoted strings are ignored.
+ */
+- (NSArray *) splitStringByCharacter:(unichar)character ignoringQuotedStrings:(BOOL)ignoreQuotedStrings
+{
+ return [self splitStringByCharacter:character skippingBrackets:NO ignoringQuotedStrings:ignoreQuotedStrings];
+}
+
+
+/*
+ * As splitStringByCharacter: ..., but allows control over both bracketing and quoting.
+ */
+- (NSArray *) splitStringByCharacter:(unichar)character skippingBrackets:(BOOL)skipBrackets ignoringQuotedStrings:(BOOL)ignoreQuotedStrings
+{
+ NSMutableArray *resultsArray = [NSMutableArray array];
+ long stringIndex = -1, nextIndex = 0;
+
+ // Walk through the string finding the character to split by, and add non-zero length strings.
+ while (1) {
+ nextIndex = [self firstOccurrenceOfCharacter:character afterIndex:stringIndex skippingBrackets:skipBrackets ignoringQuotedStrings:ignoreQuotedStrings];
+ if (nextIndex == NSNotFound) {
+ break;
+ }
+
+ if (nextIndex - stringIndex - 1 > 0) {
+ [resultsArray addObject:[string substringWithRange:NSMakeRange(stringIndex+1, nextIndex - stringIndex - 1)]];
+ }
+ stringIndex = nextIndex;
+ }
+
+ // Add the end of the string after the previously matched character where appropriate.
+ if (stringIndex + 1 < [string length]) {
+ [resultsArray addObject:[string substringWithRange:NSMakeRange(stringIndex+1, [string length] - stringIndex - 1)]];
+ }
+
+ return resultsArray;
+}
+
+
+/*
+ * A method intended for use by the functions above.
+ */
+- (long) firstOccurrenceOfCharacter:(unichar)character ignoringQuotedStrings:(BOOL)ignoreQuotedStrings
+{
+ return [self firstOccurrenceOfCharacter:character afterIndex:-1 skippingBrackets:NO ignoringQuotedStrings:ignoreQuotedStrings];
+}
+
+
+/*
+ * A method intended for use by the functions above.
+ */
+- (long) firstOccurrenceOfCharacter:(unichar)character afterIndex:(long)startIndex ignoringQuotedStrings:(BOOL)ignoreQuotedStrings
+{
+ return [self firstOccurrenceOfCharacter:character afterIndex:startIndex skippingBrackets:NO ignoringQuotedStrings:ignoreQuotedStrings];
+}
+
+
+/*
+ * A method intended for use by the functions above.
+ */
+- (long) firstOccurrenceOfCharacter:(unichar)character afterIndex:(long)startIndex skippingBrackets:(BOOL)skipBrackets ignoringQuotedStrings:(BOOL)ignoreQuotedStrings
+{
+ long currentStringIndex, quotedStringEndIndex;
+ unichar currentCharacter;
+ long stringLength = [string length];
+ int bracketingLevel = 0;
+
+ // Sanity check inputs
+ if (startIndex < -1) startIndex = -1;
+
+ // Walk along the string, processing characters
+ for (currentStringIndex = startIndex + 1; currentStringIndex < stringLength; currentStringIndex++) {
+ currentCharacter = [string characterAtIndex:currentStringIndex];
+
+ // Check for the ending character, and if it has been found and quoting/brackets is valid, return.
+ if (currentCharacter == character) {
+ if (!skipBrackets || bracketingLevel <= 0) {
+ return currentStringIndex;
+ }
+ }
+
+ // Process strings and comments as appropriate
+ switch (currentCharacter) {
+
+ // When quote characters are encountered and strings are not being ignored, walk to the end of the quoted string.
+ case '\'':
+ case '"':
+ case '`':
+ if (!ignoreQuotedStrings) break;
+ quotedStringEndIndex = [self endIndexOfStringQuotedByCharacter:currentCharacter startingAtIndex:currentStringIndex+1];
+ if (quotedStringEndIndex == NSNotFound) {
+ return NSNotFound;
+ }
+ currentStringIndex = quotedStringEndIndex;
+ break;
+
+ // For opening brackets increment the bracket count
+ case '(':
+ bracketingLevel++;
+ break;
+
+ // For closing brackets decrement the bracket count
+ case ')':
+ bracketingLevel--;
+
+ // For comments starting "--[\s]", ensure the start syntax is valid before proceeding.
+ case '-':
+ if (stringLength < currentStringIndex + 2) break;
+ if ([string characterAtIndex:currentStringIndex+1] != '-') break;
+ if (![[NSCharacterSet whitespaceAndNewlineCharacterSet] characterIsMember:[string characterAtIndex:currentStringIndex+2]]) break;
+ currentStringIndex = [self endIndexOfCommentOfType:SPDoubleDashComment startingAtIndex:currentStringIndex];
+ break;
+
+ case '#':
+ currentStringIndex = [self endIndexOfCommentOfType:SPHashComment startingAtIndex:currentStringIndex];
+ break;
+
+ // For comments starting "/*", ensure the start syntax is valid before proceeding.
+ case '/':
+ if (stringLength < currentStringIndex + 1) break;
+ if ([string characterAtIndex:currentStringIndex+1] != '*') break;
+ currentStringIndex = [self endIndexOfCommentOfType:SPCStyleComment startingAtIndex:currentStringIndex];
+ break;
+ }
+ }
+
+ // If no matches have been made in this string, return NSNotFound.
+ return NSNotFound;
+}
+
+/*
+ * A method intended for use by the functions above.
+ */
+- (long) endIndexOfStringQuotedByCharacter:(unichar)quoteCharacter startingAtIndex:(long)index
+{
+ long currentStringIndex, stringLength, i, quotedStringLength;
+ BOOL characterIsEscaped;
+ unichar currentCharacter;
+
+ stringLength = [string length];
+
+ // Walk the string looking for the string end
+ for ( currentStringIndex = index; currentStringIndex < stringLength; currentStringIndex++) {
+ currentCharacter = [string characterAtIndex:currentStringIndex];
+
+ // If the string end is a backtick and one has been encountered, treat it as end of string
+ if (quoteCharacter == '`' && currentCharacter == '`') {
+
+ // ...as long as the next character isn't also a backtick, in which case it's being quoted. Skip both.
+ if ((currentStringIndex + 1) < stringLength && [string characterAtIndex:currentStringIndex+1] == '`') {
+ currentStringIndex++;
+ continue;
+ }
+
+ return currentStringIndex;
+
+ // Otherwise, prepare to treat the string as ended when meeting the correct boundary character....
+ } else if (currentCharacter == quoteCharacter) {
+
+ // ...but only if the string end isn't escaped with an *odd* number of escaping characters...
+ characterIsEscaped = NO;
+ i = 1;
+ quotedStringLength = currentStringIndex - 1;
+ while ((quotedStringLength - i) > 0 && [string characterAtIndex:currentStringIndex - i] == '\\') {
+ characterIsEscaped = !characterIsEscaped;
+ i++;
+ }
+
+ // If an even number have been found, it may be the end of the string - as long as the subsequent character
+ // isn't also the same character, in which case it's another form of escaping.
+ if (!characterIsEscaped) {
+ if ((currentStringIndex + 1) < stringLength && [string characterAtIndex:currentStringIndex+1] == quoteCharacter) {
+ currentStringIndex++;
+ continue;
+ }
+
+ // Really is the end of the string.
+ return currentStringIndex;
+ }
+ }
+ }
+
+ return NSNotFound;
+}
+
+/*
+ * A method intended for use by the functions above.
+ */
+- (long) endIndexOfCommentOfType:(SPCommentType)commentType startingAtIndex:(long)index
+{
+ long stringLength = [string length];
+ unichar currentCharacter;
+
+ switch (commentType) {
+
+ // For comments of type "--[\s]", start the comment processing two characters in to match the start syntax,
+ // then flow into the Hash comment handling (looking for first newline).
+ case SPDoubleDashComment:
+ index = index+2;
+
+ // For comments starting "--[\s]" and "#", continue until the first newline.
+ case SPHashComment:
+ index++;
+ for ( ; index < stringLength; index++ ) {
+ currentCharacter = [string characterAtIndex:index];
+ if (currentCharacter == '\r' || currentCharacter == '\n') {
+ return index-1;
+ }
+ }
+ break;
+
+ // For comments starting "/*", start the comment processing one character in to match the start syntax, then
+ // continue until the first matching "*/".
+ case SPCStyleComment:
+ index = index+2;
+ for ( ; index < stringLength; index++ ) {
+ if ([string characterAtIndex:index] == '*') {
+ if ((stringLength > index + 1) && [string characterAtIndex:index+1] == '/') {
+ return (index+1);
+ }
+ }
+ }
+ }
+
+ // If no match has been found, the comment must continue until the very end of the string.
+ return (stringLength-1);
+}
+
+
+/* Required and primitive methods to allow subclassing class cluster */
+#pragma mark -
+- (id) init {
+ if (self = [super init]) {
+ string = [[NSMutableString string] retain];
+ }
+ return self;
+}
+- (id) initWithBytes:(const void *)bytes length:(unsigned int)length encoding:(NSStringEncoding)encoding {
+ if (self = [super init]) {
+ string = [[NSMutableString alloc] initWithBytes:bytes length:length encoding:encoding];
+ }
+ return self;
+}
+- (id) initWithBytesNoCopy:(void *)bytes length:(unsigned int)length encoding:(NSStringEncoding)encoding freeWhenDone:(BOOL)flag {
+ if (self = [super init]) {
+ string = [[NSMutableString alloc] initWithBytesNoCopy:bytes length:length encoding:encoding freeWhenDone:flag];
+ }
+ return self;
+}
+- (id) initWithCapacity:(unsigned int)capacity {
+ if (self = [super init]) {
+ string = [[NSMutableString stringWithCapacity:capacity] retain];
+ }
+ return self;
+}
+- (id) initWithCharactersNoCopy:(unichar *)characters length:(unsigned int)length freeWhenDone:(BOOL)flag {
+ if (self = [super init]) {
+ string = [[NSMutableString alloc] initWithCharactersNoCopy:characters length:length freeWhenDone:flag];
+ }
+ return self;
+}
+- (id) initWithContentsOfFile:(id)path {
+ return [self initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL];
+}
+- (id) initWithContentsOfFile:(NSString *)path encoding:(NSStringEncoding)encoding error:(NSError **)error {
+ if (self = [super init]) {
+ string = [[NSMutableString alloc] initWithContentsOfFile:path encoding:encoding error:error];
+ }
+ return self;
+}
+- (id) initWithCString:(const char *)nullTerminatedCString encoding:(NSStringEncoding)encoding {
+ if (self = [super init]) {
+ string = [[NSMutableString alloc] initWithCString:nullTerminatedCString encoding:encoding];
+ }
+ return self;
+}
+- (id) initWithFormat:(NSString *)format, ... {
+ va_list argList;
+ va_start(argList, format);
+ id str = [self initWithFormat:format arguments:argList];
+ va_end(argList);
+ return str;
+}
+- (id) initWithFormat:(NSString *)format arguments:(va_list)argList {
+ if (self = [super init]) {
+ string = [[NSMutableString alloc] initWithFormat:format arguments:argList];
+ }
+ return self;
+}
+- (unsigned int) length {
+ return [string length];
+}
+- (unichar) characterAtIndex:(unsigned int)index {
+ return [string characterAtIndex:index];
+}
+- (id) description {
+ return [string description];
+}
+- (unsigned int) replaceOccurrencesOfString:(NSString *)target withString:(NSString *)replacement options:(unsigned)options range:(NSRange)searchRange {
+ return [string replaceOccurrencesOfString:target withString:replacement options:options range:searchRange];
+}
+- (void) setString:(NSString *)aString {
+ [string setString:aString];
+}
+- (void) replaceCharactersInRange:(NSRange)range withString:(NSString *)aString {
+ [string replaceCharactersInRange:range withString:aString];
+}
+- (void) dealloc {
+ [string release];
+ [super dealloc];
+}
+@end \ No newline at end of file
diff --git a/Source/SPTableData.h b/Source/SPTableData.h
new file mode 100644
index 00000000..fd069a6b
--- /dev/null
+++ b/Source/SPTableData.h
@@ -0,0 +1,54 @@
+//
+// SPTableData.h
+// sequel-pro
+//
+// Created by Rowan Beentje on 24/01/2009.
+// Copyright 2009 Arboreal. 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 SPTableData : NSObject {
+ IBOutlet id tableDocumentInstance;
+ IBOutlet id tableListInstance;
+
+ NSMutableArray *columns;
+ NSMutableArray *columnNames;
+ NSMutableDictionary *status;
+ NSString *tableEncoding;
+
+ CMMCPConnection *mySQLConnection;
+}
+
+- (void) setConnection:(CMMCPConnection *)theConnection;
+- (NSString *) tableEncoding;
+- (NSArray *) columns;
+- (NSDictionary *) columnWithName:(NSString *)colName;
+- (NSArray *) columnNames;
+- (NSDictionary *) columnAtIndex:(int)index;
+- (NSString *) statusValueForKey:(NSString *)aKey;
+- (NSDictionary *) statusValues;
+- (void) resetAllData;
+- (void) resetStatusData;
+- (void) resetColumnData;
+- (BOOL) updateInformationFromCreateTableForCurrentTable;
+- (NSDictionary *) informationFromCreateTableSyntaxForTable:(NSString *)tableName;
+- (BOOL) updateStatusInformationForCurrentTable;
+
+@end
diff --git a/Source/SPTableData.m b/Source/SPTableData.m
new file mode 100644
index 00000000..144c3280
--- /dev/null
+++ b/Source/SPTableData.m
@@ -0,0 +1,509 @@
+//
+// SPTableData.m
+// sequel-pro
+//
+// Created by Rowan Beentje on 24/01/2009.
+// Copyright 2009 Arboreal. 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 "CMMCPConnection.h"
+#import "CMMCPResult.h"
+#import <MCPKit_bundled/MCPKit_bundled.h>
+#import "SPTableData.h"
+#import "SPSQLParser.h"
+#import "TableDocument.h"
+#import "TablesList.h"
+
+
+@implementation SPTableData
+
+
+- (id) init
+{
+ if ((self = [super init])) {
+ columns = [[NSMutableArray alloc] init];
+ columnNames = [[NSMutableArray alloc] init];
+ status = [[NSMutableDictionary alloc] init];
+ tableEncoding = nil;
+ mySQLConnection = nil;
+ }
+
+ return self;
+}
+
+/*
+ * Set the connection for use.
+ * Called by the connect sheet methods.
+ */
+- (void) setConnection:(CMMCPConnection *)theConnection
+{
+ mySQLConnection = theConnection;
+ [mySQLConnection retain];
+}
+
+
+/*
+ * Retrieve the encoding for the current table, using or refreshing the cache as appropriate.
+ */
+- (NSString *) tableEncoding
+{
+ if (tableEncoding == nil) {
+ [self updateInformationFromCreateTableForCurrentTable];
+ }
+ return [NSString stringWithString:tableEncoding];
+}
+
+
+/*
+ * Retrieve all columns for the current table as an array, using or refreshing the cache as appropriate.
+ */
+- (NSArray *) columns
+{
+ if ([columns count] == 0) {
+ [self updateInformationFromCreateTableForCurrentTable];
+ }
+ return columns;
+}
+
+
+/*
+ * Retrieve a column with a specified name, using or refreshing the cache as appropriate.
+ */
+- (NSDictionary *) columnWithName:(NSString *)colName
+{
+ int columnIndex = [columnNames indexOfObject:colName];
+ return [columns objectAtIndex:columnIndex];
+}
+
+
+/*
+ * Retrieve column names for the current table as an array, using or refreshing the cache as appropriate.
+ */
+- (NSArray *) columnNames
+{
+ if ([columnNames count] == 0) {
+ [self updateInformationFromCreateTableForCurrentTable];
+ }
+ return columnNames;
+}
+
+
+/*
+ * Retrieve a specified column for the current table as a dictionary, using or refreshing the cache as appropriate.
+ */
+- (NSDictionary *) columnAtIndex:(int)index
+{
+ if ([columns count] == 0) {
+ [self updateInformationFromCreateTableForCurrentTable];
+ }
+ return [columns objectAtIndex:index];
+}
+
+
+/*
+ * Retrieve the table status value for a supplied key, using or refreshing the cache as appropriate.
+ */
+- (NSString *) statusValueForKey:(NSString *)aKey
+{
+ if ([status count] == 0) {
+ [self updateStatusInformationForCurrentTable];
+ }
+ return [status objectForKey:aKey];
+}
+
+
+/*
+ * Retrieve all known status values as a dictionary, using or refreshing the cache as appropriate.
+ */
+- (NSDictionary *) statusValues
+{
+ if ([status count] == 0) {
+ [self updateStatusInformationForCurrentTable];
+ }
+ return status;
+}
+
+
+/*
+ * Flushes all caches - should be used on major changes, for example table changes.
+ */
+- (void) resetAllData
+{
+ [columns removeAllObjects];
+ [columnNames removeAllObjects];
+ [status removeAllObjects];
+ if (tableEncoding != nil) {
+ [tableEncoding release];
+ tableEncoding = nil;
+ }
+}
+
+
+/*
+ * Flushes any status-related caches.
+ */
+- (void) resetStatusData
+{
+ [status removeAllObjects];
+}
+
+
+/*
+ * Flushes any field/column-related caches.
+ */
+- (void) resetColumnData
+{
+ [columns removeAllObjects];
+ [columnNames removeAllObjects];
+}
+
+
+/*
+ * Retrieves the information for the current table and stores it in cache.
+ * Returns a boolean indicating success.
+ */
+- (BOOL) updateInformationFromCreateTableForCurrentTable
+{
+ NSDictionary *tableData = [self informationFromCreateTableSyntaxForTable:[tableListInstance tableName]];
+ NSDictionary *columnData;
+ NSEnumerator *enumerator;
+
+ if (tableData == nil) {
+ [columns removeAllObjects];
+ [columnNames removeAllObjects];
+ return FALSE;
+ }
+
+ [columns addObjectsFromArray:[tableData objectForKey:@"columns"]];
+
+ enumerator = [columns objectEnumerator];
+ while (columnData = [enumerator nextObject]) {
+ [columnNames addObject:[NSString stringWithString:[columnData objectForKey:@"name"]]];
+ }
+
+ if (tableEncoding != nil) {
+ [tableEncoding release];
+ }
+ tableEncoding = [[NSString alloc] initWithString:[tableData objectForKey:@"encoding"]];
+
+ return TRUE;
+}
+
+
+/*
+ * Retrieve the CREATE TABLE string for a table and analyse it to extract the field
+ * details and table encoding.
+ * In future this could also be used to retrieve the majority of index information
+ * assuming information like cardinality isn't needed.
+ * This function is rather long due to the painful parsing required, but is fast.
+ * Returns a boolean indicating success.
+ */
+- (NSDictionary *) informationFromCreateTableSyntaxForTable:(NSString *)tableName
+{
+ SPSQLParser *createTableParser, *fieldsParser, *fieldParser, *detailParser;
+ NSMutableArray *tableColumns, *fieldStrings, *definitionParts, *detailParts;
+ NSMutableDictionary *tableColumn, *tableData;
+ NSString *detailString, *encodingString;
+ unsigned i, j, stringStart, partsArrayLength;
+
+ // Catch unselected tables and return nil
+ if ([tableName isEqualToString:@""] || !tableName) return nil;
+
+ // Retrieve the CREATE TABLE syntax for the table
+ CMMCPResult *theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW CREATE TABLE `%@`", tableName]];
+
+ // Check for any errors
+ if (![[mySQLConnection getLastErrorMessage] isEqualToString:@""]) {
+ NSRunAlertPanel(@"Error", [NSString stringWithFormat:@"An error occured while retrieving table information:\n\n%@", [mySQLConnection getLastErrorMessage]], @"OK", nil, nil);
+ return nil;
+ }
+
+ // Retrieve the table syntax string
+ NSArray *syntaxResult = [theResult fetchRowAsArray];
+ if ([[syntaxResult objectAtIndex:1] isKindOfClass:[NSData class]]) {
+ createTableParser = [[SPSQLParser alloc] initWithData:[syntaxResult objectAtIndex:1] encoding:[mySQLConnection encoding]];
+ } else {
+ createTableParser = [[SPSQLParser alloc] initWithString:[syntaxResult objectAtIndex:1]];
+ }
+
+ // Extract the fields definition string from the CREATE TABLE syntax
+ fieldsParser = [[SPSQLParser alloc] initWithString:[createTableParser trimAndReturnStringFromCharacter:'(' toCharacter:')' trimmingInclusively:YES returningInclusively:NO skippingBrackets:YES]];
+
+ // Split the fields and keys string into an array of individual elements
+ fieldStrings = [[NSMutableArray alloc] initWithArray:[fieldsParser splitStringByCharacter:',' skippingBrackets:YES]];
+
+ // fieldStrings should now hold unparsed field and key strings, while tableProperty string holds unparsed
+ // table information. Proceed further by parsing the field strings.
+ tableColumns = [[NSMutableArray alloc] init];
+ tableColumn = [[NSMutableDictionary alloc] init];
+ definitionParts = [[NSMutableArray alloc] init];
+ fieldParser = [[SPSQLParser alloc] init];
+ for (i = 0; i < [fieldStrings count]; i++) {
+
+ // Take this field/key string, trim whitespace from both ends and remove comments
+ [fieldsParser setString:[[fieldStrings objectAtIndex:i] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]];
+ [fieldsParser deleteComments];
+ if (![fieldsParser length]) {
+ continue;
+ }
+ [tableColumn removeAllObjects];
+ [definitionParts removeAllObjects];
+
+ // If the first character is a backtick, this is a field definition.
+ if ([fieldsParser characterAtIndex:0] =='`') {
+
+ // Capture the area between the two backticks as the name
+ [tableColumn setObject:[fieldsParser trimAndReturnStringFromCharacter:'`' toCharacter:'`' trimmingInclusively:YES returningInclusively:NO ignoringQuotedStrings:NO] forKey:@"name"];
+
+ // Split the remaining field definition string by spaces ready for processing
+ [definitionParts addObjectsFromArray:[fieldsParser splitStringByCharacter:' ' skippingBrackets:YES]];
+
+ // The first item is always the data type.
+ [fieldParser setString:[definitionParts objectAtIndex:0]];
+
+ // If no field length definition is present, store only the type
+ if ([fieldParser firstOccurrenceOfCharacter:'(' ignoringQuotedStrings:YES] == NSNotFound) {
+ [tableColumn setObject:[fieldParser uppercaseString] forKey:@"type"];
+
+ // Otherwise separate out the length definition for processing
+ } else {
+ detailParser = [[SPSQLParser alloc] initWithString:[[fieldParser stringToCharacter:'(' inclusively:NO] uppercaseString]];
+ [tableColumn setObject:[NSString stringWithString:detailParser] forKey:@"type"];
+
+ // For ENUMs and SETs, capture the field value options into an array for storage
+ if ([detailParser isEqualToString:@"ENUM"] || [detailParser isEqualToString:@"SET"]) {
+ [detailParser setString:[fieldParser stringFromCharacter:'(' toCharacter:')' inclusively:NO]];
+ detailParts = [[NSMutableArray alloc] initWithArray:[detailParser splitStringByCharacter:',']];
+ for (j = 0; j < [detailParts count]; j++) {
+ [detailParser setString:[detailParts objectAtIndex:j]];
+ [detailParts replaceObjectAtIndex:j withObject:[detailParser unquotedString]];
+ }
+ [tableColumn setObject:[NSArray arrayWithArray:detailParts] forKey:@"values"];
+ [detailParts release];
+
+ // For types with required or optional decimals, store as appropriate
+ } else if ([detailParser isEqualToString:@"REAL"] || [detailParser isEqualToString:@"DOUBLE"] || [detailParser isEqualToString:@"FLOAT"] || [detailParser isEqualToString:@"DECIMAL"] || [detailParser isEqualToString:@"NUMERIC"]) {
+ [detailParser setString:[fieldParser stringFromCharacter:'(' toCharacter:')' inclusively:NO]];
+ detailParts = [[NSMutableArray alloc] initWithArray:[detailParser splitStringByCharacter:',']];
+ [detailParser setString:[detailParts objectAtIndex:0]];
+ [tableColumn setObject:[detailParser unquotedString] forKey:@"length"];
+ if ([detailParts count] > 1) {
+ [detailParser setString:[detailParts objectAtIndex:1]];
+ [tableColumn setObject:[detailParser unquotedString] forKey:@"decimals"];
+ }
+ [detailParts release];
+
+ // Otherwise capture the length only.
+ } else {
+ [detailParser setString:[fieldParser stringFromCharacter:'(' toCharacter:')' inclusively:NO]];
+ [tableColumn setObject:[detailParser unquotedString] forKey:@"length"];
+ }
+ [detailParser release];
+ }
+
+ // Also capture a general column type "group" to allow behavioural switches
+ detailString = [[NSString alloc] initWithString:[tableColumn objectForKey:@"type"]];
+ if ([detailString isEqualToString:@"BIT"]) {
+ [tableColumn setObject:@"bit" forKey:@"typegrouping"];
+ } else if ([detailString isEqualToString:@"TINYINT"] || [detailString isEqualToString:@"SMALLINT"] || [detailString isEqualToString:@"MEDIUMINT"]
+ || [detailString isEqualToString:@"INT"] || [detailString isEqualToString:@"INTEGER"] || [detailString isEqualToString:@"BIGINT"]) {
+ [tableColumn setObject:@"integer" forKey:@"typegrouping"];
+ } else if ([detailString isEqualToString:@"REAL"] || [detailString isEqualToString:@"DOUBLE"] || [detailString isEqualToString:@"FLOAT"]
+ || [detailString isEqualToString:@"DECIMAL"] || [detailString isEqualToString:@"NUMERIC"]) {
+ [tableColumn setObject:@"float" forKey:@"typegrouping"];
+ } else if ([detailString isEqualToString:@"DATE"] || [detailString isEqualToString:@"TIME"] || [detailString isEqualToString:@"TIMESTAMP"]
+ || [detailString isEqualToString:@"DATETIME"] || [detailString isEqualToString:@"YEAR"]) {
+ [tableColumn setObject:@"date" forKey:@"typegrouping"];
+ } else if ([detailString isEqualToString:@"CHAR"] || [detailString isEqualToString:@"VARCHAR"]) {
+ [tableColumn setObject:@"string" forKey:@"typegrouping"];
+ } else if ([detailString isEqualToString:@"BINARY"] || [detailString isEqualToString:@"VARBINARY"]) {
+ [tableColumn setObject:@"binary" forKey:@"typegrouping"];
+ } else if ([detailString isEqualToString:@"ENUM"] || [detailString isEqualToString:@"SET"]) {
+ [tableColumn setObject:@"enum" forKey:@"typegrouping"];
+ } else if ([detailString isEqualToString:@"TINYTEXT"] || [detailString isEqualToString:@"TEXT"]
+ || [detailString isEqualToString:@"MEDIUMTEXT"] || [detailString isEqualToString:@"LONGTEXT"]) {
+ [tableColumn setObject:@"textdata" forKey:@"typegrouping"];
+
+ // Default to "blobdata". This means that future and currently unsupported types - including spatial extensions -
+ // will be preserved unmangled.
+ } else {
+ [tableColumn setObject:@"blobdata" forKey:@"typegrouping"];
+ }
+
+ // Set up some column defaults for all columns
+ [tableColumn setValue:[NSNumber numberWithBool:YES] forKey:@"null"];
+ [tableColumn setValue:[NSNumber numberWithBool:NO] forKey:@"unsigned"];
+ [tableColumn setValue:[NSNumber numberWithBool:NO] forKey:@"binary"];
+ [tableColumn setValue:[NSNumber numberWithBool:NO] forKey:@"zerofill"];
+ [tableColumn setValue:[NSNumber numberWithBool:NO] forKey:@"autoincrement"];
+
+ // Walk through the remaining column definition parts storing recognised details
+ partsArrayLength = [definitionParts count];
+ for (j = 1; j < partsArrayLength; j++) {
+ detailString = [[NSString alloc] initWithString:[[definitionParts objectAtIndex:j] uppercaseString]];
+
+ // Whether numeric fields are unsigned
+ if ([detailString isEqualToString:@"UNSIGNED"]) {
+ [tableColumn setValue:[NSNumber numberWithBool:YES] forKey:@"unsigned"];
+
+ // Whether numeric fields are zerofill
+ } else if ([detailString isEqualToString:@"ZEROFILL"]) {
+ [tableColumn setValue:[NSNumber numberWithBool:YES] forKey:@"zerofill"];
+
+ // Whether text types are binary
+ } else if ([detailString isEqualToString:@"BINARY"]) {
+ [tableColumn setValue:[NSNumber numberWithBool:YES] forKey:@"binary"];
+
+ // Whether text types have a different encoding to the table
+ } else if ([detailString isEqualToString:@"CHARSET"] && (j + 1 < partsArrayLength)) {
+ if (![[[definitionParts objectAtIndex:j+1] uppercaseString] isEqualToString:@"DEFAULT"]) {
+ [tableColumn setValue:[definitionParts objectAtIndex:j+1] forKey:@"encoding"];
+ }
+ j++;
+ } else if ([detailString isEqualToString:@"CHARACTER"] && (j + 2 < partsArrayLength)
+ && [[[definitionParts objectAtIndex:j+1] uppercaseString] isEqualToString:@"SET"]) {
+ if (![[[definitionParts objectAtIndex:j+2] uppercaseString] isEqualToString:@"DEFAULT"]) {;
+ [tableColumn setValue:[definitionParts objectAtIndex:j+2] forKey:@"encoding"];
+ }
+ j = j + 2;
+
+ // Whether text types have a different collation to the table
+ } else if ([detailString isEqualToString:@"COLLATE"] && (j + 1 < partsArrayLength)) {
+ if (![[[definitionParts objectAtIndex:j+1] uppercaseString] isEqualToString:@"DEFAULT"]) {
+ [tableColumn setValue:[definitionParts objectAtIndex:j+1] forKey:@"collation"];
+ }
+ j++;
+
+ // Whether fields are NOT NULL
+ } else if ([detailString isEqualToString:@"NOT"] && (j + 1 < partsArrayLength)
+ && [[[definitionParts objectAtIndex:j+1] uppercaseString] isEqualToString:@"NULL"]) {
+ [tableColumn setValue:[NSNumber numberWithBool:NO] forKey:@"null"];
+ j++;
+
+ // Whether fields are NULL
+ } else if ([detailString isEqualToString:@"NULL"]) {
+ [tableColumn setValue:[NSNumber numberWithBool:YES] forKey:@"null"];
+
+ // Whether fields should auto-increment
+ } else if ([detailString isEqualToString:@"AUTO_INCREMENT"]) {
+ [tableColumn setValue:[NSNumber numberWithBool:YES] forKey:@"autoincrement"];
+
+ // Field defaults
+ } else if ([detailString isEqualToString:@"DEFAULT"] && (j + 1 < partsArrayLength)) {
+ detailParser = [[SPSQLParser alloc] initWithString:[definitionParts objectAtIndex:j+1]];
+ [tableColumn setValue:[detailParser unquotedString] forKey:@"default"];
+ [detailParser release];
+ j++;
+ }
+
+ // TODO: Currently unhandled: [UNIQUE | PRIMARY] KEY | COMMENT 'foo' | COLUMN_FORMAT bar | STORAGE q | REFERENCES...
+
+ [detailString release];
+ }
+
+ // Store the column.
+ [tableColumns addObject:[NSDictionary dictionaryWithDictionary:tableColumn]];
+
+ // TODO: Otherwise it's a key definition, constraint, check, or other 'metadata'. Would be useful to parse/display these!
+ } else {
+
+ }
+ }
+ [fieldStrings release];
+ [fieldsParser release];
+ [definitionParts release];
+ [tableColumn release];
+
+ // Extract the encoding from the table properties string - other details come from TABLE STATUS.
+ NSRange charsetDefinitionRange = [createTableParser rangeOfString:@"CHARSET=" options:NSCaseInsensitiveSearch];
+ if (charsetDefinitionRange.location == NSNotFound) {
+ charsetDefinitionRange = [createTableParser rangeOfString:@"CHARACTER SET=" options:NSCaseInsensitiveSearch];
+ }
+ if (charsetDefinitionRange.location != NSNotFound) {
+ stringStart = charsetDefinitionRange.location + charsetDefinitionRange.length;
+ for (i = stringStart; i < [createTableParser length]; i++) {
+ if ([createTableParser characterAtIndex:i] == ' ') break;
+ }
+
+ // Catch the "default" character encoding:
+ if ([[[createTableParser substringWithRange:NSMakeRange(stringStart, i-stringStart)] lowercaseString] isEqualToString:@"default"]) {
+ encodingString = [[NSString alloc] initWithString:[tableDocumentInstance databaseEncoding]];
+ } else {
+ encodingString = [[NSString alloc] initWithString:[createTableParser substringWithRange:NSMakeRange(stringStart, i-stringStart)]];
+ }
+
+ // If no DEFAULT CHARSET is present, it's likely MySQL < 4; fall back to latin1.
+ } else {
+ encodingString = [[NSString alloc] initWithString:@"latin1"];
+ }
+
+ [createTableParser release];
+ [fieldParser release];
+
+ tableData = [NSMutableDictionary dictionary];
+ [tableData setObject:[NSString stringWithString:encodingString] forKey:@"encoding"];
+ [tableData setObject:[NSArray arrayWithArray:tableColumns] forKey:@"columns"];
+
+ [encodingString release];
+ [tableColumns release];
+
+ return tableData;
+}
+
+
+/*
+ * Retrieve the status of a table as a dictionary and add it to the local cache for reuse.
+ */
+- (BOOL) updateStatusInformationForCurrentTable
+{
+
+ // Catch unselected tables and return nil
+ if ([[tableListInstance tableName] isEqualToString:@""] || ![tableListInstance tableName]) return nil;
+
+ // Run the status query and retrieve as a dictionary.
+ CMMCPResult *tableStatusResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW TABLE STATUS LIKE '%@'", [tableListInstance tableName]]];
+
+ // Check for any errors
+ if (![[mySQLConnection getLastErrorMessage] isEqualToString:@""]) {
+ NSRunAlertPanel(@"Error", [NSString stringWithFormat:@"An error occured while retrieving table status:\n\n%@", [mySQLConnection getLastErrorMessage]], @"OK", nil, nil);
+ return FALSE;
+ }
+
+ // Retrieve the status as a dictionary and set as the cache
+ [status setDictionary:[tableStatusResult fetchRowAsDictionary]];
+
+ // Reassign any "Type" key - for MySQL < 4.1 - to "Engine" for consistency.
+ if ([status objectForKey:@"Type"]) {
+ [status setObject:[status objectForKey:@"Type"] forKey:@"Engine"];
+ }
+
+ return TRUE;
+}
+
+
+- (void) dealloc
+{
+ [columns release];
+ [columnNames release];
+ [status release];
+ if (tableEncoding != nil) [tableEncoding release];
+
+ [super dealloc];
+}
+
+@end
diff --git a/Source/SPTableInfo.h b/Source/SPTableInfo.h
index 4088feaf..901b2573 100644
--- a/Source/SPTableInfo.h
+++ b/Source/SPTableInfo.h
@@ -27,6 +27,7 @@
IBOutlet id infoTable;
IBOutlet id tableList;
IBOutlet id tableListInstance;
+ IBOutlet id tableDataInstance;
IBOutlet id tableDocumentInstance;
NSMutableArray *info;
diff --git a/Source/SPTableInfo.m b/Source/SPTableInfo.m
index 7bab9322..80760278 100644
--- a/Source/SPTableInfo.m
+++ b/Source/SPTableInfo.m
@@ -25,6 +25,8 @@
#import "CMMCPConnection.h"
#import "CMMCPResult.h"
#import "TableDocument.h"
+#import "TablesList.h"
+#import "SPTableData.h"
#import "SPStringAdditions.h"
#import <MCPKit_bundled/MCPKit_bundled.h>
@@ -101,72 +103,64 @@ objectValueForTableColumn:(NSTableColumn *)aTableColumn
- (void)tableChanged:(NSNotification *)notification
{
- NSString *query;
- CMMCPResult *theResult;
- NSDictionary *theRow;
+ NSDictionary *tableStatus;
[info removeAllObjects];
[info addObject:@"TABLE INFORMATION"];
- if ([tableListInstance table])
+ if ([tableListInstance tableName])
{
- if ([(NSString *)[tableListInstance table] isEqualToString:@""]) {
+ if ([[tableListInstance tableName] isEqualToString:@""]) {
[info addObject:@"multiple tables"];
-
+
} else {
- // Notify that we are about to perform a query
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:self];
- // Create the query and get results
- query = [NSString stringWithFormat:@"SHOW TABLE STATUS LIKE '%@'", [tableListInstance table]];
-
- // This line triggers a bug when opening a new window. but only after having closed a window
- theResult = [[tableDocumentInstance sharedConnection] queryString:query];
+ // Retrieve the table status information via the data cache
+ tableStatus = [tableDataInstance statusValues];
// Check for errors
- if (![[[tableDocumentInstance sharedConnection] getLastErrorMessage] isEqualToString:@""]) {
+ if (![tableStatus count]) {
[info addObject:@"error occurred"];
return;
}
-
- // Process result
- theRow = [[theResult fetch2DResultAsType:MCPTypeDictionary] lastObject];
-
+
// Check for "Create_time" == NULL
- if (![[theRow objectForKey:@"Create_time"] isNSNull]) {
- // Setup our data formatter
+ if (![[tableStatus objectForKey:@"Create_time"] isNSNull]) {
+
+ // Set up our data formatter
NSDateFormatter *createDateFormatter = [[[NSDateFormatter alloc] init] autorelease];
[createDateFormatter setDateStyle:NSDateFormatterShortStyle];
[createDateFormatter setTimeStyle:NSDateFormatterNoStyle];
-
+
// Convert our string date from the result to an NSDate.
- NSDate *create_date = [NSDate dateWithNaturalLanguageString:[theRow objectForKey:@"Create_time"]];
-
+ NSDate *create_date = [NSDate dateWithNaturalLanguageString:[tableStatus objectForKey:@"Create_time"]];
+
// Add the creation date to the infoTable
[info addObject:[NSString stringWithFormat:@"created: %@", [createDateFormatter stringFromDate:create_date]]];
}
// Check for "Update_time" == NULL - InnoDB tables don't have an update time
- if (![[theRow objectForKey:@"Update_time"] isNSNull]) {
+ if (![[tableStatus objectForKey:@"Update_time"] isNSNull]) {
+
// Setup our data formatter
NSDateFormatter *updateDateFormatter = [[[NSDateFormatter alloc] init] autorelease];
[updateDateFormatter setDateStyle:NSDateFormatterShortStyle];
[updateDateFormatter setTimeStyle:NSDateFormatterNoStyle];
-
+
// Convert our string date from the result to an NSDate.
- NSDate *update_date = [NSDate dateWithNaturalLanguageString:[theRow objectForKey:@"Update_time"]];
-
+ NSDate *update_date = [NSDate dateWithNaturalLanguageString:[tableStatus objectForKey:@"Update_time"]];
+
// Add the update date to the infoTable
[info addObject:[NSString stringWithFormat:@"updated: %@", [updateDateFormatter stringFromDate:update_date]]];
}
- [info addObject:[NSString stringWithFormat:@"rows: %@", [theRow objectForKey:@"Rows"]]];
- [info addObject:[NSString stringWithFormat:@"size: %@", [NSString stringForByteSize:[[theRow objectForKey:@"Data_length"] intValue]]]];
- [info addObject:[NSString stringWithFormat:@"encoding: %@", [[[theRow objectForKey:@"Collation"] componentsSeparatedByString:@"_"] objectAtIndex:0]]];
- [info addObject:[NSString stringWithFormat:@"auto_increment: %@", [theRow objectForKey:@"Auto_increment"]]];
+ [info addObject:[NSString stringWithFormat:@"rows: ~%@", [tableStatus objectForKey:@"Rows"]]];
+ [info addObject:[NSString stringWithFormat:@"size: %@", [NSString stringForByteSize:[[tableStatus objectForKey:@"Data_length"] intValue]]]];
+ [info addObject:[NSString stringWithFormat:@"encoding: %@", [tableDataInstance tableEncoding]]];
- // Notify that we've finished performing the query
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:self];
+ if (![[tableStatus objectForKey:@"Auto_increment"] isNSNull]) {
+ [info addObject:[NSString stringWithFormat:@"auto_increment: %@", [tableStatus objectForKey:@"Auto_increment"]]];
+ }
}
}
diff --git a/Source/TableContent.h b/Source/TableContent.h
index 54ad2481..ac7a8249 100644
--- a/Source/TableContent.h
+++ b/Source/TableContent.h
@@ -35,7 +35,7 @@
IBOutlet id tableDocumentInstance;
IBOutlet id tablesListInstance;
- IBOutlet id tableSourceInstance;
+ IBOutlet id tableDataInstance;
IBOutlet id tableWindow;
IBOutlet CMCopyTable *tableContentView;
@@ -63,7 +63,6 @@
NSString *selectedTable;
NSMutableArray *fullResult, *filteredResult, *keys;
NSMutableDictionary *oldRow;
- NSArray *fieldNames, *fieldTypes;
NSString *compareType, *sortField;
BOOL isEditingRow, isEditingNewRow, isDesc, setLimit;
NSUserDefaults *prefs;
@@ -103,7 +102,7 @@
- (NSArray *)fetchResultAsArray:(CMMCPResult *)theResult;
- (BOOL)addRowToDB;
- (NSString *)argumentForRow:(int)row;
-- (BOOL)isBlobOrText:(NSString *)fieldType;
+- (BOOL)tableContainsBlobOrTextColumns;
- (NSString *)fieldListForQuery;
- (void)sheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(NSString *)contextInfo;
- (int)getNumberOfRows;
@@ -111,7 +110,7 @@
//tableView datasource methods
- (int)numberOfRowsInTableView:(NSTableView *)aTableView;
-- (id)tableView:(NSTableView *)aTableView
+- (id)tableView:(CMCopyTable *)aTableView
objectValueForTableColumn:(NSTableColumn *)aTableColumn
row:(int)rowIndex;
- (void)tableView:(NSTableView *)aTableView
diff --git a/Source/TableContent.m b/Source/TableContent.m
index 211ee350..04df0953 100644
--- a/Source/TableContent.m
+++ b/Source/TableContent.m
@@ -26,9 +26,9 @@
#import "TableContent.h"
#import "TableDocument.h"
#import "TablesList.h"
-#import "TableSource.h"
#import "CMImageView.h"
-
+#import "SPDataCellFormatter.h"
+#import "SPTableData.h"
@implementation TableContent
@@ -59,7 +59,8 @@
{
int i;
NSNumber *colWidth;
- NSArray *theColumns;
+ NSArray *theColumns, *columnNames;
+ NSDictionary *columnDefinition;
NSTableColumn *theCol;
NSString *query;
CMMCPResult *queryResult;
@@ -82,15 +83,17 @@
[tableContentView scrollRowToVisible:0];
[tableContentView scrollColumnToVisible:0];
+ // Remove existing columns from the table
+ theColumns = [tableContentView tableColumns];
+ while ([theColumns count]) {
+ [tableContentView removeTableColumn:[theColumns objectAtIndex:0]];
+ }
+
// If no table has been supplied, reset the view to a blank table and disabled elements
if ( [aTable isEqualToString:@""] || !aTable )
{
- // Empty the table and stored data arrays
- theColumns = [tableContentView tableColumns];
- while ([theColumns count]) {
- [tableContentView removeTableColumn:[theColumns objectAtIndex:0]];
- }
+ // Empty the stored data arrays
[fullResult removeAllObjects];
[filteredResult removeAllObjects];
[tableContentView reloadData];
@@ -126,40 +129,27 @@
// Post a notification that a query will be performed
[[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:self];
-
- // Make a fast query to get fieldNames and fieldTypes for this table. This is used to decide whether to preserve the
- // current filter/sort settings, and also used when grabbing all the data as part of the fieldListForQuery method.
- queryResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SELECT * FROM `%@` LIMIT 0", selectedTable]];
- if ( queryResult == nil ) {
- NSLog(@"Loading table columns for %@ failed", aTable);
- return;
- }
- fieldNames = [[queryResult fetchFieldNames] retain];
- fieldTypes = [[queryResult fetchTypesAsArray] retain];
+ // Retrieve the field names and types for this table from the data cache. This is used when requesting all data as part
+ // of the fieldListForQuery method, and also to decide whether or not to preserve the current filter/sort settings.
+ theColumns = [tableDataInstance columns];
+ columnNames = [tableDataInstance columnNames];
// Retrieve the number of rows in the table and initially mark all as being visible.
numRows = [self getNumberOfRows];
areShowingAllRows = YES;
-
- // Remove existing columns from the table
- theColumns = [tableContentView tableColumns];
- while ([theColumns count]) {
- [tableContentView removeTableColumn:[theColumns objectAtIndex:0]];
- }
// Add the new columns to the table
- for ( i = 0 ; i < [fieldNames count] ; i++ ) {
-
+ for ( i = 0 ; i < [theColumns count] ; i++ ) {
+ columnDefinition = [theColumns objectAtIndex:i];
+
// Set up the column
- theCol = [[NSTableColumn alloc] initWithIdentifier:[fieldNames objectAtIndex:i]];
+ theCol = [[NSTableColumn alloc] initWithIdentifier:[columnDefinition objectForKey:@"name"]];
+ [[theCol headerCell] setStringValue:[columnDefinition objectForKey:@"name"]];
[theCol setEditable:YES];
- [[theCol headerCell] setStringValue:[fieldNames objectAtIndex:i]];
-
// Set up the data cell depending on the column type
NSComboBoxCell *dataCell;
- if ( [[tableSourceInstance enumFields] objectForKey:[fieldNames objectAtIndex:i]] )
- {
+ if ([[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"enum"]) {
dataCell = [[[NSComboBoxCell alloc] initTextCell:@""] autorelease];
[dataCell setButtonBordered:NO];
[dataCell setBezeled:NO];
@@ -167,33 +157,29 @@
[dataCell setCompletes:YES];
[dataCell setControlSize:NSSmallControlSize];
[dataCell addItemWithObjectValue:@"NULL"];
- [dataCell addItemsWithObjectValues:[[tableSourceInstance enumFields] objectForKey:[fieldNames objectAtIndex:i]]];
- }
- else
- {
+ [dataCell addItemsWithObjectValues:[columnDefinition objectForKey:@"values"]];
+ } else {
dataCell = [[[NSTextFieldCell alloc] initTextCell:@""] autorelease];
}
[dataCell setEditable:YES];
-
+
+ // Set the line break mode and an NSFormatter subclass which truncates long strings for display
[dataCell setLineBreakMode:NSLineBreakByTruncatingTail];
-
+ [dataCell setFormatter:[[SPDataCellFormatter new] autorelease]];
+
// Set the data cell font according to the preferences
- if ( [prefs boolForKey:@"useMonospacedFonts"] )
- {
+ if ( [prefs boolForKey:@"useMonospacedFonts"] ) {
[dataCell setFont:[NSFont fontWithName:@"Monaco" size:10]];
- }
- else
- {
+ } else {
[dataCell setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
}
-
+
// Assign the data cell
[theCol setDataCell:dataCell];
// Set the width of this column to saved value if exists
- colWidth = [[[[prefs objectForKey:@"tableColumnWidths"] objectForKey:[NSString stringWithFormat:@"%@@%@", [tableDocumentInstance database], [tableDocumentInstance host]]] objectForKey:[tablesListInstance table]] objectForKey:[fieldNames objectAtIndex:i]];
- if ( colWidth )
- {
+ colWidth = [[[[prefs objectForKey:@"tableColumnWidths"] objectForKey:[NSString stringWithFormat:@"%@@%@", [tableDocumentInstance database], [tableDocumentInstance host]]] objectForKey:[tablesListInstance tableName]] objectForKey:[columnDefinition objectForKey:@"name"]];
+ if ( colWidth ) {
[theCol setWidth:[colWidth floatValue]];
}
@@ -203,8 +189,7 @@
}
// If the table has been reloaded and the previously selected sort column is still present, reselect it.
- if (preserveCurrentView && [fieldNames containsObject:sortField])
- {
+ if (preserveCurrentView && [columnNames containsObject:sortField]) {
theCol = [tableContentView tableColumnWithIdentifier:sortField];
[tableContentView setHighlightedTableColumn:theCol];
if ( isDesc ) {
@@ -212,18 +197,15 @@
} else {
[tableContentView setIndicatorImage:[NSImage imageNamed:@"NSAscendingSortIndicator"] inTableColumn:theCol];
}
- }
// Otherwise, clear sorting
- else
- {
+ } else {
sortField = nil;
isDesc = NO;
}
// Preserve the stored filter settings if appropriate
- if (preserveCurrentView && [fieldField isEnabled])
- {
+ if (preserveCurrentView && [fieldField isEnabled]) {
preservedFilterField = [NSString stringWithString:[[fieldField selectedItem] title]];
preservedFilterComparison = [NSString stringWithString:[[compareField selectedItem] title]];
preservedFilterValue = [NSString stringWithString:[argumentField stringValue]];
@@ -232,7 +214,7 @@
// Enable and initialize filter fields (with tags for position of menu item and field position)
[fieldField setEnabled:YES];
[fieldField removeAllItems];
- [fieldField addItemsWithTitles:fieldNames];
+ [fieldField addItemsWithTitles:columnNames];
for ( i = 0 ; i < [fieldField numberOfItems] ; i++ ) {
[[fieldField itemAtIndex:i] setTag:i];
}
@@ -243,23 +225,20 @@
[filterButton setEnabled:YES];
// Restore preserved filter settings if appropriate and valid
- if (preserveCurrentView && preservedFilterField != nil && [fieldField itemWithTitle:preservedFilterField])
- {
+ if (preserveCurrentView && preservedFilterField != nil && [fieldField itemWithTitle:preservedFilterField]) {
[fieldField selectItemWithTitle:preservedFilterField];
[self setCompareTypes:self];
}
if (preserveCurrentView && preservedFilterField != nil
&& [fieldField itemWithTitle:preservedFilterField]
- && [compareField itemWithTitle:preservedFilterComparison])
- {
+ && [compareField itemWithTitle:preservedFilterComparison]) {
[compareField selectItemWithTitle:preservedFilterComparison];
[argumentField setStringValue:preservedFilterValue];
areShowingAllRows = NO;
}
// Enable or disable the limit fields according to preference setting
- if ( [prefs boolForKey:@"limitRows"] )
- {
+ if ( [prefs boolForKey:@"limitRows"] ) {
// Attempt to preserve the limit value if it's still valid
if (!preserveCurrentView || [limitRowsField intValue] < 1 || [limitRowsField intValue] >= numRows) {
@@ -272,9 +251,7 @@
[prefs integerForKey:@"limitRowsValue"]]];
if ([prefs integerForKey:@"limitRowsValue"] < numRows)
areShowingAllRows = NO;
- }
- else
- {
+ } else {
[limitRowsField setEnabled:NO];
[limitRowsButton setEnabled:NO];
[limitRowsStepper setEnabled:NO];
@@ -287,19 +264,15 @@
[copyButton setEnabled:YES];
[removeButton setEnabled:YES];
-
// Perform the data query and store the result as an array containing a dictionary per result row
query = [NSString stringWithFormat:@"SELECT %@ FROM `%@`", [self fieldListForQuery], selectedTable];
- if ( sortField )
- {
+ if ( sortField ) {
query = [NSString stringWithFormat:@"%@ ORDER BY `%@`", query, sortField];
if ( isDesc )
query = [query stringByAppendingString:@" DESC"];
}
- if ( [prefs boolForKey:@"limitRows"] )
- {
- if ( [limitRowsField intValue] <= 0 )
- {
+ if ( [prefs boolForKey:@"limitRows"] ) {
+ if ( [limitRowsField intValue] <= 0 ) {
[limitRowsField setStringValue:@"1"];
}
query = [query stringByAppendingString:
@@ -329,24 +302,29 @@
[[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:self];
}
+
/*
- Reloads the current table data, performing a new SQL query. Now attempts to preserve sort order, filters, and viewport.
+ * Reloads the current table data, performing a new SQL query. Now attempts to preserve sort order, filters, and viewport.
*/
- (IBAction)reloadTable:(id)sender
{
// Store the current viewport location
NSRect viewRect = [tableContentView visibleRect];
+ // Clear the table data column cache
+ [tableDataInstance resetColumnData];
+
[self loadTable:selectedTable];
// Restore the viewport
[tableContentView scrollRectToVisible:viewRect];
}
-- (IBAction)reloadTableValues:(id)sender
+
/*
- reload the table values without reconfiguring the tableView (with filter and limit if set)
+ * Reload the table values without reconfiguring the tableView (with filter and limit if set)
*/
+- (IBAction)reloadTableValues:(id)sender
{
NSString *queryString;
CMMCPResult *queryResult;
@@ -403,8 +381,9 @@
[[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:self];
}
-/**
- * filter the table with arguments given by the user
+
+/*
+ * Filter the table with arguments given by the user
*/
- (IBAction)filterTable:(id)sender
{
@@ -617,25 +596,32 @@
}
-//edit methods
-- (IBAction)addRow:(id)sender
+#pragma mark Edit methods
+
/*
- adds an empty row to the table-array and goes into edit mode
+ * Adds an empty row to the table-array and goes into edit mode
*/
+- (IBAction)addRow:(id)sender
{
- NSMutableDictionary *newRow = [NSMutableDictionary dictionary];
+ NSArray *columns;
+ NSMutableDictionary *column, *newRow = [NSMutableDictionary dictionary];
int i;
-
+
if ( ![self selectionShouldChangeInTableView:nil] )
return;
-
- for ( i = 0 ; i < [fieldNames count] ; i++ ) {
- // [newRow setObject:[prefs stringForKey:@"nullValue"] forKey:[fieldNames objectAtIndex:i]];
- [newRow setObject:[tableSourceInstance defaultValueForField:[fieldNames objectAtIndex:i]]
- forKey:[fieldNames objectAtIndex:i]];
+
+ columns = [[NSArray alloc] initWithArray:[tableDataInstance columns]];
+ for ( i = 0 ; i < [columns count] ; i++ ) {
+ column = [columns objectAtIndex:i];
+ if ([column objectForKey:@"default"] == nil) {
+ [newRow setObject:[prefs stringForKey:@"nullValue"] forKey:[column objectForKey:@"name"]];
+ } else {
+ [newRow setObject:[column objectForKey:@"default"] forKey:[column objectForKey:@"name"]];
+ }
}
[filteredResult addObject:newRow];
-
+ [columns release];
+
isEditingRow = YES;
isEditingNewRow = YES;
[tableContentView reloadData];
@@ -905,7 +891,7 @@
//load table if not already done
if ( ![tablesListInstance contentLoaded] ) {
- [self loadTable:(NSString *)[tablesListInstance table]];
+ [self loadTable:[tablesListInstance tableName]];
}
tableColumns = [tableContentView tableColumns];
@@ -966,28 +952,16 @@
*/
- (IBAction)setCompareTypes:(id)sender
{
- NSArray *stringFields = [NSArray arrayWithObjects:@"varstring", @"string", @"tinyblob", @"blob", @"mediumblob", @"longblob", @"set", @"enum", nil];
NSArray *stringTypes = [NSArray arrayWithObjects:NSLocalizedString(@"is", @"popup menuitem for field IS value"), NSLocalizedString(@"is not", @"popup menuitem for field IS NOT value"), NSLocalizedString(@"contains", @"popup menuitem for field CONTAINS value"), NSLocalizedString(@"contains not", @"popup menuitem for field CONTAINS NOT value"), @"IN", nil];
- NSArray *numberFields = [NSArray arrayWithObjects:@"tiny", @"short", @"long", @"int24", @"longlong", @"decimal", @"float", @"double", nil];
NSArray *numberTypes = [NSArray arrayWithObjects:@"=", @"≠", @">", @"<", @"≥", @"≤", @"IN", nil];
- NSArray *dateFields = [NSArray arrayWithObjects:@"timestamp", @"date", @"time", @"datetime", @"year", nil];
NSArray *dateTypes = [NSArray arrayWithObjects:NSLocalizedString(@"is", @"popup menuitem for field IS value"), NSLocalizedString(@"is not", @"popup menuitem for field IS NOT value"), NSLocalizedString(@"older than", @"popup menuitem for field OLDER THAN value"), NSLocalizedString(@"younger than", @"popup menuitem for field YOUNGER THAN value"), NSLocalizedString(@"older than or equal to", @"popup menuitem for field OLDER THAN OR EQUAL TO value"), NSLocalizedString(@"younger than or equal to", @"popup menuitem for field YOUNGER THAN OR EQUAL TO value"), nil];
- NSString *fieldType = [NSString stringWithString:[fieldTypes objectAtIndex:[[fieldField selectedItem] tag]]];
+ NSString *fieldTypeGrouping = [NSString stringWithString:[[tableDataInstance columnWithName:[[fieldField selectedItem] title]] objectForKey:@"typegrouping"]];
int i;
[compareField removeAllItems];
- // Why do we get "string" for enum fields? (error in framework?)
- if ( [stringFields containsObject:fieldType] ) {
- [compareField addItemsWithTitles:stringTypes];
- compareType = @"string";
- // [argumentField setFormatter:nil];
- } else if ( [numberFields containsObject:fieldType] ) {
- [compareField addItemsWithTitles:numberTypes];
- compareType = @"number";
- // [argumentField setFormatter:numberFormatter];
- } else if ( [dateFields containsObject:fieldType] ) {
+ if ( [fieldTypeGrouping isEqualToString:@"date"] ) {
[compareField addItemsWithTitles:dateTypes];
compareType = @"date";
/*
@@ -1008,8 +982,22 @@
[argumentField setFormatter:[[NSDateFormatter alloc] initWithDateFormat:@"%Y" allowNaturalLanguage:YES]];
}
*/
- } else {
- NSLog(@"ERROR: unknown type for comparision: %@", fieldType);
+
+ // TODO: A bug in the framework previously meant enum fields had to be treated as string fields for the purposes
+ // of comparison - this can now be split out to support additional comparison fucntionality if desired.
+ } else if ([fieldTypeGrouping isEqualToString:@"string"] || [fieldTypeGrouping isEqualToString:@"binary"]
+ || [fieldTypeGrouping isEqualToString:@"textdata"] || [fieldTypeGrouping isEqualToString:@"blobdata"]
+ || [fieldTypeGrouping isEqualToString:@"enum"]) {
+ [compareField addItemsWithTitles:stringTypes];
+ compareType = @"string";
+ // [argumentField setFormatter:nil];
+ } else if ([fieldTypeGrouping isEqualToString:@"bit"] || [fieldTypeGrouping isEqualToString:@"integer"]
+ || [fieldTypeGrouping isEqualToString:@"float"]) {
+ [compareField addItemsWithTitles:numberTypes];
+ compareType = @"number";
+ // [argumentField setFormatter:numberFormatter];
+ } else {
+ NSLog(@"ERROR: unknown type for comparision: %@, in %@", [[tableDataInstance columnWithName:[[fieldField selectedItem] title]] objectForKey:@"type"], fieldTypeGrouping);
}
// Add IS NULL and IS NOT NULL as they should always be available
@@ -1044,57 +1032,69 @@
[limitRowsStepper setIntValue:0];
}
-- (NSArray *)fetchResultAsArray:(CMMCPResult *)theResult
/*
- fetches the result as an array with a dictionary for each row in it
+ * Fetches the result as an array with a dictionary for each row in it
*/
+- (NSArray *)fetchResultAsArray:(CMMCPResult *)theResult
{
- NSMutableArray *tempResult = [NSMutableArray array];
+ NSArray *columns;
+ NSString *columnTypeGrouping;
+ NSMutableArray *columnsBlobStatus, *tempResult = [NSMutableArray array];
NSDictionary *tempRow;
NSMutableDictionary *modifiedRow = [NSMutableDictionary dictionary];
NSEnumerator *enumerator;
id key;
- int i,j;
-
+ int i, j;
+ BOOL columnIsBlobOrText;
+
+ columns = [tableDataInstance columns];
+ columnsBlobStatus = [[NSMutableArray alloc] init];
+ for ( i = 0 ; i < [columns count]; i++ ) {
+ columnTypeGrouping = [[columns objectAtIndex:i] objectForKey:@"typegrouping"];
+ columnIsBlobOrText = ([columnTypeGrouping isEqualToString:@"textdata"] || [columnTypeGrouping isEqualToString:@"blobdata"]);
+ [columnsBlobStatus addObject:[NSNumber numberWithBool:columnIsBlobOrText]];
+ }
+
if ([theResult numOfRows]) [theResult dataSeek:0];
for ( i = 0 ; i < [theResult numOfRows] ; i++ ) {
tempRow = [theResult fetchRowAsDictionary];
enumerator = [tempRow keyEnumerator];
+
while ( key = [enumerator nextObject] ) {
if ( [[tempRow objectForKey:key] isMemberOfClass:[NSNull class]] ) {
[modifiedRow setObject:[prefs stringForKey:@"nullValue"] forKey:key];
- /*
- //NSData objects now decoded in tableView:objectValueForTableColumn:row
- //object in result remains a NSData object
- } else if ( [[tempRow objectForKey:key] isKindOfClass:[NSData class]] ) {
- [modifiedRow setObject:[[NSString alloc] initWithData:[tempRow objectForKey:key] encoding:[mySQLConnection encoding]]
- forKey:key];
- */
} else {
[modifiedRow setObject:[tempRow objectForKey:key] forKey:key];
}
- //add values for hidden blob and text fields
- if ( [prefs boolForKey:@"dontShowBlob"] ) {
- for ( j = 0 ; j < [fieldTypes count] ; j++ ) {
- if ( [self isBlobOrText:[fieldTypes objectAtIndex:j]] ) {
- [modifiedRow setObject:NSLocalizedString(@"- blob or text -", @"value shown for hidden blob and text fields") forKey:[fieldNames objectAtIndex:j]];
- }
+ }
+
+ // Add values for hidden blob and text fields if appropriate
+ if ( [prefs boolForKey:@"dontShowBlob"] ) {
+ for ( j = 0 ; j < [columns count] ; j++ ) {
+ if ( [[columnsBlobStatus objectAtIndex:j] boolValue] ) {
+ [modifiedRow setObject:NSLocalizedString(@"- blob or text -", @"value shown for hidden blob and text fields") forKey:[[columns objectAtIndex:j] objectForKey:@"name"]];
}
}
}
+
[tempResult addObject:[NSMutableDictionary dictionaryWithDictionary:modifiedRow]];
}
+
+ [columnsBlobStatus release];
+
return tempResult;
}
-- (BOOL)addRowToDB
+
/*
- tries to write row to mysql-db
- returns YES if row written to db, otherwies NO
- returns YES if no row is beeing edited and nothing has to be written to db
+ * Tries to write a new row to the database.
+ * Returns YES if row is written to database, otherwise NO; also returns YES if no row
+ * is being edited and nothing has to be written to the database.
*/
+- (BOOL)addRowToDB
{
int rowIndex = [tableContentView selectedRow];
+ NSArray *theColumns, *columnNames;
NSMutableArray *fieldValues = [[NSMutableArray alloc] init];
NSMutableString *queryString;
NSString *query;
@@ -1108,27 +1108,34 @@
[fieldValues release];
return YES;
}
+
+ // Retrieve the field names and types for this table from the data cache. This is used when requesting all data as part
+ // of the fieldListForQuery method, and also to decide whether or not to preserve the current filter/sort settings.
+ theColumns = [tableDataInstance columns];
+ columnNames = [tableDataInstance columnNames];
- //get field values
- for ( i=0 ; i < [fieldNames count] ; i++) {
- rowObject = [[filteredResult objectAtIndex:rowIndex] objectForKey:[fieldNames objectAtIndex:i]];
- //convert the object to a string (here we can add special treatment for date-, number- and data-fields)
- if ( [[rowObject description] isEqualToString:[prefs stringForKey:@"nullValue"]] ||
- ([rowObject isMemberOfClass:[NSString class]] && [[rowObject description] isEqualToString:@""]) ) {
+ // Get the field values
+ for ( i = 0 ; i < [columnNames count] ; i++ ) {
+ rowObject = [[filteredResult objectAtIndex:rowIndex] objectForKey:[columnNames objectAtIndex:i]];
+
+ // Convert the object to a string (here we can add special treatment for date-, number- and data-fields)
+ if ( [[rowObject description] isEqualToString:[prefs stringForKey:@"nullValue"]]
+ || ([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
// problem: when a number isn't set, sequel-pro enters 0
// -> second if argument isn't necessary!
[rowValue setString:@"NULL"];
} else {
+
+ // I don't believe any of these class matches are ever met at present.
if ( [rowObject isKindOfClass:[NSCalendarDate class]] ) {
- // [rowValue setString:[NSString stringWithFormat:@"\"%@\"", [mySQLConnection prepareString:[rowObject description]]]];
[rowValue setString:[NSString stringWithFormat:@"'%@'", [mySQLConnection prepareString:[rowObject description]]]];
} else if ( [rowObject isKindOfClass:[NSNumber class]] ) {
[rowValue setString:[rowObject stringValue]];
} else if ( [rowObject isKindOfClass:[NSData class]] ) {
[rowValue setString:[NSString stringWithFormat:@"X'%@'", [mySQLConnection prepareBinaryData:rowObject]]];
} else {
- // [rowValue setString:[NSString stringWithFormat:@"\"%@\"", [mySQLConnection prepareString:[rowObject description]]]];
if ( [[rowObject description] isEqualToString:@"CURRENT_TIMESTAMP"] ) {
[rowValue setString:@"CURRENT_TIMESTAMP"];
} else {
@@ -1136,44 +1143,31 @@
}
}
}
- //escape special characters -> now escaped by framework
- /*
- for ( j = 0 ; j < [rowValue length] ; j++ ) {
- if ( [rowValue characterAtIndex:j] == '\\' ) {
- [rowValue insertString:@"\\" atIndex:j];
- j++;
- } else if ( [rowValue characterAtIndex:j] == '"' ) {
- [rowValue insertString:@"\\" atIndex:j];
- j++;
- }
- }
- */
[fieldValues addObject:[NSString stringWithString:rowValue]];
}
+ // Use INSERT syntax when creating new rows
if ( isEditingNewRow ) {
- //INSERT syntax
queryString = [NSString stringWithFormat:@"INSERT INTO `%@` (`%@`) VALUES (%@)",
- selectedTable, [fieldNames componentsJoinedByString:@"`,`"], [fieldValues componentsJoinedByString:@","]];
+ selectedTable, [columnNames componentsJoinedByString:@"`,`"], [fieldValues componentsJoinedByString:@","]];
+
+ // Use UPDATE syntax otherwise
} else {
- //UPDATE syntax
queryString = [NSMutableString stringWithFormat:@"UPDATE `%@` SET ", selectedTable];
- for ( i = 0 ; i < [fieldNames count] ; i++ ) {
+ for ( i = 0 ; i < [columnNames count] ; i++ ) {
if ( i > 0 ) {
[queryString appendString:@", "];
}
[queryString appendString:[NSString stringWithFormat:@"`%@`=%@",
- [fieldNames objectAtIndex:i], [fieldValues objectAtIndex:i]]];
+ [columnNames objectAtIndex:i], [fieldValues objectAtIndex:i]]];
}
- [fieldValues release];
[queryString appendString:[NSString stringWithFormat:@" WHERE %@", [self argumentForRow:-2]]];
}
[mySQLConnection queryString:queryString];
+ [fieldValues release];
- //NSLog( @"%@", queryString );
-
+ // If no rows have been changed, show error if appropriate.
if ( ![mySQLConnection affectedRows] ) {
- //no rows changed
if ( [prefs boolForKey:@"showError"] ) {
NSBeginAlertSheet(NSLocalizedString(@"Warning", @"warning"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil,
NSLocalizedString(@"The row was not written to the MySQL database. You probably haven't changed anything.\nReload the table to be sure that the row exists and use a primary key for your table.\n(This error can be turned off in the preferences.)", @"message of panel when no rows have been affected after writing to the db"));
@@ -1185,39 +1179,39 @@
isEditingNewRow = NO;
[tableDocumentInstance showErrorInConsole:[NSString stringWithFormat:NSLocalizedString(@"/* WARNING %@ No rows have been affected */\n", @"warning shown in the console when no rows have been affected after writing to the db"), currentTime]];
return YES;
+
+ // On success...
} else if ( [[mySQLConnection getLastErrorMessage] isEqualToString:@""] ) {
- //added new row with success
isEditingRow = NO;
+
+ // New row created successfully
if ( isEditingNewRow ) {
if ( [prefs boolForKey:@"reloadAfterAdding"] ) {
[self reloadTableValues:self];
- // if ( sortField )
[tableContentView deselectAll:self];
} else {
- //set insertId for fields with auto_increment
- queryResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW COLUMNS FROM `%@`", selectedTable]];
- if ([queryResult numOfRows]) [queryResult dataSeek:0];
- for ( i = 0 ; i < [queryResult numOfRows] ; i++ ) {
- rowObject = [queryResult fetchRowAsDictionary];
- if ( [[rowObject objectForKey:@"Extra"] isEqualToString:@"auto_increment"] ) {
+
+ // Set the insertId for fields with auto_increment
+ for ( i = 0; i < [theColumns count] ; i++ ) {
+ if ([[theColumns objectAtIndex:i] objectForKey:@"autoincrement"]) {
[[filteredResult objectAtIndex:rowIndex] setObject:[NSNumber numberWithLong:[mySQLConnection insertId]]
- forKey:[rowObject objectForKey:@"Field"]];
+ forKey:[columnNames objectAtIndex:i]];
}
}
[fullResult addObject:[filteredResult objectAtIndex:rowIndex]];
}
isEditingNewRow = NO;
+
+ // Existing row edited successfully
} else {
- //updated row with success
if ( [prefs boolForKey:@"reloadAfterEditing"] ) {
[self reloadTableValues:self];
- // if ( sortField )
[tableContentView deselectAll:self];
+
+ // TODO: this probably needs looking at... it's reloading it all itself?
} else {
- // query = [@"SELECT * FROM " stringByAppendingString:selectedTable];
query = [NSString stringWithFormat:@"SELECT %@ FROM `%@`", [self fieldListForQuery], selectedTable];
if ( sortField ) {
- // query = [query stringByAppendingString:[NSString stringWithFormat:@" ORDER BY `%@`", sortField]];
query = [NSString stringWithFormat:@"%@ ORDER BY `%@`", query, sortField];
if ( isDesc )
query = [query stringByAppendingString:@" DESC"];
@@ -1231,51 +1225,43 @@
[limitRowsField intValue]-1, [prefs integerForKey:@"limitRowsValue"]]];
}
queryResult = [mySQLConnection queryString:query];
- // [fullResult setArray:[[self fetchResultAsArray:queryResult] retain]];
[fullResult setArray:[self fetchResultAsArray:queryResult]];
}
}
return YES;
+
+ // Report errors which have occurred
} else {
- //error in mysql-query
NSBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), NSLocalizedString(@"Cancel", @"cancel button"), nil, tableWindow, self, @selector(sheetDidEnd:returnCode:contextInfo:), nil, @"addrow",
[NSString stringWithFormat:NSLocalizedString(@"Couldn't write row.\nMySQL said: %@", @"message of panel when error while adding row to db"), [mySQLConnection getLastErrorMessage]]);
return NO;
}
}
-- (NSString *)argumentForRow:(int)row
/*
- returns the WHERE argument to identify a row
- if row is -2, it uses the oldRow
- if there is one, it uses the primary key, otherwise uses all fields as argument and sets LIMIT to 1
+ * Returns the WHERE argument to identify a row.
+ * If "row" is -2, it uses the oldRow.
+ * Uses the primary key if available, otherwise uses all fields as argument and sets LIMIT to 1
*/
+- (NSString *)argumentForRow:(int)row
{
CMMCPResult *theResult;
NSDictionary *theRow;
id tempValue;
NSMutableString *value = [NSMutableString string];
NSMutableString *argument = [NSMutableString string];
+ NSString *columnType;
+ NSArray *columnNames;
int i,j;
- NSEnumerator *enumerator;
- id type;
- BOOL blob = NO;
- NSArray *numberFields = [NSArray arrayWithObjects:@"tiny", @"short", @"long", @"int24", @"longlong", @"decimal", @"float", @"double", nil];
if ( row == -1 )
return @"";
- //get primary key if there is one
- /*
- theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW INDEX FROM `%@`", selectedTable]];
- if ([theResult numOfRows]) [theResult dataSeek:0];
- for ( i = 0 ; i < [theResult numOfRows] ; i++ ) {
- theRow = [theResult fetchRowAsDictionary];
- if ( [[theRow objectForKey:@"Key_name"] isEqualToString:@"PRIMARY"] ) {
- [keys addObject:[theRow objectForKey:@"Column_name"]];
- }
- }
- */
+ // Retrieve the field names for this table from the data cache. This is used when requesting all data as part
+ // of the fieldListForQuery method, and also to decide whether or not to preserve the current filter/sort settings.
+ columnNames = [tableDataInstance columnNames];
+
+ // Get the primary key if there is one
if ( !keys ) {
setLimit = NO;
keys = [[NSMutableArray alloc] init];
@@ -1289,18 +1275,14 @@
}
}
+ // If there is no primary key, all the fields are used in the argument.
if ( ![keys count] ) {
- //if there is no primary key, take all fields as argument
- //here we have a problem when dontShowBlob == YES (we don't have the right values to use in the WHERE statement)
- [keys setArray:fieldNames];
+ [keys setArray:columnNames];
setLimit = YES;
- enumerator = [fieldTypes objectEnumerator];
- while ( (type = [enumerator nextObject]) ) {
- if ( [self isBlobOrText:type] ) {
- blob = YES;
- }
- }
- if ( [prefs boolForKey:@"dontShowBlob"] && blob ) {
+
+ // When the option to not show blob or text options is set, we have a problem - we don't have
+ // the right values to use in the WHERE statement. Throw an error if this is the case.
+ if ( [prefs boolForKey:@"dontShowBlob"] && [self tableContainsBlobOrTextColumns] ) {
NSBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil,
NSLocalizedString(@"You can't hide blob and text fields when working with tables without index.", @"message of panel when trying to edit tables without index and with hidden blob/text fields"));
[keys removeAllObjects];
@@ -1308,26 +1290,34 @@
return @"";
}
}
+
+ // Walk through the keys list constructing the argument list
for ( i = 0 ; i < [keys count] ; i++ ) {
if ( i )
[argument appendString:@" AND "];
+
+ // Use the selected row if appropriate
if ( row >= 0 ) {
- //use selected row
tempValue = [[filteredResult objectAtIndex:row] objectForKey:[keys objectAtIndex:i]];
+
+ // Otherwise use the oldRow
} else {
- //use oldRow
tempValue = [oldRow objectForKey:[keys objectAtIndex:i]];
}
+
if ( [tempValue isKindOfClass:[NSData class]] ) {
- [value setString:[[NSString alloc] initWithData:tempValue encoding:[mySQLConnection encoding]]];
+ NSString *tmpString = [[NSString alloc] initWithData:tempValue encoding:[mySQLConnection encoding]];
+ [value setString:[NSString stringWithString:tmpString]];
+ [tmpString release];
} else {
[value setString:[tempValue description]];
}
-
+
if ( [value isEqualToString:[prefs stringForKey:@"nullValue"]] ) {
- [value setString:@"NULL"];
+ [argument appendString:[NSString stringWithFormat:@"`%@` IS NULL", [keys objectAtIndex:i]]];
} else {
- //escape special characters (in WHERE statement!)
+
+ // Escape special characters (in WHERE statement!)
for ( j = 0 ; j < [value length] ; j++ ) {
if ( [value characterAtIndex:j] == '\\' ) {
[value insertString:@"\\" atIndex:j];
@@ -1342,13 +1332,10 @@
j++;
}
}
- // [value setString:[NSString stringWithFormat:@"\"%@\"", value]];
[value setString:[NSString stringWithFormat:@"'%@'", value]];
- }
- if ( [value isEqualToString:@"NULL"] ) {
- [argument appendString:[NSString stringWithFormat:@"`%@` IS NULL", [keys objectAtIndex:i]]];
- } else {
- if ( [numberFields containsObject:[fieldTypes objectAtIndex:[fieldNames indexOfObject:[keys objectAtIndex:i]]]] ) {
+
+ columnType = [[tableDataInstance columnWithName:[keys objectAtIndex:i]] objectForKey:@"typegrouping"];
+ if ( [columnType isEqualToString:@"integer"] || [columnType isEqualToString:@"float"] ) {
[argument appendString:[NSString stringWithFormat:@"`%@` = %@", [keys objectAtIndex:i], value]];
} else {
[argument appendString:[NSString stringWithFormat:@"`%@` LIKE %@", [keys objectAtIndex:i], value]];
@@ -1360,37 +1347,50 @@
return argument;
}
-- (BOOL)isBlobOrText:(NSString *)fieldType
+
/*
- returns YES if fieldType is some kind of blob or text. afaik the type of this fields is always blob, but better we test it...
- it would be nice to know if it is blob or text, but mysql doesn't want to tell it...
+ * Returns YES if the table contains any columns which are of any of the blob or text types,
+ * NO otherwise.
*/
+- (BOOL)tableContainsBlobOrTextColumns
{
- if ( [fieldType isEqualToString:@"tinyblob"] || [fieldType isEqualToString:@"blob"] ||
- [fieldType isEqualToString:@"mediumblob"] || [fieldType isEqualToString:@"longblob"] ) {
- return YES;
- } else {
- return NO;
+ int i;
+ NSArray *tableColumns = [tableDataInstance tableColumns];
+ NSString *columnTypeGrouping;
+
+ for ( i = 0 ; i < [tableColumns count]; i++ ) {
+ columnTypeGrouping = [[tableColumns objectAtIndex:i] objectForKey:@"typegrouping"];
+ if ([columnTypeGrouping isEqualToString:@"textdata"] || [columnTypeGrouping isEqualToString:@"blobdata"]) {
+ return YES;
+ }
}
+
+ return NO;
}
-- (NSString *)fieldListForQuery
/*
- returns * if dontShowBlob == NO
- returns a comma-separated list of all fields which aren't of type blob or text if dontShowBlob == YES
+ * Returns a string controlling which fields to retrieve for a query. Returns * (all fields) if the preferences
+ * option dontShowBlob isn't set; otherwise, returns a comma-separated list of all non-blob/text fields.
*/
+- (NSString *)fieldListForQuery
{
int i;
NSMutableArray *fields = [NSMutableArray array];
+ NSArray *columns = [tableDataInstance columns];
+ NSArray *columnNames = [tableDataInstance columnNames];
+ NSString *columnTypeGrouping;
if ( [prefs boolForKey:@"dontShowBlob"] ) {
- for ( i = 0 ; i < [fieldTypes count] ; i++ ) {
- if ( ![self isBlobOrText:[fieldTypes objectAtIndex:i]] ) {
- [fields addObject:[fieldNames objectAtIndex:i]];
+ for ( i = 0 ; i < [columnNames count] ; i++ ) {
+ columnTypeGrouping = [[columns objectAtIndex:i] objectForKey:@"typegrouping"];
+ if (![columnTypeGrouping isEqualToString:@"textdata"] && ![columnTypeGrouping isEqualToString:@"blobdata"]) {
+ [fields addObject:[columnNames objectAtIndex:i]];
}
}
+
+ // Always select at least one field - the first if there are no non-blob fields.
if ( [fields count] == 0 ) {
- return [NSString stringWithFormat:@"`%@`", [fieldNames objectAtIndex:0]];
+ return [NSString stringWithFormat:@"`%@`", [columnNames objectAtIndex:0]];
} else {
return [NSString stringWithFormat:@"`%@`", [fields componentsJoinedByString:@"`,`"]];
}
@@ -1543,25 +1543,22 @@
return [filteredResult count];
}
-- (id)tableView:(NSTableView *)aTableView
+- (id)tableView:(CMCopyTable *)aTableView
objectValueForTableColumn:(NSTableColumn *)aTableColumn
row:(int)rowIndex
{
id theRow, theValue;
-
+
theRow = [filteredResult objectAtIndex:rowIndex];
theValue = [theRow objectForKey:[aTableColumn identifier]];
-
+
+ // Convert data objects to their string representation in the current encoding.
if ( [theValue isKindOfClass:[NSData class]] ) {
- theValue = [[NSString alloc] initWithData:theValue encoding:[mySQLConnection encoding]];
- //show only first 50 characters to speed up interface (but return everything when this method is used to return the current result)
- // if ( ([theValue length] > 100) && aTableView ) {
+ NSString *dataRepresentation = [[NSString alloc] initWithData:theValue encoding:[mySQLConnection encoding]];
+ theValue = [NSString stringWithString:dataRepresentation];
+ [dataRepresentation release];
}
-// if ( ([(NSString *)theValue length] > 100) && aTableView ) {
-// theValue = [NSString stringWithFormat:@"%@(...)", [theValue substringToIndex:100]];
-// }
-
return theValue;
}
@@ -1716,7 +1713,7 @@ objectValueForTableColumn:(NSTableColumn *)aTableColumn
NSMutableDictionary *tableColumnWidths;
NSString *database = [NSString stringWithFormat:@"%@@%@", [tableDocumentInstance database], [tableDocumentInstance host]];
- NSString *table = (NSString *)[tablesListInstance table];
+ NSString *table = [tablesListInstance tableName];
// get tableColumnWidths object
if ( [prefs objectForKey:@"tableColumnWidths"] != nil ) {
@@ -1741,35 +1738,26 @@ objectValueForTableColumn:(NSTableColumn *)aTableColumn
[prefs setObject:tableColumnWidths forKey:@"tableColumnWidths"];
}
-- (BOOL)tableView:(NSTableView *)aTableView shouldEditTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
/*
- opens sheet if multipleLineEditingButton is clicked or field is a hidden blob or text field
+ * Confirm whether to allow editing of a row. Returns YES by default, unless the multipleLineEditingButton is in
+ * the ON state, or for blob or text fields - in those cases opens a sheet for editing instead and returns NO.
*/
+- (BOOL)tableView:(NSTableView *)aTableView shouldEditTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
{
+ int code;
+ NSString *columnTypeGrouping, *query;
NSEnumerator *enumerator;
- id type;
- BOOL blob = NO;
NSDictionary *tempRow;
NSMutableDictionary *modifiedRow = [NSMutableDictionary dictionary];
- id key;
- int code;
- NSString *query;
+ id key, theValue;
CMMCPResult *tempResult;
- id theValue;
- BOOL columnIsBlob = NO;
- // int i;
- // NSArray *columns = [aTableView tableColumns];
+ BOOL columnIsBlobOrText = NO;
+ // If not isEditingRow and the preference value for not showing blobs is set, check whether the row contains any blobs.
if ( [prefs boolForKey:@"dontShowBlob"] && !isEditingRow ) {
- //get all row values if dontShowBlob == YES and table contains blob or text field and isEditingRow = NO
- enumerator = [fieldTypes objectEnumerator];
- while ( (type = [enumerator nextObject]) ) {
- if ( [self isBlobOrText:type] ) {
- blob = YES;
- }
- }
-
- if ( blob ) {
+
+ // If the table does contain blob or text fields, load the values ready for editing.
+ if ( [self tableContainsBlobOrTextColumns] ) {
query = [NSString stringWithFormat:@"SELECT * FROM `%@` WHERE %@",
selectedTable, [self argumentForRow:[tableContentView selectedRow]]];
tempResult = [mySQLConnection queryString:query];
@@ -1787,28 +1775,19 @@ objectValueForTableColumn:(NSTableColumn *)aTableColumn
[modifiedRow setObject:[tempRow objectForKey:key] forKey:key];
}
}
- [filteredResult replaceObjectAtIndex:rowIndex
- withObject:[NSMutableDictionary dictionaryWithDictionary:modifiedRow]];
+ [filteredResult replaceObjectAtIndex:rowIndex withObject:[NSMutableDictionary dictionaryWithDictionary:modifiedRow]];
[tableContentView reloadData];
}
}
- /*
- // find the column we're trying to edit
- for ( i = 0; i < [columns count]; i++ ) {
- if ( [columns objectAtIndex:i] == aTableColumn ) {
- // this flag will let us determine if we should "force" multi-line edit.
- columnIsBlob = [self isBlobOrText:[fieldTypes objectAtIndex:i]];
- break;
- }
- }
- */
- //is the column a blob field -> if YES force sheet editing
- if ( [self isBlobOrText:[fieldTypes objectAtIndex:[fieldNames indexOfObject:[aTableColumn identifier]]]] ) {
- columnIsBlob = YES;
+ // If the selected column is a blob/text type, force sheet editing.
+ columnTypeGrouping = [[tableDataInstance columnWithName:[aTableColumn identifier]] objectForKey:@"typegrouping"];
+ if ([columnTypeGrouping isEqualToString:@"textdata"] || [columnTypeGrouping isEqualToString:@"blobdata"]) {
+ columnIsBlobOrText = YES;
}
-
- if ( [multipleLineEditingButton state] == NSOnState || columnIsBlob ) {
+
+ // Open the sheet if the multipleLineEditingButton is enabled or the column was a blob or a text.
+ if ( [multipleLineEditingButton state] == NSOnState || columnIsBlobOrText ) {
theValue = [[filteredResult objectAtIndex:rowIndex] objectForKey:[aTableColumn identifier]];
NSImage *image = nil;
editData = [theValue retain];
@@ -1816,40 +1795,31 @@ objectValueForTableColumn:(NSTableColumn *)aTableColumn
if ( [theValue isKindOfClass:[NSData class]] ) {
image = [[NSImage alloc] initWithData:theValue];
[hexTextView setString:[self dataToHex:theValue]];
- /*
- // update displayed font to monospace
- NSFont *font = [NSFont fontWithName:@"Courier" size:12.0f];
- NSRange hexRange = { 0, [[hexTextView string] length] - 1 };
- [hexTextView setFont:font range:hexRange];
- */
theValue = [[NSString alloc] initWithData:theValue encoding:[mySQLConnection encoding]];
} else {
[hexTextView setString:@""];
theValue = [theValue description];
}
-
+
[editImage setImage:image];
[editTextView setString:theValue];
[editTextView setSelectedRange:NSMakeRange(0,[[editTextView string] length])];
- //different sheets for date (with up/down arrows), number and text
- [NSApp beginSheet:editSheet
- modalForWindow:tableWindow modalDelegate:self
- didEndSelector:nil contextInfo:nil];
+
+ [NSApp beginSheet:editSheet modalForWindow:tableWindow modalDelegate:self didEndSelector:nil contextInfo:nil];
code = [NSApp runModalForWindow:editSheet];
-
+
[NSApp endSheet:editSheet];
[editSheet orderOut:nil];
-
+
if ( code ) {
if ( !isEditingRow ) {
[oldRow setDictionary:[filteredResult objectAtIndex:rowIndex]];
isEditingRow = YES;
}
- [[filteredResult objectAtIndex:rowIndex] setObject:[editData copy]
- forKey:[aTableColumn identifier]];
+ [[filteredResult objectAtIndex:rowIndex] setObject:[editData copy] forKey:[aTableColumn identifier]];
- // clean up
+ // Clean up
[editImage setImage:nil];
[editTextView setString:@""];
[hexTextView setString:@""];
@@ -1889,57 +1859,56 @@ objectValueForTableColumn:(NSTableColumn *)aTableColumn
#pragma mark -
-- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)command
/*
- traps enter and esc an make/cancel editing without entering next row
+ * Trap the enter and escape keys, overriding default behaviour and continuing/ending editing,
+ * only within the current row.
*/
+- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)command
{
+ NSString *fieldType;
int row, column, i;
row = [tableContentView editedRow];
column = [tableContentView editedColumn];
-
+
+ // Trap enter and tab keys
if ( [textView methodForSelector:command] == [textView methodForSelector:@selector(insertNewline:)] ||
- [textView methodForSelector:command] == [textView methodForSelector:@selector(insertTab:)] ) //trap enter and tab
+ [textView methodForSelector:command] == [textView methodForSelector:@selector(insertTab:)] )
{
- //save current line
[[control window] makeFirstResponder:control];
+
+ // Save the current line if it's the last field in the table
if ( column == ( [tableContentView numberOfColumns] - 1 ) ) {
[self addRowToDB];
- /*
- if ( [self addRowToDB] &&
- ( [textView methodForSelector:command] == [textView methodForSelector:@selector(insertTab:)] ) &&
- !(sortField && ([prefs boolForKey:@"reloadAfterAdding"] || [prefs boolForKey:@"reloadAfterEditing"])) ) {
- //get in edit-mode of next row if user hit tab (and result isn't sorted and reloaded)
- if ( row < ([tableContentView numberOfRows] - 1) ) {
- [tableContentView selectRow:row+1 byExtendingSelection:NO];
- [tableContentView editColumn:0 row:row+1 withEvent:nil select:YES];
- } else {
- [tableContentView selectRow:0 byExtendingSelection:NO];
- [tableContentView editColumn:0 row:0 withEvent:nil select:YES];
- }
- }
- */
} else {
- //check if next column is a blob column
+
+ // Check if next column is a blob column, and skip to the next non-blob column
i = 1;
- while ( [self isBlobOrText:[fieldTypes objectAtIndex:[fieldNames indexOfObject:[[[tableContentView tableColumns] objectAtIndex:column+i] identifier]]]] ) {
+ while (
+ (fieldType = [[tableDataInstance columnWithName:[[[tableContentView tableColumns] objectAtIndex:column+i] identifier]] objectForKey:@"typegrouping"])
+ && ([fieldType isEqualToString:@"textdata"] || [fieldType isEqualToString:@"blobdata"])
+ ) {
i++;
+
+ // If there are no columns after the latest blob or text column, save the current line.
if ( (column+i) >= [tableContentView numberOfColumns] ) {
- //there is no other column after the blob column
[self addRowToDB];
return TRUE;
}
}
- //edit the column after the blob column
+
+ // Edit the column after the blob column
[tableContentView editColumn:column+i row:row withEvent:nil select:YES];
}
return TRUE;
}
+
+ // Trap the escape key
else if ( [[control window] methodForSelector:command] == [[control window] methodForSelector:@selector(_cancelKey:)] ||
- [textView methodForSelector:command] == [textView methodForSelector:@selector(complete:)] ) //trap esc
+ [textView methodForSelector:command] == [textView methodForSelector:@selector(complete:)] )
{
- //abort editing
+
+ // Abort editing
[control abortEditing];
if ( isEditingRow && !isEditingNewRow ) {
isEditingRow = NO;
@@ -1990,8 +1959,6 @@ objectValueForTableColumn:(NSTableColumn *)aTableColumn
[filteredResult release];
[keys release];
[oldRow release];
- [fieldNames release];
- [fieldTypes release];
[compareType release];
[sortField release];
[prefs release];
diff --git a/Source/TableDocument.h b/Source/TableDocument.h
index 62798b2d..52429c7b 100644
--- a/Source/TableDocument.h
+++ b/Source/TableDocument.h
@@ -41,6 +41,7 @@
IBOutlet id tableContentInstance;
IBOutlet id customQueryInstance;
IBOutlet id tableDumpInstance;
+ IBOutlet id tableDataInstance;
IBOutlet id tableStatusInstance;
IBOutlet id tableWindow;
@@ -133,9 +134,9 @@
- (void)showErrorInConsole:(NSString *)error;
//encoding methods
-- (void)setEncoding:(NSString *)encoding;
-- (void)detectDatabaseEncoding;
-- (void)detectTableEncodingForTable:(NSString *)table;
+- (void)setConnectionEncoding:(NSString *)mysqlEncoding reloadingViews:(BOOL)reloadViews;
+- (NSString *)databaseEncoding;
+- (NSString *)connectionEncoding;
- (IBAction)chooseEncoding:(id)sender;
- (BOOL)supportsEncoding;
- (void)updateEncodingMenuWithSelectedEncoding:(NSString *)encoding;
diff --git a/Source/TableDocument.m b/Source/TableDocument.m
index 6969e561..b9cb66a6 100644
--- a/Source/TableDocument.m
+++ b/Source/TableDocument.m
@@ -33,6 +33,8 @@
#import "TableStatus.h"
#import "ImageAndTextCell.h"
#import "SPGrowlController.h"
+#import "SPSQLParser.h"
+#import "SPTableData.h"
NSString *TableDocumentFavoritesControllerSelectionIndexDidChange = @"TableDocumentFavoritesControllerSelectionIndexDidChange";
NSString *TableDocumentFavoritesControllerFavoritesDidChange = @"TableDocumentFavoritesControllerFavoritesDidChange";
@@ -190,9 +192,9 @@ NSString *TableDocumentFavoritesControllerFavoritesDidChange = @"TableDocumentFa
// set encoding
NSString *encodingName = [prefs objectForKey:@"encoding"];
if ( [encodingName isEqualToString:@"Autodetect"] ) {
- [self detectDatabaseEncoding];
+ [self setConnectionEncoding:[self databaseEncoding] reloadingViews:NO];
} else {
- [self setEncoding:[self mysqlEncodingFromDisplayEncoding:encodingName]];
+ [self setConnectionEncoding:[self mysqlEncodingFromDisplayEncoding:encodingName] reloadingViews:NO];
}
//get mysql version
theResult = [mySQLConnection queryString:@"SHOW VARIABLES LIKE 'version'"];
@@ -210,6 +212,7 @@ NSString *TableDocumentFavoritesControllerFavoritesDidChange = @"TableDocumentFa
[customQueryInstance setConnection:mySQLConnection];
[tableDumpInstance setConnection:mySQLConnection];
[tableStatusInstance setConnection:mySQLConnection];
+ [tableDataInstance setConnection:mySQLConnection];
[self setFileName:[NSString stringWithFormat:@"(MySQL %@) %@@%@ %@", mySQLVersion, [userField stringValue],
[hostField stringValue], [databaseField stringValue]]];
[tableWindow setTitle:[NSString stringWithFormat:@"(MySQL %@) %@@%@/%@", mySQLVersion, [userField stringValue],
@@ -642,7 +645,7 @@ NSString *TableDocumentFavoritesControllerFavoritesDidChange = @"TableDocumentFa
/**
* Set the encoding for the database connection
*/
-- (void)setEncoding:(NSString *)mysqlEncoding
+- (void)setConnectionEncoding:(NSString *)mysqlEncoding reloadingViews:(BOOL)reloadViews
{
// set encoding of connection and client
[mySQLConnection queryString:[NSString stringWithFormat:@"SET NAMES '%@'", mysqlEncoding]];
@@ -652,22 +655,29 @@ NSString *TableDocumentFavoritesControllerFavoritesDidChange = @"TableDocumentFa
[_encoding autorelease];
_encoding = [mysqlEncoding retain];
} else {
- [self detectDatabaseEncoding];
+ [mySQLConnection queryString:[NSString stringWithFormat:@"SET NAMES '%@'", [self databaseEncoding]]];
+ if ( ![[mySQLConnection getLastErrorMessage] isEqualToString:@""] ) {
+ NSLog(@"Error: could not set encoding to %@ nor fall back to database encoding on MySQL %@", mysqlEncoding, [self mySQLVersion]);
+ return;
+ }
}
// update the selected menu item
[self updateEncodingMenuWithSelectedEncoding:[self encodingNameFromMySQLEncoding:mysqlEncoding]];
- // reload stuff
- [tableSourceInstance reloadTable:self];
- [tableContentInstance reloadTable:self];
- [tableStatusInstance reloadTable:self];
+ // Reload stuff as appropriate
+ [tableDataInstance resetAllData];
+ if (reloadViews) {
+ if ([tablesListInstance structureLoaded]) [tableSourceInstance reloadTable:self];
+ if ([tablesListInstance contentLoaded]) [tableContentInstance reloadTable:self];
+ if ([tablesListInstance statusLoaded]) [tableStatusInstance reloadTable:self];
+ }
}
/**
* returns the current mysql encoding for this object
*/
-- (NSString *)encoding
+- (NSString *)connectionEncoding
{
return _encoding;
}
@@ -755,9 +765,10 @@ NSString *TableDocumentFavoritesControllerFavoritesDidChange = @"TableDocumentFa
}
/**
- * Autodetect the connection encoding and select the relevant encoding menu item in Database -> Database Encoding
+ * Detect and return the database connection encoding.
+ * TODO: See http://code.google.com/p/sequel-pro/issues/detail?id=134 - some question over why this [historically] uses _connection not _database...
*/
-- (void)detectDatabaseEncoding
+- (NSString *)databaseEncoding
{
// MySQL > 4.0
id mysqlEncoding = [[[mySQLConnection queryString:@"SHOW VARIABLES LIKE 'character_set_connection'"] fetchRowAsDictionary] objectForKey:@"Value"];
@@ -774,36 +785,7 @@ NSString *TableDocumentFavoritesControllerFavoritesDidChange = @"TableDocumentFa
mysqlEncoding = @"latin1";
}
- [mySQLConnection setEncoding:[CMMCPConnection encodingForMySQLEncoding:[mysqlEncoding cString]]];
-
- // save the encoding
- [_encoding autorelease];
- _encoding = [mysqlEncoding retain];
-
- // update the selected menu item
- [self updateEncodingMenuWithSelectedEncoding:[self encodingNameFromMySQLEncoding:mysqlEncoding]];
-}
-
-/**
- * Detects and sets the character set encoding of the supplied table name.
- */
-- (void)detectTableEncodingForTable:(NSString *)table;
-{
- NSString *tableCollation = [[[mySQLConnection queryString:[NSString stringWithFormat:@"SHOW TABLE STATUS LIKE '%@'", table]] fetchRowAsDictionary] objectForKey:@"Collation"];
-
- if (tableCollation != nil) {
- // Split up the collation string so we can get the encoding
- NSArray *encodingComponents = [tableCollation componentsSeparatedByString:@"_"];
-
- if ([encodingComponents count] > 0) {
- NSString *tableEncoding = [encodingComponents objectAtIndex:0];
-
- [self setEncoding:tableEncoding];
- }
- }
- else {
- NSLog(@"Error: unable to determine table collation and thus character encoding for table '%@'", table);
- }
+ return mysqlEncoding;
}
/**
@@ -811,7 +793,7 @@ NSString *TableDocumentFavoritesControllerFavoritesDidChange = @"TableDocumentFa
*/
- (IBAction)chooseEncoding:(id)sender
{
- [self setEncoding:[self mysqlEncodingFromDisplayEncoding:[(NSMenuItem *)sender title]]];
+ [self setConnectionEncoding:[self mysqlEncodingFromDisplayEncoding:[(NSMenuItem *)sender title]] reloadingViews:YES];
}
/**
@@ -995,6 +977,7 @@ NSString *TableDocumentFavoritesControllerFavoritesDidChange = @"TableDocumentFa
NSRunInformationalAlertPanel([NSString stringWithFormat:@"Checksum '%@' table", [self table]], [NSString stringWithFormat:@"Checksum: %@", [theRow objectForKey:@"Checksum"]], @"OK", nil, nil);
}
+
#pragma mark Other Methods
/**
* returns the host
@@ -1086,7 +1069,7 @@ NSString *TableDocumentFavoritesControllerFavoritesDidChange = @"TableDocumentFa
returns the currently selected table (passing the request to TablesList)
*/
{
- return (NSString *)[tablesListInstance table];
+ return [tablesListInstance tableName];
}
- (NSString *)mySQLVersion
diff --git a/Source/TableDump.h b/Source/TableDump.h
index b759d8fa..c5f83cd0 100644
--- a/Source/TableDump.h
+++ b/Source/TableDump.h
@@ -34,6 +34,7 @@
IBOutlet id tablesListInstance;
IBOutlet id tableSourceInstance;
IBOutlet id tableContentInstance;
+ IBOutlet id tableDataInstance;
IBOutlet id customQueryInstance;
IBOutlet id tableWindow;
@@ -119,7 +120,6 @@
enclosedBy:(NSString *)enclosed escapedBy:(NSString *)escaped lineEnds:(NSString *)lineEnds;
- (NSArray *)arrayForString:(NSString *)string enclosed:(NSString *)enclosed
escaped:(NSString *)escaped terminated:(NSString *)terminated;
-- (NSArray *)splitQueries:(NSString *)query;
// Export methods
- (BOOL)dumpSelectedTablesAsSqlToFileHandle:(NSFileHandle *)fileHandle;
@@ -127,7 +127,7 @@
toFileHandle:(NSFileHandle *)fileHandle
outputFieldNames:(BOOL)firstLine terminatedBy:(NSString *)terminated
enclosedBy:(NSString *)enclosed escapedBy:(NSString *)escaped
- lineEnds:(NSString *)lineEnds silently:(BOOL)silently;
+ lineEnds:(NSString *)lineEnds withNumericColumns:(NSArray *)tableColumnNumericStatus silently:(BOOL)silently;
- (BOOL)writeXmlForArray:(NSArray *)array orQueryResult:(CMMCPResult *)queryResult
toFileHandle:(NSFileHandle *)fileHandle
tableName:(NSString *)table withHeader:(BOOL)header silently:(BOOL)silently;
diff --git a/Source/TableDump.m b/Source/TableDump.m
index 3c34634b..9c27e325 100644
--- a/Source/TableDump.m
+++ b/Source/TableDump.m
@@ -29,6 +29,8 @@
#import "TableContent.h"
#import "CustomQuery.h"
#import "SPGrowlController.h"
+#import "SPSQLParser.h"
+#import "SPTableData.h"
@implementation TableDump
@@ -244,13 +246,14 @@
enclosedBy:[exportFieldsEnclosedField stringValue]
escapedBy:[exportFieldsEscapedField stringValue]
lineEnds:[exportLinesTerminatedField stringValue]
+ withNumericColumns:nil
silently:NO];
// Export the current "browse" view to a file in XML format
} else if ( [contextInfo isEqualToString:@"exportBrowseViewAsXML"] ) {
success = [self writeXmlForArray:[tableContentInstance currentResult] orQueryResult:nil
toFileHandle:fileHandle
- tableName:[tableDocumentInstance table]
+ tableName:(NSString *)[tableDocumentInstance table]
withHeader:YES
silently:NO];
@@ -263,6 +266,7 @@
enclosedBy:[exportFieldsEnclosedField stringValue]
escapedBy:[exportFieldsEscapedField stringValue]
lineEnds:[exportLinesTerminatedField stringValue]
+ withNumericColumns:nil
silently:NO];
// Export the current custom query result set to a file in XML format
@@ -353,13 +357,13 @@
- (void)importBackgroundProcess:(NSString*)filename
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- NSString *dumpFile;
+ SPSQLParser *dumpFile;
NSError **errorStr;
NSMutableString *errors = [NSMutableString string];
NSString *fileType = [[importFormatPopup selectedItem] title];
//load file into string
- dumpFile = [NSString stringWithContentsOfFile:filename
- encoding:[CMMCPConnection encodingForMySQLEncoding:[[tableDocumentInstance encoding] cString]]
+ dumpFile = [SPSQLParser stringWithContentsOfFile:filename
+ encoding:[CMMCPConnection encodingForMySQLEncoding:[[tableDocumentInstance connectionEncoding] cString]]
error:errorStr];
if ( !dumpFile ) {
@@ -398,7 +402,7 @@
[singleProgressBar startAnimation:self];
//get array with an object for each mysql-query
- queries = [self splitQueries:dumpFile];
+ queries = [dumpFile splitStringByCharacter:';'];
[singleProgressBar stopAnimation:self];
[singleProgressBar setUsesThreadedAnimation:NO];
@@ -717,9 +721,9 @@
*/
- (BOOL)dumpSelectedTablesAsSqlToFileHandle:(NSFileHandle *)fileHandle
{
- int i,j,t,rowCount, progressBarWidth, lastProgressValue, queryLength;
+ int i,j,t,rowCount, colCount, progressBarWidth, lastProgressValue, queryLength;
CMMCPResult *queryResult;
- NSString *tableName;
+ NSString *tableName, *tableColumnTypeGrouping;
NSArray *fieldNames;
NSArray *theRow;
NSMutableArray *selectedTables = [NSMutableArray array];
@@ -727,8 +731,9 @@
NSMutableString *cellValue = [NSMutableString string];
NSMutableString *sqlString = [NSMutableString string];
NSMutableString *errors = [NSMutableString string];
+ NSDictionary *tableDetails;
+ NSMutableArray *tableColumnNumericStatus;
NSStringEncoding connectionEncoding = [mySQLConnection encoding];
- NSScanner *sqlNumericTester;
id createTableSyntax;
// Reset the interface
@@ -811,6 +816,20 @@
fieldNames = [queryResult fetchFieldNames];
rowCount = [queryResult numOfRows];
+ // Retrieve the table details via the data class, and use it to build an array containing column numeric status
+ tableDetails = [NSDictionary dictionaryWithDictionary:[tableDataInstance informationFromCreateTableSyntaxForTable:tableName]];
+ colCount = [[tableDetails objectForKey:@"columns"] count];
+ tableColumnNumericStatus = [NSMutableArray arrayWithCapacity:colCount];
+ for ( j = 0; j < colCount ; j++ ) {
+ tableColumnTypeGrouping = [[[tableDetails objectForKey:@"columns"] objectAtIndex:j] objectForKey:@"typegrouping"];
+ if ([tableColumnTypeGrouping isEqualToString:@"bit"] || [tableColumnTypeGrouping isEqualToString:@"integer"]
+ || [tableColumnTypeGrouping isEqualToString:@"float"]) {
+ [tableColumnNumericStatus addObject:[NSNumber numberWithBool:YES]];
+ } else {
+ [tableColumnNumericStatus addObject:[NSNumber numberWithBool:NO]];
+ }
+ }
+
// Update the progress text and set the progress bar back to determinate
[singleProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %i of %i (%@): Dumping...", @"text showing that app is writing data for table dump"), (i+1), [selectedTables count], tableName]];
[singleProgressText displayIfNeeded];
@@ -840,7 +859,7 @@
[singleProgressBar displayIfNeeded];
}
- for ( t = 0 ; t < [theRow count] ; t++ ) {
+ for ( t = 0 ; t < colCount ; t++ ) {
// Add NULL values directly to the output row
if ( [[theRow objectAtIndex:t] isMemberOfClass:[NSNull class]] ) {
@@ -861,14 +880,8 @@
} else {
- // Until we have access to field types, test whether this cell contains a
- // number via use of an NSScanner and a check of the first couple of
- // characters (0[^.] is not a number). If it is a number, add the number directly.
- sqlNumericTester = [NSScanner scannerWithString:cellValue];
- if ([sqlNumericTester scanFloat:nil] && [sqlNumericTester isAtEnd] &&
- ([cellValue characterAtIndex:0] != '0'
- || [cellValue length] == 1
- || ([cellValue length] > 1 && [cellValue characterAtIndex:1] == '.'))) {
+ // If this is a numeric column type, add the number directly.
+ if ( [[tableColumnNumericStatus objectAtIndex:t] boolValue] ) {
[sqlString appendString:cellValue];
// Otherwise add a quoted string with special characters escaped
@@ -944,10 +957,16 @@
/*
Takes an array and writes it in CSV format to the supplied NSFileHandle
*/
-- (BOOL)writeCsvForArray:(NSArray *)array orQueryResult:(CMMCPResult *)queryResult toFileHandle:(NSFileHandle *)fileHandle outputFieldNames:(BOOL)outputFieldNames terminatedBy:(NSString *)fieldSeparatorString
- enclosedBy:(NSString *)enclosingString escapedBy:(NSString *)escapeString lineEnds:(NSString *)lineEndString silently:(BOOL)silently;
+- (BOOL)writeCsvForArray:(NSArray *)array orQueryResult:(CMMCPResult *)queryResult toFileHandle:(NSFileHandle *)fileHandle
+ outputFieldNames:(BOOL)outputFieldNames
+ terminatedBy:(NSString *)fieldSeparatorString
+ enclosedBy:(NSString *)enclosingString
+ escapedBy:(NSString *)escapeString
+ lineEnds:(NSString *)lineEndString
+ withNumericColumns:(NSArray *)tableColumnNumericStatus
+ silently:(BOOL)silently;
{
- NSStringEncoding tableEncoding = [CMMCPConnection encodingForMySQLEncoding:[[tableDocumentInstance encoding] cString]];
+ NSStringEncoding tableEncoding = [CMMCPConnection encodingForMySQLEncoding:[[tableDocumentInstance connectionEncoding] cString]];
NSMutableString *csvCell = [NSMutableString string];
NSMutableArray *csvRow = [NSMutableArray array];
NSMutableString *csvString = [NSMutableString string];
@@ -1072,14 +1091,21 @@
} else {
- // Until we have access to field types, test whether this cell contains a number via use of an
- // NSScanner and a check of the first couple of characters.
+ // Test whether this cell contains a number
if ([[csvRow objectAtIndex:j] isKindOfClass:[NSData class]]) {
csvCellIsNumeric = FALSE;
+
+ // If an array of bools supplying information as to whether the column is numeric has been supplied, use it.
+ } else if (tableColumnNumericStatus != nil) {
+ csvCellIsNumeric = [[tableColumnNumericStatus objectAtIndex:j] boolValue];
+
+ // Or fall back to testing numeric content via an NSScanner.
} else {
csvNumericTester = [NSScanner scannerWithString:csvCell];
- csvCellIsNumeric = [csvNumericTester scanFloat:nil] && [csvNumericTester isAtEnd] &&
- ([csvCell characterAtIndex:0] != '0' || [csvCell characterAtIndex:1] == '.');
+ csvCellIsNumeric = [csvNumericTester scanFloat:nil] && [csvNumericTester isAtEnd]
+ && ([csvCell characterAtIndex:0] != '0'
+ || [csvCell length] == 1
+ || ([csvCell length] > 1 && [csvCell characterAtIndex:1] == '.'));
}
// Escape any occurrences of the escaping character
@@ -1292,7 +1318,7 @@
*/
- (BOOL)writeXmlForArray:(NSArray *)array orQueryResult:(CMMCPResult *)queryResult toFileHandle:(NSFileHandle *)fileHandle tableName:(NSString *)table withHeader:(BOOL)header silently:(BOOL)silently
{
- NSStringEncoding tableEncoding = [CMMCPConnection encodingForMySQLEncoding:[[tableDocumentInstance encoding] cString]];
+ NSStringEncoding tableEncoding = [CMMCPConnection encodingForMySQLEncoding:[[tableDocumentInstance connectionEncoding] cString]];
NSMutableArray *xmlTags = [NSMutableArray array];
NSMutableArray *xmlRow = [NSMutableArray array];
NSMutableString *xmlString = [NSMutableString string];
@@ -1444,13 +1470,15 @@
*/
- (BOOL)exportTables:(NSArray *)selectedTables toFileHandle:(NSFileHandle *)fileHandle usingFormat:(NSString *)type
{
- int i;
+ int i, j;
CMMCPResult *queryResult;
- NSString *tableName;
+ NSString *tableName, *tableColumnTypeGrouping;
NSMutableString *infoString = [NSMutableString string];
NSMutableString *errors = [NSMutableString string];
NSStringEncoding connectionEncoding = [mySQLConnection encoding];
NSMutableString *csvLineEnd;
+ NSDictionary *tableDetails;
+ NSMutableArray *tableColumnNumericStatus;
// Reset the interface
[errorsView setString:@""];
@@ -1514,7 +1542,20 @@
if ( [type isEqualToString:@"csv"] && [selectedTables count] > 1) {
[fileHandle writeData:[[NSString stringWithFormat:@"Table %@%@%@", tableName, csvLineEnd, csvLineEnd] dataUsingEncoding:connectionEncoding]];
}
-
+NSDate *startDate = [NSDate date];
+ // Retrieve the table details via the data class, and use it to build an array containing column numeric status
+ tableDetails = [NSDictionary dictionaryWithDictionary:[tableDataInstance informationFromCreateTableSyntaxForTable:tableName]];
+ tableColumnNumericStatus = [NSMutableArray array];
+ for ( j = 0; j < [[tableDetails objectForKey:@"columns"] count] ; j++ ) {
+ tableColumnTypeGrouping = [[[tableDetails objectForKey:@"columns"] objectAtIndex:j] objectForKey:@"typegrouping"];
+ if ([tableColumnTypeGrouping isEqualToString:@"bit"] || [tableColumnTypeGrouping isEqualToString:@"integer"]
+ || [tableColumnTypeGrouping isEqualToString:@"float"]) {
+ [tableColumnNumericStatus addObject:[NSNumber numberWithBool:YES]];
+ } else {
+ [tableColumnNumericStatus addObject:[NSNumber numberWithBool:NO]];
+ }
+ }
+
// Retrieve all the content within this table
queryResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SELECT * FROM `%@`", tableName]];
@@ -1541,8 +1582,9 @@
enclosedBy:[exportMultipleFieldsEnclosedField stringValue]
escapedBy:[exportMultipleFieldsEscapedField stringValue]
lineEnds:[exportMultipleLinesTerminatedField stringValue]
+ withNumericColumns:tableColumnNumericStatus
silently:YES];
-
+ NSLog(@"CSV export took %f s", [[NSDate date] timeIntervalSinceDate:startDate]);
// Add a spacer to the file
[fileHandle writeData:[[NSString stringWithFormat:@"%@%@%@", csvLineEnd, csvLineEnd, csvLineEnd] dataUsingEncoding:connectionEncoding]];
} else if ( [type isEqualToString:@"xml"] ) {
@@ -1693,87 +1735,6 @@
return [NSArray arrayWithArray:tempArray];
}
-- (NSArray *)splitQueries:(NSString *)query
-/*
- splits the queries by ;'s which aren't inside any ", ' or ` characters
- */
-{
- NSMutableString *queries = [NSMutableString stringWithString:query];
- NSMutableArray *queryArray = [NSMutableArray array];
- char stringType = nil;
- BOOL inString = NO;
- BOOL escaped;
- unsigned lineStart = 0;
- unsigned i, j, x, currentLineLength;
-
- //parse string
- for ( i = 0 ; i < [queries length] ; i++ ) {
- if ( inString ) {
- //we are in a string
- //look for end of string
- for ( ; i < [queries length] ; i++ ) {
-
- // For the backtick character treat the string as ended
- if ( ([queries characterAtIndex:i] == '`') && (stringType == '`') ) {
-
- inString = NO;
- break;
-
- // Otherwise, prepare to treat the string as ended after a stringType....
- } else if ( [queries characterAtIndex:i] == stringType ) {
-
- // ...but only if the stringType isn't escaped with an *odd* number of escaping characters.
- escaped = NO;
- j = 1;
- currentLineLength = i - lineStart;
- while ( ((currentLineLength-j) > 0) && ([queries characterAtIndex:i-j] == '\\') ) {
- escaped = !escaped;
- j++;
- }
-
- // If an odd number have been found, it really is the end of the string.
- if ( !escaped ) {
- inString = NO;
- break;
- }
- }
- }
- } else if ( ([queries characterAtIndex:i] == '#') ||
- ((i+2<[queries length]) &&
- ([queries characterAtIndex:i] == '-') &&
- ([queries characterAtIndex:i+1] == '-') &&
- ([queries characterAtIndex:i+2] == ' ')) ) {
- //it's a comment -> delete it
- x = i;
- while ( (x<[queries length]) && ([queries characterAtIndex:x] != '\r') && ([queries characterAtIndex:x] != '\n') ) {
- x++;
- }
- [queries deleteCharactersInRange:NSMakeRange(i,x-i)];
- } else if ( [queries characterAtIndex:i] == ';' ) {
- //we are at the end of a query
- [queryArray addObject:[queries substringWithRange:NSMakeRange(lineStart, (i-lineStart))]];
- while ( ((i+1)<[queries length]) && (([queries characterAtIndex:i+1]=='\n') || ([queries characterAtIndex:i+1]=='\r') || ([queries characterAtIndex:i+1]==' ')) ) {
- i++;
- }
- lineStart = i + 1;
- } else if ( ([queries characterAtIndex:i] == '\'') ||
- ([queries characterAtIndex:i] == '"') ||
- ([queries characterAtIndex:i] == '`') ) {
- //we are entering a string
- inString = YES;
- stringType = [queries characterAtIndex:i];
- }
- }
-
- //add rest of string to array (if last line has not ended with a ";")
- if ( lineStart < [queries length] ) {
- [queryArray addObject:[queries substringWithRange:NSMakeRange(lineStart, ([queries length]-lineStart))]];
- }
-
- //return array
- return [NSArray arrayWithArray:queryArray];
-}
-
//additional methods
- (void)setConnection:(CMMCPConnection *)theConnection
diff --git a/Source/TableSource.h b/Source/TableSource.h
index 2a62232c..29cbfbaa 100644
--- a/Source/TableSource.h
+++ b/Source/TableSource.h
@@ -31,6 +31,7 @@
@interface TableSource : NSObject {
IBOutlet id tablesListInstance;
+ IBOutlet id tableDataInstance;
IBOutlet id tableWindow;
IBOutlet id indexSheet;
diff --git a/Source/TableSource.m b/Source/TableSource.m
index b9b1a3c1..d9c7a595 100644
--- a/Source/TableSource.m
+++ b/Source/TableSource.m
@@ -24,6 +24,7 @@
#import "TableSource.h"
#import "TablesList.h"
+#import "SPTableData.h"
@implementation TableSource
@@ -41,19 +42,16 @@ loads aTable, put it in an array, update the tableViewColumns and reload the tab
NSEnumerator *extrasEnumerator;
id extra;
int i;
-
+
selectedTable = aTable;
[tableSourceView deselectAll:self];
-
+
if ( isEditingRow )
return;
-
+
// empty variables
[enumFields removeAllObjects];
-
- //query started
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:self];
-
+
if ( [aTable isEqualToString:@""] || !aTable ) {
[tableFields removeAllObjects];
[indexes removeAllObjects];
@@ -70,14 +68,14 @@ loads aTable, put it in an array, update the tableViewColumns and reload the tab
[tableTypeButton setEnabled:NO];
tableType = nil;
- //query finished
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:self];
-
[scanner release];
return;
}
+ //query started
+ [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:self];
+
//perform queries and load results in array (each row as a dictionary)
tableSourceResult = [[mySQLConnection queryString:[NSString stringWithFormat:@"SHOW COLUMNS FROM `%@`", selectedTable]] retain];
@@ -91,17 +89,10 @@ loads aTable, put it in an array, update the tableViewColumns and reload the tab
// [indexes setArray:[[self fetchResultAsArray:indexResult] retain]];
[indexes setArray:[self fetchResultAsArray:indexResult]];
[indexResult release];
-
- CMMCPResult *tableStatusResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW TABLE STATUS LIKE '%@'", selectedTable]];
+
+ // Retrieve the table type via the table data's status cache (which automatically maps Type to Engine)
[tableType release];
-
- NSDictionary *tempRow = [tableStatusResult fetchRowAsDictionary];
- if ( [tempRow objectForKey:@"Type"]) {
- tableType = [tempRow objectForKey:@"Type"];
- } else {
- tableType = [tempRow objectForKey:@"Engine"];
- }
- [tableType retain];
+ tableType = [[NSString stringWithString:[tableDataInstance statusValueForKey:@"Engine"]] retain];
//get table default values
if ( defaultValues ) {
@@ -384,8 +375,9 @@ opens alertsheet and asks for confirmation
// [[NSNotificationCenter defaultCenter] postNotificationName:@"SelectedTableStatusHasChanged" object:self];
- // Mark the content table for refresh
+ // Mark the content table for refresh and update column caches
[tablesListInstance setContentRequiresReload:YES];
+ [tableDataInstance resetColumnData];
} else {
[sender selectItemWithTitle:tableType];
NSBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil,
@@ -655,8 +647,9 @@ returns YES if no row is beeing edited and nothing has to be written to db
isEditingNewRow = NO;
[self loadTable:selectedTable];
- // Mark the content table for refresh
+ // Mark the content table and column caches for refresh
[tablesListInstance setContentRequiresReload:YES];
+ [tableDataInstance resetColumnData];
return YES;
} else {
@@ -711,8 +704,9 @@ returns YES if no row is beeing edited and nothing has to be written to db
if ( [[mySQLConnection getLastErrorMessage] isEqualToString:@""] ) {
[self loadTable:selectedTable];
- // Mark the content table for refresh
+ // Mark the content table and column cache for refresh
[tablesListInstance setContentRequiresReload:YES];
+ [tableDataInstance resetColumnData];
} else {
NSBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil,
[NSString stringWithFormat:NSLocalizedString(@"Couldn't remove field %@.\nMySQL said: %@", @"message of panel when field cannot be removed"),
@@ -768,7 +762,7 @@ returns an array containing the field names of the selected table
//load table if not already done
if ( ![tablesListInstance structureLoaded] ) {
- [self loadTable:(NSString *)[tablesListInstance table]];
+ [self loadTable:[tablesListInstance tableName]];
}
//get field names
@@ -883,37 +877,61 @@ would result in a position change.
}
/*
-Having validated a drop, perform the field/column reordering to match.
-*/
-- (BOOL)tableView:(NSTableView*)tableView acceptDrop:(id <NSDraggingInfo>)info row:(int)destinationRow dropOperation:(NSTableViewDropOperation)operation
+ * Having validated a drop, perform the field/column reordering to match.
+ */
+- (BOOL)tableView:(NSTableView*)tableView acceptDrop:(id <NSDraggingInfo>)info row:(int)destinationRowIndex dropOperation:(NSTableViewDropOperation)operation
{
- int originalRow;
+ int originalRowIndex;
NSMutableString *queryString;
+ NSDictionary *originalRow;
- // Extract the original row position from the pasteboard.
- originalRow = [[[info draggingPasteboard] stringForType:@"SequelProPasteboard"] intValue];
+ // Extract the original row position from the pasteboard and retrieve the details
+ originalRowIndex = [[[info draggingPasteboard] stringForType:@"SequelProPasteboard"] intValue];
+ originalRow = [[NSDictionary alloc] initWithDictionary:[tableFields objectAtIndex:originalRowIndex]];
[[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:self];
// Begin construction of the reordering query
queryString = [NSMutableString stringWithFormat:@"ALTER TABLE `%@` MODIFY COLUMN `%@` %@", selectedTable,
- [[tableFields objectAtIndex:originalRow] objectForKey:@"Field"],
- [[tableFields objectAtIndex:originalRow] objectForKey:@"Type"]];
+ [originalRow objectForKey:@"Field"],
+ [originalRow objectForKey:@"Type"]];
// Add the length parameter if necessary
- if ( [[tableFields objectAtIndex:originalRow] objectForKey:@"Length"] &&
- ![[[tableFields objectAtIndex:originalRow] objectForKey:@"Length"] isEqualToString:@""])
- {
- [queryString appendString:[NSString stringWithFormat:@"(%@)",
- [[tableFields objectAtIndex:originalRow] objectForKey:@"Length"]]];
+ if ( [originalRow objectForKey:@"Length"] && ![[originalRow objectForKey:@"Length"] isEqualToString:@""]) {
+ [queryString appendString:[NSString stringWithFormat:@"(%@)", [originalRow objectForKey:@"Length"]]];
+ }
+
+ // Add unsigned, zerofill, binary, not null if necessary
+ if ([[originalRow objectForKey:@"unsigned"] isEqualToString:@"1"]) {
+ [queryString appendString:@" UNSIGNED"];
+ }
+ if ([[originalRow objectForKey:@"zerofill"] isEqualToString:@"1"]) {
+ [queryString appendString:@" ZEROFILL"];
+ }
+ if ([[originalRow objectForKey:@"binary"] isEqualToString:@"1"]) {
+ [queryString appendString:@" BINARY"];
+ }
+ if ([[originalRow objectForKey:@"Null"] isEqualToString:@"NO"] ) {
+ [queryString appendString:@" NOT NULL"];
+ }
+
+ // Add the default value
+ if ([[originalRow objectForKey:@"Default"] isEqualToString:[prefs objectForKey:@"nullValue"]]) {
+ if ([[originalRow objectForKey:@"Null"] isEqualToString:@"YES"]) {
+ [queryString appendString:@" DEFAULT NULL"];
+ }
+ } else if ( [[originalRow objectForKey:@"Type"] isEqualToString:@"timestamp"] && ([[[originalRow objectForKey:@"Default"] uppercaseString] isEqualToString:@"CURRENT_TIMESTAMP"]) ) {
+ [queryString appendString:@" DEFAULT CURRENT_TIMESTAMP"];
+ } else {
+ [queryString appendString:[NSString stringWithFormat:@" DEFAULT '%@'", [mySQLConnection prepareString:[originalRow objectForKey:@"Default"]]]];
}
// Add the new location
- if ( destinationRow == 0 ){
+ if ( destinationRowIndex == 0 ){
[queryString appendString:@" FIRST"];
} else {
[queryString appendString:[NSString stringWithFormat:@" AFTER `%@`",
- [[tableFields objectAtIndex:destinationRow-1] objectForKey:@"Field"]]];
+ [[tableFields objectAtIndex:destinationRowIndex-1] objectForKey:@"Field"]]];
}
// Run the query; report any errors, or reload the table on success
@@ -923,18 +941,20 @@ Having validated a drop, perform the field/column reordering to match.
[NSString stringWithFormat:NSLocalizedString(@"Couldn't move field. MySQL said: %@", @"message of panel when field cannot be added in drag&drop operation"), [mySQLConnection getLastErrorMessage]]);
} else {
[self loadTable:selectedTable];
- if ( originalRow < destinationRow ) {
- [tableSourceView selectRow:destinationRow-1 byExtendingSelection:NO];
+ if ( originalRowIndex < destinationRowIndex ) {
+ [tableSourceView selectRow:destinationRowIndex-1 byExtendingSelection:NO];
} else {
- [tableSourceView selectRow:destinationRow byExtendingSelection:NO];
+ [tableSourceView selectRow:destinationRowIndex byExtendingSelection:NO];
}
}
[[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:self];
- // Mark the content table for refresh
+ // Mark the content table and column caches for refresh
[tablesListInstance setContentRequiresReload:YES];
+ [tableDataInstance resetColumnData];
+ [originalRow release];
return YES;
}
diff --git a/Source/TableStatus.h b/Source/TableStatus.h
index cb631435..73b68fd7 100644
--- a/Source/TableStatus.h
+++ b/Source/TableStatus.h
@@ -29,6 +29,8 @@
@interface TableStatus : NSObject
{
+ IBOutlet id tableDataInstance;
+
IBOutlet id commentsBox;
IBOutlet id rowsNumber;
IBOutlet id rowsFormat;
diff --git a/Source/TableStatus.m b/Source/TableStatus.m
index 5bfd5513..d1a4d45b 100644
--- a/Source/TableStatus.m
+++ b/Source/TableStatus.m
@@ -1,4 +1,6 @@
#import "TableStatus.h"
+#import "SPTableData.h"
+#import "SPStringAdditions.h"
@implementation TableStatus
@@ -13,15 +15,21 @@
[mySQLConnection retain];
}
-- (NSString*)getSQLColumnValue:(NSString *)withName usingFields:(NSDictionary*)fields withLabel:(NSString*)label
+- (NSString*)formatValueWithKey:(NSString *)aKey inDictionary:(NSDictionary*)statusDict withLabel:(NSString*)label
{
- NSString* value = [fields objectForKey:withName];
- if([value isKindOfClass:[NSNull class]])
- {
- value = @"--";
+ NSString* value = [statusDict objectForKey:aKey];
+ if([value isKindOfClass:[NSNull class]]) {
+ value = @"--";
+ } else {
+
+ // Format size strings
+ if ([aKey isEqualToString:@"Data_length"] || [aKey isEqualToString:@"Max_data_length"]
+ || [aKey isEqualToString:@"Index_length"] || [aKey isEqualToString:@"Data_free"]) {
+ value = [NSString stringForByteSize:[value intValue]];
+ }
}
- NSString* labelVal = [NSString stringWithFormat:@"%@: %@",label,value];
+ NSString* labelVal = [NSString stringWithFormat:@"%@: %@", label, value];
return labelVal;
}
@@ -31,76 +39,61 @@
// Store the table name away for future use...
selectedTable = aTable;
- // Notify any listeners that a query is about to begin...
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:self];
-
- // no table selected
+ // No table selected
if([aTable isEqualToString:@""] || !aTable) {
[tableName setStringValue:@"Name: --"];
[tableType setStringValue:@"Type: --"];
[tableCreatedAt setStringValue:@"Created At: --"];
[tableUpdatedAt setStringValue:@"Updated At: --"];
-
+
// Assign the row values...
[rowsNumber setStringValue:@"Number Of: --"];
[rowsFormat setStringValue:@"Format: --"];
[rowsAvgLength setStringValue:@"Avg. Length: --"];
[rowsAutoIncrement setStringValue:@"Auto Increment: --"];
-
+
// Assign the size values...
[sizeData setStringValue:@"Data: --"];
[sizeMaxData setStringValue:@"Max Data: --"];
[sizeIndex setStringValue:@"Index: --"];
[sizeFree setStringValue:@"Free: --"];
-
+
// Finally, set the value of the comments box
[commentsBox setStringValue:@"--"];
-
- // Tell everyone we've finished with our query...
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:self];
+
return;
}
-
- // Run the query to retrieve the status of the selected table. We'll then use this information to populate
- // the associated view's controls.
- tableStatusResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW TABLE STATUS LIKE '%@'", selectedTable]];
-
- statusFields = [tableStatusResult fetchRowAsDictionary];
-
+
+ // Retrieve the table status information via the table data cache
+ statusFields = [tableDataInstance statusValues];
+
// Assign the table values...
[tableName setStringValue:[NSString stringWithFormat:@"Name: %@",selectedTable]];
- if ( [statusFields objectForKey:@"Type"] ) {
- [tableType setStringValue:[self getSQLColumnValue:@"Type" usingFields:statusFields withLabel:@"Type"]];
- } else {
- // mysql > 4.1
- [tableType setStringValue:[self getSQLColumnValue:@"Engine" usingFields:statusFields withLabel:@"Type"]];
- }
- [tableCreatedAt setStringValue:[self getSQLColumnValue:@"Create_time" usingFields:statusFields withLabel:@"Created At"]];
- [tableUpdatedAt setStringValue:[self getSQLColumnValue:@"Update_time" usingFields:statusFields withLabel:@"Updated At"]];
-
+ [tableType setStringValue:[self formatValueWithKey:@"Engine" inDictionary:statusFields withLabel:@"Type"]];
+ [tableCreatedAt setStringValue:[self formatValueWithKey:@"Create_time" inDictionary:statusFields withLabel:@"Created At"]];
+ [tableUpdatedAt setStringValue:[self formatValueWithKey:@"Update_time" inDictionary:statusFields withLabel:@"Updated At"]];
+
// Assign the row values...
- [rowsNumber setStringValue:[self getSQLColumnValue:@"Rows" usingFields:statusFields withLabel:@"Number Of"]];
- [rowsFormat setStringValue:[self getSQLColumnValue:@"Row_format" usingFields:statusFields withLabel:@"Format"]];
- [rowsAvgLength setStringValue:[self getSQLColumnValue:@"Avg_row_length" usingFields:statusFields withLabel:@"Avg. Length"]];
- [rowsAutoIncrement setStringValue:[self getSQLColumnValue:@"Auto_increment" usingFields:statusFields withLabel:@"Auto Increment"]];
+ [rowsNumber setStringValue:[self formatValueWithKey:@"Rows" inDictionary:statusFields withLabel:@"Approx. Number"]];
+ [rowsFormat setStringValue:[self formatValueWithKey:@"Row_format" inDictionary:statusFields withLabel:@"Format"]];
+ [rowsAvgLength setStringValue:[self formatValueWithKey:@"Avg_row_length" inDictionary:statusFields withLabel:@"Avg. Length"]];
+ [rowsAutoIncrement setStringValue:[self formatValueWithKey:@"Auto_increment" inDictionary:statusFields withLabel:@"Auto Increment"]];
// Assign the size values...
- [sizeData setStringValue:[self getSQLColumnValue:@"Data_length" usingFields:statusFields withLabel:@"Data"]];
- [sizeMaxData setStringValue:[self getSQLColumnValue:@"Max_data_length" usingFields:statusFields withLabel:@"Max Data"]];
- [sizeIndex setStringValue:[self getSQLColumnValue:@"Index_length" usingFields:statusFields withLabel:@"Index"]];
- [sizeFree setStringValue:[self getSQLColumnValue:@"Data_free" usingFields:statusFields withLabel:@"Free"]];
-
+ [sizeData setStringValue:[self formatValueWithKey:@"Data_length" inDictionary:statusFields withLabel:@"Data"]];
+ [sizeMaxData setStringValue:[self formatValueWithKey:@"Max_data_length" inDictionary:statusFields withLabel:@"Max Data"]];
+ [sizeIndex setStringValue:[self formatValueWithKey:@"Index_length" inDictionary:statusFields withLabel:@"Index"]];
+ [sizeFree setStringValue:[self formatValueWithKey:@"Data_free" inDictionary:statusFields withLabel:@"Free"]];
+
// Finally, assign the comments...
[commentsBox setStringValue:[statusFields objectForKey:@"Comment"]];
-
- // Tell everyone we've finished with our query...
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:self];
-
+
return;
}
- (IBAction)reloadTable:(id)sender
{
+ [tableDataInstance resetStatusData];
[self loadTable:selectedTable];
}
diff --git a/Source/TablesList.h b/Source/TablesList.h
index 7270219b..d2cb20da 100644
--- a/Source/TablesList.h
+++ b/Source/TablesList.h
@@ -35,6 +35,7 @@
IBOutlet id tableContentInstance;
IBOutlet id customQueryInstance;
IBOutlet id tableDumpInstance;
+ IBOutlet id tableDataInstance;
IBOutlet id tableStatusInstance;
IBOutlet id tableWindow;
@@ -68,7 +69,7 @@
- (void)doPerformQueryService:(NSString *)query;
//getter methods
-- (NSString *)table;
+- (NSString *)tableName;
- (NSArray *)tables;
- (BOOL)structureLoaded;
- (BOOL)contentLoaded;
diff --git a/Source/TablesList.m b/Source/TablesList.m
index 40ea87c5..e92c7b13 100644
--- a/Source/TablesList.m
+++ b/Source/TablesList.m
@@ -26,6 +26,7 @@
#import "TableDocument.h"
#import "TableSource.h"
#import "TableContent.h"
+#import "SPTableData.h"
#import "TableDump.h"
#import "ImageAndTextCell.h"
#import "CMMCPConnection.h"
@@ -295,7 +296,7 @@ selects customQuery tab and passes query to customQueryInstance
/*
returns the currently selected table or nil if no table or mulitple tables are selected
*/
-- (NSString *)table
+- (NSString *)tableName
{
if ( [tablesListView numberOfSelectedRows] == 1 ) {
return [tables objectAtIndex:[tablesListView selectedRow]];
@@ -395,9 +396,8 @@ Mark the content table for refresh when it's next switched to
contentLoaded = NO;
statusLoaded = NO;
} else if ( [tabView indexOfTabViewItem:[tabView selectedTabViewItem]] == 1 ) {
- [tableSourceInstance loadTable:anObject];
[tableContentInstance loadTable:anObject];
- structureLoaded = YES;
+ structureLoaded = NO;
contentLoaded = YES;
statusLoaded = NO;
} else if ( [tabView indexOfTabViewItem:[tabView selectedTabViewItem]] == 3 ) {
@@ -451,9 +451,8 @@ Mark the content table for refresh when it's next switched to
contentLoaded = NO;
statusLoaded = NO;
} else if ( [tabView indexOfTabViewItem:[tabView selectedTabViewItem]] == 1 ) {
- [tableSourceInstance loadTable:anObject];
[tableContentInstance loadTable:anObject];
- structureLoaded = YES;
+ structureLoaded = NO;
contentLoaded = YES;
statusLoaded = NO;
} else if ( [tabView indexOfTabViewItem:[tabView selectedTabViewItem]] == 3 ) {
@@ -549,15 +548,27 @@ traps enter and esc and edit/cancel without entering next row
- (void)tableViewSelectionDidChange:(NSNotification *)aNotification
{
if ( [tablesListView numberOfSelectedRows] == 1 ) {
+
+ // Reset the table information caches
+ [tableDataInstance resetAllData];
+
+ // If encoding is set to Autodetect, update the connection character set encoding
+ // based on the newly selected table's encoding - but only if it differs from the current encoding.
+ if ([[[NSUserDefaults standardUserDefaults] objectForKey:@"encoding"] isEqualToString:@"Autodetect"]) {
+ if (![[tableDataInstance tableEncoding] isEqualToString:[tableDocumentInstance connectionEncoding]]) {
+ [tableDocumentInstance setConnectionEncoding:[tableDataInstance tableEncoding] reloadingViews:NO];
+ [tableDataInstance resetAllData];
+ }
+ }
+
if ( [tabView indexOfTabViewItem:[tabView selectedTabViewItem]] == 0 ) {
[tableSourceInstance loadTable:[tables objectAtIndex:[tablesListView selectedRow]]];
structureLoaded = YES;
contentLoaded = NO;
statusLoaded = NO;
} else if ( [tabView indexOfTabViewItem:[tabView selectedTabViewItem]] == 1 ) {
- [tableSourceInstance loadTable:[tables objectAtIndex:[tablesListView selectedRow]]];
[tableContentInstance loadTable:[tables objectAtIndex:[tablesListView selectedRow]]];
- structureLoaded = YES;
+ structureLoaded = NO;
contentLoaded = YES;
statusLoaded = NO;
} else if ( [tabView indexOfTabViewItem:[tabView selectedTabViewItem]] == 3 ) {
@@ -574,11 +585,6 @@ traps enter and esc and edit/cancel without entering next row
// set window title
[tableWindow setTitle:[NSString stringWithFormat:@"(MySQL %@) %@@%@/%@/%@", [tableDocumentInstance mySQLVersion], [tableDocumentInstance user],
[tableDocumentInstance host], [tableDocumentInstance database], [tables objectAtIndex:[tablesListView selectedRow]]]];
-
- // Update connection characater set encoding based on the table's encoding if required
- if ([[[NSUserDefaults standardUserDefaults] objectForKey:@"encoding"] isEqualToString:@"Autodetect"]) {
- [tableDocumentInstance detectTableEncodingForTable:[tables objectAtIndex:[tablesListView selectedRow]]];
- }
} else {
[tableSourceInstance loadTable:nil];
[tableContentInstance loadTable:nil];
@@ -646,18 +652,11 @@ loads structure or source if tab selected the first time
if ( [tablesListView numberOfSelectedRows] == 1 ) {
if ( ([tabView indexOfTabViewItem:[tabView selectedTabViewItem]] == 0) && !structureLoaded ) {
-
[tableSourceInstance loadTable:[tables objectAtIndex:[tablesListView selectedRow]]];
structureLoaded = YES;
}
if ( ([tabView indexOfTabViewItem:[tabView selectedTabViewItem]] == 1) && !contentLoaded ) {
-
- if ( !structureLoaded ) {
- [tableSourceInstance loadTable:[tables objectAtIndex:[tablesListView selectedRow]]];
- structureLoaded = YES;
- }
-
[tableContentInstance loadTable:[tables objectAtIndex:[tablesListView selectedRow]]];
contentLoaded = YES;
}
@@ -683,6 +682,7 @@ loads structure or source if tab selected the first time
tables = [[NSMutableArray alloc] init];
structureLoaded = NO;
contentLoaded = NO;
+ statusLoaded = NO;
[tables addObject:NSLocalizedString(@"TABLES",@"header for table list")];
return self;
}