| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent e3fda69 commit 72c37b1
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1188,6 +1188,18 @@ returns an empty iterator. The prepared statement [parameters are bound][] using | |||
| 1188 | 1188 | the values in `namedParameters` and `anonymousParameters`. See | |
| 1189 | 1189 | [Binding parameters][]. | |
| 1190 | 1190 | ||
| 1191 | + ### `statement.resetStats()` | ||
| 1192 | + | ||
| 1193 | + <!-- YAML | ||
| 1194 | + added: REPLACEME | ||
| 1195 | + --> | ||
| 1196 | + | ||
| 1197 | + Resets every counter reported by [`statement.stat()`][] back to zero, except | ||
| 1198 | + `memused`, which reports current memory usage and cannot be reset. This | ||
| 1199 | + method is a wrapper around [`sqlite3_stmt_status()`][] and is useful for | ||
| 1200 | + measuring a specific workload without the counts accumulated by earlier | ||
| 1201 | + executions of the same prepared statement. | ||
| 1202 | + | ||
| 1191 | 1203 | ### `statement.run([namedParameters][, ...anonymousParameters])` | |
| 1192 | 1204 | ||
| 1193 | 1205 | <!-- YAML | |
@@ -1314,6 +1326,43 @@ added: REPLACEME | |||
| 1314 | 1326 | Finalizes the prepared statement. If the prepared statement is already | |
| 1315 | 1327 | finalized, then this is a no-op. | |
| 1316 | 1328 | ||
| 1329 | + ### `statement.stat(counter)` | ||
| 1330 | + | ||
| 1331 | + <!-- YAML | ||
| 1332 | + added: REPLACEME | ||
| 1333 | + --> | ||
| 1334 | + | ||
| 1335 | + * `counter` {string} The name of the counter to read. One of: | ||
| 1336 | + | ||
| 1337 | + * `'fullscanStep'` The number of times SQLite has stepped forward in a table | ||
| 1338 | + as part of a full table scan. | ||
| 1339 | + * `'sort'` The number of sort operations that have occurred. | ||
| 1340 | + * `'autoindex'` The number of rows inserted into transient indices that were | ||
| 1341 | + created automatically to help joins run faster. | ||
| 1342 | + * `'vmStep'` The number of virtual machine operations executed by the | ||
| 1343 | + prepared statement. | ||
| 1344 | + * `'reprepare'` The number of times the statement has been automatically | ||
| 1345 | + reprepared due to schema changes or changes to bound parameters. | ||
| 1346 | + * `'run'` The number of execution cycles started by the prepared statement. | ||
| 1347 | + * `'filterMiss'` The number of times the Bloom filter returned a result that | ||
| 1348 | + required the join step to be processed as normal. | ||
| 1349 | + * `'filterHit'` The number of times a join step was bypassed because a Bloom | ||
| 1350 | + filter returned not-found. | ||
| 1351 | + * `'memused'` The approximate number of bytes of heap memory used to store | ||
| 1352 | + the prepared statement. | ||
| 1353 | + | ||
| 1354 | + * Returns: {number} The current value of the requested counter. | ||
| 1355 | + | ||
| 1356 | + Returns one of the runtime counters that SQLite tracks for this prepared | ||
| 1357 | + statement. This method is a wrapper around [`sqlite3_stmt_status()`][] and does | ||
| 1358 | + not reset the counter. Asserting that a statement does not perform a full table | ||
| 1359 | + scan (`statement.stat('fullscanStep') === 0`) is a useful check to guard | ||
| 1360 | + against degenerate performance. | ||
| 1361 | + | ||
| 1362 | + The `'filterMiss'` and `'filterHit'` counters require SQLite 3.38.0 or later. | ||
| 1363 | + Builds linked against an older SQLite with `--shared-sqlite` do not expose them, | ||
| 1364 | + and passing either name throws `ERR_INVALID_ARG_VALUE`. | ||
| 1365 | + | ||
| 1317 | 1366 | ## Class: `SQLTagStore` | |
| 1318 | 1367 | ||
| 1319 | 1368 | <!-- YAML | |
@@ -1832,6 +1881,7 @@ callback function to indicate what type of operation is being authorized. | |||
| 1832 | 1881 | [`sqlite3_serialize()`]: https://sqlite.org/c3ref/serialize.html | |
| 1833 | 1882 | [`sqlite3_set_authorizer()`]: https://sqlite.org/c3ref/set_authorizer.html | |
| 1834 | 1883 | [`sqlite3_sql()`]: https://www.sqlite.org/c3ref/expanded_sql.html | |
| 1884 | + [`sqlite3_stmt_status()`]: https://www.sqlite.org/c3ref/stmt_status.html | ||
| 1835 | 1885 | [`sqlite3changeset_apply()`]: https://www.sqlite.org/session/sqlite3changeset_apply.html | |
| 1836 | 1886 | [`sqlite3session_attach()`]: https://www.sqlite.org/session/sqlite3session_attach.html | |
| 1837 | 1887 | [`sqlite3session_changeset()`]: https://www.sqlite.org/session/sqlite3session_changeset.html | |
@@ -1840,6 +1890,7 @@ callback function to indicate what type of operation is being authorized. | |||
| 1840 | 1890 | [`sqlite3session_patchset()`]: https://www.sqlite.org/session/sqlite3session_patchset.html | |
| 1841 | 1891 | [`statement.setAllowBareNamedParameters()`]: #statementsetallowbarenamedparametersenabled | |
| 1842 | 1892 | [`statement.setAllowUnknownNamedParameters()`]: #statementsetallowunknownnamedparametersenabled | |
| 1893 | + [`statement.stat()`]: #statementstatcounter | ||
| 1843 | 1894 | [busy timeout]: https://sqlite.org/c3ref/busy_timeout.html | |
| 1844 | 1895 | [connection]: https://www.sqlite.org/c3ref/sqlite3.html | |
| 1845 | 1896 | [data types]: https://www.sqlite.org/datatype3.html | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -177,9 +177,11 @@ Local<DictionaryTemplate> getLazyIterTemplate(Environment* env) { | |||
| 177 | 177 | } | |
| 178 | 178 | } // namespace | |
| 179 | 179 | ||
| 180 | - // Helper function to find limit info from JS property name | ||
| 181 | - static constexpr const LimitInfo* GetLimitInfoFromName(std::string_view name) { | ||
| 182 | - for (const auto& info : kLimitMapping) { | ||
| 180 | + // Helper function to look up a mapping entry by its JS-facing name | ||
| 181 | + template <typename T, size_t N> | ||
| 182 | + static constexpr const T* FindByJsName(const std::array<T, N>& mapping, | ||
| 183 | + std::string_view name) { | ||
| 184 | + for (const auto& info : mapping) { | ||
| 183 | 185 | if (name == info.js_name) { | |
| 184 | 186 | return &info; | |
| 185 | 187 | } | |
@@ -793,7 +795,8 @@ Intercepted DatabaseSyncLimits::LimitsGetter( | |||
| 793 | 795 | Isolate* isolate = env->isolate(); | |
| 794 | 796 | ||
| 795 | 797 | Utf8Value prop_name(isolate, property); | |
| 796 | - const LimitInfo* limit_info = GetLimitInfoFromName(prop_name.ToStringView()); | ||
| 798 | + const LimitInfo* limit_info = | ||
| 799 | + FindByJsName(kLimitMapping, prop_name.ToStringView()); | ||
| 797 | 800 | ||
| 798 | 801 | if (limit_info == nullptr) { | |
| 799 | 802 | return Intercepted::kNo; // Unknown property, let default handling occur | |
@@ -825,7 +828,8 @@ Intercepted DatabaseSyncLimits::LimitsSetter( | |||
| 825 | 828 | Isolate* isolate = env->isolate(); | |
| 826 | 829 | ||
| 827 | 830 | Utf8Value prop_name(isolate, property); | |
| 828 | - const LimitInfo* limit_info = GetLimitInfoFromName(prop_name.ToStringView()); | ||
| 831 | + const LimitInfo* limit_info = | ||
| 832 | + FindByJsName(kLimitMapping, prop_name.ToStringView()); | ||
| 829 | 833 | ||
| 830 | 834 | if (limit_info == nullptr) { | |
| 831 | 835 | return Intercepted::kNo; | |
@@ -873,7 +877,8 @@ Intercepted DatabaseSyncLimits::LimitsQuery( | |||
| 873 | 877 | ||
| 874 | 878 | Isolate* isolate = info.GetIsolate(); | |
| 875 | 879 | Utf8Value prop_name(isolate, property); | |
| 876 | - const LimitInfo* limit_info = GetLimitInfoFromName(prop_name.ToStringView()); | ||
| 880 | + const LimitInfo* limit_info = | ||
| 881 | + FindByJsName(kLimitMapping, prop_name.ToStringView()); | ||
| 877 | 882 | ||
| 878 | 883 | if (!limit_info) { | |
| 879 | 884 | return Intercepted::kNo; | |
@@ -2694,6 +2699,7 @@ void StatementSync::Finalize() { | |||
| 2694 | 2699 | ||
| 2695 | 2700 | void StatementSync::InvalidateColumnNameCache() { | |
| 2696 | 2701 | cached_column_names_.clear(); | |
| 2702 | + cached_column_names_reprepare_count_ = -1; | ||
| 2697 | 2703 | } | |
| 2698 | 2704 | ||
| 2699 | 2705 | inline bool StatementSync::IsFinalized() { | |
@@ -3350,6 +3356,60 @@ void StatementSync::ExpandedSQLGetter(const FunctionCallbackInfo<Value>& args) { | |||
| 3350 | 3356 | args.GetReturnValue().Set(result); | |
| 3351 | 3357 | } | |
| 3352 | 3358 | ||
| 3359 | + void StatementSync::Stat(const FunctionCallbackInfo<Value>& args) { | ||
| 3360 | + StatementSync* stmt; | ||
| 3361 | + ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This()); | ||
| 3362 | + Environment* env = Environment::GetCurrent(args); | ||
| 3363 | + THROW_AND_RETURN_ON_BAD_STATE( | ||
| 3364 | + env, stmt->IsFinalized(), "statement has been finalized"); | ||
| 3365 | + Isolate* isolate = env->isolate(); | ||
| 3366 | + | ||
| 3367 | + if (!args[0]->IsString()) { | ||
| 3368 | + THROW_ERR_INVALID_ARG_TYPE(isolate, | ||
| 3369 | + "The \"counter\" argument must be a string."); | ||
| 3370 | + return; | ||
| 3371 | + } | ||
| 3372 | + | ||
| 3373 | + Utf8Value counter(isolate, args[0].As<String>()); | ||
| 3374 | + const StatusInfo* status_info = | ||
| 3375 | + FindByJsName(kStatusMapping, counter.ToStringView()); | ||
| 3376 | + if (status_info == nullptr) { | ||
| 3377 | + THROW_ERR_INVALID_ARG_VALUE( | ||
| 3378 | + isolate, "The \"counter\" argument is not a valid statistic name."); | ||
| 3379 | + return; | ||
| 3380 | + } | ||
| 3381 | + | ||
| 3382 | + // The reset flag is always false; the counter is read without being cleared. | ||
| 3383 | + int value = sqlite3_stmt_status( | ||
| 3384 | + stmt->statement_.get(), status_info->sqlite_status_id, false); | ||
| 3385 | + args.GetReturnValue().Set(Integer::New(isolate, value)); | ||
| 3386 | + } | ||
| 3387 | + | ||
| 3388 | + void StatementSync::ResetStats(const FunctionCallbackInfo<Value>& args) { | ||
| 3389 | + StatementSync* stmt; | ||
| 3390 | + ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This()); | ||
| 3391 | + Environment* env = Environment::GetCurrent(args); | ||
| 3392 | + THROW_AND_RETURN_ON_BAD_STATE( | ||
| 3393 | + env, stmt->IsFinalized(), "statement has been finalized"); | ||
| 3394 | + | ||
| 3395 | + // sqlite3_stmt_status() resets a single counter per call, so every exposed | ||
| 3396 | + // counter is visited. The returned value is the pre-reset one and is unused. | ||
| 3397 | + // SQLITE_STMTSTATUS_MEMUSED is skipped: it reports current memory usage | ||
| 3398 | + // rather than an accumulated counter, and SQLite ignores the reset flag for | ||
| 3399 | + // it. | ||
| 3400 | + for (const auto& info : kStatusMapping) { | ||
| 3401 | + if (info.sqlite_status_id == SQLITE_STMTSTATUS_MEMUSED) { | ||
| 3402 | + continue; | ||
| 3403 | + } | ||
| 3404 | + sqlite3_stmt_status(stmt->statement_.get(), info.sqlite_status_id, true); | ||
| 3405 | + } | ||
| 3406 | + | ||
| 3407 | + // The column name cache is keyed on SQLITE_STMTSTATUS_REPREPARE, which was | ||
| 3408 | + // just zeroed. Without invalidating, a later re-prepare can make the counter | ||
| 3409 | + // match the cached generation again and the stale names would be reused. | ||
| 3410 | + stmt->InvalidateColumnNameCache(); | ||
| 3411 | + } | ||
| 3412 | + | ||
| 3353 | 3413 | void StatementSync::SetAllowBareNamedParameters( | |
| 3354 | 3414 | const FunctionCallbackInfo<Value>& args) { | |
| 3355 | 3415 | StatementSync* stmt; | |
@@ -3775,6 +3835,8 @@ Local<FunctionTemplate> StatementSync::GetConstructorTemplate( | |||
| 3775 | 3835 | tmpl, | |
| 3776 | 3836 | FIXED_ONE_BYTE_STRING(isolate, "expandedSQL"), | |
| 3777 | 3837 | StatementSync::ExpandedSQLGetter); | |
| 3838 | + SetProtoMethodNoSideEffect(isolate, tmpl, "stat", StatementSync::Stat); | ||
| 3839 | + SetProtoMethod(isolate, tmpl, "resetStats", StatementSync::ResetStats); | ||
| 3778 | 3840 | SetProtoMethod(isolate, | |
| 3779 | 3841 | tmpl, | |
| 3780 | 3842 | "setAllowBareNamedParameters", | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -52,6 +52,32 @@ static_assert( | |||
| 52 | 52 | CheckLimitIndices(), | |
| 53 | 53 | "Each kLimitMapping entry's sqlite_limit_id must match its index"); | |
| 54 | 54 | ||
| 55 | + // Mapping from JavaScript counter names to SQLite statement status constants | ||
| 56 | + struct StatusInfo { | ||
| 57 | + std::string_view js_name; | ||
| 58 | + int sqlite_status_id; | ||
| 59 | + }; | ||
| 60 | + | ||
| 61 | + // SQLITE_STMTSTATUS_FILTER_HIT and SQLITE_STMTSTATUS_FILTER_MISS require | ||
| 62 | + // SQLite >= 3.38.0. Older shared-library builds omit the two counters. | ||
| 63 | + #if SQLITE_VERSION_NUMBER >= 3038000 | ||
| 64 | + #define NODE_SQLITE_HAS_FILTER_STATUS 1 | ||
| 65 | + #endif | ||
| 66 | + | ||
| 67 | + inline constexpr auto kStatusMapping = std::to_array<StatusInfo>({ | ||
| 68 | + {"fullscanStep", SQLITE_STMTSTATUS_FULLSCAN_STEP}, | ||
| 69 | + {"sort", SQLITE_STMTSTATUS_SORT}, | ||
| 70 | + {"autoindex", SQLITE_STMTSTATUS_AUTOINDEX}, | ||
| 71 | + {"vmStep", SQLITE_STMTSTATUS_VM_STEP}, | ||
| 72 | + {"reprepare", SQLITE_STMTSTATUS_REPREPARE}, | ||
| 73 | + {"run", SQLITE_STMTSTATUS_RUN}, | ||
| 74 | + #ifdef NODE_SQLITE_HAS_FILTER_STATUS | ||
| 75 | + {"filterMiss", SQLITE_STMTSTATUS_FILTER_MISS}, | ||
| 76 | + {"filterHit", SQLITE_STMTSTATUS_FILTER_HIT}, | ||
| 77 | + #endif | ||
| 78 | + {"memused", SQLITE_STMTSTATUS_MEMUSED}, | ||
| 79 | + }); | ||
| 80 | + | ||
| 55 | 81 | class DatabaseOpenConfiguration { | |
| 56 | 82 | public: | |
| 57 | 83 | explicit DatabaseOpenConfiguration(std::string&& location) | |
@@ -284,6 +310,8 @@ class StatementSync : public BaseObject { | |||
| 284 | 310 | static void SourceSQLGetter(const v8::FunctionCallbackInfo<v8::Value>& args); | |
| 285 | 311 | static void ExpandedSQLGetter( | |
| 286 | 312 | const v8::FunctionCallbackInfo<v8::Value>& args); | |
| 313 | + static void Stat(const v8::FunctionCallbackInfo<v8::Value>& args); | ||
| 314 | + static void ResetStats(const v8::FunctionCallbackInfo<v8::Value>& args); | ||
| 287 | 315 | static void SetAllowBareNamedParameters( | |
| 288 | 316 | const v8::FunctionCallbackInfo<v8::Value>& args); | |
| 289 | 317 | static void SetAllowUnknownNamedParameters( | |
| Back | FazBrowse Home | New Git URL |
0 commit comments