@@ -23,12 +23,23 @@ def verify(username, password):
2323 conn = duckdb .connect (path )
2424 return True
2525
26+ def convert_to_ndjson (result ):
27+ columns = result .description
28+ data = result .fetchall ()
29+
30+ ndjson_lines = []
31+ for row in data :
32+ row_dict = {columns [i ][0 ]: row [i ] for i in range (len (columns ))}
33+ ndjson_lines .append (json .dumps (row_dict ))
34+
35+ return '\n ' .join (ndjson_lines ).encode ()
36+
2637def convert_to_clickhouse_jsoncompact (result , query_time ):
2738 columns = result .description
2839 data = result .fetchall ()
29-
40+
3041 meta = [{"name" : col [0 ], "type" : col [1 ]} for col in columns ]
31-
42+
3243 json_result = {
3344 "meta" : meta ,
3445 "data" : data ,
@@ -40,29 +51,28 @@ def convert_to_clickhouse_jsoncompact(result, query_time):
4051 "bytes_read" : sum (len (str (item )) for row in data for item in row )
4152 }
4253 }
43-
54+
4455 return json .dumps (json_result )
4556
4657def duckdb_query_with_errmsg (query , format ):
4758 try :
4859 start_time = time .time ()
4960 result = conn .execute (query )
5061 query_time = time .time () - start_time
51-
62+
5263 if format .lower () == 'jsoncompact' :
5364 output = convert_to_clickhouse_jsoncompact (result , query_time )
5465 elif format .lower () == 'jsoneachrow' :
55- rows = result .fetchall ()
56- output = '\n ' .join (json .dumps (row ) for row in rows ).encode ()
66+ output = convert_to_ndjson (result )
5767 elif format .lower () == 'tsv' :
5868 output = result .df ().to_csv (sep = '\t ' , index = False )
5969 else :
6070 output = result .fetchall ()
61-
71+
6272 # Ensure output is in bytes before returning
6373 if isinstance (output , list ):
6474 output = json .dumps (output ).encode () # Convert list to JSON string and then encode
65-
75+
6676 return output , b""
6777 except Exception as e :
6878 return b"" , str (e ).encode ()
@@ -92,13 +102,13 @@ def clickhouse():
92102 response = app .response_class (status = 200 )
93103 response .headers ['Content-Type' ] = 'application/json'
94104 response .headers ['Accept-Ranges' ] = 'bytes' # Indicate that range requests are supported
95-
105+
96106 # Set Content-Length for HEAD request
97107 content_length = len (result ) if isinstance (result , bytes ) else len (result .decode ())
98108 response .headers ['Content-Length' ] = content_length
99-
109+
100110 return response
101-
111+
102112 return result , 200
103113
104114 # Log any warnings or errors
@@ -116,20 +126,20 @@ def play():
116126 body = request .get_data () or None
117127 format = request .args .get ('default_format' , default = "JSONCompact" , type = str )
118128 database = request .args .get ('database' , default = "" , type = str )
119-
129+
120130 if query is None :
121131 query = ""
122-
132+
123133 if body is not None :
124134 data = " " .join (body .decode ('utf-8' ).strip ().splitlines ())
125135 query = f"{ query } { data } "
126-
136+
127137 if not query :
128138 return "Error: no query parameter provided" , 400
129-
139+
130140 if database :
131141 query = f"ATTACH '{ database } ' AS db; USE db; { query } "
132-
142+
133143 result , errmsg = duckdb_query_with_errmsg (query .strip (), format )
134144 if len (errmsg ) == 0 :
135145 return result , 200
@@ -156,4 +166,3 @@ def handle_404(e):
156166
157167if __name__ == '__main__' :
158168 app .run (host = host , port = port )
159-
0 commit comments