-
Notifications
You must be signed in to change notification settings - Fork 0
/
play.html
1163 lines (995 loc) · 50.6 KB
/
play.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="icon" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDkgOCI+PHN0eWxlPi5ve2ZpbGw6I2ZjMH0ucntmaWxsOnJlZH08L3N0eWxlPjxwYXRoIGQ9Ik0wLDcgaDEgdjEgaC0xIHoiIGNsYXNzPSJyIi8+PHBhdGggZD0iTTAsMCBoMSB2NyBoLTEgeiIgY2xhc3M9Im8iLz48cGF0aCBkPSJNMiwwIGgxIHY4IGgtMSB6IiBjbGFzcz0ibyIvPjxwYXRoIGQ9Ik00LDAgaDEgdjggaC0xIHoiIGNsYXNzPSJvIi8+PHBhdGggZD0iTTYsMCBoMSB2OCBoLTEgeiIgY2xhc3M9Im8iLz48cGF0aCBkPSJNOCwzLjI1IGgxIHYxLjUgaC0xIHoiIGNsYXNzPSJvIi8+PC9zdmc+">
<title>ClickHouse Query</title>
<!-- Code Style:
Do not use any JavaScript or CSS frameworks or preprocessors.
This HTML page should not require any build systems (node.js, npm, gulp, etc.)
This HTML page should not be minified, instead it should be reasonably minimalistic by itself.
This HTML page should not load any external resources on load.
(CSS and JavaScript must be embedded directly to the page. No external fonts or images should be loaded).
This UI should look as lightweight, clean and fast as possible.
All UI elements must be aligned in pixel-perfect way.
There should not be any animations.
No unexpected changes in positions of elements while the page is loading.
Navigation by keyboard should work.
64-bit numbers must display correctly.
-->
<!-- Development Roadmap:
1. Support readonly servers.
Check if readonly = 1 (with SELECT FROM system.settings) to avoid sending settings. It can be done once on address/credentials change.
It can be done in background, e.g. wait 100 ms after address/credentials change and do the check.
Also it can provide visual indication that credentials are correct.
-->
<style>
:root {
--background-color: #DDF8FF; /* Or #FFFBEF; actually many pastel colors look great for light theme. */
--element-background-color: #FFF;
--bar-color: #F8F4F0; /* Light bar in background of table cells. */
--border-color: #EEE;
--shadow-color: rgba(0, 0, 0, 0.1);
--button-color: #FFAA00; /* Orange on light-cyan is especially good. */
--text-color: #000;
--button-active-color: #F00;
--button-active-text-color: #FFF;
--misc-text-color: #888;
--error-color: #FEE; /* Light-pink on light-cyan is so neat, I even want to trigger errors to see this cool combination of colors. */
--table-header-color: #F8F8F8;
--table-hover-color: #FFF8EF;
--null-color: #A88;
--link-color: #06D;
--logo-color: #CEE;
--logo-color-active: #BDD;
}
[data-theme="dark"] {
--background-color: #000;
--element-background-color: #102030;
--bar-color: #182838;
--border-color: #111;
--shadow-color: rgba(255, 255, 255, 0.1);
--text-color: #CCC;
--button-color: #FFAA00;
--button-text-color: #000;
--button-active-color: #F00;
--button-active-text-color: #FFF;
--misc-text-color: #888;
--error-color: #400;
--table-header-color: #102020;
--table-hover-color: #003333;
--null-color: #A88;
--link-color: #4BDAF7;
--logo-color: #222;
--logo-color-active: #333;
}
*
{
box-sizing: border-box;
/* For iPad */
margin: 0;
border-radius: 0;
tab-size: 4;
}
html, body
{
height: 100%;
margin: 0;
/* This enables position: sticky on controls */
overflow: auto;
}
html
{
/* The fonts that have full support for hinting. */
font-family: Liberation Sans, DejaVu Sans, sans-serif, Noto Color Emoji, Apple Color Emoji, Segoe UI Emoji;
background: var(--background-color);
color: var(--text-color);
}
body
{
/* This element will show scroll-bar on overflow, and the scroll-bar will be outside of the padding. */
padding: 0.5rem;
}
#controls
{
/* When a page will be scrolled horizontally due to large table size, keep controls in place. */
position: sticky;
left: 0;
}
/* Otherwise Webkit based browsers will display ugly border on focus. */
textarea, input, button
{
outline: none;
border: none;
color: var(--text-color);
}
.monospace
{
/* Prefer fonts that have full hinting info. This is important for non-retina displays.
Also I personally dislike "Ubuntu" font due to the similarity of 'r' and 'г' (it looks very ignorant). */
font-family: Liberation Mono, DejaVu Sans Mono, MonoLisa, Consolas, monospace;
}
.monospace-table
{
/* Liberation is worse than DejaVu for block drawing characters. */
font-family: DejaVu Sans Mono, Liberation Mono, MonoLisa, Consolas, monospace;
}
.shadow
{
box-shadow: 0 0 1rem var(--shadow-color);
}
input, textarea
{
border: 1px solid var(--border-color);
/* The font must be not too small (to be inclusive) and not too large (it's less practical and make general feel of insecurity) */
font-size: 11pt;
padding: 0.25rem;
background-color: var(--element-background-color);
}
#query
{
/* Make enough space for even big queries. */
height: 20vh;
/* Keeps query text-area's width full screen even when user adjusting the width of the query box. */
min-width: 100%;
}
#inputs
{
white-space: nowrap;
}
#url
{
width: 70%;
}
#user
{
width: 15%;
}
#password
{
width: 15%;
}
#run_div
{
margin-top: 1rem;
}
#run
{
color: var(--button-text-color);
background-color: var(--button-color);
padding: 0.25rem 1rem;
cursor: pointer;
font-weight: bold;
font-size: 100%; /* Otherwise button element will have lower font size. */
}
#run:hover, #run:focus
{
color: var(--button-active-text-color);
background-color: var(--button-active-color);
}
#stats
{
float: right;
color: var(--misc-text-color);
}
#toggle-light, #toggle-dark
{
float: right;
padding-right: 0.5rem;
cursor: pointer;
}
.hint
{
color: var(--misc-text-color);
}
#data_div
{
margin-top: 1rem;
}
#data-table
{
width: 100%;
border-collapse: collapse;
border-spacing: 0;
}
/* Will be displayed when user specified custom format. */
#data-unparsed
{
background-color: var(--element-background-color);
margin-top: 0rem;
padding: 0.25rem 0.5rem;
display: none;
}
td
{
background-color: var(--element-background-color);
/* For wide tables any individual column will be no more than 50% of page width. */
max-width: 50vw;
/* The content is cut unless you hover. */
overflow: hidden;
text-overflow: ellipsis;
padding: 0.25rem 0.5rem;
border: 1px solid var(--border-color);
white-space: pre;
vertical-align: top;
}
.right
{
text-align: right;
}
th
{
padding: 0.25rem 0.5rem;
text-align: center;
background-color: var(--table-header-color);
border: 1px solid var(--border-color);
}
/* The row under mouse pointer is highlight for better legibility. */
tr:hover, tr:hover td
{
background-color: var(--table-hover-color);
}
tr:hover
{
box-shadow: 0 0 1rem rgba(0, 0, 0, 0.1);
}
#error
{
background: var(--error-color);
white-space: pre-wrap;
padding: 0.5rem 1rem;
display: none;
}
/* When mouse pointer is over table cell, will display full text (with wrap) instead of cut.
* We also keep it for some time on mouseout for "hysteresis" effect.
*/
td.left:hover, .td-hover-hysteresis
{
white-space: pre-wrap;
max-width: none;
}
.td-selected
{
white-space: pre-wrap;
max-width: none;
background-color: var(--table-hover-color);
border: 2px solid var(--border-color);
}
td.transposed
{
max-width: none;
overflow: auto;
white-space: pre-wrap;
}
td.empty-result
{
text-align: center;
vertical-align: middle;
}
.row-number
{
width: 1%;
text-align: right;
background-color: var(--table-header-color);
color: var(--misc-text-color);
}
div.empty-result
{
opacity: 10%;
font-size: 7vw;
font-family: Liberation Sans, DejaVu Sans, sans-serif;
}
/* The style for SQL NULL */
.null
{
color: var(--null-color);
}
@keyframes hourglass-animation {
0% {
transform: rotate(-180deg);
}
50% {
transform: rotate(-180deg);
}
100% {
transform: none;
}
}
#hourglass
{
display: none;
margin-left: 1rem;
font-size: 110%;
color: #888;
animation: hourglass-animation 1s linear infinite;
}
#check-mark
{
display: none;
padding-left: 1rem;
font-size: 110%;
color: #080;
}
a, a:visited
{
color: var(--link-color);
text-decoration: none;
}
#graph
{
display: none;
}
/* This is for graph in svg */
text
{
font-size: 14px;
fill: var(--text-color);
}
.node rect
{
fill: var(--element-background-color);
filter: drop-shadow(.2rem .2rem .2rem var(--shadow-color));
}
.edgePath path
{
stroke: var(--text-color);
}
marker
{
fill: var(--text-color);
}
#logo
{
fill: var(--logo-color);
}
#cloud-logo
{
color: var(--background-color);
text-shadow: 0rem 0rem 2rem var(--logo-color);
font-size: 10vw;
display: block;
}
#logo:hover
{
fill: var(--logo-color-active);
color: var(--logo-color-active);
}
#cloud-logo:hover
{
filter: brightness(150%);
}
#logo-container
{
text-align: center;
margin-top: 5em;
line-height: 0.75;
}
#chart
{
background-color: var(--element-background-color);
filter: drop-shadow(.2rem .2rem .2rem var(--shadow-color));
display: none;
height: 70vh;
}
/* This is for charts (uPlot), Copyright (c) 2022 Leon Sorokin, MIT License, https://github.com/leeoniya/uPlot/ */
.u-wrap {position: relative;user-select: none;}
.u-over, .u-under, .u-axis {position: absolute;}
.u-under {overflow: hidden;}
.uplot canvas {display: block;position: relative;width: 100%;height: 100%;}
.u-legend {margin: auto;text-align: center; margin-top: 1em; font-family: Liberation Mono, DejaVu Sans Mono, MonoLisa, Consolas, monospace;}
.u-inline {display: block;}
.u-inline * {display: inline-block;}
.u-inline tr {margin-right: 16px;}
.u-legend th {font-weight: 600;}
.u-legend th > * {vertical-align: middle;display: inline-block;}
.u-legend td { min-width: 13em; }
.u-legend .u-marker {width: 1em;height: 1em;margin-right: 4px;background-clip: padding-box !important;}
.u-inline.u-live th::after {content: ":";vertical-align: middle;}
.u-inline:not(.u-live) .u-value {display: none;}
.u-series > * {padding: 4px;}
.u-series th {cursor: pointer;}
.u-legend .u-off > * {opacity: 0.3;}
.u-select {background: rgba(0,0,0,0.07);position: absolute;pointer-events: none;}
.u-cursor-x, .u-cursor-y {position: absolute;left: 0;top: 0;pointer-events: none;will-change: transform;z-index: 100;}
.u-hz .u-cursor-x, .u-vt .u-cursor-y {height: 100%;border-right: 1px dashed #607D8B;}
.u-hz .u-cursor-y, .u-vt .u-cursor-x {width: 100%;border-bottom: 1px dashed #607D8B;}
.u-cursor-pt {position: absolute;top: 0;left: 0;border-radius: 50%;border: 0 solid;pointer-events: none;will-change: transform;z-index: 100;/*this has to be !important since we set inline "background" shorthand */background-clip: padding-box !important;}
.u-axis.u-off, .u-select.u-off, .u-cursor-x.u-off, .u-cursor-y.u-off, .u-cursor-pt.u-off {display: none;}
</style>
</head>
<body>
<div id="controls">
<div id="inputs">
<input class="monospace shadow" id="url" type="text" value="http://localhost:8123/" placeholder="url" /><input class="monospace shadow" id="user" type="text" value="default" placeholder="user" /><input class="monospace shadow" id="password" type="password" placeholder="password" />
</div>
<div id="query_div">
<textarea autofocus spellcheck="false" data-gramm="false" class="monospace shadow" id="query"></textarea>
</div>
<div id="run_div">
<button class="shadow" id="run">Run</button>
<span class="hint"> (Ctrl/Cmd+Enter)</span>
<span id="hourglass">⧗</span>
<span id="check-mark">✔</span>
<span id="stats"></span>
<span id="toggle-dark">🌑</span><span id="toggle-light">🌞</span>
</div>
</div>
<div id="data_div">
<table class="monospace-table shadow" id="data-table"></table>
<pre class="monospace-table shadow" id="data-unparsed"></pre>
</div>
<div id="chart"></div>
<svg id="graph" fill="none"></svg>
<p id="error" class="monospace shadow">
</p>
<p id="logo-container">
<a href="https://clickhouse.com/">
<svg id="logo" width="50%" viewBox="0 0 180 28" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Powered by ClickHouse</title>
<g transform="translate(21, 0)">
<path d="M9.76370444,20.9560972 C8.43447886,20.9735251 7.11430823,20.7384252 5.875478,20.2636703 C4.72047444,19.8054437 3.67723122,19.1113387 2.81453379,18.2271205 C1.90927726,17.3283139 1.2048111,16.2531042 0.746328237,15.0704684 C-0.248776079,12.2964995 -0.248776079,9.27056268 0.746328237,6.4965938 C1.67159165,4.07354026 3.61800681,2.16434552 6.08229856,1.26266086 C7.35832383,0.792264319 8.71148185,0.557532775 10.0739353,0.570233938 C11.1612577,0.548839362 12.2465336,0.672146838 13.3003359,0.936812899 C14.1575307,1.16603291 14.9890302,1.47989577 15.7821826,1.8736258 L15.0376286,4.17492705 C14.334754,3.70811103 13.5779913,3.32526638 12.7832845,3.03445917 C11.8308864,2.71897047 10.8304102,2.56740693 9.82575061,2.58641822 C7.96245266,2.56886976 6.1853824,3.35817309 4.96546756,4.74516099 C4.30442786,5.50472232 3.79877758,6.38298172 3.47635956,7.33157921 C3.08814385,8.46618422 2.89929764,9.65767851 2.91794407,10.8548103 C2.90195973,12.0219322 3.07653544,13.184001 3.43499545,14.2965795 C3.73382292,15.2302331 4.21899806,16.0957206 4.86205728,16.8422667 C5.46077204,17.5238868 6.21094713,18.0604846 7.05435517,18.41041 C7.95798877,18.7824827 8.9290709,18.969569 9.90847883,18.9602785 C10.8005951,18.9648543 11.68759,18.827308 12.5350999,18.5529685 C13.3447055,18.2769814 14.1214074,17.9150606 14.8514901,17.4735971 L15.575362,19.2046644 C14.8756447,19.7279796 14.0929921,20.1339593 13.2589718,20.4062288 C12.1346344,20.7859604 10.952228,20.9719742 9.76370444,20.9560972 L9.76370444,20.9560972 Z" id="Path"></path>
<polygon id="Path" points="21.2008811 0 21.2008811 20.6709803 18.7397165 20.6709803 18.7397165 0 21.2008811 0"></polygon>
<path d="M26.5161694,4.15456155 C26.0981094,4.18404053 25.6871808,4.03620327 25.3868859,3.74828616 C25.0865909,3.46036905 24.9255529,3.05981554 24.9443332,2.64751471 C24.9293499,2.23543398 25.0776822,1.83376364 25.3579743,1.52741233 C25.9917593,0.922143971 26.9992154,0.922143971 27.6330004,1.52741233 C27.9227354,1.82938962 28.0789406,2.23223687 28.0673236,2.64751471 C28.0795295,3.0508229 27.9224226,3.44126169 27.6330004,3.7268861 C27.3352303,4.0129939 26.9320049,4.16740365 26.5161694,4.15456155 L26.5161694,4.15456155 Z M27.7570927,6.31330432 L27.7570927,20.5691528 L25.3166102,20.5691528 L25.3166102,6.31330432 L27.7777748,6.31330432 L27.7570927,6.31330432 Z" id="Shape"></path>
<path d="M38.2015308,20.9561849 C37.2331788,20.9606473 36.2723959,20.7880054 35.3680892,20.4469598 C34.5040176,20.1277138 33.7207114,19.6274001 33.072381,18.9806439 C32.4086321,18.3083453 31.8882536,17.5120269 31.5419089,16.6386117 C30.789066,14.6134632 30.789066,12.3911868 31.5419089,10.3660384 C31.8953146,9.48650873 32.4305899,8.68894988 33.1137451,8.02400613 C33.8006138,7.36063917 34.6265105,6.85322326 35.5335456,6.53732479 C36.4665869,6.19626518 37.4544707,6.0237894 38.4497154,6.02818735 C39.2367736,6.02254268 40.0215928,6.11144966 40.7867877,6.29293882 C41.402916,6.43127907 41.9904819,6.67235105 42.5240804,7.00573124 L41.7381623,9.14410851 C41.2326995,8.76428196 40.6844527,8.44310218 40.1042799,8.18693012 C39.4990142,7.95244732 38.8516533,7.84158602 38.2015308,7.86108215 C37.5775701,7.85677667 36.958946,7.97445444 36.3815099,8.20729561 C35.8033669,8.44328317 35.2871426,8.80537627 34.8717198,9.2663015 C34.4220004,9.77237252 34.0776687,10.3605404 33.8582991,10.9973688 C33.5836299,11.7832018 33.4506403,12.6102801 33.4653401,13.4412285 C33.3646964,14.9221144 33.8543791,16.3832889 34.8303557,17.5143281 C35.8193073,18.4542588 37.1597853,18.9483223 38.5324437,18.8788165 C39.7296588,18.8515966 40.8932941,18.483801 41.8829366,17.8198106 L42.4413521,19.6119744 C41.8863529,20.0201826 41.2635134,20.3302813 40.6006492,20.5284218 C39.8277327,20.7904738 39.0186907,20.9346965 38.2015308,20.9561849 L38.2015308,20.9561849 Z" id="Path"></path>
<path d="M47.9427789,0 L47.9427789,20.6709803 L45.4402502,20.6709803 L45.4402502,0 L47.9427789,0 Z M54.3542161,20.6709803 L48.2736918,13.0135531 L54.2714879,6.33366982 L56.8360628,6.33366982 L50.9003129,12.7284361 L57.2497039,20.6709803 L54.3542161,20.6709803 L54.3542161,20.6709803 Z" id="Shape"></path>
<polygon id="Path" points="73.1128405 11.8730852 62.7718127 11.8730852 62.7718127 20.6506148 60.1038276 20.6506148 60.1038276 0.855350908 62.7718127 0.855350908 62.7718127 9.89763193 73.1128405 9.89763193 73.1128405 0.855350908 75.7808256 0.855350908 75.7808256 20.6709803 73.1128405 20.6709803"></polygon>
<path d="M86.3079919,20.9560972 C85.38699,20.956828 84.4743522,20.7840259 83.6193247,20.4469598 C82.7801539,20.1243214 82.0191689,19.6317014 81.3856627,19.0010094 C80.7288968,18.3272298 80.2155719,17.5309446 79.8758726,16.6589772 C79.1241518,14.6056391 79.1241518,12.35828 79.8758726,10.3049419 C80.2139667,9.43820628 80.7276537,8.64828849 81.3856627,7.98327514 C82.0210949,7.3549028 82.7815529,6.86262399 83.6193247,6.53732479 C84.4743522,6.20025867 85.38699,6.0274377 86.3079919,6.02818492 C87.235663,6.02783377 88.154999,6.20059422 89.0173412,6.53732479 C89.8608646,6.86326706 90.6279639,7.35528929 91.2716852,7.98327514 C91.9432058,8.64563226 92.4709473,9.43552137 92.8228394,10.3049419 C93.5941835,12.3548594 93.5941835,14.6090597 92.8228394,16.6589772 C92.5001582,17.5252355 92.0083217,18.3208853 91.3750955,19.0010094 C90.7336187,19.6317283 89.9659063,20.1241438 89.1207514,20.4469598 C88.2261198,20.7965781 87.270406,20.9695718 86.3079919,20.9560972 Z M86.3079919,19.1028369 C86.8871906,19.1004754 87.4613802,18.9970483 88.0039204,18.7973545 C88.5665616,18.5933999 89.0704796,18.2579374 89.4723464,17.8198106 C89.93364,17.3107661 90.2854168,16.714971 90.5064492,16.0683778 C90.7858877,15.2345245 90.9187812,14.3598326 90.8994082,13.4819595 C90.921252,12.6039337 90.7882958,11.7288286 90.5064492,10.8955413 C90.2829062,10.2561715 89.9312542,9.66751308 89.4723464,9.16447401 C89.0704796,8.72634722 88.5665616,8.39088465 88.0039204,8.18693012 C87.4613802,7.98723626 86.8871906,7.88380922 86.3079919,7.88144765 C85.7421967,7.88338153 85.1815027,7.98690253 84.6534274,8.18693012 C84.1067964,8.39867016 83.6180769,8.73344525 83.2263656,9.16447401 C82.771108,9.66662382 82.426093,10.2559587 82.2129449,10.8955413 C81.9432303,11.7312023 81.817401,12.6054092 81.8406679,13.4819595 C81.8197008,14.3583785 81.9454766,15.2322137 82.2129449,16.0683778 C82.4234966,16.7151843 82.7686424,17.3116787 83.2263656,17.8198106 C83.6175627,18.2514112 84.1064306,18.586288 84.6534274,18.7973545 C85.1815027,18.9973821 85.7421967,19.1009031 86.3079919,19.1028369 L86.3079919,19.1028369 Z" id="Shape"></path>
<path d="M104.218652,20.1211118 C103.261113,20.713834 102.142858,21.0051032 101.012933,20.9560972 C99.7999523,21.0051122 98.6193584,20.5636475 97.7451686,19.7341674 C96.8353108,18.7009502 96.3878256,17.3496664 96.5042453,15.9869158 L96.5042453,6.31330432 L98.9860919,6.31330432 L98.9860919,15.9869158 C98.9742754,16.4701161 99.0441695,16.9518863 99.1929125,17.4125006 C99.3056456,17.7606047 99.5046708,18.0755709 99.77201,18.328948 C100.016574,18.5594167 100.314849,18.7272509 100.640656,18.81772 C100.990271,18.9161408 101.352523,18.9641592 101.716123,18.9602785 C102.655549,19.0038445 103.582007,18.7308795 104.342744,18.1863895 C105.051689,17.6370184 105.630443,16.9420231 106.038673,16.1498398 L106.038673,6.31330432 L108.499837,6.31330432 L108.499837,20.5691528 L106.328222,20.5691528 L106.100719,18.0031001 L106.100719,18.0031001 C105.687922,18.8692089 105.035644,19.6032598 104.218652,20.1211118 L104.218652,20.1211118 Z" id="Path"></path>
<path d="M116.421065,20.9560972 C115.801233,20.9950241 115.179512,20.9950241 114.55968,20.9560972 C114.092617,20.8964402 113.62981,20.8080164 113.173982,20.6913458 C112.813228,20.605003 112.460411,20.4891975 112.119197,20.3451323 C111.834495,20.2200141 111.558175,20.0771664 111.291915,19.9174568 L112.098515,17.8809071 C112.279343,18.026767 112.473161,18.156273 112.677613,18.2678515 C112.976636,18.4329534 113.287727,18.5759069 113.608305,18.695527 C114.017173,18.8537321 114.439319,18.9763932 114.869911,19.0621059 C115.380772,19.1609067 115.900459,19.2086684 116.421065,19.2046644 C117.260254,19.2611102 118.095255,19.0466179 118.799501,18.5936995 C119.330358,18.1920161 119.630919,17.5621502 119.606101,16.9033632 C119.629117,16.3456326 119.383876,15.8098319 118.944275,15.4574128 C118.248621,14.9827695 117.47054,14.6376468 116.648567,14.4391379 L114.869911,13.8485385 C114.332933,13.6600961 113.82489,13.3996834 113.36012,13.0746496 C112.918371,12.7629353 112.551287,12.3597625 112.284654,11.8934507 C111.990937,11.3416153 111.84837,10.7239217 111.871013,10.1012869 C111.7928,8.92823263 112.331666,7.79778562 113.298074,7.10755873 C114.467654,6.39295223 115.832855,6.05154147 117.206983,6.13001484 C118.07528,6.11663174 118.942387,6.19859987 119.79224,6.37440081 C120.389496,6.49652839 120.971868,6.68085438 121.529532,6.92426925 L120.950435,8.96081903 C120.479952,8.73041885 119.996597,8.52643737 119.502691,8.3498541 C118.797193,8.1146398 118.054965,8.00432014 117.310393,8.02400613 C116.535101,7.9880419 115.762277,8.13465573 115.056049,8.45168159 C114.47936,8.75940804 114.145458,9.37689414 114.208085,10.0198249 C114.202,10.3323024 114.296078,10.6387208 114.476951,10.8955413 C114.663996,11.1520366 114.903538,11.3670997 115.180141,11.5268718 C115.506126,11.720303 115.852962,11.8774057 116.214244,11.9952782 L117.455167,12.3618572 L119.130414,12.9117256 C119.664383,13.1015363 120.166356,13.3692768 120.619522,13.70598 C121.602378,14.4140087 122.154912,15.5634667 122.087948,16.7608047 C122.121004,17.4696719 121.956505,18.1739382 121.612261,18.7973545 C121.300726,19.3338382 120.869299,19.7934828 120.350655,20.1414773 C119.804193,20.5059941 119.194247,20.7683295 118.551316,20.9153662 C117.845409,21.0123699 117.130265,21.0260437 116.421065,20.9560972 Z" id="Path"></path>
<path d="M136.379248,19.4286849 C135.765636,19.9144267 135.063746,20.2807344 134.311043,20.5080563 C133.397586,20.8060079 132.439751,20.9505822 131.477601,20.9357317 C129.484022,21.0457076 127.539179,20.3034303 126.141631,18.899182 C124.819952,17.3656124 124.152831,15.3875844 124.280246,13.3801321 C124.263071,12.3194805 124.438164,11.2643153 124.797297,10.2642109 C125.111353,9.39844373 125.596363,8.60246478 126.224359,7.92217864 C126.799559,7.29363386 127.504769,6.79365416 128.292565,6.4558628 C129.13245,6.11650376 130.03203,5.94349361 130.939868,5.94672536 C131.84844,5.93569173 132.749586,6.10900306 133.587171,6.4558628 C134.355957,6.77570775 135.026242,7.28828126 135.531284,7.94254414 C136.069381,8.66407624 136.423989,9.5021104 136.565387,10.3864039 C136.756685,11.5054228 136.756685,12.6480576 136.565387,13.7670765 L126.720728,13.7670765 C126.831033,17.1749031 128.464915,18.8788165 131.622376,18.8788165 C132.38185,18.8941966 133.138087,18.7769813 133.856038,18.532603 C134.495821,18.304128 135.106567,18.0034291 135.676058,17.6365211 L136.379248,19.4286849 Z M131.022596,7.73888916 C130.017668,7.7293132 129.052936,8.12697327 128.354611,8.83862604 C127.526752,9.75405949 127.031943,10.9148261 126.948231,12.1378367 L134.331725,12.1378367 C134.534684,10.9751452 134.259342,9.78072052 133.566489,8.81826055 C132.931983,8.09270485 131.993938,7.6946933 131.022596,7.73888916 Z" id="Shape"></path>
</g>
</svg>
</a>
<a id="cloud-logo" href="https://clickhouse.cloud/">☁</a>
</p>
</body>
<script type="text/javascript">
/// Incremental request number. When response is received,
/// if its request number does not equal to the current request number, response will be ignored.
/// This is to avoid race conditions.
let request_num = 0;
/// Save query in history only if it is different.
let previous_query = '';
const current_url = new URL(window.location);
const opened_locally = location.protocol == 'file:';
const server_address = current_url.searchParams.get('url');
if (server_address) {
document.getElementById('url').value = server_address;
} else if (!opened_locally) {
/// Substitute the address of the server where the page is served.
document.getElementById('url').value = location.origin;
}
/// Substitute user name if it's specified in the query string
const user_from_url = current_url.searchParams.get('user');
if (user_from_url) {
document.getElementById('user').value = user_from_url;
}
const pass_from_url = current_url.searchParams.get('password');
if (pass_from_url) {
document.getElementById('password').value = pass_from_url;
/// Browsers don't allow manipulating history for the 'file:' protocol.
if (!opened_locally) {
let replaced_pass = current_url.searchParams;
replaced_pass.delete('password');
window.history.replaceState(null, '',
window.location.origin + window.location.pathname + '?'
+ replaced_pass.toString() + window.location.hash);
}
}
function postImpl(posted_request_num, query)
{
const user = document.getElementById('user').value;
const password = document.getElementById('password').value;
const server_address = document.getElementById('url').value;
var url = server_address +
(server_address.indexOf('?') >= 0 ? '&' : '?') +
/// Ask server to allow cross-domain requests.
'add_http_cors_header=1' +
'&default_format=JSONCompact' +
/// Safety settings to prevent results that browser cannot display.
'&max_result_rows=1000&max_result_bytes=10000000&result_overflow_mode=break';
// If play.html is opened locally, append username and password to the URL parameter to avoid CORS issue.
if (opened_locally) {
url += '&user=' + encodeURIComponent(user) +
'&password=' + encodeURIComponent(password)
}
const xhr = new XMLHttpRequest;
xhr.open('POST', url, true);
// If play.html is open normally, use Basic auth to prevent username and password being exposed in URL parameters
if (!opened_locally) {
xhr.setRequestHeader("Authorization", "Basic " + btoa(user+":"+password));
}
xhr.onreadystatechange = function()
{
if (posted_request_num != request_num) {
return;
} else if (this.readyState === XMLHttpRequest.DONE) {
renderResponse(this.status, this.response);
/// The query is saved in browser history (in state JSON object)
/// as well as in URL fragment identifier.
if (query != previous_query) {
const state = {
query: query,
status: this.status,
response: this.response.length > 100000 ? null : this.response /// Lower than the browser's limit.
};
const title = "ClickHouse Query: " + query;
let history_url = window.location.pathname + '?user=' + encodeURIComponent(user);
if (server_address != location.origin) {
/// Save server's address in URL if it's not identical to the address of the play UI.
history_url += '&url=' + encodeURIComponent(server_address);
}
history_url += '#' + window.btoa(query);
if (previous_query == '') {
history.replaceState(state, title, history_url);
} else {
history.pushState(state, title, history_url);
}
document.title = title;
previous_query = query;
}
} else {
//console.log(this);
}
}
document.getElementById('check-mark').style.display = 'none';
document.getElementById('hourglass').style.display = 'inline-block';
xhr.send(query);
}
function renderResponse(status, response) {
document.getElementById('hourglass').style.display = 'none';
if (status === 200) {
let json;
try { json = JSON.parse(response); } catch (e) {}
if (json !== undefined && json.statistics !== undefined) {
renderResult(json);
} else if (Array.isArray(json) && json.length == 2 &&
Array.isArray(json[0]) && Array.isArray(json[1]) && json[0].length > 1 && json[0].length == json[1].length) {
/// If user requested FORMAT JSONCompactColumns, we will render it as a chart.
renderChart(json);
} else {
renderUnparsedResult(response);
}
document.getElementById('check-mark').style.display = 'inline';
} else {
/// TODO: Proper rendering of network errors.
renderError(response);
}
}
let query_area = document.getElementById('query');
window.onpopstate = function(event) {
if (!event.state) {
return;
}
query_area.value = event.state.query;
if (!event.state.response) {
clear();
return;
}
renderResponse(event.state.status, event.state.response);
};
if (window.location.hash) {
query_area.value = window.atob(window.location.hash.substr(1));
}
function post()
{
++request_num;
let query = query_area.value;
postImpl(request_num, query);
}
document.getElementById('run').onclick = function()
{
post();
}
document.onkeydown = function(event)
{
/// Firefox has code 13 for Enter and Chromium has code 10.
if ((event.metaKey || event.ctrlKey) && (event.keyCode == 13 || event.keyCode == 10)) {
post();
}
}
/// Pressing Tab in textarea will increase indentation.
/// But for accessibility reasons, we will fall back to tab navigation if the user already used Tab for that.
let user_prefers_tab_navigation = false;
[...document.querySelectorAll('input')].map(elem => {
elem.onkeydown = (e) => {
if (e.key == 'Tab') { user_prefers_tab_navigation = true; }
};
});
query_area.onkeydown = (e) => {
if (e.key == 'Tab' && !event.shiftKey && !user_prefers_tab_navigation) {
let elem = e.target;
let selection_start = elem.selectionStart;
let selection_end = elem.selectionEnd;
elem.value = elem.value.substring(0, elem.selectionStart) + ' ' + elem.value.substring(elem.selectionEnd);
elem.selectionStart = selection_start + 4;
elem.selectionEnd = selection_start + 4;
e.preventDefault();
return false;
} else if (e.key === 'Enter' && !(event.metaKey || event.ctrlKey)) {
// If the user presses Enter, and the previous line starts with spaces,
// then we will insert the same number of spaces.
const elem = e.target;
if (elem.selectionStart !== elem.selectionEnd) {
// If there is a selection, then we will not insert spaces.
return;
}
const cursor_pos = elem.selectionStart;
const elem_value = elem.value;
const text_before_cursor = elem_value.substring(0, cursor_pos);
const text_after_cursor = elem_value.substring(cursor_pos);
const prev_lines = text_before_cursor.split('\n');
const prev_line = prev_lines.pop();
const lead_spaces = prev_line.match(/^\s*/)[0];
if (!lead_spaces) {
return;
}
// Add leading spaces to the current line.
elem.value = text_before_cursor + '\n' + lead_spaces + text_after_cursor;
elem.selectionStart = cursor_pos + lead_spaces.length + 1;
elem.selectionEnd = elem.selectionStart;
e.preventDefault();
return false;
}
};
function clearElement(id)
{
let elem = document.getElementById(id);
while (elem.firstChild) {
elem.removeChild(elem.lastChild);
}
elem.style.display = 'none';
}
function clear()
{
clearElement('data-table');
clearElement('graph');
clearElement('chart');
clearElement('data-unparsed');
clearElement('error');
document.getElementById('check-mark').display = 'none';
document.getElementById('hourglass').display = 'none';
document.getElementById('stats').innerText = '';
document.getElementById('logo-container').style.display = 'block';
}
function formatReadable(number = 0, decimals = 2, units = []) {
const k = 1000;
const i = number ? Math.floor(Math.log(number) / Math.log(k)) : 0;
const unit = units[i];
const dm = unit ? decimals : 0;
return Number(number / Math.pow(k, i)).toFixed(dm) + unit;
}
function formatReadableBytes(bytes) {
const units = [' B', ' KB', ' MB', ' GB', ' TB', ' PB', ' EB', ' ZB', ' YB'];
return formatReadable(bytes, 2, units);
}
function formatReadableRows(rows) {
const units = ['', ' thousand', ' million', ' billion', ' trillion', ' quadrillion'];
return formatReadable(rows, 2, units);
}
function renderResult(response)
{
clear();
let stats = document.getElementById('stats');
const seconds = response.statistics.elapsed.toFixed(3);
const rows = response.statistics.rows_read;
const bytes = response.statistics.bytes_read;
const formatted_bytes = formatReadableBytes(bytes);
const formatted_rows = formatReadableRows(rows);
stats.innerText = `Elapsed: ${seconds} sec, read ${formatted_rows} rows, ${formatted_bytes}.`;
/// We can also render graphs if user performed EXPLAIN PIPELINE graph=1 or EXPLAIN AST graph = 1
if (response.data.length > 3 && query_area.value.match(/^\s*EXPLAIN/i) && typeof(response.data[0][0]) === "string" && response.data[0][0].startsWith("digraph")) {
renderGraph(response);
} else {
renderTable(response);
}
}
function renderCell(cell, col_idx, settings)
{
let td = document.createElement('td');
let is_null = (cell === null);
let is_link = false;
/// Test: SELECT number, toString(number) AS str, number % 2 ? number : NULL AS nullable, range(number) AS arr, CAST((['hello', 'world'], [number, number % 2]) AS Map(String, UInt64)) AS map FROM numbers(10)
let text;
if (is_null) {
text = 'ᴺᵁᴸᴸ';
} else if (typeof(cell) === 'object') {
text = JSON.stringify(cell);
} else {
text = cell;
/// If it looks like URL, create a link. This is for convenience.
if (typeof(cell) == 'string' && cell.match(/^https?:\/\/\S+$/)) {
is_link = true;
}
}
let node = document.createTextNode(text);
if (is_link) {
let link = document.createElement('a');
link.appendChild(node);
link.href = text;
link.setAttribute('target', '_blank');
node = link;
}
if (settings.is_transposed) {
td.className = 'left transposed';
} else {
td.className = settings.column_is_number[col_idx] ? 'right' : 'left';
}
if (is_null) {
td.className += ' null';
}
/// If it's a number, render bar in background.
if (!settings.is_transposed && settings.column_need_render_bars[col_idx] && text > 0) {
const ratio = 100 * text / settings.column_maximums[col_idx];
let div = document.createElement('div');
div.style.width = '100%';
div.style.background = `linear-gradient(to right,
var(--bar-color) 0%, var(--bar-color) ${ratio}%,
transparent ${ratio}%, transparent 100%)`;
div.appendChild(node);
node = div;
}
td.appendChild(node);
return td;
}
function renderTableTransposed(response)
{
let tbody = document.createElement('tbody');
for (let col_idx in response.meta) {
let tr = document.createElement('tr');
{
let th = document.createElement('th');
th.className = 'right';
th.style.width = '0';
th.appendChild(document.createTextNode(response.meta[col_idx].name));
tr.appendChild(th);
}
for (let row_idx in response.data)
{
let cell = response.data[row_idx][col_idx];
const td = renderCell(cell, col_idx, {is_transposed: true});
tr.appendChild(td);
}
if (response.data.length == 0 && col_idx == 0)
{
/// If result is empty, show this fact with a style.
let td = document.createElement('td');
td.rowSpan = response.meta.length;
td.className = 'empty-result';
let div = document.createElement('div');
div.appendChild(document.createTextNode("empty result"));
div.className = 'empty-result';
td.appendChild(div);
tr.appendChild(td);
}
tbody.appendChild(tr);
}
let table = document.getElementById('data-table');
table.appendChild(tbody);
table.style.display = 'table';
}
function renderTable(response)
{
if (response.data.length <= 1 && response.meta.length >= 5) {
renderTableTransposed(response)
return;
}
const should_display_row_numbers = response.data.length > 3;
let thead = document.createElement('thead');
if (should_display_row_numbers) {
let th = document.createElement('th');
th.className = 'row-number';
th.appendChild(document.createTextNode('№'));
thead.appendChild(th);
}
for (let idx in response.meta) {
let th = document.createElement('th');
const name = document.createTextNode(response.meta[idx].name);
th.appendChild(name);
thead.appendChild(th);
}
/// To prevent hanging the browser, limit the number of cells in a table.
/// It's important to have the limit on number of cells, not just rows, because tables may be wide or narrow.
/// Also we permit rendering of more records but only if elapsed time is not large.
const max_rows = 10000 / response.meta.length;
const max_render_ms = 200;
let row_num = 0;
const column_is_number = response.meta.map(elem => !!elem.type.match(/^(Nullable\()?(U?Int|Decimal|Float)/));
const column_maximums = column_is_number.map((elem, idx) => elem ? Math.max(...response.data.map(row => row[idx])) : 0);
const column_minimums = column_is_number.map((elem, idx) => elem ? Math.min(...response.data.map(row => Math.max(0, row[idx]))) : 0);
const column_need_render_bars = column_is_number.map((elem, idx) => column_maximums[idx] > 0 && column_maximums[idx] > column_minimums[idx]);
const settings = {
is_transposed: false,
column_is_number: column_is_number,
column_maximums: column_maximums,
column_minimums: column_minimums,
column_need_render_bars: column_need_render_bars,
};
const start_time = performance.now();
let tbody = document.createElement('tbody');
for (let row_idx in response.data) {
let tr = document.createElement('tr');
if (should_display_row_numbers) {
let td = document.createElement('td');
td.className = 'row-number';
td.appendChild(document.createTextNode(1 + +row_idx));
tr.appendChild(td);
}
for (let col_idx in response.data[row_idx]) {
let cell = response.data[row_idx][col_idx];
const td = renderCell(cell, col_idx, settings);
td.onclick = () => { td.classList.add('td-selected') };
td.onmouseenter = () => {
td.classList.add('td-hover-hysteresis');
td.onmouseleave = () => {
setTimeout(() => { td && td.classList.remove('td-hover-hysteresis') }, 1000);
};
};
tr.appendChild(td);
}
tbody.appendChild(tr);
++row_num;
if (row_num >= max_rows && performance.now() - start_time >= max_render_ms) {
break;
}
}
let table = document.getElementById('data-table');
table.appendChild(thead);
table.appendChild(tbody);
table.style.display = 'table';
}
/// A function to render raw data when non-default format is specified.
function renderUnparsedResult(response)
{
clear();
let data = document.getElementById('data-unparsed')
if (response === '') {
/// TODO: Fade or remove previous result when new request will be performed.
response = 'Ok.';
}
data.innerText = response;
/// inline-block make width adjust to the size of content.
data.style.display = 'inline-block';
}
function renderError(response)
{
clear();
let message = response;
try {
let json = JSON.parse(response);
if (json.exception) {