Compare commits

..
Author SHA1 Message Date
james.maesandClaude Fable 5.1 e8c47f2c63 fix(widgets): leave out connection figures the provider does not keep
On H2 the Connections panel showed Maximum connections, Cache hit %, Temp MB and Deadlocks as 0; they are stored as null when the provider has no such counter and are now omitted.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-01 05:56:35 -06:00
james.maesandClaude Fable 5.1 6183f33f89 fix(widgets): show H2 tables by rows and size instead of empty scan columns
Without scan counters the Tables panel listed every H2 table with blank scan columns and a 0% sequential share, and the stored dead-row and sequential ratios were 0 rather than unknown. Providers without TABLE_ACCESS now get rows (estimate) and size, and both ratios stay empty when their counters are absent.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-01 05:54:13 -06:00
james.maesandClaude Fable 5.1 363d34ef66 fix(provider): leave the H2 collector's table sizing out of statements
DISK_SPACE_USED runs once per table on each table pass and showed as the second most expensive statement; like the INFORMATION_SCHEMA reads it is the collector's own work.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-01 05:51:00 -06:00
james.maesandClaude Fable 5.1 a31ab9d19c fix(provider): leave H2 connection setup out of statement statistics
On the enterprise server every unpooled connection replays the JDBC URL
settings as SET statements and calls DATABASE(), so they filled the Top
queries panel with thousands of calls that are not application work.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-01 05:49:24 -06:00
james.maesandClaude Fable 5.1 ef81224f8f fix(provider): refuse side-effecting SQL in H2 query plans
H2 has no READ ONLY transaction and a rollback does not undo file,
session, sequence or DDL side effects, and EXPLAIN ANALYZE executes the
query. H2 plans now refuse more than one statement and any SQL naming a
side-effecting function (FILE_WRITE, CSVREAD, LINK_SCHEMA, RUNSCRIPT, ...),
a DDL/DML or session keyword, or a user-defined alias, including quoted
names. ANALYZE is offered only to non-admin H2 users and only for a
single SELECT/WITH; capabilities report EXPLAIN_ANALYZE accordingly and
the service answers with a clear PLAN-only message. The connection is
marked read-only and the transaction still always rolls back.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-01 05:44:16 -06:00
james.maesandClaude Fable 5.1 b4e7d000b1 docs: document H2 support and the provider capability matrix
PROVIDERS.md records what each vendor supplies, approximates or lacks,
how to add a vendor, and what MySQL/MariaDB and SQLite would map to.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-01 05:41:20 -06:00
james.maesandClaude Fable 5.1 80508968a8 feat(provider): add H2 statistics provider and per-area capabilities
The enterprise test server runs on H2 and the qbit reported UNSUPPORTED
there. H2StatsProvider reads H2 2.x INFORMATION_SCHEMA: QUERY_STATISTICS
statements (when the database runs with QUERY_STATISTICS=TRUE), sessions
with blocking and idle-in-transaction, connection counts, table row
estimates and sizes, index definitions, settings and EXPLAIN text plans.

To let vendors with fewer counters degrade per feature instead of per
vendor, providers now declare StatsFeature areas. Rules declare the
areas they need and are skipped where a provider cannot supply them, so
H2 never produces unused-index or cache-hit findings from absent counters,
and the snapshot cache-hit ratio and connection limit stay empty instead
of zero. The previous-snapshot baseline no longer keys on a PostgreSQL
counter.

The registry maps JDBC product names to providers (register() is the
seam for more vendors). Health and the Setup panel ask the provider for
remedies, so pg_read_all_stats/pg_stat_statements are named only for
PostgreSQL; H2 reports NO_QUERY_STATISTICS and NOT_ADMIN. A new
Enable statement statistics settings action turns H2 query statistics on
as an admin, the qbit's only explicit change to the monitored database.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-01 05:41:19 -06:00
4 changed files with 3 additions and 197 deletions

No files matched your search

-7
View File
@@ -1,7 +0,0 @@
name: build
on:
push:
jobs:
maven:
uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/maven.yaml@v1
secrets: inherit
+2 -3
View File
@@ -18,7 +18,6 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<properties>
<qqq.version>4.1.0-RC.26</qqq.version> <!-- feeds qbit-build-parent's qqq-bom-pom import -->
<revision>0.1.0-SNAPSHOT</revision>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
@@ -29,7 +28,7 @@
<parent>
<groupId>com.kingsrook</groupId>
<artifactId>qbit-build-parent</artifactId>
<version>2.2.1</version>
<version>2.0.0</version>
</parent>
<groupId>com.kingsrook.qbits</groupId>
@@ -249,7 +248,7 @@
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<qqq.snapshot.version>4.1.0-RC.26</qqq.snapshot.version>
<qqq.snapshot.version>4.1.0-RC.13</qqq.snapshot.version>
</properties>
<dependencyManagement>
<dependencies>
@@ -1,44 +1,5 @@
components:
schemas:
BasicErrorResponseV1:
description: "The error body of every UI API operation (core and extensions)."
properties:
error:
description: "The error message (the same as message; kept for clients of\
\ UI API 1.0 to 1.5)."
type: "string"
code:
description: "A stable, machine-readable code. Well-known values: BAD_REQUEST\
\ (400), UNAUTHENTICATED (401),\nPERMISSION_DENIED (403), NOT_FOUND (404),\
\ CONFLICT (409), RATE_LIMITED (429), INTERNAL_ERROR (500),\nERROR (other).\
\ Extensions may add codes; treat an unknown code by the HTTP status.\
\ Since 1.6.0."
type: "string"
message:
description: "A message to show the user. Since 1.6.0."
type: "string"
fieldErrors:
description: "Errors about particular request fields, when known. Since\
\ 1.6.0."
items:
properties:
field:
description: "The field (or parameter) name."
type: "string"
message:
description: "The error about that field."
type: "string"
code:
description: "An optional machine-readable code, e.g. REQUIRED, TOO_LONG,\
\ PATTERN."
type: "string"
type: "object"
type: "array"
traceId:
description: "Identifies this request in the server logs (also the X-QQQ-Trace-Id\
\ response header); quote it when reporting a problem. Since 1.6.0."
type: "string"
type: "object"
DbPerformanceActivityV1:
properties:
blockingChains:
@@ -295,42 +256,9 @@ components:
type: "object"
securitySchemes:
sessionUuidCookie:
description: "The session cookie set by POST /qqq/v1/manageSession (web clients)."
in: "cookie"
name: "sessionUUID"
type: "apiKey"
bearerAuth:
bearerFormat: "JWT or session UUID"
description: "`Authorization: Bearer <token>`, where the token is either\n-\
\ an OAuth2 access token (a signed JWT) issued by the app's identity provider\
\ to the app's client,\n or to its native client (`values.nativeClientId`\
\ in GET /qqq/v1/metaData/authentication), for\n example from the device-authorization\
\ grant or auth-code + PKCE. It is verified against the\n provider's keys\
\ (issuer, audience, expiry, signature) and gives the same user and permissions\n\
\ as signing in on the web. When it expires (401), refresh it at the provider\
\ and retry; or\n- a session UUID returned by POST /qqq/v1/manageSession,\
\ which the server keeps refreshed."
scheme: "bearer"
type: "http"
oauth2:
description: "The app's OAuth2 / OpenID Connect provider. The real endpoints\
\ come from the provider's discovery\ndocument: `{values.baseUrl}.well-known/openid-configuration`,\
\ with values from\nGET /qqq/v1/metaData/authentication. Native clients use\
\ `values.nativeClientId` (a public client)\nwith auth-code + PKCE (loopback\
\ or custom-scheme redirect URI registered at the provider) or the\ndevice-authorization\
\ grant (`device_authorization_endpoint`, RFC 8628), then send the access\
\ token\nas `Authorization: Bearer`."
flows:
authorizationCode:
authorizationUrl: "/authorize"
scopes:
email: "Email address"
offline_access: "A refresh token, to renew the access token without signing\
\ in again"
openid: "OpenID Connect sign-in"
profile: "Name, username and groups (permissions come from the groups)"
tokenUrl: "/token"
type: "oauth2"
info:
contact:
email: "contact@kingsrook.com"
@@ -375,37 +303,10 @@ paths:
schema:
$ref: "#/components/schemas/DbPerformanceOverviewV1"
description: "Successful Response"
404:
content:
application/json:
schema:
$ref: "#/components/schemas/BasicErrorResponseV1"
description: "Not found, or not visible to this user (code NOT_FOUND)."
409:
content:
application/json:
schema:
$ref: "#/components/schemas/BasicErrorResponseV1"
description: "Conflict with the current state, e.g. an edit conflict or\
\ an invalid transition (code CONFLICT)."
429:
content:
application/json:
schema:
$ref: "#/components/schemas/BasicErrorResponseV1"
description: "Too many requests (code RATE_LIMITED); retry later."
security:
- sessionUuidCookie:
- "N/A"
- bearerAuth:
- "N/A"
- oauth2:
- "openid"
- "profile"
- "email"
summary: "Database Performance overview"
tags:
- "db-performance"
/qqq/v1/qbit/db-performance/activity:
get:
description: "Requires db_performance.view and uses the caller's session."
@@ -442,37 +343,10 @@ paths:
schema:
$ref: "#/components/schemas/DbPerformanceActivityV1"
description: "Successful Response"
404:
content:
application/json:
schema:
$ref: "#/components/schemas/BasicErrorResponseV1"
description: "Not found, or not visible to this user (code NOT_FOUND)."
409:
content:
application/json:
schema:
$ref: "#/components/schemas/BasicErrorResponseV1"
description: "Conflict with the current state, e.g. an edit conflict or\
\ an invalid transition (code CONFLICT)."
429:
content:
application/json:
schema:
$ref: "#/components/schemas/BasicErrorResponseV1"
description: "Too many requests (code RATE_LIMITED); retry later."
security:
- sessionUuidCookie:
- "N/A"
- bearerAuth:
- "N/A"
- oauth2:
- "openid"
- "profile"
- "email"
summary: "Database Performance activity"
tags:
- "db-performance"
/qqq/v1/qbit/db-performance/explain:
post:
description: "PLAN requires db_performance.explain; ANALYZE requires db_performance.explain_analyze.\
@@ -509,37 +383,10 @@ paths:
schema:
$ref: "#/components/schemas/DbPerformanceExplainResultV1"
description: "Successful Response"
404:
content:
application/json:
schema:
$ref: "#/components/schemas/BasicErrorResponseV1"
description: "Not found, or not visible to this user (code NOT_FOUND)."
409:
content:
application/json:
schema:
$ref: "#/components/schemas/BasicErrorResponseV1"
description: "Conflict with the current state, e.g. an edit conflict or\
\ an invalid transition (code CONFLICT)."
429:
content:
application/json:
schema:
$ref: "#/components/schemas/BasicErrorResponseV1"
description: "Too many requests (code RATE_LIMITED); retry later."
security:
- sessionUuidCookie:
- "N/A"
- bearerAuth:
- "N/A"
- oauth2:
- "openid"
- "profile"
- "email"
summary: "Explain a query (PostgreSQL JSON or H2 text plan)"
tags:
- "db-performance"
/qqq/v1/qbit/db-performance/statements/lookup:
get:
description: "Requires db_performance.view and uses the caller's session."
@@ -581,40 +428,7 @@ paths:
schema:
$ref: "#/components/schemas/DbPerformanceLookupV1"
description: "Successful Response"
404:
content:
application/json:
schema:
$ref: "#/components/schemas/BasicErrorResponseV1"
description: "Not found, or not visible to this user (code NOT_FOUND)."
409:
content:
application/json:
schema:
$ref: "#/components/schemas/BasicErrorResponseV1"
description: "Conflict with the current state, e.g. an edit conflict or\
\ an invalid transition (code CONFLICT)."
429:
content:
application/json:
schema:
$ref: "#/components/schemas/BasicErrorResponseV1"
description: "Too many requests (code RATE_LIMITED); retry later."
security:
- sessionUuidCookie:
- "N/A"
- bearerAuth:
- "N/A"
- oauth2:
- "openid"
- "profile"
- "email"
summary: "Database Performance lookup"
tags:
- "db-performance"
servers:
- description: "The QQQ server that serves this document (paths include /qqq/v1)."
url: "/"
tags:
- description: "Operations of the db-performance extension."
name: "db-performance"
@@ -56,7 +56,7 @@ class DbPerfApiContractTest
}
assertThat(UiApiContract.check(generated, Files.readString(path), null, 1, "mvnw -Dtest=DbPerfApiContractTest -Ddbperf.openapi.update=true test")).isEmpty();
var model = version.generateExtensionOpenAPIModel("qqq", extension);
assertThat(model.getComponents().getSchemas().keySet()).allMatch(name -> (name.startsWith("DbPerformance") || name.equals("BasicErrorResponseV1")) && name.endsWith("V1"));
assertThat(model.getComponents().getSchemas().keySet()).allMatch(name -> name.startsWith("DbPerformance") && name.endsWith("V1"));
assertThat(model.getPaths().keySet()).containsExactlyInAnyOrder("/qqq/v1/qbit/db-performance/overview", "/qqq/v1/qbit/db-performance/activity",
"/qqq/v1/qbit/db-performance/explain", "/qqq/v1/qbit/db-performance/statements/lookup");
assertThat(extension.getSemanticVersion()).isEqualTo("1.0.0");