aboutsummaryrefslogtreecommitdiffstats
path: root/Source
diff options
context:
space:
mode:
authorMax <post@wickenrode.com>2015-04-22 22:41:58 +0200
committerMax <post@wickenrode.com>2015-04-22 22:41:58 +0200
commit5ef3f0df72d89ad85680a02048c27e2460e2c685 (patch)
tree1c5cfc4fa18f95acacf5b53299e834781198877b /Source
parent75738e17389d9d66713fdde60cfd59774c934557 (diff)
downloadsequelpro-5ef3f0df72d89ad85680a02048c27e2460e2c685.tar.gz
sequelpro-5ef3f0df72d89ad85680a02048c27e2460e2c685.tar.bz2
sequelpro-5ef3f0df72d89ad85680a02048c27e2460e2c685.zip
Move some complex logic into its own class
so it will easier to write some unit tests and refactor some of the code.
Diffstat (limited to 'Source')
-rw-r--r--Source/SPTableContent.m167
-rw-r--r--Source/SPTableFilterParser.h61
-rw-r--r--Source/SPTableFilterParser.m225
3 files changed, 297 insertions, 156 deletions
diff --git a/Source/SPTableContent.m b/Source/SPTableContent.m
index 7b60d152..308c101b 100644
--- a/Source/SPTableContent.m
+++ b/Source/SPTableContent.m
@@ -61,6 +61,7 @@
#endif
#import "SPCustomQuery.h"
#import "SPThreadAdditions.h"
+#import "SPTableFilterParser.h"
#import <pthread.h>
#import <SPMySQL/SPMySQL.h>
@@ -1104,8 +1105,6 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper
BOOL caseSensitive = (([[[NSApp onMainThread] currentEvent] modifierFlags]
& (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask|NSCommandKeyMask)) > 0);
- NSString *filterString;
-
if(contentFilters == nil) {
NSLog(@"Fatal error while retrieving content filters. No filters found.");
NSBeep();
@@ -1133,160 +1132,16 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper
return nil;
}
-
- NSUInteger numberOfArguments = [[filter objectForKey:@"NumberOfArguments"] integerValue];
-
- BOOL suppressLeadingTablePlaceholder = NO;
- if([filter objectForKey:@"SuppressLeadingFieldPlaceholder"])
- suppressLeadingTablePlaceholder = YES;
-
- // argument if Filter requires only one argument
- NSMutableString *argument = [[NSMutableString alloc] initWithString:[argumentField stringValue]];
-
- // If the filter field is empty and the selected filter does not require
- // only one argument, then no filtering is required - return nil.
- if (![argument length] && numberOfArguments == 1) {
- [argument release];
- return nil;
- }
-
- // arguments if Filter requires two arguments
- NSMutableString *firstBetweenArgument = [[NSMutableString alloc] initWithString:[firstBetweenField stringValue]];
- NSMutableString *secondBetweenArgument = [[NSMutableString alloc] initWithString:[secondBetweenField stringValue]];
-
- // If filter requires two arguments and either of the argument fields are empty
- // return nil.
- if (numberOfArguments == 2) {
- if (([firstBetweenArgument length] == 0) || ([secondBetweenArgument length] == 0)) {
- [argument release];
- [firstBetweenArgument release];
- [secondBetweenArgument release];
- return nil;
- }
- }
-
- // Retrieve actual WHERE clause
- NSMutableString *clause = [[NSMutableString alloc] init];
- [clause setString:[filter objectForKey:@"Clause"]];
-
- [clause replaceOccurrencesOfRegex:@"(?<!\\\\)\\$BINARY " withString:(caseSensitive) ? @"BINARY " : @""];
- [clause flushCachedRegexData];
- [clause replaceOccurrencesOfRegex:@"(?<!\\\\)\\$CURRENT_FIELD" withString:([fieldField titleOfSelectedItem]) ? [[fieldField titleOfSelectedItem] backtickQuotedString] : @""];
- [clause flushCachedRegexData];
-
- // Escape % sign for format insertion ie if number of arguments is greater than 0
- if(numberOfArguments > 0)
- [clause replaceOccurrencesOfRegex:@"%" withString:@"%%"];
- [clause flushCachedRegexData];
-
- // Replace placeholder ${} by %@
- NSRange matchedRange;
- NSString *re = @"(?<!\\\\)\\$\\{.*?\\}";
- if([clause isMatchedByRegex:re]) {
- while([clause isMatchedByRegex:re]) {
- matchedRange = [clause rangeOfRegex:re];
- [clause replaceCharactersInRange:matchedRange withString:@"%@"];
- [clause flushCachedRegexData];
- }
- }
-
- // Check number of placeholders and given 'NumberOfArguments'
- if([clause replaceOccurrencesOfString:@"%@" withString:@"%@" options:NSLiteralSearch range:NSMakeRange(0, [clause length])] != numberOfArguments) {
- NSLog(@"Error while setting filter string. “NumberOfArguments” differs from the number of arguments specified in “Clause”.");
- NSBeep();
- [argument release];
- [firstBetweenArgument release];
- [secondBetweenArgument release];
- [clause release];
- return nil;
- }
-
- // Construct the filter string according the required number of arguments
-
- if(suppressLeadingTablePlaceholder) {
- if (numberOfArguments == 2) {
- filterString = [NSString stringWithFormat:clause,
- [self escapeFilterArgument:firstBetweenArgument againstClause:clause],
- [self escapeFilterArgument:secondBetweenArgument againstClause:clause]];
- } else if (numberOfArguments == 1) {
- filterString = [NSString stringWithFormat:clause, [self escapeFilterArgument:argument againstClause:clause]];
- } else {
- filterString = [NSString stringWithString:clause];
- if(numberOfArguments > 2) {
- NSLog(@"Filter with more than 2 arguments is not yet supported.");
- NSBeep();
- }
- }
- } else {
- if (numberOfArguments == 2) {
- filterString = [NSString stringWithFormat:@"%@ %@",
- [[fieldField titleOfSelectedItem] backtickQuotedString],
- [NSString stringWithFormat:clause,
- [self escapeFilterArgument:firstBetweenArgument againstClause:clause],
- [self escapeFilterArgument:secondBetweenArgument againstClause:clause]]];
- } else if (numberOfArguments == 1) {
- filterString = [NSString stringWithFormat:@"%@ %@",
- [[fieldField titleOfSelectedItem] backtickQuotedString],
- [NSString stringWithFormat:clause, [self escapeFilterArgument:argument againstClause:clause]]];
- } else {
- filterString = [NSString stringWithFormat:@"%@ %@",
- [[fieldField titleOfSelectedItem] backtickQuotedString], clause];
- if(numberOfArguments > 2) {
- NSLog(@"Filter with more than 2 arguments is not yet supported.");
- NSBeep();
- }
- }
- }
-
- [argument release];
- [firstBetweenArgument release];
- [secondBetweenArgument release];
- [clause release];
-
- // Return the filter string
- return filterString;
-}
-
-/**
- * Escape argument by looking for used quoting strings in a clause. Attempt to
- * be smart - use a single escape for most clauses, doubling up for LIKE clauses.
- * Also attempt to not escape what look like common escape sequences - \n, \r, \t.
- *
- * @param argument The to be used filter argument which should be be escaped
- *
- * @param clause The entire WHERE filter clause
- *
- */
-- (NSString *)escapeFilterArgument:(NSString *)argument againstClause:(NSString *)clause
-{
- BOOL clauseIsLike = [clause isMatchedByRegex:@"(?i)\\blike\\b.*?%(?!@)"];
- NSString *recognizedEscapeSequences, *escapeSequence, *regexTerm;
- NSMutableString *arg = [argument mutableCopy];
-
- // Determine the character set not to escape slashes before, and the escape depth
- if (clauseIsLike) {
- recognizedEscapeSequences = @"nrt_%";
- escapeSequence = @"\\\\\\\\\\\\\\\\";
- } else {
- recognizedEscapeSequences = @"nrt";
- escapeSequence = @"\\\\\\\\";
- }
- regexTerm = [NSString stringWithFormat:@"(\\\\)(?![%@])", recognizedEscapeSequences];
-
- // Escape slashes appropriately
- [arg replaceOccurrencesOfRegex:regexTerm withString:escapeSequence];
- [arg flushCachedRegexData];
-
- // Get quote sign for escaping - this should work for 99% of all cases
- NSString *quoteSign = [clause stringByMatching:@"([\"'])[^\\1]*?%@[^\\1]*?\\1" capture:1L];
-
- // Escape argument
- if(quoteSign != nil && [quoteSign length] == 1) {
- [arg replaceOccurrencesOfRegex:[NSString stringWithFormat:@"(%@)", quoteSign] withString:@"\\\\$1"];
- [arg flushCachedRegexData];
- }
-
- return [arg autorelease];
+
+ SPTableFilterParser *parser = [[[SPTableFilterParser alloc] initWithFilterClause:[filter objectForKey:@"Clause"] numberOfArguments:[[filter objectForKey:@"NumberOfArguments"] integerValue]] autorelease];
+ [parser setArgument:[argumentField stringValue]];
+ [parser setFirstBetweenArgument:[firstBetweenField stringValue]];
+ [parser setSecondBetweenArgument:[secondBetweenField stringValue]];
+ [parser setSuppressLeadingTablePlaceholder:(!![filter objectForKey:@"SuppressLeadingFieldPlaceholder"])];
+ [parser setCaseSensitive:caseSensitive];
+ [parser setCurrentField:[fieldField titleOfSelectedItem]];
+
+ return [parser filterString];
}
/**
diff --git a/Source/SPTableFilterParser.h b/Source/SPTableFilterParser.h
new file mode 100644
index 00000000..069c802c
--- /dev/null
+++ b/Source/SPTableFilterParser.h
@@ -0,0 +1,61 @@
+//
+// SPTableFilterParser.h
+// sequel-pro
+//
+// Created by Max Lohrmann on 22.04.15.
+// Relocated from existing files. Previous copyright applies.
+//
+// Permission is hereby granted, free of charge, to any person
+// obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal in the Software without
+// restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following
+// conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+//
+// More info at <https://github.com/sequelpro/sequelpro>
+
+#import <Foundation/Foundation.h>
+
+@interface SPTableFilterParser : NSObject
+{
+ NSString *_clause;
+ NSUInteger numberOfArguments;
+
+ NSString *_currentField;
+ NSString *_argument;
+ NSString *_firstBetweenArgument;
+ NSString *_secondsBetweenArgument;
+
+ BOOL caseSensitive;
+ BOOL suppressLeadingTablePlaceholder;
+}
+
+- (id)initWithFilterClause:(NSString *)filter numberOfArguments:(NSUInteger)numArgs;
+
+@property(readonly) NSString *clause;
+@property(readonly) NSUInteger numberOfArguments;
+
+@property BOOL suppressLeadingTablePlaceholder;
+@property BOOL caseSensitive;
+@property(copy) NSString *currentField;
+@property(copy) NSString *argument;
+@property(copy) NSString *firstBetweenArgument;
+@property(copy) NSString *secondBetweenArgument;
+
+- (NSString *)filterString;
+
+@end
diff --git a/Source/SPTableFilterParser.m b/Source/SPTableFilterParser.m
new file mode 100644
index 00000000..96ba8368
--- /dev/null
+++ b/Source/SPTableFilterParser.m
@@ -0,0 +1,225 @@
+//
+// SPTableFilterParser.m
+// sequel-pro
+//
+// Created by Max Lohrmann on 22.04.15.
+// Relocated from existing files. Previous copyright applies.
+//
+// Permission is hereby granted, free of charge, to any person
+// obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal in the Software without
+// restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following
+// conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+//
+// More info at <https://github.com/sequelpro/sequelpro>
+
+#import "SPTableFilterParser.h"
+#import "RegexKitLite.h"
+
+@interface SPTableFilterParser (Private)
++ (NSString *)escapeFilterArgument:(NSString *)argument againstClause:(NSString *)clause;
+@end
+
+@implementation SPTableFilterParser
+
+@synthesize suppressLeadingTablePlaceholder = suppressLeadingTablePlaceholder;
+@synthesize caseSensitive = caseSensitive;
+@synthesize currentField = _currentField;
+@synthesize argument = _argument;
+@synthesize firstBetweenArgument = _firstBetweenArgument;
+@synthesize secondBetweenArgument = _secondBetweenArgument;
+@synthesize numberOfArguments = numberOfArguments;
+@synthesize clause = _clause;
+
+- (id)initWithFilterClause:(NSString *)filter numberOfArguments:(NSUInteger)numArgs
+{
+ self = [super init];
+ if (self) {
+ numberOfArguments = numArgs;
+ _clause = [filter copy];
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ SPClear(_clause);
+
+ [self setCurrentField:nil];
+ [self setArgument:nil];
+ [self setFirstBetweenArgument:nil];
+ [self setSecondBetweenArgument:nil];
+
+ [super dealloc];
+}
+
+- (NSString *)filterString
+{
+ NSString *filterString;
+
+ // argument if Filter requires only one argument
+ NSMutableString *argument = [[NSMutableString alloc] initWithString:_argument];
+
+ // If the filter field is empty and the selected filter does not require
+ // only one argument, then no filtering is required - return nil.
+ if (![argument length] && numberOfArguments == 1) {
+ [argument release];
+ return nil;
+ }
+
+ // arguments if Filter requires two arguments
+ NSMutableString *firstBetweenArgument = [[NSMutableString alloc] initWithString:_firstBetweenArgument];
+ NSMutableString *secondBetweenArgument = [[NSMutableString alloc] initWithString:_secondBetweenArgument];
+
+ // If filter requires two arguments and either of the argument fields are empty
+ // return nil.
+ if (numberOfArguments == 2) {
+ if (([firstBetweenArgument length] == 0) || ([secondBetweenArgument length] == 0)) {
+ [argument release];
+ [firstBetweenArgument release];
+ [secondBetweenArgument release];
+ return nil;
+ }
+ }
+
+ // Retrieve actual WHERE clause
+ NSMutableString *clause = [[NSMutableString alloc] init];
+ [clause setString:_clause];
+
+ [clause replaceOccurrencesOfRegex:@"(?<!\\\\)\\$BINARY " withString:(caseSensitive) ? @"BINARY " : @""];
+ [clause flushCachedRegexData];
+ [clause replaceOccurrencesOfRegex:@"(?<!\\\\)\\$CURRENT_FIELD" withString:(_currentField) ? [_currentField backtickQuotedString] : @""];
+ [clause flushCachedRegexData];
+
+ // Escape % sign for format insertion ie if number of arguments is greater than 0
+ if(numberOfArguments > 0)
+ [clause replaceOccurrencesOfRegex:@"%" withString:@"%%"];
+ [clause flushCachedRegexData];
+
+ // Replace placeholder ${} by %@
+ NSRange matchedRange;
+ NSString *re = @"(?<!\\\\)\\$\\{.*?\\}";
+ if([clause isMatchedByRegex:re]) {
+ while([clause isMatchedByRegex:re]) {
+ matchedRange = [clause rangeOfRegex:re];
+ [clause replaceCharactersInRange:matchedRange withString:@"%@"];
+ [clause flushCachedRegexData];
+ }
+ }
+
+ // Check number of placeholders and given 'NumberOfArguments'
+ if([clause replaceOccurrencesOfString:@"%@" withString:@"%@" options:NSLiteralSearch range:NSMakeRange(0, [clause length])] != numberOfArguments) {
+ SPLog(@"Error while setting filter string. “NumberOfArguments” differs from the number of arguments specified in “Clause”.");
+ NSBeep();
+ [argument release];
+ [firstBetweenArgument release];
+ [secondBetweenArgument release];
+ [clause release];
+ return nil;
+ }
+
+ // Construct the filter string according the required number of arguments
+
+ if(suppressLeadingTablePlaceholder) {
+ if (numberOfArguments == 2) {
+ filterString = [NSString stringWithFormat:clause,
+ [[self class] escapeFilterArgument:firstBetweenArgument againstClause:clause],
+ [[self class] escapeFilterArgument:secondBetweenArgument againstClause:clause]];
+ } else if (numberOfArguments == 1) {
+ filterString = [NSString stringWithFormat:clause, [[self class] escapeFilterArgument:argument againstClause:clause]];
+ } else {
+ filterString = [NSString stringWithString:clause];
+ if(numberOfArguments > 2) {
+ SPLog(@"Filter with more than 2 arguments is not yet supported.");
+ NSBeep();
+ }
+ }
+ } else {
+ if (numberOfArguments == 2) {
+ filterString = [NSString stringWithFormat:@"%@ %@",
+ [_currentField backtickQuotedString],
+ [NSString stringWithFormat:clause,
+ [[self class] escapeFilterArgument:firstBetweenArgument againstClause:clause],
+ [[self class] escapeFilterArgument:secondBetweenArgument againstClause:clause]]];
+ } else if (numberOfArguments == 1) {
+ filterString = [NSString stringWithFormat:@"%@ %@",
+ [_currentField backtickQuotedString],
+ [NSString stringWithFormat:clause, [[self class] escapeFilterArgument:argument againstClause:clause]]];
+ } else {
+ filterString = [NSString stringWithFormat:@"%@ %@",
+ [_currentField backtickQuotedString], clause];
+ if(numberOfArguments > 2) {
+ SPLog(@"Filter with more than 2 arguments is not yet supported.");
+ NSBeep();
+ }
+ }
+ }
+
+ [argument release];
+ [firstBetweenArgument release];
+ [secondBetweenArgument release];
+ [clause release];
+
+ // Return the filter string
+ return filterString;
+}
+
+/**
+ * Escape argument by looking for used quoting strings in a clause. Attempt to
+ * be smart - use a single escape for most clauses, doubling up for LIKE clauses.
+ * Also attempt to not escape what look like common escape sequences - \n, \r, \t.
+ *
+ * @param argument The to be used filter argument which should be be escaped
+ *
+ * @param clause The entire WHERE filter clause
+ *
+ */
++ (NSString *)escapeFilterArgument:(NSString *)argument againstClause:(NSString *)clause
+{
+ BOOL clauseIsLike = [clause isMatchedByRegex:@"(?i)\\blike\\b.*?%(?!@)"];
+ NSString *recognizedEscapeSequences, *escapeSequence, *regexTerm;
+ NSMutableString *arg = [argument mutableCopy];
+
+ // Determine the character set not to escape slashes before, and the escape depth
+ if (clauseIsLike) {
+ recognizedEscapeSequences = @"nrt_%";
+ escapeSequence = @"\\\\\\\\\\\\\\\\";
+ } else {
+ recognizedEscapeSequences = @"nrt";
+ escapeSequence = @"\\\\\\\\";
+ }
+ regexTerm = [NSString stringWithFormat:@"(\\\\)(?![%@])", recognizedEscapeSequences];
+
+ // Escape slashes appropriately
+ [arg replaceOccurrencesOfRegex:regexTerm withString:escapeSequence];
+ [arg flushCachedRegexData];
+
+ // Get quote sign for escaping - this should work for 99% of all cases
+ NSString *quoteSign = [clause stringByMatching:@"([\"'])[^\\1]*?%@[^\\1]*?\\1" capture:1L];
+
+ // Escape argument
+ if(quoteSign != nil && [quoteSign length] == 1) {
+ [arg replaceOccurrencesOfRegex:[NSString stringWithFormat:@"(%@)", quoteSign] withString:@"\\\\$1"];
+ [arg flushCachedRegexData];
+ }
+
+ return [arg autorelease];
+}
+
+
+@end