Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added ability to run sql queries on sqlite files #294

Merged
merged 1 commit into from
Mar 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,23 @@
// which Flying Meat Inc. licenses this file to you.

#import <Foundation/Foundation.h>
#import "FLEXSQLResult.h"

@protocol FLEXDatabaseManager <NSObject>

@required

+ (instancetype)managerForDatabase:(NSString *)path;

- (BOOL)open;

/// @return a list of all table names
- (NSArray<NSString *> *)queryAllTables;
- (NSArray<NSString *> *)queryAllColumnsWithTableName:(NSString *)tableName;
- (NSArray<NSArray *> *)queryAllDataWithTableName:(NSString *)tableName;
- (NSArray<NSString *> *)queryAllColumnsOfTable:(NSString *)tableName;
- (NSArray<NSArray *> *)queryAllDataInTable:(NSString *)tableName;

@optional

- (FLEXSQLResult *)executeStatement:(NSString *)SQLStatement;

@end
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#import "FLEXRealmDatabaseManager.h"
#import "NSArray+Functional.h"
#import "FLEXSQLResult.h"

#if __has_include(<Realm/Realm.h>)
#import <Realm/Realm.h>
Expand Down Expand Up @@ -68,15 +69,15 @@ - (BOOL)open {
}];
}

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

- (NSArray<NSArray *> *)queryAllDataWithTableName:(NSString *)tableName {
- (NSArray<NSArray *> *)queryAllDataInTable:(NSString *)tableName {
RLMObjectSchema *objectSchema = [self.realm.schema schemaForClassName:tableName];
RLMResults *results = [self.realm allObjects:tableName];
if (results.count == 0 || !objectSchema) {
Expand Down
41 changes: 41 additions & 0 deletions Classes/GlobalStateExplorers/DatabaseBrowser/FLEXSQLResult.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// FLEXSQLResult.h
// FLEX
//
// Created by Tanner on 3/3/20.
// Copyright © 2020 Flipboard. All rights reserved.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface FLEXSQLResult : NSObject

/// Describes the result of a non-select query, or an error of any kind of query
+ (instancetype)message:(NSString *)message;
/// @param rowData A list of rows, where each element in the row
/// corresponds to the column given in /c columnNames
+ (instancetype)columns:(NSArray<NSString *> *)columnNames
rows:(NSArray<NSArray<NSString *> *> *)rowData;

@property (nonatomic, readonly, nullable) NSString *message;

/// A list of column names
@property (nonatomic, readonly, nullable) NSArray<NSString *> *columns;
/// A list of rows, where each element in the row corresponds
/// to the value of the column at the same index in \c columns.
///
/// That is, given a row, looping over the contents of the row and
/// the contents of \c columns will give you key-value pairs of
/// column names to column values for that row.
@property (nonatomic, readonly, nullable) NSArray<NSArray<NSString *> *> *rows;
/// A list of rows where the fields are paired to column names.
///
/// This property is lazily constructed by looping over
/// the rows and columns present in the other two properties.
@property (nonatomic, readonly, nullable) NSArray<NSDictionary<NSString *, id> *> *keyedRows;

@end

NS_ASSUME_NONNULL_END
47 changes: 47 additions & 0 deletions Classes/GlobalStateExplorers/DatabaseBrowser/FLEXSQLResult.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// FLEXSQLResult.m
// FLEX
//
// Created by Tanner on 3/3/20.
// Copyright © 2020 Flipboard. All rights reserved.
//

#import "FLEXSQLResult.h"
#import "NSArray+Functional.h"

@implementation FLEXSQLResult
@synthesize keyedRows = _keyedRows;

+ (instancetype)message:(NSString *)message {
return [[self alloc] initWithmessage:message columns:nil rows:nil];
}

+ (instancetype)columns:(NSArray<NSString *> *)columnNames rows:(NSArray<NSArray<NSString *> *> *)rowData {
return [[self alloc] initWithmessage:nil columns:columnNames rows:rowData];
}

- (id)initWithmessage:(NSString *)message columns:(NSArray *)columns rows:(NSArray<NSArray *> *)rows {
NSParameterAssert(message || (columns && rows));
NSParameterAssert(columns.count == rows.firstObject.count);

self = [super init];
if (self) {
_message = message;
_columns = columns;
_rows = rows;
}

return self;
}

- (NSArray<NSDictionary<NSString *,id> *> *)keyedRows {
if (!_keyedRows) {
_keyedRows = [self.rows flex_mapped:^id(NSArray<NSString *> *row, NSUInteger idx) {
return [NSDictionary dictionaryWithObjects:row forKeys:self.columns];
}];
}

return _keyedRows;
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@
#import "FLEXSQLiteDatabaseManager.h"
#import "FLEXManager.h"
#import "NSArray+Functional.h"
#import "FLEXSQLResult.h"
#import <sqlite3.h>

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

@interface FLEXSQLiteDatabaseManager ()
@property (nonatomic, readonly) sqlite3 *db;
@property (nonatomic) sqlite3 *db;
@property (nonatomic, copy) NSString *path;
@end

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

- (BOOL)open {
if (_db) {
if (self.db) {
return YES;
}

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

return YES;
}

- (BOOL)close {
if (!_db) {
if (!self.db) {
return YES;
}

Expand All @@ -84,88 +85,85 @@ - (BOOL)close {
}
} while (retry);

_db = nil;
self.db = nil;
return YES;
}

- (NSArray<NSString *> *)queryAllTables {
return [[self executeQuery:QUERY_TABLENAMES_SQL] flex_mapped:^id(NSArray *table, NSUInteger idx) {
return [[self executeStatement:QUERY_TABLENAMES].rows flex_mapped:^id(NSArray *table, NSUInteger idx) {
return table.firstObject;
}];
}

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

return [results flex_mapped:^id(NSDictionary *column, NSUInteger idx) {
return [results.keyedRows flex_mapped:^id(NSDictionary *column, NSUInteger idx) {
return column[@"name"];
}];
}

- (NSArray<NSArray *> *)queryAllDataWithTableName:(NSString *)tableName {
return [self executeQuery:[@"SELECT * FROM "
- (NSArray<NSArray *> *)queryAllDataInTable:(NSString *)tableName {
return [self executeStatement:[@"SELECT * FROM "
stringByAppendingString:tableName
]];
]].rows;
}

#pragma mark - Private

/// @return an array of rows, where each row is an array
/// containing the values of each column for that row
- (NSArray<NSArray *> *)executeQuery:(NSString *)sql {
- (FLEXSQLResult *)executeStatement:(NSString *)sql {
[self open];

NSMutableArray<NSArray *> *results = [NSMutableArray array];
FLEXSQLResult *result = nil;

sqlite3_stmt *pstmt;
if (sqlite3_prepare_v2(_db, sql.UTF8String, -1, &pstmt, 0) == SQLITE_OK) {
while (sqlite3_step(pstmt) == SQLITE_ROW) {
int num_cols = sqlite3_data_count(pstmt);
if (num_cols > 0) {
int columnCount = sqlite3_column_count(pstmt);

[results addObject:[NSArray flex_forEachUpTo:columnCount map:^id(NSUInteger i) {
NSMutableArray<NSArray *> *rows = [NSMutableArray new];

// Grab columns
int columnCount = sqlite3_column_count(pstmt);
NSArray<NSString *> *columns = [NSArray flex_forEachUpTo:columnCount map:^id(NSUInteger i) {
return @(sqlite3_column_name(pstmt, (int)i));
}];

// Execute statement
int status;
while ((status = sqlite3_step(pstmt)) == SQLITE_ROW) {
// Grab rows if this is a selection query
int dataCount = sqlite3_data_count(pstmt);
if (dataCount > 0) {
[rows addObject:[NSArray flex_forEachUpTo:columnCount map:^id(NSUInteger i) {
return [self objectForColumnIndex:(int)i stmt:pstmt];
}]];
}
}
}

[self close];
return results;
}

/// Like \c executeQuery: except that a list of dictionaries are returned,
/// where the keys are column names and the values are the data.
- (NSArray<NSDictionary *> *)executeQueryWithColumns:(NSString *)sql {
[self open];

NSMutableArray<NSDictionary *> *results = [NSMutableArray array];

sqlite3_stmt *pstmt;
if (sqlite3_prepare_v2(_db, sql.UTF8String, -1, &pstmt, 0) == SQLITE_OK) {
while (sqlite3_step(pstmt) == SQLITE_ROW) {
int num_cols = sqlite3_data_count(pstmt);
if (num_cols > 0) {
int columnCount = sqlite3_column_count(pstmt);


NSMutableDictionary *rowFields = [NSMutableDictionary new];
for (int i = 0; i < columnCount; i++) {
id value = [self objectForColumnIndex:(int)i stmt:pstmt];
rowFields[@(sqlite3_column_name(pstmt, i))] = value;
}

[results addObject:rowFields];

if (status == SQLITE_DONE) {
if (rows.count) {
// We selected some rows
result = [FLEXSQLResult columns:columns rows:rows];
} else {
// We executed a query like INSERT, UDPATE, or DELETE
int rowsAffected = sqlite3_changes(_db);
NSString *message = [NSString stringWithFormat:@"%d row(s) affected", rowsAffected];
result = [FLEXSQLResult message:message];
}
} else {
// An error occured executing the query
result = [FLEXSQLResult message:@(sqlite3_errmsg(_db) ?: "(Execution: empty error)")];
}
} else {
// An error occurred creating the prepared statement
result = [FLEXSQLResult message:@(sqlite3_errmsg(_db) ?: "(Prepared statement: empty error)")];
}

sqlite3_finalize(pstmt);
[self close];
return results;
return result;
}


#pragma mark - Private

- (id)objectForColumnIndex:(int)columnIdx stmt:(sqlite3_stmt*)stmt {
int columnType = sqlite3_column_type(stmt, columnIdx);

Expand All @@ -184,7 +182,7 @@ - (id)objectForColumnIndex:(int)columnIdx stmt:(sqlite3_stmt*)stmt {
return [self stringForColumnIndex:columnIdx stmt:stmt] ?: NSNull.null;
}
}

- (NSString *)stringForColumnIndex:(int)columnIdx stmt:(sqlite3_stmt *)stmt {
if (sqlite3_column_type(stmt, columnIdx) == SQLITE_NULL || columnIdx < 0) {
return nil;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

@interface FLEXTableContentViewController : UIViewController

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

@end
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,32 @@
@interface FLEXTableContentViewController () <
FLEXMultiColumnTableViewDataSource, FLEXMultiColumnTableViewDelegate
>
@property (nonatomic, readonly) NSArray<NSString *> *columns;
@property (nonatomic, copy) NSArray<NSArray *> *rows;

@property (nonatomic) FLEXMultiColumnTableView *multiColumnView;
@end

@implementation FLEXTableContentViewController

- (void)viewDidLoad {
[super viewDidLoad];
self.edgesForExtendedLayout = UIRectEdgeNone;

+ (instancetype)columns:(NSArray<NSString *> *)columnNames
rows:(NSArray<NSArray<NSString *> *> *)rowData {
FLEXTableContentViewController *controller = [self new];
controller->_columns = columnNames;
controller->_rows = rowData;
return controller;
}

- (void)loadView {
[super loadView];

[self.view addSubview:self.multiColumnView];
}

- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
- (void)viewDidLoad {
[super viewDidLoad];

self.edgesForExtendedLayout = UIRectEdgeNone;
[self.multiColumnView reloadData];
}

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

_multiColumnView.dataSource = self;
_multiColumnView.delegate = self;
_multiColumnView.dataSource = self;
_multiColumnView.delegate = self;
}

return _multiColumnView;
Expand Down
Loading