diff --git a/README.md b/README.md index 81562df..cc239fe 100644 --- a/README.md +++ b/README.md @@ -33,12 +33,36 @@ Inspired/compatible with [react-native-sqlite-storage](https://github.com/andpor ```typescript interface QueryResult { - status: 0 | 1; // 0 for correct execution - message: string; // if status === 1, here you will find error description - rows: any[]; + status?: 0 | 1; // 0 for correct execution insertId?: number; + rowsAffected: number; + message?: string; + rows?: { + /** Raw array with all dataset */ + _array: any[]; + /** The lengh of the dataset */ + length: number; + }; + /** + * Query metadata, avaliable only for select query results + */ + metadata?: ColumnMetadata[]; } +/** + * Column metadata + * Describes some information about columns fetched by the query + */ +declare type ColumnMetadata = { + /** The name used for this column for this resultset */ + columnName: string; + /** The declared column type for this column, when fetched directly from a table or a View resulting from a table column. "UNKNOWN" for dynamic values, like function returned ones. */ + columnDeclaredType: string; + /** + * The index for this column for this resultset*/ + columnIndex: number; +}; + interface BatchQueryResult { status?: 0 | 1; rowsAffected?: number; @@ -116,6 +140,24 @@ if (!result.status) { } ``` +In some scenarios, dynamic applications may need to get some metadata information about the returned resultset. +This can be done testing the returned data directly, but in some cases may not be enough, like when data is stored outside +storage datatypes, like booleans or datetimes. When fetching data directly from tables or views linked to table columns, SQLite is able +to identify the table declared types: + +```typescript +let result = sqlite.executeSql('myDatabase', 'SELECT int_column_1, bol_column_2 FROM sometable'); +if (!result.status) { + // result.status undefined or 0 === sucess + for (let i = 0; i < result.metadata.length; i++) { + const column = result.metadata[i]; + console.log(`${column.columnName} - ${column.columnDeclaredType}`); + // Output: + // int_column_1 - INTEGER + // bol_column_2 - BOOLEAN + } +} +``` Batch execution allows transactional execution of a set of commands ```typescript diff --git a/cpp/JSIHelper.cpp b/cpp/JSIHelper.cpp index c46ef46..defc8c6 100644 --- a/cpp/JSIHelper.cpp +++ b/cpp/JSIHelper.cpp @@ -126,7 +126,7 @@ void jsiQueryArgumentsToSequelParam(jsi::Runtime &rt, jsi::Value const ¶ms, } } -jsi::Value createSequelQueryExecutionResult(jsi::Runtime &rt, SQLiteOPResult status, vector> *results) +jsi::Value createSequelQueryExecutionResult(jsi::Runtime &rt, SQLiteOPResult status, vector> *results, vector *metadata) { jsi::Object res = jsi::Object(rt); if (status.type == SQLiteOk) @@ -183,7 +183,23 @@ jsi::Value createSequelQueryExecutionResult(jsi::Runtime &rt, SQLiteOPResult sta rows.setProperty(rt, "_array", move(array)); res.setProperty(rt, "rows", move(rows)); } - rows.setProperty(rt, "status", jsi::Value(0)); + + if(metadata != NULL) + { + size_t column_count = metadata->size(); + auto column_array = jsi::Array(rt, column_count); + for (int i = 0; i < column_count; i++) { + auto column = metadata->at(i); + jsi::Object column_object = jsi::Object(rt); + column_object.setProperty(rt, "columnName", jsi::String::createFromUtf8(rt, column.colunmName.c_str())); + column_object.setProperty(rt, "columnDeclaredType", jsi::String::createFromUtf8(rt, column.columnDeclaredType.c_str())); + column_object.setProperty(rt, "columnIndex", jsi::Value(column.columnIndex)); + column_array.setValueAtIndex(rt, i, move(column_object)); + } + res.setProperty(rt, "metadata", move(column_array)); + } + + res.setProperty(rt, "status", jsi::Value(0)); rows.setProperty(rt, "length", jsi::Value((int)rowCount)); } else diff --git a/cpp/JSIHelper.h b/cpp/JSIHelper.h index fa5012c..9372937 100644 --- a/cpp/JSIHelper.h +++ b/cpp/JSIHelper.h @@ -86,6 +86,16 @@ struct SequelBatchOperationResult int commands; }; +/** + * Describe column information of a resultset + */ +struct QuickColumnMetadata +{ + string colunmName; + int columnIndex; + string columnDeclaredType; +}; + /** * Fill the target vector with parsed parameters * */ @@ -99,6 +109,6 @@ QuickValue createIntegerQuickValue(double value); QuickValue createInt64QuickValue(long long value); QuickValue createDoubleQuickValue(double value); QuickValue createArrayBufferQuickValue(uint8_t *arrayBufferValue, size_t arrayBufferSize); -jsi::Value createSequelQueryExecutionResult(jsi::Runtime &rt, SQLiteOPResult status, vector> *results); +jsi::Value createSequelQueryExecutionResult(jsi::Runtime &rt, SQLiteOPResult status, vector> *results, vector *metadata); #endif /* JSIHelper_h */ diff --git a/cpp/installer.cpp b/cpp/installer.cpp index 602d0a5..4f070ed 100644 --- a/cpp/installer.cpp +++ b/cpp/installer.cpp @@ -182,10 +182,11 @@ void install(jsi::Runtime &rt, std::shared_ptr jsCallInvoker // Filling the results vector> results; - auto status = sqliteExecute(dbName, query, ¶ms, &results); + vector metadata; + auto status = sqliteExecute(dbName, query, ¶ms, &results, &metadata); // Converting results into a JSI Response - auto jsiResult = createSequelQueryExecutionResult(rt, status, &results); + auto jsiResult = createSequelQueryExecutionResult(rt, status, &results, &metadata); return move(jsiResult); }); @@ -407,12 +408,13 @@ void install(jsi::Runtime &rt, std::shared_ptr jsCallInvoker { // Inside the new worker thread, we can now call sqlite operations vector> results; - auto status = sqliteExecute(dbName, query, params.get(), &results); - invoker->invokeAsync([&rt, results = make_shared>>(results), status_copy = move(status), callback] + vector metadata; + auto status = sqliteExecute(dbName, query, params.get(), &results, &metadata); + invoker->invokeAsync([&rt, results = make_shared>>(results), metadata = make_shared>(metadata), status_copy = move(status), callback] { // Now, back into the JavaScript thread, we can translate the results // back to a JSI Object to pass on the callback - auto jsiResult = createSequelQueryExecutionResult(rt, status_copy, results.get()); + auto jsiResult = createSequelQueryExecutionResult(rt, status_copy, results.get(), metadata.get()); callback->asObject(rt).asFunction(rt).call(rt, move(jsiResult)); }); } catch (std::exception &exc) diff --git a/cpp/sqlbatchexecutor.cpp b/cpp/sqlbatchexecutor.cpp index 951bafa..3eb8984 100644 --- a/cpp/sqlbatchexecutor.cpp +++ b/cpp/sqlbatchexecutor.cpp @@ -60,7 +60,7 @@ SequelBatchOperationResult executeBatch(std::string dbName, vectorat(i); // We do not provide a datastructure to receive query data because we don't need/want to handle this results in a batch execution - auto result = sqliteExecute(dbName, command.sql, command.params.get(), NULL); + auto result = sqliteExecute(dbName, command.sql, command.params.get(), NULL, NULL); if(result.type == SQLiteError) { return SequelBatchOperationResult { diff --git a/cpp/sqliteBridge.cpp b/cpp/sqliteBridge.cpp index 9066388..28fae2f 100644 --- a/cpp/sqliteBridge.cpp +++ b/cpp/sqliteBridge.cpp @@ -226,7 +226,7 @@ void bindStatement(sqlite3_stmt *statement, vector *values) } } -SQLiteOPResult sqliteExecute(string const dbName, string const &query, vector *params, vector> *results) +SQLiteOPResult sqliteExecute(string const dbName, string const &query, vector *params, vector> *results, vector *metadata) { // Check if db connection is opened if (dbMap.count(dbName) == 0) @@ -263,7 +263,7 @@ SQLiteOPResult sqliteExecute(string const dbName, string const &query, vector row; while (isConsuming) @@ -334,12 +334,29 @@ SQLiteOPResult sqliteExecute(string const dbName, string const &query, vectorpush_back(move(row)); break; case SQLITE_DONE: + if(metadata != NULL) + { + i = 0; + count = sqlite3_column_count(statement); + while (i < count) + { + column_name = sqlite3_column_name(statement, i); + const char *tp = sqlite3_column_decltype(statement, i); + column_declared_type = tp != NULL ? tp : "UNKNOWN"; + QuickColumnMetadata meta = { + .colunmName = column_name, + .columnIndex = i, + .columnDeclaredType = column_declared_type, + }; + metadata->push_back(meta); + i++; + } + } isConsuming = false; break; diff --git a/cpp/sqliteBridge.h b/cpp/sqliteBridge.h index f301455..c225a34 100644 --- a/cpp/sqliteBridge.h +++ b/cpp/sqliteBridge.h @@ -21,6 +21,6 @@ SQLiteOPResult sqliteRemoveDb(string const dbName, string const docPath); // SequelResult sequel_attach(string const &dbName); -SQLiteOPResult sqliteExecute(string const dbName, string const &query, vector *values, vector> *result); +SQLiteOPResult sqliteExecute(string const dbName, string const &query, vector *values, vector> *result, vector *metadata); SequelLiteralUpdateResult sqliteExecuteLiteral(string const dbName, string const &query); diff --git a/src/index.ts b/src/index.ts index 3f897f2..07b2142 100644 --- a/src/index.ts +++ b/src/index.ts @@ -40,8 +40,26 @@ interface QueryResult { */ item: (idx: number) => any; }; + /** + * Query metadata, avaliable only for select query results + */ + metadata?: ColumnMetadata[]; } +/** + * Column metadata + * Describes some information about columns fetched by the query + */ +declare type ColumnMetadata = { + /** The name used for this column for this resultset */ + columnName: string; + /** The declared column type for this column, when fetched directly from a table or a View resulting from a table column. "UNKNOWN" for dynamic values, like function returned ones. */ + columnDeclaredType: string; + /** + * The index for this column for this resultset*/ + columnIndex: number; +}; + /** * Allows the execution of bulk of sql commands * inside a transaction