sqlui 0.1.66 → 0.1.67

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7b3cb90804665afcc2bfbefe7729bc1c80e62db6848770ad4537cfac031aad1f
4
- data.tar.gz: 6fed2c90062a097f9ece30edc28dfcf0e32b72322bf7a94ebe40cdeaa22ad9d7
3
+ metadata.gz: 7b91f535db1da5166bfebd7b9d9097db156b429aa283c6084e1f79bb348f56c4
4
+ data.tar.gz: bec7b344b15ee7730f88e77ad88910a8e316ac135aa9f66a4ea96aafecf763ac
5
5
  SHA512:
6
- metadata.gz: 65bcb424d283e50825d1352889f61e02d2439281e01af02a63c980df5ca94f433569b13f6d7ea69b906f1647d452313ac96a2ae509af29009fa43d35a3864d6e
7
- data.tar.gz: b3663fe92817d2cbb195f758bea14a744081dbd56c415cd83a449f9fca70d04b9ffe8900d7a28970a6262641b3daf56f33fd9730003f4e1adc845ab801de3242
6
+ metadata.gz: e470fbc8f9ea18c9de08eb05ce67a491f6a0b42a9e0895033566f97953da3e630963f431192411af6471dc678979e45e67aa845fbde3b07093abc4c95c119e3d
7
+ data.tar.gz: bd4030d8b1e3095873b887c36dad1303b63aa1e020851ce5fa3336b8b5f055b6b33b20b21db28a203350c3cc7746cd1dc422a221a08412903a39b8d0f6a79afa
data/.release-version CHANGED
@@ -1 +1 @@
1
- 0.1.66
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 {
@@ -25127,6 +25127,23 @@
25127
25127
  }
25128
25128
  }
25129
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
+
25130
25147
  function selectStructureTab () {
25131
25148
  Array.prototype.forEach.call(document.getElementsByClassName('structure-element'), function (selected) {
25132
25149
  selected.style.display = 'flex';
@@ -25138,6 +25155,9 @@
25138
25155
 
25139
25156
  const schemasElement = document.getElementById('schemas');
25140
25157
  const tablesElement = document.getElementById('tables');
25158
+ const statsElement = document.getElementById('stats');
25159
+ statsElement.innerHTML = statsHtml(null);
25160
+
25141
25161
  const columnsElement = document.getElementById('columns');
25142
25162
  const indexesElement = document.getElementById('indexes');
25143
25163
 
@@ -25166,6 +25186,10 @@
25166
25186
  schemasElement.appendChild(optionElement);
25167
25187
  });
25168
25188
  schemasElement.addEventListener('change', function () {
25189
+ while (statsElement.firstChild) {
25190
+ statsElement.removeChild(statsElement.firstChild);
25191
+ }
25192
+ statsElement.innerHTML = statsHtml(null);
25169
25193
  while (tablesElement.firstChild) {
25170
25194
  tablesElement.removeChild(tablesElement.firstChild);
25171
25195
  }
@@ -25187,6 +25211,9 @@
25187
25211
  });
25188
25212
  }
25189
25213
  tablesElement.addEventListener('change', function () {
25214
+ while (statsElement.firstChild) {
25215
+ statsElement.removeChild(statsElement.firstChild);
25216
+ }
25190
25217
  while (columnsElement.firstChild) {
25191
25218
  columnsElement.removeChild(columnsElement.firstChild);
25192
25219
  }
@@ -25196,7 +25223,9 @@
25196
25223
  const schemaName = schemaNames.length === 1 ? schemaNames[0] : schemasElement.value;
25197
25224
  const tableName = tablesElement.value;
25198
25225
  const table = window.metadata.schemas[schemaName].tables[tableName];
25226
+ const info = table.info;
25199
25227
 
25228
+ statsElement.innerHTML = statsHtml(info);
25200
25229
  const columnEntries = Object.entries(table.columns);
25201
25230
  if (columnEntries.length > 0) {
25202
25231
  const columns = Object.keys(columnEntries[0][1]);
@@ -25284,7 +25313,7 @@
25284
25313
  viewUrl.searchParams.set('file', file.filename);
25285
25314
 
25286
25315
  const viewLinkElement = document.createElement('a');
25287
- viewLinkElement.classList.add('view-link');
25316
+ viewLinkElement.classList.add('link', 'view-link');
25288
25317
  viewLinkElement.innerText = 'view';
25289
25318
  viewLinkElement.href = viewUrl.pathname + viewUrl.search;
25290
25319
  addEventListener(viewLinkElement, 'click', (event) => {
@@ -25298,7 +25327,7 @@
25298
25327
  runUrl.searchParams.set('run', 'true');
25299
25328
 
25300
25329
  const runLinkElement = document.createElement('a');
25301
- runLinkElement.classList.add('run-link');
25330
+ runLinkElement.classList.add('link', 'run-link');
25302
25331
  runLinkElement.innerText = 'run';
25303
25332
  runLinkElement.href = runUrl.pathname + runUrl.search;
25304
25333
  addEventListener(runLinkElement, 'click', (event) => {
@@ -25309,25 +25338,31 @@
25309
25338
 
25310
25339
  const nameElement = document.createElement('h2');
25311
25340
  nameElement.innerText = file.filename;
25341
+ nameElement.classList.add('name');
25312
25342
 
25313
- const nameAndLinksElement = document.createElement('div');
25314
- nameAndLinksElement.classList.add('name-and-links');
25315
- nameAndLinksElement.appendChild(nameElement);
25316
- nameAndLinksElement.appendChild(viewLinkElement);
25317
- nameAndLinksElement.appendChild(runLinkElement);
25343
+ const linksElement = document.createElement('div');
25344
+ linksElement.classList.add('links');
25345
+ linksElement.appendChild(viewLinkElement);
25346
+ linksElement.appendChild(runLinkElement);
25318
25347
 
25319
25348
  const descriptionElement = document.createElement('p');
25320
25349
  descriptionElement.innerText = file.description;
25350
+ descriptionElement.classList.add('description');
25321
25351
 
25322
25352
  const divElement = document.createElement('div');
25323
25353
  divElement.classList.add('saved-list-item');
25324
- divElement.appendChild(nameAndLinksElement);
25354
+ divElement.appendChild(nameElement);
25355
+ divElement.appendChild(linksElement);
25325
25356
  divElement.appendChild(descriptionElement);
25326
25357
 
25327
25358
  savedElement.appendChild(divElement);
25328
25359
  });
25329
25360
  }
25330
25361
 
25362
+ function valueOrNullHtml (value) {
25363
+ return value == null ? '<span style="color: #888">null</span>' : value
25364
+ }
25365
+
25331
25366
  function submitAll (target, event) {
25332
25367
  submit(target, event);
25333
25368
  }
@@ -25973,6 +26008,12 @@
25973
26008
  }
25974
26009
  });
25975
26010
 
26011
+ document.addEventListener('keydown', (event) => {
26012
+ if (event.code === 'Escape') {
26013
+ focus();
26014
+ }
26015
+ });
26016
+
25976
26017
  window.onload = function () {
25977
26018
  Promise.all([
25978
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.66
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-09 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