laketower 0.6.0__py3-none-any.whl → 0.6.2__py3-none-any.whl

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.

Potentially problematic release.


This version of laketower might be problematic. Click here for more details.

@@ -0,0 +1,282 @@
1
+
2
+ /*!
3
+ * ----------------------------------------------------------------------------
4
+ * Halfmoon CSS - Modern theme
5
+ * Copyright (c) 2023, Tahmid Khan | MIT License | https://www.gethalfmoon.com
6
+ * ----------------------------------------------------------------------------
7
+ * The above notice must be included in its entirety when this file is used.
8
+ */
9
+
10
+ /* Color palette */
11
+
12
+ [data-bs-core=modern] {
13
+ /* Gray */
14
+
15
+ --bs-slate-hue: 216;
16
+ --bs-slate-saturation: 20%;
17
+
18
+ /* Light gray */
19
+
20
+ --bs-lightgray-hue: var(--bs-slate-hue);
21
+ --bs-lightgray-saturation: var(--bs-slate-saturation);
22
+
23
+ /* Sable (almost black) */
24
+
25
+ --bs-sable-hue: var(--bs-darkgray-hue);
26
+ --bs-sable-saturation: var(--bs-darkgray-saturation);
27
+ --bs-sable-100-hsl: var(--bs-sable-hue), var(--bs-sable-saturation), 31%;
28
+ --bs-sable-200-hsl: var(--bs-sable-hue), var(--bs-sable-saturation), 29%;
29
+ --bs-sable-300-hsl: var(--bs-sable-hue), var(--bs-sable-saturation), 27%;
30
+ --bs-sable-400-hsl: var(--bs-sable-hue), var(--bs-sable-saturation), 25%;
31
+ --bs-sable-500-hsl: var(--bs-sable-hue), var(--bs-sable-saturation), 23%;
32
+ --bs-sable-600-hsl: var(--bs-sable-hue), var(--bs-sable-saturation), 21%;
33
+ --bs-sable-700-hsl: var(--bs-sable-hue), var(--bs-sable-saturation), 19%;
34
+ --bs-sable-800-hsl: var(--bs-sable-hue), var(--bs-sable-saturation), 17%;
35
+ --bs-sable-900-hsl: var(--bs-sable-hue), var(--bs-sable-saturation), 15%;
36
+ --bs-sable-100: hsl(var(--bs-sable-100-hsl));
37
+ --bs-sable-200: hsl(var(--bs-sable-200-hsl));
38
+ --bs-sable-300: hsl(var(--bs-sable-300-hsl));
39
+ --bs-sable-400: hsl(var(--bs-sable-400-hsl));
40
+ --bs-sable-500: hsl(var(--bs-sable-500-hsl));
41
+ --bs-sable-600: hsl(var(--bs-sable-600-hsl));
42
+ --bs-sable-700: hsl(var(--bs-sable-700-hsl));
43
+ --bs-sable-800: hsl(var(--bs-sable-800-hsl));
44
+ --bs-sable-900: hsl(var(--bs-sable-900-hsl));
45
+ --bs-sable-hsl: var(--bs-sable-500-hsl);
46
+ --bs-sable: hsl(var(--bs-sable-hsl));
47
+ --bs-sable-foreground-hsl: var(--bs-white-hsl);
48
+ --bs-sable-foreground: hsl(var(--bs-sable-foreground-hsl));
49
+ --bs-sable-text-emphasis-hsl: var(--bs-sable-600-hsl);
50
+ --bs-sable-text-emphasis: hsl(var(--bs-sable-text-emphasis-hsl));
51
+ --bs-sable-hover-bg: var(--bs-sable-600);
52
+ --bs-sable-active-bg: var(--bs-sable-700);
53
+ --bs-sable-bg-subtle: hsl(var(--bs-sable-hue), var(--bs-sable-saturation), 70%);
54
+ --bs-sable-border-subtle: var(--bs-sable-400);
55
+ --bs-sable-checkbox-svg: var(--bs-checkbox-svg-light);
56
+ --bs-sable-dash-svg: var(--bs-dash-svg-light);
57
+ --bs-sable-radio-svg: var(--bs-radio-svg-light);
58
+ --bs-sable-switch-svg: var(--bs-switch-svg-light);
59
+
60
+ /* Primary */
61
+
62
+ --bs-primary-hue: var(--bs-navy-hue);
63
+ --bs-primary-saturation: var(--bs-navy-saturation);
64
+ --bs-primary-100-hsl: var(--bs-navy-100-hsl);
65
+ --bs-primary-200-hsl: var(--bs-navy-200-hsl);
66
+ --bs-primary-300-hsl: var(--bs-navy-300-hsl);
67
+ --bs-primary-400-hsl: var(--bs-navy-400-hsl);
68
+ --bs-primary-500-hsl: var(--bs-navy-500-hsl);
69
+ --bs-primary-600-hsl: var(--bs-navy-600-hsl);
70
+ --bs-primary-700-hsl: var(--bs-navy-700-hsl);
71
+ --bs-primary-800-hsl: var(--bs-navy-800-hsl);
72
+ --bs-primary-900-hsl: var(--bs-navy-900-hsl);
73
+ --bs-primary-100: var(--bs-navy-100);
74
+ --bs-primary-200: var(--bs-navy-200);
75
+ --bs-primary-300: var(--bs-navy-300);
76
+ --bs-primary-400: var(--bs-navy-400);
77
+ --bs-primary-500: var(--bs-navy-500);
78
+ --bs-primary-600: var(--bs-navy-600);
79
+ --bs-primary-700: var(--bs-navy-700);
80
+ --bs-primary-800: var(--bs-navy-800);
81
+ --bs-primary-900: var(--bs-navy-900);
82
+ --bs-primary-hsl: var(--bs-navy-hsl);
83
+ --bs-primary: var(--bs-navy);
84
+ --bs-primary-foreground-hsl: var(--bs-navy-foreground-hsl);
85
+ --bs-primary-foreground: var(--bs-navy-foreground);
86
+ --bs-primary-text-emphasis-hsl: var(--bs-navy-text-emphasis-hsl);
87
+ --bs-primary-text-emphasis: var(--bs-navy-text-emphasis);
88
+ --bs-primary-hover-bg: var(--bs-navy-hover-bg);
89
+ --bs-primary-active-bg: var(--bs-navy-active-bg);
90
+ --bs-primary-bg-subtle: var(--bs-navy-bg-subtle);
91
+ --bs-primary-border-subtle: var(--bs-navy-border-subtle);
92
+ --bs-primary-checkbox-svg: var(--bs-navy-checkbox-svg);
93
+ --bs-primary-dash-svg: var(--bs-navy-dash-svg);
94
+ --bs-primary-radio-svg: var(--bs-navy-radio-svg);
95
+ --bs-primary-switch-svg: var(--bs-navy-switch-svg);
96
+
97
+ /* Info */
98
+
99
+ --bs-info-hue: var(--bs-blue-hue);
100
+ --bs-info-saturation: var(--bs-blue-saturation);
101
+ --bs-info-100-hsl: var(--bs-blue-100-hsl);
102
+ --bs-info-200-hsl: var(--bs-blue-200-hsl);
103
+ --bs-info-300-hsl: var(--bs-blue-300-hsl);
104
+ --bs-info-400-hsl: var(--bs-blue-400-hsl);
105
+ --bs-info-500-hsl: var(--bs-blue-500-hsl);
106
+ --bs-info-600-hsl: var(--bs-blue-600-hsl);
107
+ --bs-info-700-hsl: var(--bs-blue-700-hsl);
108
+ --bs-info-800-hsl: var(--bs-blue-800-hsl);
109
+ --bs-info-900-hsl: var(--bs-blue-900-hsl);
110
+ --bs-info-100: var(--bs-blue-100);
111
+ --bs-info-200: var(--bs-blue-200);
112
+ --bs-info-300: var(--bs-blue-300);
113
+ --bs-info-400: var(--bs-blue-400);
114
+ --bs-info-500: var(--bs-blue-500);
115
+ --bs-info-600: var(--bs-blue-600);
116
+ --bs-info-700: var(--bs-blue-700);
117
+ --bs-info-800: var(--bs-blue-800);
118
+ --bs-info-900: var(--bs-blue-900);
119
+ --bs-info-hsl: var(--bs-blue-hsl);
120
+ --bs-info: var(--bs-blue);
121
+ --bs-info-foreground-hsl: var(--bs-blue-foreground-hsl);
122
+ --bs-info-foreground: var(--bs-blue-foreground);
123
+ --bs-info-text-emphasis-hsl: var(--bs-blue-text-emphasis-hsl);
124
+ --bs-info-text-emphasis: var(--bs-blue-text-emphasis);
125
+ --bs-info-hover-bg: var(--bs-blue-hover-bg);
126
+ --bs-info-active-bg: var(--bs-blue-active-bg);
127
+ --bs-info-bg-subtle: var(--bs-blue-bg-subtle);
128
+ --bs-info-border-subtle: var(--bs-blue-border-subtle);
129
+ --bs-info-checkbox-svg: var(--bs-blue-checkbox-svg);
130
+ --bs-info-dash-svg: var(--bs-blue-dash-svg);
131
+ --bs-info-radio-svg: var(--bs-blue-radio-svg);
132
+ --bs-info-switch-svg: var(--bs-blue-switch-svg);
133
+ }
134
+
135
+ [data-bs-core=modern][data-bs-theme=dark] {
136
+ /* Dark gray */
137
+
138
+ --bs-darkgray-text-emphasis-hsl: var(--bs-darkgray-200-hsl);
139
+ --bs-darkgray-text-emphasis: hsl(var(--bs-darkgray-text-emphasis-hsl));
140
+
141
+ /* Sable (black) */
142
+
143
+ --bs-sable-text-emphasis-hsl: var(--bs-sable-400-hsl);
144
+ --bs-sable-text-emphasis: hsl(var(--bs-sable-text-emphasis-hsl));
145
+ --bs-sable-bg-subtle: hsl(var(--bs-sable-hue), var(--bs-sable-saturation), 14%);
146
+ --bs-sable-border-subtle: var(--bs-sable-600);
147
+
148
+ /* Blue */
149
+
150
+ --bs-blue-text-emphasis-hsl: var(--bs-blue-300-hsl);
151
+ --bs-blue-text-emphasis: hsl(var(--bs-blue-text-emphasis-hsl));
152
+
153
+ /* Primary */
154
+
155
+ --bs-primary-hue: var(--bs-sky-hue);
156
+ --bs-primary-saturation: var(--bs-sky-saturation);
157
+ --bs-primary-100-hsl: var(--bs-sky-100-hsl);
158
+ --bs-primary-200-hsl: var(--bs-sky-200-hsl);
159
+ --bs-primary-300-hsl: var(--bs-sky-300-hsl);
160
+ --bs-primary-400-hsl: var(--bs-sky-400-hsl);
161
+ --bs-primary-500-hsl: var(--bs-sky-500-hsl);
162
+ --bs-primary-600-hsl: var(--bs-sky-600-hsl);
163
+ --bs-primary-700-hsl: var(--bs-sky-700-hsl);
164
+ --bs-primary-800-hsl: var(--bs-sky-800-hsl);
165
+ --bs-primary-900-hsl: var(--bs-sky-900-hsl);
166
+ --bs-primary-100: var(--bs-sky-100);
167
+ --bs-primary-200: var(--bs-sky-200);
168
+ --bs-primary-300: var(--bs-sky-300);
169
+ --bs-primary-400: var(--bs-sky-400);
170
+ --bs-primary-500: var(--bs-sky-500);
171
+ --bs-primary-600: var(--bs-sky-600);
172
+ --bs-primary-700: var(--bs-sky-700);
173
+ --bs-primary-800: var(--bs-sky-800);
174
+ --bs-primary-900: var(--bs-sky-900);
175
+ --bs-primary-hsl: var(--bs-sky-hsl);
176
+ --bs-primary: var(--bs-sky);
177
+ --bs-primary-foreground-hsl: var(--bs-sky-foreground-hsl);
178
+ --bs-primary-foreground: var(--bs-sky-foreground);
179
+ --bs-primary-text-emphasis-hsl: var(--bs-sky-text-emphasis-hsl);
180
+ --bs-primary-text-emphasis: var(--bs-sky-text-emphasis);
181
+ --bs-primary-hover-bg: var(--bs-sky-hover-bg);
182
+ --bs-primary-active-bg: var(--bs-sky-active-bg);
183
+ --bs-primary-bg-subtle: var(--bs-sky-bg-subtle);
184
+ --bs-primary-border-subtle: var(--bs-sky-border-subtle);
185
+ --bs-primary-checkbox-svg: var(--bs-sky-checkbox-svg);
186
+ --bs-primary-dash-svg: var(--bs-sky-dash-svg);
187
+ --bs-primary-radio-svg: var(--bs-sky-radio-svg);
188
+ --bs-primary-switch-svg: var(--bs-sky-switch-svg);
189
+
190
+ /* Info */
191
+
192
+ --bs-info-text-emphasis-hsl: var(--bs-blue-text-emphasis-hsl);
193
+ --bs-info-text-emphasis: var(--bs-blue-text-emphasis);
194
+ --bs-info-bg-subtle: var(--bs-blue-bg-subtle);
195
+ --bs-info-border-subtle: var(--bs-blue-border-subtle);
196
+ }
197
+
198
+ /* Variables */
199
+
200
+ [data-bs-core=modern] {
201
+ /* Link */
202
+
203
+ --bs-link-color-hsl: var(--bs-info-text-emphasis-hsl);
204
+ --bs-link-hover-color-hsl: var(--bs-info-hsl);
205
+
206
+ /* Content (used as needed in cards, panels, menus, etc.) */
207
+
208
+ --bs-content-bg-hsl: var(--bs-body-bg-hsl);
209
+ --bs-content-border-color: var(--bs-border-color);
210
+
211
+ /* Form */
212
+
213
+ --bs-form-focus-border-color: var(--bs-info-border-subtle);
214
+ --bs-form-focus-shadow-hsl: var(--bs-info-hsl);
215
+ --bs-form-check-focus-border-color: var(--bs-info-border-subtle);
216
+ }
217
+
218
+ [data-bs-core=modern]:not([data-bs-theme=dark]) {
219
+ /* Background */
220
+
221
+ --bs-body-bg-hsl: var(--bs-white-hsl);
222
+ --bs-secondary-bg-hsl: var(--bs-lightgray-hue), var(--bs-lightgray-saturation), 98.75%;
223
+ --bs-tertiary-bg-hsl: var(--bs-lightgray-hue), var(--bs-lightgray-saturation), 97.5%;
224
+
225
+ /* Border */
226
+
227
+ --bs-border-color: var(--bs-lightgray-700);
228
+ --bs-border-color-light: var(--bs-lightgray-500);
229
+ }
230
+
231
+ [data-bs-core=modern][data-bs-theme=dark] {
232
+ /* Background */
233
+
234
+ --bs-body-bg-hsl: var(--bs-sable-900-hsl);
235
+ --bs-secondary-bg-hsl: var(--bs-sable-800-hsl);
236
+ --bs-tertiary-bg-hsl: var(--bs-sable-700-hsl);
237
+
238
+ /* Border */
239
+
240
+ --bs-border-color: var(--bs-gray-900);
241
+
242
+ /* Content (used as needed in cards, panels, menus, etc.) */
243
+
244
+ --bs-content-floating-bg-hsl: var(--bs-sable-hue), var(--bs-sable-saturation), 16.5%;
245
+
246
+ /* Action (used as needed in buttons, inputs, menu items, page links, etc.) */
247
+
248
+ --bs-action-border-color: var(--bs-border-color);
249
+
250
+ /* Contextual buttons */
251
+
252
+ --bs-ctx-btn-border-color: transparent;
253
+ --bs-ctx-btn-bg-clip: border-box;
254
+
255
+ /* Action bar (used as needed in range, progress, etc.) */
256
+
257
+ --bs-actionbar-border-color: hsla(var(--bs-white-hsl), 0.075);
258
+ --bs-progresstrack-border-width: 0;
259
+ --bs-progresstrack-box-shadow: inset 0 0 0 var(--bs-border-width) var(--bs-actionbar-border-color);
260
+ --bs-progresstrack-bg-clip: border-box;
261
+ }
262
+
263
+ /* Sidebar */
264
+
265
+ [data-bs-core=modern] .sidebar {
266
+ --bs-sidebar-item-padding-x: 1rem;
267
+ --bs-sidebar-item-padding-y: 0.25rem;
268
+ --bs-sidebar-header-font-weight: var(--bs-font-weight-bold);
269
+ --bs-sidebar-divider-bg: var(--bs-sidebar-bg);
270
+ }
271
+
272
+ [data-bs-core=modern] .sidebar-nav .nav-link {
273
+ border-left: var(--bs-border-width) solid var(--bs-border-color-light);
274
+ }
275
+
276
+ [data-bs-core=modern] .sidebar-nav .nav-link.active,
277
+ [data-bs-core=modern] .sidebar-nav .nav-link.show {
278
+ font-weight: var(--bs-font-weight-bold);
279
+ border-color: currentColor;
280
+ -webkit-font-smoothing: antialiased;
281
+ -moz-osx-font-smoothing: grayscale;
282
+ }
laketower/tables.py CHANGED
@@ -229,6 +229,17 @@ def load_datasets(table_configs: list[ConfigTable]) -> dict[str, padataset.Datas
229
229
  return tables_dataset
230
230
 
231
231
 
232
+ def extract_query_parameter_names(sql: str) -> set[str]:
233
+ parsed_sql = sqlglot.parse(sql, dialect=sqlglot.dialects.duckdb.DuckDB)
234
+ return {
235
+ str(node.this)
236
+ for statement in parsed_sql
237
+ if statement is not None
238
+ for node in statement.walk()
239
+ if isinstance(node, sqlglot.expressions.Placeholder)
240
+ }
241
+
242
+
232
243
  def generate_table_query(
233
244
  table_name: str,
234
245
  limit: int | None = None,
@@ -260,8 +271,13 @@ def generate_table_statistics_query(table_name: str) -> str:
260
271
 
261
272
 
262
273
  def execute_query(
263
- tables_datasets: dict[str, padataset.Dataset], sql_query: str
274
+ tables_datasets: dict[str, padataset.Dataset],
275
+ sql_query: str,
276
+ sql_params: dict[str, str] = {},
264
277
  ) -> pd.DataFrame:
278
+ if not sql_query:
279
+ raise ValueError("Error: Cannot execute empty SQL query")
280
+
265
281
  try:
266
282
  conn = duckdb.connect()
267
283
  for table_name, table_dataset in tables_datasets.items():
@@ -272,8 +288,8 @@ def execute_query(
272
288
 
273
289
  view_name = f"{table_name}_view"
274
290
  conn.register(view_name, table_dataset)
275
- conn.execute(f'create table "{table_name}" as select * from "{view_name}"') # nosec B608
276
- return conn.execute(sql_query).df()
291
+ conn.execute(f'create view "{table_name}" as select * from "{view_name}"') # nosec B608
292
+ return conn.execute(sql_query, parameters=sql_params).df()
277
293
  except duckdb.Error as e:
278
294
  raise ValueError(str(e)) from e
279
295
 
@@ -4,12 +4,10 @@
4
4
  <head>
5
5
  <meta charset="utf-8">
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1">
7
- <title>Laketower</title>
8
- <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
9
- <link href="https://cdn.jsdelivr.net/npm/halfmoon@2.0.2/css/halfmoon.min.css" rel="stylesheet"
10
- integrity="sha256-RjeFzczeuZHCyS+Gvz+kleETzBF/o84ZRHukze/yv6o=" crossorigin="anonymous">
11
- <link href="https://cdn.jsdelivr.net/npm/halfmoon@2.0.2/css/cores/halfmoon.modern.css" rel="stylesheet"
12
- integrity="sha256-DD6elX+jPmbFYPsGvzodUv2+9FHkxHlVtQi0/RJVULs=" crossorigin="anonymous">
7
+ <title>{{ app_metadata.app_name }}</title>
8
+ <link href="{{ url_for('static', path='/vendor/bootstrap-icons/bootstrap-icons.min.css') }}" rel="stylesheet">
9
+ <link href="{{ url_for('static', path='/vendor/halfmoon/halfmoon.min.css') }}" rel="stylesheet">
10
+ <link href="{{ url_for('static', path='/vendor/halfmoon/halfmoon.modern.css') }}" rel="stylesheet">
13
11
  </head>
14
12
 
15
13
  <body class="ps-md-sbwidth">
@@ -17,7 +15,7 @@
17
15
  <nav class="sidebar offcanvas-start offcanvas-md" tabindex="-1" id="sidebar">
18
16
  <div class="offcanvas-header border-bottom">
19
17
  <a class="sidebar-brand" href="/">
20
- Laketower
18
+ {{ app_metadata.app_name }}
21
19
  </a>
22
20
  <button
23
21
  type="button"
@@ -28,8 +26,8 @@
28
26
  >
29
27
  </button>
30
28
  </div>
31
- <div class="offcanvas-body">
32
- <ul class="sidebar-nav">
29
+ <div class="offcanvas-body d-flex flex-column">
30
+ <ul class="sidebar-nav flex-grow-1 overflow-auto">
33
31
  <li>
34
32
  <h6 class="sidebar-header">Tables</h6>
35
33
  </li>
@@ -68,6 +66,17 @@
68
66
  </li>
69
67
  {% endfor %}
70
68
  </ul>
69
+
70
+ <div class="mt-auto pt-3 border-top flex-shrink-0">
71
+ <small class="text-muted px-3 d-block">
72
+ <a href="https://github.com/datalpia/laketower/releases/tag/{{ app_metadata.app_version }}"
73
+ target="_blank"
74
+ class="text-muted text-decoration-none">
75
+ v{{ app_metadata.app_version }}
76
+ <i class="bi-box-arrow-up-right ms-1" style="font-size: 0.7em;"></i>
77
+ </a>
78
+ </small>
79
+ </div>
71
80
  </div>
72
81
  </nav>
73
82
 
@@ -78,8 +87,8 @@
78
87
  </button>
79
88
 
80
89
  <a href="/" class="navbar-brand d-flex align-items-center d-md-none ms-auto ms-md-0">
81
- Laketower
82
- </a>
90
+ {{ app_metadata.app_name }}
91
+ </a>
83
92
  </div>
84
93
  </nav>
85
94
  </header>
@@ -90,14 +99,8 @@
90
99
  </div>
91
100
  </main>
92
101
 
93
- <footer></footer>
94
-
95
- <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js"
96
- integrity="sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r"
97
- crossorigin="anonymous"></script>
98
- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.min.js"
99
- integrity="sha384-0pUGZvbkm6XF6gxjEnlmuGrJXVbNuzT9qBBavbLwCsOGabYfZo0T0to5eqruptLy"
100
- crossorigin="anonymous"></script>
102
+ <script src="{{ url_for('static', path='/vendor/bootstrap/bootstrap.bundle.min.js') }}"></script>
103
+ {% block extra_scripts %}{% endblock %}
101
104
  </body>
102
105
 
103
106
  </html>
@@ -5,16 +5,30 @@
5
5
  <div class="col">
6
6
  <h2 class="mb-3">{{ query.title }}</h2>
7
7
 
8
+ {% if query.description %}
9
+ <div>
10
+ {{ query.description | render_markdown | safe }}
11
+ </div>
12
+ {% endif %}
13
+
8
14
  <form action="{{ request.url.path }}" method="get">
9
- <div class="mb-3">
10
- <textarea disabled name="sql" rows="5" class="form-control">{{ query.sql }}</textarea>
15
+ {% if sql_params|length %}
16
+ <h3 class="mb-3">Parameters</h3>
17
+ {% for param_name, param_value in sql_params.items() %}
18
+ <div class="row mb-3">
19
+ <label for="param-{{ param_name }}" class="col-form-label col-sm-2">{{ param_name }}</label>
20
+ <div class="col-sm-4">
21
+ <input id="param-{{ param_name }}" class="form-control" name="{{ param_name }}" value="{{ param_value }}">
22
+ </div>
11
23
  </div>
24
+ {% endfor %}
25
+ {% endif %}
12
26
 
13
27
  <div class="mb-3">
14
28
  <div class="d-flex justify-content-end">
15
29
  <div class="row">
16
30
  <div class="col">
17
- <a href="/tables/query?sql={{ query.sql | urlencode }}" class="btn btn-secondary" type="button" >
31
+ <a href="/tables/query?sql={{ query.sql | urlencode }}{% if request.query_params | length > 0 %}&{{ request.query_params.multi_items() | urlencode}}{% endif %}" class="btn btn-secondary" type="button">
18
32
  <i class="bi-code" aria-hidden="true"></i> Edit SQL
19
33
  </a>
20
34
  </div>
@@ -63,4 +77,4 @@
63
77
  {% endif %}
64
78
  </div>
65
79
  </div>
66
- {% endblock %}
80
+ {% endblock %}
@@ -8,9 +8,21 @@
8
8
 
9
9
  <form action="{{ request.url.path }}" method="get">
10
10
  <div class="mb-3">
11
- <textarea name="sql" rows="5" class="form-control">{{ sql_query }}</textarea>
11
+ <textarea id="sql-editor" name="sql" rows="5" class="form-control">{{ sql_query }}</textarea>
12
12
  </div>
13
13
 
14
+ {% if sql_params|length %}
15
+ <h3 class="mb-3">Parameters</h3>
16
+ {% for param_name, param_value in sql_params.items() %}
17
+ <div class="row mb-3">
18
+ <label for="param-{{ param_name }}" class="col-form-label col-sm-2">{{ param_name }}</label>
19
+ <div class="col-sm-4">
20
+ <input id="param-{{ param_name }}" class="form-control" name="{{ param_name }}" value="{{ param_value }}">
21
+ </div>
22
+ </div>
23
+ {% endfor %}
24
+ {% endif %}
25
+
14
26
  <div class="mb-3">
15
27
  <div class="d-flex justify-content-end">
16
28
  <button type="submit" class="btn btn-primary">
@@ -54,4 +66,16 @@
54
66
  {% endif %}
55
67
  </div>
56
68
  </div>
69
+ {% endblock %}
70
+
71
+ {% block extra_scripts %}
72
+ <script src="{{ url_for('static', path='/editor.bundle.js') }}"></script>
73
+ <script>
74
+ window.addEventListener("DOMContentLoaded", () => {
75
+ const textArea = document.querySelector("textarea#sql-editor")
76
+ textArea.style.display = "none";
77
+ const sqlSchema = {{ sql_schema | tojson }}
78
+ const sqlEditor = editor.createEditor(textArea, { readOnly: false, dialect: 'duckdb', schema: sqlSchema})
79
+ })
80
+ </script>
57
81
  {% endblock %}