Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d3da0b6
feat(P0-3): Fix demo seed and prioritize pg_stat_statements
cursoragent Sep 26, 2026
0b61bfa
feat(Performance): use pg_stat_statements for PostgreSQL without log …
cursoragent Sep 26, 2026
3e76ddd
feat(seed): add sample digest entry visible in web Digest section
cursoragent Sep 26, 2026
8073fc3
feat(seed): pin demo shop as default connection
cursoragent Sep 26, 2026
75be4ae
fix(seed): generate digest from actual seeded data instead of hardcoded
cursoragent Sep 26, 2026
b1d7275
fix: detect postgres dbType and make agent upstream optional
cursoragent Sep 26, 2026
c3205c0
fix(demo): lower slow query threshold to 10ms and add deliberately sl…
cursoragent Sep 26, 2026
70b22b1
test(slow-queries): add tests for pg_stat_statements default path
cursoragent Sep 26, 2026
a994735
fix(seed): fix schema compatibility for user_digest_preference and sc…
cursoragent Sep 26, 2026
976bc48
fix(seed): resolve dashboard JSON escaping with external HTML file
cursoragent Sep 26, 2026
50e05bf
fix: address P0-3 demo seed remaining blockers
cursoragent Sep 26, 2026
ef1475c
fix: exclude pg_stat_statements functions from Brain schema introspec…
cursoragent Sep 26, 2026
60af74a
fix: apply extension view filter to scanSchema as well as getTablesAn…
cursoragent Sep 26, 2026
e787f99
test: update PostgresIntrospectionProviderTest for schema-qualified i…
cursoragent Sep 26, 2026
84252d8
fix(seed): remove fabricated index recommendations and performance_ac…
cursoragent Sep 26, 2026
a8f7026
fix(digest): preserve underscores in identifiers and hide debug signa…
cursoragent Sep 26, 2026
e4b9a22
fix(dashboard): format dates as 'Sep 26' instead of ISO timestamp
cursoragent Sep 26, 2026
5ea091a
fix(seed): scale up data volume to make queries genuinely slow (>100ms)
cursoragent Sep 26, 2026
03e6dd5
fix(seed): run workload queries as standalone statements, not inside …
cursoragent Sep 26, 2026
ac7cc50
fix(seed): run workload queries as standalone SQL for pg_stat_statements
cursoragent Sep 26, 2026
b62d69f
fix(seed): use query patterns that exceed 100ms threshold
cursoragent Sep 26, 2026
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
43 changes: 43 additions & 0 deletions .env.compose
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# ── DeepSQL Compose Testing Environment ────────────────────────────────────

# Database
DB_PASSWORD=a9QcVCjTuNbrHMZWZTpyQA

# Security
SECURITY_JWT_SECRET=QfZNdsrGel3et6o+RsngpP4rgeejcUVTsP8AQVf8rgmhdXatv7s2/VXPQOTi7TW1ETvK6bHVHNJvqsaGJ8fzDg==
SECURITY_AUTH_ENABLED=false

# Encryption
ENCRYPTION_KEY_ID=compose-test-key
ENCRYPTION_KEYS=compose-test-key:lhyHcPuJT1LnGeTN2qF1CJUJg4b+I9+1c2XgQqZgpuw=

# Valkey/Redis
DEEPSQL_VALKEY_PASSWORD=4Q4QVUFWExxVH5mkQxjyiw

# Agent
AGENT_PROVISION_SECRET=bNNNF/FIvFISaCuqCW42/E28XJI/GJz49cF18ahz/rQ=

# Vector store (pgvector mode - self-hosted)
VECTOR_STORE_TYPE=pgvector
AZURE_SEARCH_ENABLED=false
SPRING_AUTOCONFIGURE_EXCLUDE=org.springframework.ai.vectorstore.azure.autoconfigure.AzureVectorStoreAutoConfiguration

# Profile
SPRING_PROFILES_ACTIVE=prod

# Demo seeding
DEEPSQL_SEED_DEMO_DATA=1
DEEPSQL_SEED_CONNECTION_NAME="Demo Shop"

# CORS
CORS_ALLOWED_ORIGINS=http://localhost:3000,http://127.0.0.1:*,http://localhost:*

# Admin bootstrap
SECURITY_ADMIN_BOOTSTRAP_ENABLED=true
ADMIN_BOOTSTRAP_SECRET=guaHaduXAk4/Jp3Tjat5KxQgeFtXPg1HUYfFuuCEhng=
DEEPSQL_INITIAL_ADMIN_EMAIL=admin@localhost
DEEPSQL_INITIAL_ADMIN_PASSWORD=admin123

# Ports
DEEPSQL_FRONTEND_PORT=3000
DEEPSQL_BACKEND_PORT=8080
33 changes: 33 additions & 0 deletions .env.e2e-test
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# DeepSQL Self-Host E2E Test Configuration
# No LLM keys - testing deterministic features only

# Vault Database
DB_PASSWORD=postgres_e2e_test_pwd

# Security
SECURITY_JWT_SECRET=e2e-test-jwt-secret-must-be-at-least-32-bytes-long
SECURITY_AUTH_ENABLED=true
ADMIN_BOOTSTRAP_SECRET=e2e-bootstrap-secret
SECURITY_ADMIN_BOOTSTRAP_ENABLED=true

# Encryption
ENCRYPTION_KEYS=e2e-key-1:HnnMZeUyB+skPkPlviB5cGHAlG1HnJLUIhB4fxRdLnA=
ENCRYPTION_KEY_ID=e2e-key-1

# Valkey/Redis
DEEPSQL_VALKEY_PASSWORD=valkey_e2e_test_pwd

# Admin credentials for seed script
DEEPSQL_INITIAL_ADMIN_EMAIL=admin@e2e-test.local
DEEPSQL_INITIAL_ADMIN_PASSWORD=AdminE2ETest123!

# No LLM configuration - testing deterministic features
# DEEPSQL_CHAT_PROVIDER not set
# DEEPSQL_CHAT_API_KEY not set

# CORS
CORS_ALLOWED_ORIGINS=http://localhost:3000,http://127.0.0.1:*

# Vector store - pgvector (no Azure)
# Exclude Azure vector store autoconfiguration when using pgvector
SPRING_AUTOCONFIGURE_EXCLUDE=org.springframework.ai.vectorstore.azure.autoconfigure.AzureVectorStoreAutoConfiguration
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,21 @@ public Object schemaDocumentationDedupeBootstrap(DataSource dataSource,
// One transaction: a half-applied dedupe (rows deleted, index missing)
// would silently re-accumulate duplicates until the next boot.
new TransactionTemplate(txManager).executeWithoutResult(status -> {
int repointed = jdbc.update("""
UPDATE code_knowledge_suggestion s
SET applied_doc_id = l.keep_id
FROM (%s) l
WHERE s.applied_doc_id = l.id
""".formatted(LOSERS));
int repointed = 0;
if (tableExists(jdbc, "code_knowledge_suggestion")) {
repointed = jdbc.update("""
UPDATE code_knowledge_suggestion s
SET applied_doc_id = l.keep_id
FROM (%s) l
WHERE s.applied_doc_id = l.id
""".formatted(LOSERS));
}

int embeddings = jdbc.update(
"DELETE FROM rag_documents WHERE id IN (SELECT id FROM (%s) l)".formatted(LOSERS));
int embeddings = 0;
if (tableExists(jdbc, "rag_documents")) {
embeddings = jdbc.update(
"DELETE FROM rag_documents WHERE id IN (SELECT id FROM (%s) l)".formatted(LOSERS));
}

int removed = jdbc.update(
"DELETE FROM schema_documentation WHERE id IN (SELECT id FROM (%s) l)".formatted(LOSERS));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,33 @@ public class PostgresIntrospectionProvider implements IntrospectionProvider {
+ "AND %1$s NOT LIKE 'pg_temp_%%' "
+ "AND %1$s NOT LIKE 'pg_toast_temp_%%'";

/**
* Extension-created system views that should be excluded from Brain even if
* they somehow appear in a user schema. pg_stat_statements is in pg_catalog
* (so already excluded by schema filter), but this provides defense in depth.
*/
static final String EXCLUDED_EXTENSION_VIEWS_SQL =
"NOT IN ('pg_stat_statements', 'pg_stat_statements_info', 'pg_buffercache')";

/**
* Extension-created functions that should be excluded from Brain. These are
* internal extension functions not useful for application queries.
*/
static final String EXCLUDED_EXTENSION_FUNCTIONS_SQL =
"NOT IN ('pg_stat_statements', 'pg_stat_statements_info', 'pg_stat_statements_reset', 'pg_buffercache_pages', 'pg_buffercache_summary')";

static String nonSystemSchemaPredicate(String column) {
return column + " " + String.format(NON_SYSTEM_SCHEMA_SQL, column);
}

static String excludeExtensionViewsPredicate(String column) {
return column + " " + EXCLUDED_EXTENSION_VIEWS_SQL;
}

static String excludeExtensionFunctionsPredicate(String column) {
return column + " " + EXCLUDED_EXTENSION_FUNCTIONS_SQL;
}

/** Map / snapshot key that survives duplicate table names across schemas. */
static String qualifiedTableKey(String schema, String table) {
String s = (schema == null || schema.isBlank()) ? DEFAULT_SCHEMA : schema;
Expand Down Expand Up @@ -74,6 +97,7 @@ private List<DatabaseObject> getTablesAndViews(Connection connection) throws SQL

String schemaPred = nonSystemSchemaPredicate("t.schemaname");
String viewPred = nonSystemSchemaPredicate("v.schemaname");
String extViewPred = excludeExtensionViewsPredicate("v.viewname");
String query = """
SELECT t.schemaname as schema_name, t.tablename as name, 'table' as type,
CASE
Expand All @@ -89,9 +113,9 @@ private List<DatabaseObject> getTablesAndViews(Connection connection) throws SQL
WHERE %s AND c.relkind IN ('r', 'p')
UNION ALL
SELECT v.schemaname as schema_name, v.viewname as name, 'view' as type, 0 as row_count
FROM pg_views v WHERE %s
FROM pg_views v WHERE %s AND %s
ORDER BY schema_name, type, name
""".formatted(schemaPred, viewPred);
""".formatted(schemaPred, viewPred, extViewPred);

try (Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(query)) {
Expand All @@ -115,13 +139,15 @@ private List<DatabaseObject> getTablesAndViews(Connection connection) throws SQL
private List<DatabaseObject> getFunctions(Connection connection) throws SQLException {
List<DatabaseObject> objects = new ArrayList<>();

String schemaPred = nonSystemSchemaPredicate("n.nspname");
String extFuncPred = excludeExtensionFunctionsPredicate("p.proname");
String query = """
SELECT n.nspname as schema_name, p.proname as name, pg_get_functiondef(p.oid) as definition
FROM pg_proc p
JOIN pg_namespace n ON p.pronamespace = n.oid
WHERE %s AND p.prokind = 'f'
WHERE %s AND %s AND p.prokind = 'f'
ORDER BY n.nspname, p.proname
""".formatted(nonSystemSchemaPredicate("n.nspname"));
""".formatted(schemaPred, extFuncPred);

try (Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(query)) {
Expand Down Expand Up @@ -435,6 +461,7 @@ public SchemaMetadata scanSchema(Connection connection, String database) throws
schema.setDatabaseName(database);

// Get all tables and views across non-system schemas (W2a).
// Views are further filtered to exclude extension-created system views.
String tablesQuery = "SELECT t.schemaname, t.tablename, 'table' as type, "
+ "pg_total_relation_size(quote_ident(t.schemaname)||'.'||quote_ident(t.tablename)) as size_bytes, "
+ "CASE "
Expand All @@ -451,7 +478,7 @@ public SchemaMetadata scanSchema(Connection connection, String database) throws
+ "UNION ALL "
+ "SELECT v.schemaname, v.viewname as tablename, 'view' as type, 0 as size_bytes, 0 as row_count "
+ "FROM pg_views v "
+ "WHERE " + nonSystemSchemaPredicate("v.schemaname") + " "
+ "WHERE " + nonSystemSchemaPredicate("v.schemaname") + " AND " + excludeExtensionViewsPredicate("v.viewname") + " "
+ "ORDER BY schemaname, tablename";

Map<String, TableMetadata> tableMap = new HashMap<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ void getDatabaseObjects_returnsTables() throws SQLException {
.thenReturn(false) // No functions
.thenReturn(false); // No procedures

when(resultSet.getString("schema_name")).thenReturn("public");
when(resultSet.getString("name")).thenReturn("users");
when(resultSet.getString("type")).thenReturn("table");
when(resultSet.getObject("row_count")).thenReturn(100L);
Expand All @@ -70,6 +71,7 @@ void getDatabaseObjects_returnsTables() throws SQLException {
assertEquals(1, objects.size());
assertEquals("users", objects.get(0).getName());
assertEquals("table", objects.get(0).getType());
assertEquals("public", objects.get(0).getSchema());
}

@Test
Expand Down Expand Up @@ -216,6 +218,7 @@ void scanSchema_returnsSchemaMetadata() throws SQLException {
.thenReturn(true)
.thenReturn(false);

when(resultSet.getString("schemaname")).thenReturn("public");
when(resultSet.getString("tablename")).thenReturn("users");
when(resultSet.getString("type")).thenReturn("table");
when(resultSet.getObject("row_count")).thenReturn(100L);
Expand All @@ -228,6 +231,7 @@ void scanSchema_returnsSchemaMetadata() throws SQLException {
assertNotNull(schema.getTables());
assertEquals(1, schema.getTables().size());
assertEquals("users", schema.getTables().get(0).getName());
assertEquals("public", schema.getTables().get(0).getSchema());
}

@Test
Expand All @@ -240,8 +244,10 @@ void getForeignKeys_returnsRelationships() throws SQLException {
.thenReturn(false);

when(resultSet.getString("constraint_name")).thenReturn("fk_orders_user");
when(resultSet.getString("source_schema")).thenReturn("public");
when(resultSet.getString("source_table")).thenReturn("orders");
when(resultSet.getString("source_column")).thenReturn("user_id");
when(resultSet.getString("target_schema")).thenReturn("public");
when(resultSet.getString("target_table")).thenReturn("users");
when(resultSet.getString("target_column")).thenReturn("id");

Expand All @@ -250,6 +256,7 @@ void getForeignKeys_returnsRelationships() throws SQLException {
assertNotNull(relationships);
assertEquals(1, relationships.size());
assertEquals("fk_orders_user", relationships.get(0).getConstraintName());
// qualifyForConsumers returns just the table name for "public" schema
assertEquals("orders", relationships.get(0).getFromTable());
assertEquals("user_id", relationships.get(0).getFromColumn());
assertEquals("users", relationships.get(0).getToTable());
Expand Down Expand Up @@ -294,6 +301,7 @@ void scanSchema_fallsBackToExactCountWhenEstimateMissing() throws SQLException {
when(foreignKeysStatement.executeQuery(anyString())).thenReturn(foreignKeysResultSet);

when(resultSet.next()).thenReturn(true).thenReturn(false);
when(resultSet.getString("schemaname")).thenReturn("public");
when(resultSet.getString("tablename")).thenReturn("dim_route");
when(resultSet.getString("type")).thenReturn("table");
when(resultSet.getObject("row_count")).thenReturn(null);
Expand Down Expand Up @@ -339,4 +347,108 @@ void getColumnDetails_returnsDetails() throws SQLException {
assertEquals("character varying", details.get(0).getDataType());
assertFalse(details.get(0).getIsNullable());
}

// ─── pg_stat_statements extension view exclusion tests ───────────────────────

@Test
void excludeExtensionViewsPredicate_excludesPgStatStatements() {
String predicate = PostgresIntrospectionProvider.excludeExtensionViewsPredicate("v.viewname");

assertTrue(predicate.contains("pg_stat_statements"),
"Exclusion predicate should mention pg_stat_statements");
assertTrue(predicate.contains("pg_stat_statements_info"),
"Exclusion predicate should mention pg_stat_statements_info");
assertTrue(predicate.contains("pg_buffercache"),
"Exclusion predicate should mention pg_buffercache");
assertTrue(predicate.contains("NOT IN"),
"Exclusion predicate should use NOT IN clause");
}

@Test
void nonSystemSchemaPredicate_excludesPgCatalog() {
String predicate = PostgresIntrospectionProvider.nonSystemSchemaPredicate("t.schemaname");

assertTrue(predicate.contains("pg_catalog"),
"Schema predicate should exclude pg_catalog");
assertTrue(predicate.contains("information_schema"),
"Schema predicate should exclude information_schema");
assertTrue(predicate.contains("pg_toast"),
"Schema predicate should exclude pg_toast");
}

@Test
void getTablesAndViews_queryExcludesExtensionViews() throws SQLException {
// Capture all SQL queries and find the tables/views one
ArgumentCaptor<String> sqlCaptor = ArgumentCaptor.forClass(String.class);
when(connection.createStatement()).thenReturn(statement);
when(statement.executeQuery(sqlCaptor.capture())).thenReturn(resultSet);
when(resultSet.next()).thenReturn(false);

// getDatabaseObjects calls getTablesAndViews internally
List<DatabaseObject> objects = provider.getDatabaseObjects(connection, "test_db");

// Find the tables/views query among all executed queries
String tablesViewsQuery = sqlCaptor.getAllValues().stream()
.filter(q -> q.contains("pg_tables") || q.contains("pg_views"))
.findFirst()
.orElse(null);

assertNotNull(tablesViewsQuery, "Should have executed a query accessing pg_tables or pg_views");

// The query for views should contain the extension exclusion
assertTrue(tablesViewsQuery.contains("pg_views"),
"Query should access pg_views");
assertTrue(tablesViewsQuery.contains("pg_tables"),
"Query should access pg_tables");

// Verify schema exclusions are present
assertTrue(tablesViewsQuery.contains("NOT IN"),
"Query should have NOT IN clause for exclusions");
assertTrue(tablesViewsQuery.contains("pg_stat_statements"),
"Query should exclude pg_stat_statements extension views");
}

@Test
void extensionViewExclusion_isExactMatch() {
// Verify the exclusion predicate uses exact matches, not prefix matches
String predicate = PostgresIntrospectionProvider.EXCLUDED_EXTENSION_VIEWS_SQL;

// The excluded list should be specific, exact names only
assertTrue(predicate.contains("'pg_stat_statements'"),
"Predicate should exclude exactly 'pg_stat_statements'");
assertTrue(predicate.contains("'pg_stat_statements_info'"),
"Predicate should exclude exactly 'pg_stat_statements_info'");
assertTrue(predicate.contains("'pg_buffercache'"),
"Predicate should exclude exactly 'pg_buffercache'");

// Verify it's a NOT IN list (exact match semantics, not LIKE pattern)
assertTrue(predicate.startsWith("NOT IN"),
"Predicate should use NOT IN for exact matching");
}

@Test
void excludeExtensionFunctionsPredicate_excludesPgStatStatementsFunctions() {
String predicate = PostgresIntrospectionProvider.excludeExtensionFunctionsPredicate("p.proname");

assertTrue(predicate.contains("pg_stat_statements"),
"Exclusion predicate should mention pg_stat_statements");
assertTrue(predicate.contains("pg_stat_statements_info"),
"Exclusion predicate should mention pg_stat_statements_info");
assertTrue(predicate.contains("pg_stat_statements_reset"),
"Exclusion predicate should mention pg_stat_statements_reset");
assertTrue(predicate.contains("NOT IN"),
"Exclusion predicate should use NOT IN clause");
}

@Test
void extensionFunctionExclusion_isExactMatch() {
String predicate = PostgresIntrospectionProvider.EXCLUDED_EXTENSION_FUNCTIONS_SQL;

assertTrue(predicate.contains("'pg_stat_statements'"),
"Predicate should exclude exactly 'pg_stat_statements'");
assertTrue(predicate.contains("'pg_stat_statements_reset'"),
"Predicate should exclude exactly 'pg_stat_statements_reset'");
assertTrue(predicate.startsWith("NOT IN"),
"Predicate should use NOT IN for exact matching");
}
}
Loading
Loading