1- import indentString from 'indent-string' ;
2- import type { Column } from 'node-sql-parser' ;
3- import { Parser } from 'node-sql-parser' ;
4- import { useMemo , useState } from 'react' ;
1+ import { action , remove } from 'mobx' ;
2+ import { observer } from 'mobx-react-lite' ;
53import { formatDialect , sqlite } from 'sql-formatter' ;
4+ import { DEFAULT_QUERY , Query , parseOne , state } from './state' ;
5+ import { FC } from 'react' ;
66
7- function getColumnNames ( columns : Column [ ] ) {
8- const names : string [ ] = [ ] ;
9-
10- for ( const column of columns ) {
11- if ( column . as ) {
12- names . push ( column . as ) ;
13- } else if ( column . expr . type === 'column_ref' ) {
14- names . push ( column . expr . column ) ;
15- } else {
16- console . warn ( 'Cannot parse column def' , column ) ;
17- }
18- }
19-
20- return names ;
21- }
22-
23- const DEFAULT_QUERY = `select
24- id,
25- employees.name,
26- ranking as my_rank
27- from
28- employees
29- limit
30- 2` ;
31-
32- function generateJsonQuery ( queryName : string , columns : string [ ] , sql : string ) {
33- const result = `select json_object(
34- '${ queryName } ',
35- (
36- select
37- json_group_array(
38- json_object(${ columns . map ( ( col ) => `'${ col } ', ${ col } ` ) . join ( ', ' ) } )
39- )
40- from
41- (
42- ${ indentString ( sql , 8 ) }
43- )
44- )
45- ) as json_result` ;
46-
47- return result ;
48- }
49-
50- export function Page ( ) {
51- const [ queryName , setQueryName ] = useState ( 'my_query_name' ) ;
52- const [ query , setQuery ] = useState ( DEFAULT_QUERY ) ;
53-
54- const parsed = useMemo ( ( ) => {
55- try {
56- const parser = new Parser ( ) ;
57- const ast = parser . astify ( query , { database : 'sqlite' } ) ;
58- console . log ( ast ) ;
59- const first = Array . isArray ( ast ) ? ast [ 0 ] : ast ;
60- if ( first . type !== 'select' ) return undefined ;
61- const columns = first . columns ;
62- if ( ! Array . isArray ( columns ) ) return undefined ;
63- const names = getColumnNames ( columns ) ;
64-
65- const jsonQuery = generateJsonQuery ( queryName , names , query ) ;
66-
67- return { names, jsonQuery } ;
68- } catch ( err ) {
69- console . warn ( err ) ;
70- return undefined ;
71- }
72- } , [ query , queryName ] ) ;
7+ export const Page = observer ( function Page ( ) {
8+ const parsed = state . parsed ;
739
7410 return (
7511 < div className = "flex flex-col gap-2" >
76- < div > SQLite Query Tool</ div >
12+ < div > SQLite JSON Query Tool</ div >
7713 < div className = "p-2 border rounded prose" >
7814 < p >
7915 The < a href = "https://www.sqlite.org/json1.html" > JSON API of SQLite</ a > { ' ' }
@@ -85,54 +21,89 @@ export function Page() {
8521 This tool will try to auto-detect your column names and generate a
8622 wrapper query that is ready to go without any dependencies.
8723 </ p >
88- < p >
89- Using the generated pattern below, you can also combine multiple
90- unrelated queries. For example:
91- </ p >
92- < div className = "form-textarea whitespace-pre-wrap border-gray-200 font-mono text-sm" > { `select json_object(
93- 'query_one',
94- query_one,
95- 'query_two',
96- query_two
97- ) as json_result` } </ div >
9824 </ div >
9925
100- < label > Query name</ label >
26+ { state . queries . map ( ( query , index ) => (
27+ < >
28+ { index !== 0 ? < hr /> : null }
29+ < QueryEditor key = { index } index = { index } query = { query } />
30+ </ >
31+ ) ) }
32+
33+ < hr />
34+
35+ < div >
36+ < button
37+ onClick = { action ( ( ) => {
38+ const newQuery = { ...DEFAULT_QUERY } ;
39+ newQuery . name += '_' + ( state . queries . length + 1 ) ;
40+ state . queries . push ( newQuery ) ;
41+ } ) }
42+ className = "p-1 border bg-gray-100 hover:bg-gray-200 active:bg-gray-200"
43+ >
44+ Add another query
45+ </ button >
46+ </ div >
47+
48+ < hr />
49+
50+ < div > Wrapped query:</ div >
51+
52+ < div className = "rounded bg-blue-50 form-textarea w-full whitespace-pre-wrap text-sm" >
53+ { parsed }
54+ </ div >
55+ </ div >
56+ ) ;
57+ } ) ;
58+
59+ const QueryEditor = observer ( function QueryEditor ( {
60+ index,
61+ query,
62+ } : {
63+ index : number ;
64+ query : Query ;
65+ } ) {
66+ const names = parseOne ( query ) ;
67+
68+ return (
69+ < >
70+ < label > Query { index + 1 } </ label >
10171 < input
10272 type = "text"
103- className = "form-input"
104- value = { queryName }
105- onChange = { ( event ) => setQueryName ( event . target . value ) }
73+ className = "form-input rounded "
74+ value = { query . name }
75+ onChange = { action ( ( event ) => ( query . name = event . target . value ) ) }
10676 />
10777 < textarea
108- className = "form-textarea w-full"
78+ className = "form-textarea w-full rounded "
10979 rows = { 10 }
110- value = { query }
111- onChange = { ( event ) => setQuery ( event . target . value ) }
80+ value = { query . sql }
81+ onChange = { action ( ( event ) => ( query . sql = event . target . value ) ) }
11282 />
113- < button
114- onClick = { ( ) => {
115- setQuery (
116- formatDialect ( query , {
83+ < div className = "flex gap-2" >
84+ < button
85+ onClick = { action ( ( ) => {
86+ query . sql = formatDialect ( query . sql , {
11787 dialect : sqlite ,
11888 tabWidth : 2 ,
119- } )
120- ) ;
121- } }
122- className = "p-1 border bg-gray-100 hover:bg-gray-200 active:bg-gray-200"
123- >
124- Format
125- </ button >
126- < div >
127- Detected columns:{ ' ' }
128- < span className = "font-bold" > { parsed ?. names . join ( ', ' ) } </ span >
89+ } ) ;
90+ } ) }
91+ className = "p-1 border bg-gray-100 hover:bg-gray-200 active:bg-gray-200"
92+ >
93+ Format
94+ </ button >
95+ < button
96+ onClick = { action ( ( ) => {
97+ remove ( state . queries , index as any ) ;
98+ } ) }
99+ className = "p-1 border bg-gray-100 hover:bg-gray-200 active:bg-gray-200"
100+ >
101+ Remove query
102+ </ button >
129103 </ div >
130-
131- < div > Wrapped query:</ div >
132-
133- < div className = "form-textarea w-full whitespace-pre-wrap text-sm" >
134- { parsed ?. jsonQuery }
104+ < div >
105+ Detected columns: < span className = "font-bold" > { names ?. join ( ', ' ) } </ span >
135106 </ div >
136- </ div >
107+ </ >
137108 ) ;
138- }
109+ } ) ;
0 commit comments