Skip to content

Commit 15d7d07

Browse files
Les MelnychukNSExceptional
authored andcommitted
Add ability to run SQL queries
1 parent a556ece commit 15d7d07

File tree

9 files changed

+232
-86
lines changed

9 files changed

+232
-86
lines changed

Classes/GlobalStateExplorers/DatabaseBrowser/FLEXDatabaseManager.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,23 @@
1212
// which Flying Meat Inc. licenses this file to you.
1313

1414
#import <Foundation/Foundation.h>
15+
#import "FLEXSQLResult.h"
1516

1617
@protocol FLEXDatabaseManager <NSObject>
1718

1819
@required
20+
1921
+ (instancetype)managerForDatabase:(NSString *)path;
2022

2123
- (BOOL)open;
24+
2225
/// @return a list of all table names
2326
- (NSArray<NSString *> *)queryAllTables;
24-
- (NSArray<NSString *> *)queryAllColumnsWithTableName:(NSString *)tableName;
25-
- (NSArray<NSArray *> *)queryAllDataWithTableName:(NSString *)tableName;
27+
- (NSArray<NSString *> *)queryAllColumnsOfTable:(NSString *)tableName;
28+
- (NSArray<NSArray *> *)queryAllDataInTable:(NSString *)tableName;
29+
30+
@optional
31+
32+
- (FLEXSQLResult *)executeStatement:(NSString *)SQLStatement;
2633

2734
@end

Classes/GlobalStateExplorers/DatabaseBrowser/FLEXRealmDatabaseManager.m

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#import "FLEXRealmDatabaseManager.h"
1010
#import "NSArray+Functional.h"
11+
#import "FLEXSQLResult.h"
1112

1213
#if __has_include(<Realm/Realm.h>)
1314
#import <Realm/Realm.h>
@@ -68,15 +69,15 @@ - (BOOL)open {
6869
}];
6970
}
7071

71-
- (NSArray<NSString *> *)queryAllColumnsWithTableName:(NSString *)tableName {
72+
- (NSArray<NSString *> *)queryAllColumnsOfTable:(NSString *)tableName {
7273
RLMObjectSchema *objectSchema = [self.realm.schema schemaForClassName:tableName];
7374
// Map each column to its name
7475
return [objectSchema.properties flex_mapped:^id(RLMProperty *property, NSUInteger idx) {
7576
return property.name;
7677
}];
7778
}
7879

79-
- (NSArray<NSArray *> *)queryAllDataWithTableName:(NSString *)tableName {
80+
- (NSArray<NSArray *> *)queryAllDataInTable:(NSString *)tableName {
8081
RLMObjectSchema *objectSchema = [self.realm.schema schemaForClassName:tableName];
8182
RLMResults *results = [self.realm allObjects:tableName];
8283
if (results.count == 0 || !objectSchema) {
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//
2+
// FLEXSQLResult.h
3+
// FLEX
4+
//
5+
// Created by Tanner on 3/3/20.
6+
// Copyright © 2020 Flipboard. All rights reserved.
7+
//
8+
9+
#import <Foundation/Foundation.h>
10+
11+
NS_ASSUME_NONNULL_BEGIN
12+
13+
@interface FLEXSQLResult : NSObject
14+
15+
/// Describes the result of a non-select query, or an error of any kind of query
16+
+ (instancetype)message:(NSString *)message;
17+
/// @param rowData A list of rows, where each element in the row
18+
/// corresponds to the column given in /c columnNames
19+
+ (instancetype)columns:(NSArray<NSString *> *)columnNames
20+
rows:(NSArray<NSArray<NSString *> *> *)rowData;
21+
22+
@property (nonatomic, readonly, nullable) NSString *message;
23+
24+
/// A list of column names
25+
@property (nonatomic, readonly, nullable) NSArray<NSString *> *columns;
26+
/// A list of rows, where each element in the row corresponds
27+
/// to the value of the column at the same index in \c columns.
28+
///
29+
/// That is, given a row, looping over the contents of the row and
30+
/// the contents of \c columns will give you key-value pairs of
31+
/// column names to column values for that row.
32+
@property (nonatomic, readonly, nullable) NSArray<NSArray<NSString *> *> *rows;
33+
/// A list of rows where the fields are paired to column names.
34+
///
35+
/// This property is lazily constructed by looping over
36+
/// the rows and columns present in the other two properties.
37+
@property (nonatomic, readonly, nullable) NSArray<NSDictionary<NSString *, id> *> *keyedRows;
38+
39+
@end
40+
41+
NS_ASSUME_NONNULL_END
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//
2+
// FLEXSQLResult.m
3+
// FLEX
4+
//
5+
// Created by Tanner on 3/3/20.
6+
// Copyright © 2020 Flipboard. All rights reserved.
7+
//
8+
9+
#import "FLEXSQLResult.h"
10+
#import "NSArray+Functional.h"
11+
12+
@implementation FLEXSQLResult
13+
@synthesize keyedRows = _keyedRows;
14+
15+
+ (instancetype)message:(NSString *)message {
16+
return [[self alloc] initWithmessage:message columns:nil rows:nil];
17+
}
18+
19+
+ (instancetype)columns:(NSArray<NSString *> *)columnNames rows:(NSArray<NSArray<NSString *> *> *)rowData {
20+
return [[self alloc] initWithmessage:nil columns:columnNames rows:rowData];
21+
}
22+
23+
- (id)initWithmessage:(NSString *)message columns:(NSArray *)columns rows:(NSArray<NSArray *> *)rows {
24+
NSParameterAssert(message || (columns && rows));
25+
NSParameterAssert(columns.count == rows.firstObject.count);
26+
27+
self = [super init];
28+
if (self) {
29+
_message = message;
30+
_columns = columns;
31+
_rows = rows;
32+
}
33+
34+
return self;
35+
}
36+
37+
- (NSArray<NSDictionary<NSString *,id> *> *)keyedRows {
38+
if (!_keyedRows) {
39+
_keyedRows = [self.rows flex_mapped:^id(NSArray<NSString *> *row, NSUInteger idx) {
40+
return [NSDictionary dictionaryWithObjects:row forKeys:self.columns];
41+
}];
42+
}
43+
44+
return _keyedRows;
45+
}
46+
47+
@end

Classes/GlobalStateExplorers/DatabaseBrowser/FLEXSQLiteDatabaseManager.m

Lines changed: 53 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@
99
#import "FLEXSQLiteDatabaseManager.h"
1010
#import "FLEXManager.h"
1111
#import "NSArray+Functional.h"
12+
#import "FLEXSQLResult.h"
1213
#import <sqlite3.h>
1314

14-
static NSString * const QUERY_TABLENAMES_SQL = @"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name";
15+
static NSString * const QUERY_TABLENAMES = @"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name";
1516

1617
@interface FLEXSQLiteDatabaseManager ()
17-
@property (nonatomic, readonly) sqlite3 *db;
18+
@property (nonatomic) sqlite3 *db;
1819
@property (nonatomic, copy) NSString *path;
1920
@end
2021

@@ -36,7 +37,7 @@ - (instancetype)initWithPath:(NSString *)path {
3637
}
3738

3839
- (BOOL)open {
39-
if (_db) {
40+
if (self.db) {
4041
return YES;
4142
}
4243

@@ -57,9 +58,9 @@ - (BOOL)open {
5758

5859
return YES;
5960
}
60-
61+
6162
- (BOOL)close {
62-
if (!_db) {
63+
if (!self.db) {
6364
return YES;
6465
}
6566

@@ -84,88 +85,85 @@ - (BOOL)close {
8485
}
8586
} while (retry);
8687

87-
_db = nil;
88+
self.db = nil;
8889
return YES;
8990
}
9091

9192
- (NSArray<NSString *> *)queryAllTables {
92-
return [[self executeQuery:QUERY_TABLENAMES_SQL] flex_mapped:^id(NSArray *table, NSUInteger idx) {
93+
return [[self executeStatement:QUERY_TABLENAMES].rows flex_mapped:^id(NSArray *table, NSUInteger idx) {
9394
return table.firstObject;
9495
}];
9596
}
9697

97-
- (NSArray<NSString *> *)queryAllColumnsWithTableName:(NSString *)tableName {
98+
- (NSArray<NSString *> *)queryAllColumnsOfTable:(NSString *)tableName {
9899
NSString *sql = [NSString stringWithFormat:@"PRAGMA table_info('%@')",tableName];
99-
NSArray<NSDictionary *> *results = [self executeQueryWithColumns:sql];
100+
FLEXSQLResult *results = [self executeStatement:sql];
100101

101-
return [results flex_mapped:^id(NSDictionary *column, NSUInteger idx) {
102+
return [results.keyedRows flex_mapped:^id(NSDictionary *column, NSUInteger idx) {
102103
return column[@"name"];
103104
}];
104105
}
105106

106-
- (NSArray<NSArray *> *)queryAllDataWithTableName:(NSString *)tableName {
107-
return [self executeQuery:[@"SELECT * FROM "
107+
- (NSArray<NSArray *> *)queryAllDataInTable:(NSString *)tableName {
108+
return [self executeStatement:[@"SELECT * FROM "
108109
stringByAppendingString:tableName
109-
]];
110+
]].rows;
110111
}
111112

112-
#pragma mark - Private
113-
114-
/// @return an array of rows, where each row is an array
115-
/// containing the values of each column for that row
116-
- (NSArray<NSArray *> *)executeQuery:(NSString *)sql {
113+
- (FLEXSQLResult *)executeStatement:(NSString *)sql {
117114
[self open];
118115

119-
NSMutableArray<NSArray *> *results = [NSMutableArray array];
116+
FLEXSQLResult *result = nil;
120117

121118
sqlite3_stmt *pstmt;
122119
if (sqlite3_prepare_v2(_db, sql.UTF8String, -1, &pstmt, 0) == SQLITE_OK) {
123-
while (sqlite3_step(pstmt) == SQLITE_ROW) {
124-
int num_cols = sqlite3_data_count(pstmt);
125-
if (num_cols > 0) {
126-
int columnCount = sqlite3_column_count(pstmt);
127-
128-
[results addObject:[NSArray flex_forEachUpTo:columnCount map:^id(NSUInteger i) {
120+
NSMutableArray<NSArray *> *rows = [NSMutableArray new];
121+
122+
// Grab columns
123+
int columnCount = sqlite3_column_count(pstmt);
124+
NSArray<NSString *> *columns = [NSArray flex_forEachUpTo:columnCount map:^id(NSUInteger i) {
125+
return @(sqlite3_column_name(pstmt, (int)i));
126+
}];
127+
128+
// Execute statement
129+
int status;
130+
while ((status = sqlite3_step(pstmt)) == SQLITE_ROW) {
131+
// Grab rows if this is a selection query
132+
int dataCount = sqlite3_data_count(pstmt);
133+
if (dataCount > 0) {
134+
[rows addObject:[NSArray flex_forEachUpTo:columnCount map:^id(NSUInteger i) {
129135
return [self objectForColumnIndex:(int)i stmt:pstmt];
130136
}]];
131137
}
132138
}
133-
}
134-
135-
[self close];
136-
return results;
137-
}
138-
139-
/// Like \c executeQuery: except that a list of dictionaries are returned,
140-
/// where the keys are column names and the values are the data.
141-
- (NSArray<NSDictionary *> *)executeQueryWithColumns:(NSString *)sql {
142-
[self open];
143-
144-
NSMutableArray<NSDictionary *> *results = [NSMutableArray array];
145-
146-
sqlite3_stmt *pstmt;
147-
if (sqlite3_prepare_v2(_db, sql.UTF8String, -1, &pstmt, 0) == SQLITE_OK) {
148-
while (sqlite3_step(pstmt) == SQLITE_ROW) {
149-
int num_cols = sqlite3_data_count(pstmt);
150-
if (num_cols > 0) {
151-
int columnCount = sqlite3_column_count(pstmt);
152-
153-
154-
NSMutableDictionary *rowFields = [NSMutableDictionary new];
155-
for (int i = 0; i < columnCount; i++) {
156-
id value = [self objectForColumnIndex:(int)i stmt:pstmt];
157-
rowFields[@(sqlite3_column_name(pstmt, i))] = value;
158-
}
159-
160-
[results addObject:rowFields];
139+
140+
if (status == SQLITE_DONE) {
141+
if (rows.count) {
142+
// We selected some rows
143+
result = [FLEXSQLResult columns:columns rows:rows];
144+
} else {
145+
// We executed a query like INSERT, UDPATE, or DELETE
146+
int rowsAffected = sqlite3_changes(_db);
147+
NSString *message = [NSString stringWithFormat:@"%d row(s) affected", rowsAffected];
148+
result = [FLEXSQLResult message:message];
161149
}
150+
} else {
151+
// An error occured executing the query
152+
result = [FLEXSQLResult message:@(sqlite3_errmsg(_db) ?: "(Execution: empty error)")];
162153
}
154+
} else {
155+
// An error occurred creating the prepared statement
156+
result = [FLEXSQLResult message:@(sqlite3_errmsg(_db) ?: "(Prepared statement: empty error)")];
163157
}
164158

159+
sqlite3_finalize(pstmt);
165160
[self close];
166-
return results;
161+
return result;
167162
}
168163

164+
165+
#pragma mark - Private
166+
169167
- (id)objectForColumnIndex:(int)columnIdx stmt:(sqlite3_stmt*)stmt {
170168
int columnType = sqlite3_column_type(stmt, columnIdx);
171169

@@ -184,7 +182,7 @@ - (id)objectForColumnIndex:(int)columnIdx stmt:(sqlite3_stmt*)stmt {
184182
return [self stringForColumnIndex:columnIdx stmt:stmt] ?: NSNull.null;
185183
}
186184
}
187-
185+
188186
- (NSString *)stringForColumnIndex:(int)columnIdx stmt:(sqlite3_stmt *)stmt {
189187
if (sqlite3_column_type(stmt, columnIdx) == SQLITE_NULL || columnIdx < 0) {
190188
return nil;

Classes/GlobalStateExplorers/DatabaseBrowser/FLEXTableContentViewController.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
@interface FLEXTableContentViewController : UIViewController
1212

13-
@property (nonatomic) NSArray<NSString *> *columns;
14-
@property (nonatomic) NSArray<NSArray *> *rows;
13+
+ (instancetype)columns:(NSArray<NSString *> *)columnNames
14+
rows:(NSArray<NSArray<NSString *> *> *)rowData;
1515

1616
@end

Classes/GlobalStateExplorers/DatabaseBrowser/FLEXTableContentViewController.m

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,32 @@
1414
@interface FLEXTableContentViewController () <
1515
FLEXMultiColumnTableViewDataSource, FLEXMultiColumnTableViewDelegate
1616
>
17+
@property (nonatomic, readonly) NSArray<NSString *> *columns;
18+
@property (nonatomic, copy) NSArray<NSArray *> *rows;
19+
1720
@property (nonatomic) FLEXMultiColumnTableView *multiColumnView;
1821
@end
1922

2023
@implementation FLEXTableContentViewController
2124

22-
- (void)viewDidLoad {
23-
[super viewDidLoad];
24-
self.edgesForExtendedLayout = UIRectEdgeNone;
25-
25+
+ (instancetype)columns:(NSArray<NSString *> *)columnNames
26+
rows:(NSArray<NSArray<NSString *> *> *)rowData {
27+
FLEXTableContentViewController *controller = [self new];
28+
controller->_columns = columnNames;
29+
controller->_rows = rowData;
30+
return controller;
31+
}
32+
33+
- (void)loadView {
34+
[super loadView];
2635

2736
[self.view addSubview:self.multiColumnView];
2837
}
2938

30-
- (void)viewWillAppear:(BOOL)animated {
31-
[super viewWillAppear:animated];
39+
- (void)viewDidLoad {
40+
[super viewDidLoad];
41+
42+
self.edgesForExtendedLayout = UIRectEdgeNone;
3243
[self.multiColumnView reloadData];
3344
}
3445

@@ -38,8 +49,8 @@ - (FLEXMultiColumnTableView *)multiColumnView {
3849
initWithFrame:FLEXRectSetSize(CGRectZero, self.view.frame.size)
3950
];
4051

41-
_multiColumnView.dataSource = self;
42-
_multiColumnView.delegate = self;
52+
_multiColumnView.dataSource = self;
53+
_multiColumnView.delegate = self;
4354
}
4455

4556
return _multiColumnView;

0 commit comments

Comments
 (0)