import { useState } from 'react';
import type { Field, RuleGroupType } from 'react-querybuilder';
import { formatQuery, QueryBuilder } from 'react-querybuilder';
interface Parameter {
id: string;
name: string;
label?: string;
value?: string;
}
const genId = () => crypto.randomUUID();
const fields: Field[] = [
{ name: 'firstName', label: 'First Name', valueSources: ['value', 'parameter'] },
{ name: 'lastName', label: 'Last Name', valueSources: ['value', 'parameter'] },
];
const initialQuery: RuleGroupType = {
combinator: 'and',
rules: [
{ field: 'firstName', operator: '=', value: 'p1', valueSource: 'parameter' },
{ field: 'lastName', operator: 'beginsWith', value: 'V' },
],
};
const initialParameters: Parameter[] = [
{ id: genId(), name: 'p1', label: 'Param 1', value: 'Steve' },
{ id: genId(), name: 'p2', label: 'Param 2' },
];
const stripPrefix = (key: string) => key.replace(/^[^A-Za-z0-9_]+/, '');
const ParameterManager = ({
parameters,
onChange,
}: {
parameters: Parameter[];
onChange: (parameters: Parameter[]) => void;
}) => {
const update = (index: number, key: keyof Parameter, value: string) => {
const next = parameters.map((p, i) => (i === index ? { ...p, [key]: value } : p));
onChange(next);
};
const remove = (index: number) => onChange(parameters.filter((_, i) => i !== index));
const add = () => onChange([...parameters, { id: genId(), name: `p${parameters.length + 1}` }]);
return (
<div style={{ marginBottom: '1rem' }}>
<h4>Parameter manager</h4>
<table>
<thead>
<tr>
<th>Name</th>
<th>Label</th>
<th>Value</th>
<th />
</tr>
</thead>
<tbody>
{parameters.map((p, i) => (
<tr key={p.id}>
<td>
<input
value={p.name}
placeholder="p1"
onChange={e => update(i, 'name', e.target.value)}
/>
</td>
<td>
<input
value={p.label ?? ''}
placeholder="Param 1"
onChange={e => update(i, 'label', e.target.value)}
/>
</td>
<td>
<input
value={p.value ?? ''}
placeholder="(no value)"
onChange={e => update(i, 'value', e.target.value)}
/>
</td>
<td>
<button type="button" onClick={() => remove(i)}>
×
</button>
</td>
</tr>
))}
</tbody>
</table>
<button type="button" onClick={add}>
Add parameter
</button>
</div>
);
};
export default () => {
const [query, setQuery] = useState(initialQuery);
const [parameters, setParameters] = useState(initialParameters);
const getParameters = () =>
parameters.filter(p => p.name).map(p => ({ name: p.name, label: p.label || p.name }));
const valueByName = new Map(parameters.map(p => [p.name, p.value]));
const { sql, params } = formatQuery(query, 'parameterized_named');
const mergedParams = Object.fromEntries(
Object.keys(params).map(key => {
const value = valueByName.get(stripPrefix(key));
return [key, value ? value : null];
})
);
return (
<div>
<ParameterManager parameters={parameters} onChange={setParameters} />
<QueryBuilder
fields={fields}
query={query}
onQueryChange={setQuery}
getParameters={getParameters}
/>
<h4>SQL</h4>
<pre>{sql}</pre>
<h4>Parameters</h4>
<pre>{JSON.stringify(mergedParams, null, 2)}</pre>
</div>
);
};