glam/frontend/PHASE3_TASK6_QUERY_BUILDER.md
kempersc 2761857b0d Add scripts for converting OWL/Turtle ontology to Mermaid and PlantUML diagrams
- Implemented `owl_to_mermaid.py` to convert OWL/Turtle files into Mermaid class diagrams.
- Implemented `owl_to_plantuml.py` to convert OWL/Turtle files into PlantUML class diagrams.
- Added two new PlantUML files for custodian multi-aspect diagrams.
2025-11-22 23:01:13 +01:00

695 lines
17 KiB
Markdown

# Phase 3 - Task 6: Advanced Query Builder
**Date**: 2025-11-22
**Priority**: Medium
**Estimated Time**: 4-5 hours
**Status**: Ready to Start ⏳
---
## Overview
Build a visual SPARQL query builder that allows users to construct queries through a graphical interface, with live syntax preview and pre-built templates.
---
## Goals
1. **Visual Query Construction**: Drag-and-drop or form-based interface for building SPARQL queries
2. **Live Syntax Preview**: Real-time SPARQL syntax display with syntax highlighting
3. **Query Templates**: Library of pre-built queries for common use cases
4. **Syntax Validation**: Client-side query validation before execution
5. **Query Management**: Save, load, and share queries
---
## Deliverables
### 1. Query Builder Component (`src/components/query/QueryBuilder.tsx`)
**Features**:
- Subject-Predicate-Object triple pattern builder
- Filter conditions (FILTER, REGEX, LANG, etc.)
- Graph patterns (OPTIONAL, UNION, MINUS)
- Aggregation (COUNT, SUM, AVG, etc.)
- Ordering and pagination (ORDER BY, LIMIT, OFFSET)
- Prefix management (common prefixes pre-configured)
**UI Elements**:
```typescript
interface QueryBuilderState {
prefixes: Map<string, string>; // PREFIX declarations
selectVariables: string[]; // SELECT ?var1 ?var2
wherePatterns: TriplePattern[]; // WHERE { ... }
filters: FilterExpression[]; // FILTER(...)
groupBy: string[]; // GROUP BY ?var
orderBy: OrderClause[]; // ORDER BY ?var
limit: number | null; // LIMIT n
offset: number | null; // OFFSET n
}
interface TriplePattern {
subject: string; // ?var or URI
predicate: string; // ?var or URI
object: string; // ?var or URI or literal
optional: boolean; // OPTIONAL { ... }
}
```
---
### 2. Query Editor Component (`src/components/query/QueryEditor.tsx`)
**Features**:
- Syntax highlighted text editor (using `@codemirror/lang-sparql`)
- Line numbers and bracket matching
- Auto-completion for variables and prefixes
- Error highlighting for invalid syntax
- Format/beautify query button
**Libraries**:
```bash
npm install @uiw/react-codemirror @codemirror/lang-sparql
```
**Example**:
```typescript
import CodeMirror from '@uiw/react-codemirror';
import { sparql } from '@codemirror/lang-sparql';
export function QueryEditor({ value, onChange }: QueryEditorProps) {
return (
<CodeMirror
value={value}
height="400px"
extensions={[sparql()]}
onChange={onChange}
theme="light"
/>
);
}
```
---
### 3. Query Templates Library (`src/lib/sparql/templates.ts`)
**Pre-built Queries**:
```typescript
export interface QueryTemplate {
id: string;
name: string;
description: string;
category: 'basic' | 'advanced' | 'aggregation' | 'geographic';
query: string;
variables: string[]; // Replaceable variables (e.g., %CITY%, %TYPE%)
}
export const QUERY_TEMPLATES: QueryTemplate[] = [
{
id: 'find-all-museums',
name: 'Find All Museums',
description: 'List all museums with names',
category: 'basic',
query: `
PREFIX schema: <http://schema.org/>
SELECT ?museum ?name WHERE {
?museum a schema:Museum .
?museum schema:name ?name .
}
ORDER BY ?name
LIMIT 100
`,
variables: [],
},
{
id: 'institutions-in-city',
name: 'Institutions in City',
description: 'Find all institutions in a specific city',
category: 'geographic',
query: `
PREFIX schema: <http://schema.org/>
SELECT ?institution ?name ?type WHERE {
?institution a ?type .
?institution schema:name ?name .
?institution schema:address ?addr .
?addr schema:addressLocality "%CITY%" .
}
ORDER BY ?name
`,
variables: ['%CITY%'],
},
{
id: 'count-by-type',
name: 'Count Institutions by Type',
description: 'Aggregate count of institutions grouped by type',
category: 'aggregation',
query: `
PREFIX schema: <http://schema.org/>
PREFIX cpov: <http://data.europa.eu/m8g/>
SELECT ?type (COUNT(?institution) AS ?count) WHERE {
?institution a ?type .
FILTER(?type IN (schema:Museum, schema:Library, schema:ArchiveOrganization))
}
GROUP BY ?type
ORDER BY DESC(?count)
`,
variables: [],
},
{
id: 'wikidata-links',
name: 'Find Wikidata Links',
description: 'Institutions with Wikidata identifiers',
category: 'advanced',
query: `
PREFIX owl: <http://www.w3.org/2002/07/owl#>
PREFIX schema: <http://schema.org/>
SELECT ?institution ?name ?wikidataURI WHERE {
?institution schema:name ?name .
?institution owl:sameAs ?wikidataURI .
FILTER(STRSTARTS(STR(?wikidataURI), "http://www.wikidata.org/entity/Q"))
}
LIMIT 100
`,
variables: [],
},
];
```
---
### 4. Query Validator (`src/lib/sparql/validator.ts`)
**Validation Rules**:
- Syntax validation (basic SPARQL grammar)
- Prefix validation (all prefixes declared)
- Variable validation (all variables bound)
- Filter validation (correct syntax)
**Implementation**:
```typescript
export interface ValidationResult {
isValid: boolean;
errors: ValidationError[];
warnings: ValidationWarning[];
}
export interface ValidationError {
line: number;
column: number;
message: string;
severity: 'error' | 'warning';
}
export function validateSparqlQuery(query: string): ValidationResult {
const errors: ValidationError[] = [];
const warnings: ValidationWarning[] = [];
// Check for required SELECT/CONSTRUCT/ASK/DESCRIBE
if (!query.match(/\b(SELECT|CONSTRUCT|ASK|DESCRIBE)\b/i)) {
errors.push({
line: 1,
column: 1,
message: 'Query must contain SELECT, CONSTRUCT, ASK, or DESCRIBE',
severity: 'error',
});
}
// Check for WHERE clause
if (!query.match(/\bWHERE\s*\{/i)) {
errors.push({
line: 1,
column: 1,
message: 'Query must contain WHERE clause',
severity: 'error',
});
}
// Check for unbalanced braces
const openBraces = (query.match(/\{/g) || []).length;
const closeBraces = (query.match(/\}/g) || []).length;
if (openBraces !== closeBraces) {
errors.push({
line: 1,
column: 1,
message: 'Unbalanced braces in query',
severity: 'error',
});
}
// Check for undefined prefixes
const usedPrefixes = new Set<string>();
const declaredPrefixes = new Set<string>();
// Find all PREFIX declarations
const prefixRegex = /PREFIX\s+(\w+):/gi;
let match;
while ((match = prefixRegex.exec(query)) !== null) {
declaredPrefixes.add(match[1]);
}
// Find all prefix usages
const usageRegex = /(\w+):[^\s]+/g;
while ((match = usageRegex.exec(query)) !== null) {
if (match[0].startsWith('PREFIX')) continue;
usedPrefixes.add(match[1]);
}
// Check for undeclared prefixes
for (const prefix of usedPrefixes) {
if (!declaredPrefixes.has(prefix)) {
warnings.push({
line: 1,
column: 1,
message: `Prefix '${prefix}' used but not declared`,
severity: 'warning',
});
}
}
return {
isValid: errors.length === 0,
errors,
warnings,
};
}
```
---
### 5. Query Builder Page (`src/pages/QueryBuilder.tsx`)
**Layout**:
```
+----------------------------------+
| Navigation Header |
+----------------------------------+
| |
| +-----------------------------+ |
| | Template Selection Sidebar | |
| | - Basic Queries | |
| | - Advanced Queries | |
| | - Aggregation | |
| | - Geographic | |
| +-----------------------------+ |
| |
| +-----------------------------+ |
| | Query Builder Panel | |
| | - Add Triple Pattern | |
| | - Add Filter | |
| | - Set Limit/Offset | |
| +-----------------------------+ |
| |
| +-----------------------------+ |
| | SPARQL Syntax Preview | |
| | (CodeMirror with syntax | |
| | highlighting) | |
| +-----------------------------+ |
| |
| +-----------------------------+ |
| | Validation Results | |
| | ✓ Valid SPARQL | |
| | ⚠ 2 warnings | |
| +-----------------------------+ |
| |
| [Execute Query] [Save] [Clear] |
+----------------------------------+
```
**Component Structure**:
```typescript
export function QueryBuilderPage() {
const [queryState, setQueryState] = useState<QueryBuilderState>(initialState);
const [syntaxPreview, setSyntaxPreview] = useState('');
const [validation, setValidation] = useState<ValidationResult | null>(null);
// Generate SPARQL from visual builder state
useEffect(() => {
const sparql = generateSparql(queryState);
setSyntaxPreview(sparql);
// Validate on change
const result = validateSparqlQuery(sparql);
setValidation(result);
}, [queryState]);
return (
<div className="query-builder-page">
<TemplateSidebar onSelectTemplate={loadTemplate} />
<QueryBuilderPanel state={queryState} onChange={setQueryState} />
<QueryEditor value={syntaxPreview} onChange={setSyntaxPreview} />
<ValidationPanel result={validation} />
<ActionButtons onExecute={executeQuery} onSave={saveQuery} />
</div>
);
}
```
---
## Implementation Steps
### Step 1: Create Query Templates (30 min)
```bash
touch src/lib/sparql/templates.ts
```
**Tasks**:
- Define `QueryTemplate` interface
- Create 10-15 pre-built queries covering:
- Basic SELECT queries
- Aggregation (COUNT, GROUP BY)
- Geographic filtering (city, country)
- Wikidata links
- Relationship queries (branches, partnerships)
- Collection queries
---
### Step 2: Create Query Validator (45 min)
```bash
touch src/lib/sparql/validator.ts
touch tests/unit/sparql-validator.test.ts
```
**Tasks**:
- Implement basic syntax validation
- Check for required clauses (SELECT, WHERE)
- Validate prefix declarations
- Check for unbalanced braces
- Variable binding checks
- Write 10+ unit tests
---
### Step 3: Install CodeMirror (15 min)
```bash
cd /Users/kempersc/apps/glam/frontend
npm install @uiw/react-codemirror @codemirror/lang-sparql
```
**Tasks**:
- Install dependencies
- Test CodeMirror integration in simple component
- Configure syntax highlighting theme
- Add auto-completion for common prefixes
---
### Step 4: Create Query Editor Component (30 min)
```bash
touch src/components/query/QueryEditor.tsx
touch src/components/query/QueryEditor.css
```
**Tasks**:
- Implement CodeMirror wrapper
- Add syntax highlighting
- Add line numbers
- Add bracket matching
- Style for dark/light theme compatibility
---
### Step 5: Create Query Builder Component (90 min)
```bash
touch src/components/query/QueryBuilder.tsx
touch src/components/query/QueryBuilder.css
```
**Tasks**:
- Implement `QueryBuilderState` interface
- Create triple pattern builder UI
- Add filter condition builder
- Implement LIMIT/OFFSET controls
- Add prefix management UI
- Create `generateSparql()` function to convert state to SPARQL string
---
### Step 6: Create Query Builder Page (45 min)
```bash
touch src/pages/QueryBuilder.tsx
touch src/pages/QueryBuilder.css
```
**Tasks**:
- Layout page structure
- Integrate all components
- Add template selection sidebar
- Wire up state management
- Add save/load functionality
- Implement validation display
---
### Step 7: Add Routing and Navigation (15 min)
**Update `src/App.tsx`**:
```typescript
{
path: '/query-builder',
element: <QueryBuilderPage />,
}
```
**Update `src/components/layout/Navigation.tsx`**:
```typescript
<NavLink to="/query-builder">Query Builder</NavLink>
```
---
### Step 8: Write Tests (30 min)
```bash
touch tests/unit/query-validator.test.ts
touch tests/unit/query-templates.test.ts
touch tests/unit/query-builder.test.tsx
```
**Test Coverage**:
- Validator: 15+ tests (syntax, prefixes, variables)
- Templates: 5+ tests (template loading, variable substitution)
- Builder: 10+ tests (state management, SPARQL generation)
---
## Testing Checklist
### Manual Testing
- [ ] Load query from template
- [ ] Edit query in visual builder
- [ ] See live SPARQL preview update
- [ ] Validation shows errors for invalid syntax
- [ ] Validation shows warnings for undeclared prefixes
- [ ] Execute query (shows error if Oxigraph not running)
- [ ] Save query to localStorage
- [ ] Load saved query
- [ ] Export query as .sparql file
- [ ] Syntax highlighting works in dark/light theme
### Automated Testing
- [ ] All validator tests pass
- [ ] All template tests pass
- [ ] All builder component tests pass
- [ ] Zero TypeScript errors
- [ ] Build succeeds
---
## Success Criteria
1. ✅ User can select from 10+ pre-built query templates
2. ✅ User can visually construct queries (triple patterns, filters, LIMIT)
3. ✅ Live SPARQL syntax preview updates in real-time
4. ✅ Syntax validation shows errors and warnings
5. ✅ User can save/load queries
6. ✅ User can edit raw SPARQL in CodeMirror editor
7. ✅ Syntax highlighting works correctly
8. ✅ All tests pass
9. ✅ Zero build errors
10. ✅ Documentation complete
---
## Libraries and Tools
### Required Dependencies
```bash
# Syntax highlighting
npm install @uiw/react-codemirror @codemirror/lang-sparql
# Optional: SPARQL parser for advanced validation
npm install sparqljs
```
### Optional Enhancements (Future)
- `sparqljs` - Full SPARQL parser for advanced validation
- `@rdfjs/types` - TypeScript types for RDF data model
- `yasqe` - Yet Another SPARQL Editor (alternative to CodeMirror)
---
## Example Queries to Support
### Query 1: Find All Museums in Denmark
```sparql
PREFIX schema: <http://schema.org/>
SELECT ?museum ?name ?city WHERE {
?museum a schema:Museum .
?museum schema:name ?name .
?museum schema:address ?addr .
?addr schema:addressLocality ?city .
?addr schema:addressCountry "DK" .
}
ORDER BY ?city ?name
LIMIT 100
```
### Query 2: Count Institutions by Type
```sparql
PREFIX schema: <http://schema.org/>
SELECT ?type (COUNT(?inst) AS ?count) WHERE {
?inst a ?type .
FILTER(?type IN (schema:Museum, schema:Library, schema:ArchiveOrganization))
}
GROUP BY ?type
ORDER BY DESC(?count)
```
### Query 3: Institutions with Wikidata and ISIL
```sparql
PREFIX schema: <http://schema.org/>
PREFIX owl: <http://www.w3.org/2002/07/owl#>
SELECT ?inst ?name ?wikidata ?isil WHERE {
?inst schema:name ?name .
?inst owl:sameAs ?wikidata .
?inst schema:identifier ?isil .
FILTER(STRSTARTS(STR(?wikidata), "http://www.wikidata.org/entity/Q"))
FILTER(CONTAINS(?isil, "ISIL:"))
}
LIMIT 50
```
---
## Notes
### Oxigraph Not Required Yet
**Important**: Task 6 (Query Builder) does NOT require Oxigraph to be running. The query builder generates SPARQL strings and validates syntax, but does not execute queries.
**Query Execution**: Implemented in Task 7 (SPARQL Execution)
### State Persistence
Save query builder state to localStorage:
```typescript
// Save current query
const savedQuery = {
name: 'My Query',
query: syntaxPreview,
timestamp: Date.now(),
};
localStorage.setItem('saved-queries', JSON.stringify([savedQuery]));
```
### Integration with GraphContext
Query builder should integrate with existing `GraphContext`:
```typescript
const { graphData } = useGraphContext();
// Use graph data to suggest variables and URIs in autocomplete
const availableTypes = extractTypesFromGraph(graphData);
const availablePredicates = extractPredicatesFromGraph(graphData);
```
---
## References
### SPARQL Documentation
- **W3C SPARQL 1.1 Query**: https://www.w3.org/TR/sparql11-query/
- **SPARQL Tutorial**: https://www.w3.org/2009/Talks/0615-qbe/
- **SPARQL Examples**: https://www.linkeddatatools.com/querying-semantic-data
### CodeMirror Documentation
- **React CodeMirror**: https://uiwjs.github.io/react-codemirror/
- **SPARQL Language Support**: https://codemirror.net/try/?example=SPARQL
### Project Documentation
- **Triplestore Setup**: `TRIPLESTORE_OXIGRAPH_SETUP.md`
- **RDF Datasets**: `data/rdf/README.md`
- **Schema Documentation**: `schemas/20251121/rdf/`
---
## Timeline
| Step | Task | Time | Status |
|------|------|------|--------|
| 1 | Create query templates | 30 min | ⏳ |
| 2 | Create query validator | 45 min | ⏳ |
| 3 | Install CodeMirror | 15 min | ⏳ |
| 4 | Create query editor | 30 min | ⏳ |
| 5 | Create query builder | 90 min | ⏳ |
| 6 | Create query builder page | 45 min | ⏳ |
| 7 | Add routing/navigation | 15 min | ⏳ |
| 8 | Write tests | 30 min | ⏳ |
| **Total** | | **4-5 hours** | |
---
## Next Steps
After completing Task 6:
### Task 7: SPARQL Query Execution (6-8 hours)
1. Install and configure Oxigraph server
2. Load Denmark dataset (43,429 triples)
3. Create SPARQL client module
4. Implement query execution hook
5. Create results viewer component
6. Add export functionality (CSV, JSON, RDF)
7. Add query performance metrics
**See**: `TRIPLESTORE_OXIGRAPH_SETUP.md` for detailed Task 7 plan
---
**Status**: Ready to Start ⏳
**Estimated Completion**: 4-5 hours
**Dependencies**: None (can start immediately)
**Next Task**: Task 7 - SPARQL Execution (requires Oxigraph)
**Last Updated**: 2025-11-22
**Author**: OpenCode AI Agent