From ddfdb8cd14ce8e223cc2a78d460472282c6aeb8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Fraz=C3=A3o?= Date: Tue, 12 Apr 2022 16:29:40 -0300 Subject: [PATCH 1/3] Resultset metadata - Feature to return resultset metadata on the low level API to help dynamic applications to analise column declared data types that can be "different" from storage datatypes, like datetime or booleans --- README.md | 58 +++++++++++++++++++++++++++++++++++++--- cpp/JSIHelper.cpp | 20 ++++++++++++-- cpp/JSIHelper.h | 12 ++++++++- cpp/installer.cpp | 12 +++++---- cpp/sqlbatchexecutor.cpp | 2 +- cpp/sqliteBridge.cpp | 23 +++++++++++++--- cpp/sqliteBridge.h | 2 +- src/index.ts | 23 ++++++++++++++++ 8 files changed, 136 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 81562df..40a86b5 100644 --- a/README.md +++ b/README.md @@ -33,12 +33,46 @@ 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; + /** A convenience function to acess the index based the row object + * @param idx the row index + * @returns the row structure identified by column names + */ + item: (idx: number) => any; + }; + /** + * Query metadata, avaliable only for select query results + */ + metadata?: ResultsetMetadata; } +/** + * 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; +}; + +/** + * Collection of columns that represents the resultset of a query + */ +declare type ResultsetMetadata = ColumnMetadata[]; + interface BatchQueryResult { status?: 0 | 1; rowsAffected?: number; @@ -116,6 +150,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 enought, 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..bbc89cb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -40,8 +40,31 @@ interface QueryResult { */ item: (idx: number) => any; }; + /** + * Query metadata, avaliable only for select query results + */ + metadata?: ResultsetMetadata; } +/** + * 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; +}; + +/** + * Collection of columns that represents the resultset of a query + */ +declare type ResultsetMetadata = ColumnMetadata[]; + /** * Allows the execution of bulk of sql commands * inside a transaction From cf21e7774dee57e037ed7f549f22fd1038fa8aec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Fraz=C3=A3o?= Date: Wed, 13 Apr 2022 08:14:27 -0300 Subject: [PATCH 2/3] Typo fixes and metadata types simplfied --- README.md | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 40a86b5..cc239fe 100644 --- a/README.md +++ b/README.md @@ -42,16 +42,11 @@ interface QueryResult { _array: any[]; /** The lengh of the dataset */ length: number; - /** A convenience function to acess the index based the row object - * @param idx the row index - * @returns the row structure identified by column names - */ - item: (idx: number) => any; }; /** * Query metadata, avaliable only for select query results */ - metadata?: ResultsetMetadata; + metadata?: ColumnMetadata[]; } /** @@ -68,11 +63,6 @@ declare type ColumnMetadata = { columnIndex: number; }; -/** - * Collection of columns that represents the resultset of a query - */ -declare type ResultsetMetadata = ColumnMetadata[]; - interface BatchQueryResult { status?: 0 | 1; rowsAffected?: number; @@ -151,7 +141,7 @@ 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 enought, like when data is stored outside +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: From 757e2c24efcbf00ca390e1dcc8056b8d67e1709d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Fraz=C3=A3o?= Date: Thu, 14 Apr 2022 10:46:01 -0300 Subject: [PATCH 3/3] Removed boilerplate type definition --- src/index.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/index.ts b/src/index.ts index bbc89cb..07b2142 100644 --- a/src/index.ts +++ b/src/index.ts @@ -43,7 +43,7 @@ interface QueryResult { /** * Query metadata, avaliable only for select query results */ - metadata?: ResultsetMetadata; + metadata?: ColumnMetadata[]; } /** @@ -60,11 +60,6 @@ declare type ColumnMetadata = { columnIndex: number; }; -/** - * Collection of columns that represents the resultset of a query - */ -declare type ResultsetMetadata = ColumnMetadata[]; - /** * Allows the execution of bulk of sql commands * inside a transaction