sqlite-hub 0.9.0 → 0.9.3

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.
@@ -100,8 +100,11 @@ function normalizeMediaTaggingConfigRecord(config = {}) {
100
100
  };
101
101
  }
102
102
 
103
- function isChartCompatibleQueryType(queryType) {
104
- return String(queryType ?? "").trim().toLowerCase() === "select";
103
+ function isChartCompatibleQuery(queryType, rawSql = "") {
104
+ return (
105
+ String(queryType ?? "").trim().toLowerCase() === "select" ||
106
+ detectQueryType(rawSql) === "select"
107
+ );
105
108
  }
106
109
 
107
110
  class AppStateStore {
@@ -752,7 +755,7 @@ class AppStateStore {
752
755
  previewSql: buildSqlPreview(row.raw_sql ?? row.rawSql),
753
756
  lastRun,
754
757
  chartsEligible:
755
- isChartCompatibleQueryType(queryType) &&
758
+ isChartCompatibleQuery(queryType, row.raw_sql ?? row.rawSql) &&
756
759
  (!lastRun || String(lastRun.status ?? "").trim().toLowerCase() !== "error"),
757
760
  };
758
761
  }
@@ -1252,12 +1255,12 @@ class AppStateStore {
1252
1255
  LIMIT 1
1253
1256
  )
1254
1257
  WHERE q.database_key = ?
1255
- AND q.query_type = 'select'
1256
1258
  AND COALESCE(latest.status, 'success') != 'error'
1257
1259
  ORDER BY q.last_used_at DESC, q.id DESC
1258
1260
  `)
1259
1261
  .all(normalizedDatabaseKey)
1260
- .map((row) => this.decorateQueryHistoryRow(row));
1262
+ .map((row) => this.decorateQueryHistoryRow(row))
1263
+ .filter((item) => item.chartsEligible);
1261
1264
  }
1262
1265
 
1263
1266
  getQueryHistoryItemById(historyId) {
@@ -1367,7 +1370,7 @@ class AppStateStore {
1367
1370
  getChartQueryHistoryItemForDatabase(historyId, databaseKey) {
1368
1371
  const item = this.getQueryHistoryItemForDatabase(historyId, databaseKey);
1369
1372
 
1370
- if (!isChartCompatibleQueryType(item.queryType)) {
1373
+ if (!isChartCompatibleQuery(item.queryType, item.rawSql)) {
1371
1374
  throw new ValidationError("Only SELECT queries can be opened in Charts.");
1372
1375
  }
1373
1376
 
@@ -53,7 +53,23 @@ function normalizeRequiredColumn(value, label) {
53
53
  }
54
54
 
55
55
  function normalizeSortDirection(value, fallback = "asc") {
56
- return String(value ?? "").trim().toLowerCase() === "desc" ? "desc" : fallback;
56
+ const normalized = String(value ?? "").trim().toLowerCase();
57
+
58
+ if (normalized === "asc" || normalized === "desc") {
59
+ return normalized;
60
+ }
61
+
62
+ return fallback;
63
+ }
64
+
65
+ function normalizeBarSortBy(value, fallback = "x") {
66
+ const normalized = String(value ?? "").trim().toLowerCase();
67
+
68
+ if (normalized === "x" || normalized === "y") {
69
+ return normalized;
70
+ }
71
+
72
+ return fallback;
57
73
  }
58
74
 
59
75
  function normalizeChartConfig(chartType, config = {}) {
@@ -67,7 +83,8 @@ function normalizeChartConfig(chartType, config = {}) {
67
83
  y_column: normalizeRequiredColumn(source.y_column, "Bar chart y column"),
68
84
  show_legend: normalizeBooleanFlag(source.show_legend, true),
69
85
  show_labels: normalizeBooleanFlag(source.show_labels, false),
70
- sort_direction: normalizeSortDirection(source.sort_direction, "asc"),
86
+ sort_by: normalizeBarSortBy(source.sort_by, "y"),
87
+ sort_direction: normalizeSortDirection(source.sort_direction, "desc"),
71
88
  };
72
89
  case "line":
73
90
  return {
@@ -37,11 +37,174 @@ function getLeadingKeywords(sql = "", limit = 3) {
37
37
  return matches.slice(0, limit).map((token) => token.toLowerCase());
38
38
  }
39
39
 
40
+ function readWordAt(value = "", index = 0) {
41
+ const match = String(value).slice(index).match(/^[A-Za-z]+/);
42
+ return match ? match[0].toLowerCase() : "";
43
+ }
44
+
45
+ function skipWhitespace(value = "", index = 0) {
46
+ let cursor = index;
47
+
48
+ while (cursor < value.length && /\s/.test(value[cursor])) {
49
+ cursor += 1;
50
+ }
51
+
52
+ return cursor;
53
+ }
54
+
55
+ function advanceQuotedSql(value = "", index = 0) {
56
+ const quote = value[index];
57
+ const closingQuote = quote === "[" ? "]" : quote;
58
+ let cursor = index + 1;
59
+
60
+ while (cursor < value.length) {
61
+ if (value[cursor] === closingQuote) {
62
+ if ((quote === "'" || quote === '"') && value[cursor + 1] === closingQuote) {
63
+ cursor += 2;
64
+ continue;
65
+ }
66
+
67
+ return cursor + 1;
68
+ }
69
+
70
+ cursor += 1;
71
+ }
72
+
73
+ return value.length;
74
+ }
75
+
76
+ function isSqlWordBoundary(value = "", index = 0) {
77
+ return index < 0 || index >= value.length || !/[A-Za-z0-9_]/.test(value[index]);
78
+ }
79
+
80
+ function findTopLevelWord(value = "", word = "", startIndex = 0) {
81
+ const normalizedWord = String(word).toLowerCase();
82
+ let cursor = startIndex;
83
+ let depth = 0;
84
+
85
+ while (cursor < value.length) {
86
+ const char = value[cursor];
87
+
88
+ if (char === "'" || char === '"' || char === "`" || char === "[") {
89
+ cursor = advanceQuotedSql(value, cursor);
90
+ continue;
91
+ }
92
+
93
+ if (char === "(") {
94
+ depth += 1;
95
+ cursor += 1;
96
+ continue;
97
+ }
98
+
99
+ if (char === ")") {
100
+ depth = Math.max(0, depth - 1);
101
+ cursor += 1;
102
+ continue;
103
+ }
104
+
105
+ if (
106
+ depth === 0 &&
107
+ value.slice(cursor, cursor + normalizedWord.length).toLowerCase() === normalizedWord &&
108
+ isSqlWordBoundary(value, cursor - 1) &&
109
+ isSqlWordBoundary(value, cursor + normalizedWord.length)
110
+ ) {
111
+ return cursor;
112
+ }
113
+
114
+ cursor += 1;
115
+ }
116
+
117
+ return -1;
118
+ }
119
+
120
+ function findMatchingParen(value = "", openIndex = 0) {
121
+ let cursor = openIndex;
122
+ let depth = 0;
123
+
124
+ while (cursor < value.length) {
125
+ const char = value[cursor];
126
+
127
+ if (char === "'" || char === '"' || char === "`" || char === "[") {
128
+ cursor = advanceQuotedSql(value, cursor);
129
+ continue;
130
+ }
131
+
132
+ if (char === "(") {
133
+ depth += 1;
134
+ } else if (char === ")") {
135
+ depth -= 1;
136
+
137
+ if (depth === 0) {
138
+ return cursor;
139
+ }
140
+ }
141
+
142
+ cursor += 1;
143
+ }
144
+
145
+ return -1;
146
+ }
147
+
148
+ function resolveCteStatementKeyword(sql = "") {
149
+ const stripped = stripBlockComments(stripLineComments(sql)).trim();
150
+ let cursor = skipWhitespace(stripped, 0);
151
+
152
+ if (readWordAt(stripped, cursor) !== "with") {
153
+ return "";
154
+ }
155
+
156
+ cursor += 4;
157
+ cursor = skipWhitespace(stripped, cursor);
158
+
159
+ if (readWordAt(stripped, cursor) === "recursive") {
160
+ cursor += "recursive".length;
161
+ }
162
+
163
+ while (cursor < stripped.length) {
164
+ const asIndex = findTopLevelWord(stripped, "as", cursor);
165
+
166
+ if (asIndex < 0) {
167
+ return "with";
168
+ }
169
+
170
+ cursor = skipWhitespace(stripped, asIndex + 2);
171
+
172
+ if (readWordAt(stripped, cursor) === "not") {
173
+ cursor = skipWhitespace(stripped, cursor + 3);
174
+ }
175
+
176
+ if (readWordAt(stripped, cursor) === "materialized") {
177
+ cursor = skipWhitespace(stripped, cursor + "materialized".length);
178
+ }
179
+
180
+ if (stripped[cursor] !== "(") {
181
+ return "with";
182
+ }
183
+
184
+ const closeIndex = findMatchingParen(stripped, cursor);
185
+
186
+ if (closeIndex < 0) {
187
+ return "with";
188
+ }
189
+
190
+ cursor = skipWhitespace(stripped, closeIndex + 1);
191
+
192
+ if (stripped[cursor] === ",") {
193
+ cursor = skipWhitespace(stripped, cursor + 1);
194
+ continue;
195
+ }
196
+
197
+ return readWordAt(stripped, cursor) || "with";
198
+ }
199
+
200
+ return "with";
201
+ }
202
+
40
203
  function resolveEffectiveKeyword(sql = "") {
41
- const [firstKeyword, secondKeyword, thirdKeyword] = getLeadingKeywords(sql, 3);
204
+ const [firstKeyword, secondKeyword] = getLeadingKeywords(sql, 3);
42
205
 
43
206
  if (firstKeyword === "with") {
44
- return secondKeyword === "recursive" ? thirdKeyword ?? "with" : secondKeyword ?? "with";
207
+ return resolveCteStatementKeyword(sql);
45
208
  }
46
209
 
47
210
  if (firstKeyword === "explain") {
package/shortkeys.md ADDED
@@ -0,0 +1,5 @@
1
+ # Meddia Tagging
2
+
3
+ ## tagging queue
4
+
5
+ `shift + enter` triggers tagged & next