sqlui 0.1.66 → 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: 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