sqlui 0.1.65 → 0.1.67

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dccd56c50fbd55f848e0af58c735e04a2359f752d83bc5733011de736d88e47c
4
- data.tar.gz: 2d1399f6aa1ac923101aad2d409c7488fec210107426ec2480475626fa57dbb2
3
+ metadata.gz: 7b91f535db1da5166bfebd7b9d9097db156b429aa283c6084e1f79bb348f56c4
4
+ data.tar.gz: bec7b344b15ee7730f88e77ad88910a8e316ac135aa9f66a4ea96aafecf763ac
5
5
  SHA512:
6
- metadata.gz: a60671998954c01b16253548b4ed9217a931cd9fded5d0af3262c506950665993d0261d4b0e6ecb942acd970e98b2d166cf2a8ee4f2f1cb1917ce50ed3054c55
7
- data.tar.gz: 22b4e49d4b7ab49298f80c2e11a6da30c418d49b4a9f212c5441cf3e43136bcf460f6b399916ebd4e607f6c4090e74d9038885eae2ad750f26c274ed153336fb
6
+ metadata.gz: e470fbc8f9ea18c9de08eb05ce67a491f6a0b42a9e0895033566f97953da3e630963f431192411af6471dc678979e45e67aa845fbde3b07093abc4c95c119e3d
7
+ data.tar.gz: bd4030d8b1e3095873b887c36dad1303b63aa1e020851ce5fa3336b8b5f055b6b33b20b21db28a203350c3cc7746cd1dc422a221a08412903a39b8d0f6a79afa
data/.release-version CHANGED
@@ -1 +1 @@
1
- 0.1.65
1
+ 0.1.67
@@ -10,6 +10,7 @@ class DatabaseMetadata
10
10
  def lookup(client, database_config)
11
11
  result = load_columns(client, database_config)
12
12
  load_stats(result, client, database_config)
13
+ load_tables(result, client, database_config)
13
14
 
14
15
  result
15
16
  end
@@ -91,7 +92,7 @@ class DatabaseMetadata
91
92
  from information_schema.statistics
92
93
  #{where_clause}
93
94
  order by table_schema, table_name, if(index_name = "PRIMARY", 0, index_name), seq_in_index;
94
- SQL
95
+ SQL
95
96
  )
96
97
  stats_result.each do |row|
97
98
  table_schema = row.shift
@@ -110,5 +111,81 @@ class DatabaseMetadata
110
111
  column[:column_name] = column_name
111
112
  end
112
113
  end
114
+
115
+ def load_tables(result, client, _database_config)
116
+ tables_result = client.query(
117
+ <<~SQL
118
+ SELECT
119
+ TABLE_SCHEMA,
120
+ TABLE_NAME,
121
+ CREATE_TIME,
122
+ UPDATE_TIME,
123
+ ENGINE,
124
+ TABLE_ROWS,
125
+ AVG_ROW_LENGTH,
126
+ DATA_LENGTH,
127
+ INDEX_LENGTH,
128
+ TABLE_COLLATION,
129
+ AUTO_INCREMENT
130
+ FROM information_schema.TABLES;
131
+ SQL
132
+ )
133
+ tables_hash = {}
134
+ tables_result.each do |row|
135
+ table_schema = row.shift
136
+ table_name = row.shift
137
+ tables_hash[table_schema] ||= {}
138
+ tables_hash[table_schema][table_name] = {
139
+ created_at: row.shift,
140
+ updated_at: row.shift,
141
+ engine: row.shift,
142
+ rows: add_thousands_separator(row.shift),
143
+ average_row_size: pretty_data_size(row.shift),
144
+ data_size: pretty_data_size(row.shift),
145
+ index_size: pretty_data_size(row.shift),
146
+ encoding: row.shift,
147
+ auto_increment: add_thousands_separator(row.shift)
148
+ }
149
+ end
150
+ result.each do |table_schema, schema_hash|
151
+ schema_hash[:tables].each do |table_name, table|
152
+ table[:info] = tables_hash[table_schema][table_name]
153
+ end
154
+ end
155
+ end
156
+
157
+ def pretty_data_size(size)
158
+ if size.nil?
159
+ return nil
160
+ elsif size < 1024
161
+ unit = "byte#{size == 1 ? '' : 's'}"
162
+ converted = size
163
+ elsif size < 1024 * 1024
164
+ unit = 'KiB'
165
+ converted = (size.to_f / 1024).round(2)
166
+ elsif size < 1024 * 1024 * 1024
167
+ unit = 'MiB'
168
+ converted = (size.to_f / (1024 * 1024)).round(2)
169
+ elsif size < 1024 * 1024 * 1024 * 1024
170
+ unit = 'GiB'
171
+ converted = (size.to_f / (1024 * 1024 * 1024)).round(2)
172
+ end
173
+
174
+ "#{add_thousands_separator(converted)} #{unit}"
175
+ end
176
+
177
+ def add_thousands_separator(value)
178
+ return nil if value.nil?
179
+
180
+ integer = value.round(1).floor
181
+ fractional = ((value.round(1) - integer) * 10).floor
182
+
183
+ with_commas = integer.to_s.reverse.scan(/.{1,3}/).join(',').reverse
184
+ if fractional.zero?
185
+ with_commas
186
+ else
187
+ "#{with_commas}.#{fractional}"
188
+ end
189
+ end
113
190
  end
114
191
  end
data/app/sql_parser.rb CHANGED
@@ -40,10 +40,10 @@ class SqlParser
40
40
  multi_commented = true
41
41
  elsif multi_commented && char == '*' && scanner.peek(1) == '/'
42
42
  multi_commented = false
43
- elsif multi_commented
43
+ elsif multi_commented || (single_commented && char != "\n")
44
44
  next
45
45
  elsif !single_quoted && !double_quoted && !escaped && !multi_commented &&
46
- char == '-' && scanner.peek(1).match?(/-[ \n\t]/)
46
+ char == '-' && scanner.peek(2).match?(/-[ \n\t]/)
47
47
  current += scanner.getch
48
48
  single_commented = true
49
49
  elsif !single_quoted && !double_quoted && !escaped && !multi_commented && char == '#'
@@ -51,7 +51,7 @@ class SqlParser
51
51
  elsif single_commented && char == "\n"
52
52
  single_commented = false
53
53
  elsif single_commented
54
- single_quoted = false if char == "\n"
54
+ single_commented = false if char == "\n"
55
55
  elsif char == '\\'
56
56
  escaped = true
57
57
  elsif char == "'"
@@ -11,18 +11,6 @@
11
11
  margin: 0;
12
12
  }
13
13
 
14
- h1 {
15
- font-size: 22px;
16
- }
17
-
18
- h2 {
19
- font-size: 20px;
20
- }
21
-
22
- p {
23
- font-size: 18px;
24
- }
25
-
26
14
  .header-box {
27
15
  display: flex;
28
16
  flex-direction: row;
@@ -37,6 +25,12 @@
37
25
  align-items: center;
38
26
  justify-content: start;
39
27
  color: #333;
28
+ font-size: 22px;
29
+ }
30
+
31
+ .header {
32
+ font-weight: bold;
33
+ padding-left: 5px;
40
34
  }
41
35
 
42
36
  .server-name {
@@ -45,30 +39,28 @@
45
39
  font-weight: normal;
46
40
  }
47
41
 
48
- .name-and-links {
42
+ .links {
49
43
  display: flex;
50
44
  flex-direction: row;
51
45
  align-items: center;
46
+ margin-top: 5px;
52
47
  }
53
48
 
54
- .header {
55
- font-weight: bold;
56
- padding-left: 5px;
57
- }
58
-
59
- .database a {
60
- margin-right: 5px;
49
+ .link {
50
+ margin-right: 10px;
61
51
  color: darkblue;
62
- font-size: 16px;
52
+ font-size: 17px;
63
53
  text-decoration: none;
64
54
  }
65
55
 
66
- .database h2 {
67
- margin: 0 10px 0 0;
56
+ .name {
57
+ margin: 0 200px 0 0;
58
+ font-size: 20px;
68
59
  }
69
60
 
70
- .database p {
71
- margin: 10px 0 0;
61
+ .description {
62
+ margin: 20px 0 0;
63
+ font-size: 18px;
72
64
  }
73
65
 
74
66
  .database {
@@ -90,11 +82,11 @@
90
82
  </div>
91
83
  <% config.database_configs.each do |database_config| %>
92
84
  <div class="database">
93
- <div class="name-and-links">
94
- <h2 class='name'><%= database_config.display_name %></h2>
95
- <a class='query-link' href="<%= "#{config.base_url_path}/#{database_config.url_path}/query" %>">query</a>
96
- <a class='saved-link' href="<%= "#{config.base_url_path}/#{database_config.url_path}/saved" %>">saved</a>
97
- <a class='structure-link' href="<%= "#{config.base_url_path}/#{database_config.url_path}/structure" %>">structure</a>
85
+ <h2 class='name'><%= database_config.display_name %></h2>
86
+ <div class="links">
87
+ <a class='link query-link' href="<%= "#{config.base_url_path}/#{database_config.url_path}/query" %>">query</a>
88
+ <a class='link saved-link' href="<%= "#{config.base_url_path}/#{database_config.url_path}/saved" %>">saved</a>
89
+ <a class='link structure-link' href="<%= "#{config.base_url_path}/#{database_config.url_path}/structure" %>">structure</a>
98
90
  </div>
99
91
  <p class='description'>
100
92
  <%= database_config.description %>
data/app/views/sqlui.erb CHANGED
@@ -84,10 +84,16 @@
84
84
 
85
85
  <div id="structure-box" class="tab-content-element structure-element" style="display: none;">
86
86
  <div class="structure-wrapper">
87
- <select id="schemas" size="4">
88
- </select>
89
- <select id="tables" size="4">
90
- </select>
87
+ <div id="schemas-tables-and-stats">
88
+ <div id="schemas-and-tables">
89
+ <select id="schemas" size="4">
90
+ </select>
91
+ <select id="tables" size="4">
92
+ </select>
93
+ </div>
94
+ <div id="stats">
95
+ </div>
96
+ </div>
91
97
  <div id="table-info">
92
98
  <div id="columns">
93
99
  </div>
@@ -8,18 +8,6 @@ body {
8
8
  overflow: hidden;
9
9
  }
10
10
 
11
- h1 {
12
- font-size: 22px;
13
- }
14
-
15
- h2 {
16
- font-size: 20px;
17
- }
18
-
19
- p {
20
- font-size: 18px;
21
- }
22
-
23
11
  #loading-box {
24
12
  font-family: monospace;
25
13
  display: flex;
@@ -48,6 +36,7 @@ p {
48
36
  align-items: center;
49
37
  justify-content: start;
50
38
  color: #333;
39
+ font-size: 22px;
51
40
  }
52
41
 
53
42
  #header {
@@ -126,6 +115,7 @@ p {
126
115
  cursor: row-resize;
127
116
  flex-direction: column;
128
117
  align-items: center;
118
+ padding-left: 220px; /* To center the resizer icon. Ok, it's a hack. Get over it. */
129
119
  }
130
120
 
131
121
  #cancel-button {
@@ -135,7 +125,7 @@ p {
135
125
  border: 1px solid #888;
136
126
  height: 32px;
137
127
  font-size: 18px;
138
- margin: 0 220px 0 0; /* To center the resizer icon. Ok, it's a hack. Get over it. */
128
+ margin: 0;
139
129
  }
140
130
 
141
131
  #cancel-button-spacer {
@@ -258,6 +248,7 @@ p {
258
248
  display: flex;
259
249
  flex-direction: column;
260
250
  }
251
+
261
252
  table tbody tr td {
262
253
  height: 21px;
263
254
  }
@@ -384,16 +375,28 @@ thead {
384
375
  font-family: Helvetica, sans-serif;
385
376
  }
386
377
 
387
- #saved-box h2 {
388
- font-weight: bold;
378
+ .links {
379
+ display: flex;
380
+ flex-direction: row;
381
+ align-items: center;
382
+ margin-top: 5px;
389
383
  }
390
384
 
391
- .saved-list-item:last-child {
392
- border-top: none !important;
385
+ .link {
386
+ margin-right: 10px;
387
+ color: darkblue;
388
+ font-size: 17px;
389
+ text-decoration: none;
393
390
  }
394
391
 
395
- #saved-box p {
396
- margin: 0;
392
+ .name {
393
+ margin: 0 200px 0 0;
394
+ font-size: 20px;
395
+ }
396
+
397
+ .description {
398
+ margin: 20px 0 0;
399
+ font-size: 18px;
397
400
  }
398
401
 
399
402
  .saved-list-item {
@@ -402,25 +405,8 @@ thead {
402
405
  padding: 10px;
403
406
  }
404
407
 
405
- .saved-list-item h2 {
406
- margin: 0 10px 0 0;
407
- }
408
-
409
- .saved-list-item p {
410
- margin: 10px 0 0;
411
- }
412
-
413
- .name-and-links {
414
- display: flex;
415
- flex-direction: row;
416
- align-items: center;
417
- }
418
-
419
- .name-and-links a {
420
- margin-right: 5px;
421
- color: darkblue;
422
- font-size: 16px;
423
- text-decoration: none;
408
+ .saved-list-item:last-child {
409
+ border-bottom: none;
424
410
  }
425
411
 
426
412
  .cm-editor.cm-focused {
@@ -435,10 +421,49 @@ thead {
435
421
  height: 100%;
436
422
  }
437
423
 
424
+ #schemas-and-tables {
425
+ display: flex;
426
+ flex-direction: row;
427
+ flex: 1;
428
+ }
429
+
430
+ #schemas-tables-and-stats {
431
+ display: flex;
432
+ flex-direction: column;
433
+ border-right: 1px solid #ddd;
434
+ min-width: 400px;
435
+ }
436
+
437
+ #stats {
438
+ border-top: 1px solid #ddd;
439
+ padding: 5px 0;
440
+ }
441
+
442
+ #stats table {
443
+ width: 100%;
444
+ }
445
+
446
+ #stats table td {
447
+ padding: 2px 5px;
448
+ font-size: 18px;
449
+ color: #333;
450
+ }
451
+
452
+ table td:nth-child(1) {
453
+ text-align: left;
454
+ font-family: Helvetica, sans-serif;
455
+ }
456
+
457
+ table td:nth-child(2) {
458
+ text-align: right;
459
+ font-family: monospace;
460
+ }
461
+
438
462
  #schemas, #tables {
439
463
  border: none;
440
464
  display: flex;
441
- min-width: 200px;
465
+ flex: 1;
466
+ padding: 5px
442
467
  }
443
468
 
444
469
  #table-info {
@@ -21908,6 +21908,7 @@
21908
21908
  }
21909
21909
 
21910
21910
  function copyTextToClipboard (text) {
21911
+ if (text === null) text = '';
21911
21912
  const type = 'text/plain';
21912
21913
  const blob = new Blob([text], { type });
21913
21914
  navigator.clipboard.write([new window.ClipboardItem({ [type]: blob })]);
@@ -24744,9 +24745,17 @@
24744
24745
  popupElement.appendChild(contentElement);
24745
24746
 
24746
24747
  let renderedText = text;
24747
- if (typeof text === 'string' && text.match(/^\s*(?:\{.*\}|\[.*\])\s*$/)) {
24748
+ let clipboardText = text;
24749
+ let formatted = false;
24750
+ if (text === null) {
24751
+ renderedText = 'null';
24752
+ contentElement.style.color = '#888';
24753
+ clipboardText = '';
24754
+ } else if (typeof text === 'string' && text.match(/^\s*(?:\{.*\}|\[.*\])\s*$/)) {
24748
24755
  try {
24749
24756
  renderedText = JSON.stringify(JSON.parse(text), null, 2);
24757
+ clipboardText = renderedText;
24758
+ formatted = renderedText !== text;
24750
24759
  } catch (_) { }
24751
24760
  }
24752
24761
  contentElement.innerText = renderedText;
@@ -24762,11 +24771,11 @@
24762
24771
  buttonBarElement.appendChild(copyElement);
24763
24772
 
24764
24773
  copyElement.addEventListener('click', (event) => {
24765
- copyTextToClipboard(renderedText);
24774
+ copyTextToClipboard(clipboardText);
24766
24775
  toast('Text copied to clipboard.');
24767
24776
  });
24768
24777
 
24769
- if (renderedText !== text) {
24778
+ if (formatted) {
24770
24779
  const copyOriginalElement = document.createElement('input');
24771
24780
  copyOriginalElement.classList.add(styles$1.button);
24772
24781
  copyOriginalElement.type = 'button';
@@ -25118,6 +25127,23 @@
25118
25127
  }
25119
25128
  }
25120
25129
 
25130
+ function statsHtml (info) {
25131
+ const hidden = info == null;
25132
+ info ||= {};
25133
+ return `
25134
+ <table ${hidden ? 'style="visibility: hidden;"' : ''}>
25135
+ <tr><td>created:</td><td>${valueOrNullHtml(info.created_at)}</td></tr>
25136
+ <tr><td>updated:</td><td>${valueOrNullHtml(info.updated_at)}</td></tr>
25137
+ <tr><td>data size:</td><td>${valueOrNullHtml(info.data_size)}</td></tr>
25138
+ <tr><td>index size:</td><td>${valueOrNullHtml(info.index_size)}</td></tr>
25139
+ <tr><td>rows:</td><td>${valueOrNullHtml(info.rows)}</td></tr>
25140
+ <tr><td>row size:</td><td>${valueOrNullHtml(info.average_row_size)}</td></tr>
25141
+ <tr><td>encoding:</td><td>${valueOrNullHtml(info.encoding)}</td></tr>
25142
+ <tr><td>auto increment:</td><td>${valueOrNullHtml(info.auto_increment)}</td></tr>
25143
+ </table>
25144
+ `
25145
+ }
25146
+
25121
25147
  function selectStructureTab () {
25122
25148
  Array.prototype.forEach.call(document.getElementsByClassName('structure-element'), function (selected) {
25123
25149
  selected.style.display = 'flex';
@@ -25129,6 +25155,9 @@
25129
25155
 
25130
25156
  const schemasElement = document.getElementById('schemas');
25131
25157
  const tablesElement = document.getElementById('tables');
25158
+ const statsElement = document.getElementById('stats');
25159
+ statsElement.innerHTML = statsHtml(null);
25160
+
25132
25161
  const columnsElement = document.getElementById('columns');
25133
25162
  const indexesElement = document.getElementById('indexes');
25134
25163
 
@@ -25157,6 +25186,10 @@
25157
25186
  schemasElement.appendChild(optionElement);
25158
25187
  });
25159
25188
  schemasElement.addEventListener('change', function () {
25189
+ while (statsElement.firstChild) {
25190
+ statsElement.removeChild(statsElement.firstChild);
25191
+ }
25192
+ statsElement.innerHTML = statsHtml(null);
25160
25193
  while (tablesElement.firstChild) {
25161
25194
  tablesElement.removeChild(tablesElement.firstChild);
25162
25195
  }
@@ -25178,6 +25211,9 @@
25178
25211
  });
25179
25212
  }
25180
25213
  tablesElement.addEventListener('change', function () {
25214
+ while (statsElement.firstChild) {
25215
+ statsElement.removeChild(statsElement.firstChild);
25216
+ }
25181
25217
  while (columnsElement.firstChild) {
25182
25218
  columnsElement.removeChild(columnsElement.firstChild);
25183
25219
  }
@@ -25187,7 +25223,9 @@
25187
25223
  const schemaName = schemaNames.length === 1 ? schemaNames[0] : schemasElement.value;
25188
25224
  const tableName = tablesElement.value;
25189
25225
  const table = window.metadata.schemas[schemaName].tables[tableName];
25226
+ const info = table.info;
25190
25227
 
25228
+ statsElement.innerHTML = statsHtml(info);
25191
25229
  const columnEntries = Object.entries(table.columns);
25192
25230
  if (columnEntries.length > 0) {
25193
25231
  const columns = Object.keys(columnEntries[0][1]);
@@ -25275,7 +25313,7 @@
25275
25313
  viewUrl.searchParams.set('file', file.filename);
25276
25314
 
25277
25315
  const viewLinkElement = document.createElement('a');
25278
- viewLinkElement.classList.add('view-link');
25316
+ viewLinkElement.classList.add('link', 'view-link');
25279
25317
  viewLinkElement.innerText = 'view';
25280
25318
  viewLinkElement.href = viewUrl.pathname + viewUrl.search;
25281
25319
  addEventListener(viewLinkElement, 'click', (event) => {
@@ -25289,7 +25327,7 @@
25289
25327
  runUrl.searchParams.set('run', 'true');
25290
25328
 
25291
25329
  const runLinkElement = document.createElement('a');
25292
- runLinkElement.classList.add('run-link');
25330
+ runLinkElement.classList.add('link', 'run-link');
25293
25331
  runLinkElement.innerText = 'run';
25294
25332
  runLinkElement.href = runUrl.pathname + runUrl.search;
25295
25333
  addEventListener(runLinkElement, 'click', (event) => {
@@ -25300,25 +25338,31 @@
25300
25338
 
25301
25339
  const nameElement = document.createElement('h2');
25302
25340
  nameElement.innerText = file.filename;
25341
+ nameElement.classList.add('name');
25303
25342
 
25304
- const nameAndLinksElement = document.createElement('div');
25305
- nameAndLinksElement.classList.add('name-and-links');
25306
- nameAndLinksElement.appendChild(nameElement);
25307
- nameAndLinksElement.appendChild(viewLinkElement);
25308
- nameAndLinksElement.appendChild(runLinkElement);
25343
+ const linksElement = document.createElement('div');
25344
+ linksElement.classList.add('links');
25345
+ linksElement.appendChild(viewLinkElement);
25346
+ linksElement.appendChild(runLinkElement);
25309
25347
 
25310
25348
  const descriptionElement = document.createElement('p');
25311
25349
  descriptionElement.innerText = file.description;
25350
+ descriptionElement.classList.add('description');
25312
25351
 
25313
25352
  const divElement = document.createElement('div');
25314
25353
  divElement.classList.add('saved-list-item');
25315
- divElement.appendChild(nameAndLinksElement);
25354
+ divElement.appendChild(nameElement);
25355
+ divElement.appendChild(linksElement);
25316
25356
  divElement.appendChild(descriptionElement);
25317
25357
 
25318
25358
  savedElement.appendChild(divElement);
25319
25359
  });
25320
25360
  }
25321
25361
 
25362
+ function valueOrNullHtml (value) {
25363
+ return value == null ? '<span style="color: #888">null</span>' : value
25364
+ }
25365
+
25322
25366
  function submitAll (target, event) {
25323
25367
  submit(target, event);
25324
25368
  }
@@ -25651,6 +25695,9 @@
25651
25695
  return wrapperElement
25652
25696
  } else {
25653
25697
  cellElement.style.textAlign = columnType === 'string' ? 'left' : 'right';
25698
+ if (value === null) {
25699
+ cellElement.style.color = '#888';
25700
+ }
25654
25701
  return document.createTextNode(value)
25655
25702
  }
25656
25703
  };
@@ -25961,6 +26008,12 @@
25961
26008
  }
25962
26009
  });
25963
26010
 
26011
+ document.addEventListener('keydown', (event) => {
26012
+ if (event.code === 'Escape') {
26013
+ focus();
26014
+ }
26015
+ });
26016
+
25964
26017
  window.onload = function () {
25965
26018
  Promise.all([
25966
26019
  google.charts.load('current', { packages: ['corechart', 'line'] }),
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sqlui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.65
4
+ version: 0.1.67
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Dower
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-02-08 00:00:00.000000000 Z
11
+ date: 2023-02-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: airbrake