• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import { BaseElement, element } from '../../base-ui/BaseElement';
17import { LitTable } from '../../base-ui/table/lit-table';
18import '../../base-ui/table/lit-table';
19import { LitTableColumn } from '../../base-ui/table/lit-table-column';
20import { info } from '../../log/Log';
21import { LitProgressBar } from '../../base-ui/progress-bar/LitProgressBar';
22import { PageNation } from '../../base-ui/chart/pagenation/PageNation';
23import { PaginationBox } from '../../base-ui/chart/pagenation/PaginationBox';
24import { SpStatisticsHttpUtil } from '../../statistics/util/SpStatisticsHttpUtil';
25import { getAllSql } from './trace/base/CommonSql';
26import { LitIcon } from '../../base-ui/icon/LitIcon';
27import {queryCustomizeSelect} from "../database/sql/SqlLite.sql";
28import { SpQuerySQLHtml } from './SpQuerySQL.html';
29
30@element('sp-query-sql')
31export class SpQuerySQL extends BaseElement {
32  private queryTableEl: LitTable | undefined;
33  private notSupportList: Array<string> | undefined = [];
34  private querySize: HTMLElement | undefined;
35  private keyList: Array<string> | undefined;
36  private selector: HTMLTextAreaElement | undefined;
37  private isSupportSql: boolean = true;
38  private response: HTMLDivElement | undefined;
39  private statDataArray: unknown[] = [];
40  private sliceData: unknown[] = [];
41  private querySqlErrorText: string = '';
42  private progressLoad: LitProgressBar | undefined;
43  private pagination: PaginationBox | undefined;
44  private sqlListDiv: HTMLDivElement | undefined;
45
46  initElements(): void {
47    this.progressLoad = this.shadowRoot?.querySelector('.load-query-sql') as LitProgressBar;
48    this.selector = this.shadowRoot?.querySelector('.sql-select') as HTMLTextAreaElement;
49    this.queryTableEl = this.shadowRoot?.querySelector('lit-table') as LitTable;
50    this.queryTableEl.setAttribute('data-query-scene', '');
51    this.querySize = this.shadowRoot?.querySelector('.query_size') as HTMLElement;
52    this.response = this.shadowRoot?.querySelector('#dataResult') as HTMLDivElement;
53    this.pagination = this.shadowRoot?.querySelector('.pagination-box') as PaginationBox;
54    this.notSupportList?.push('insert', 'delete', 'update', 'drop', 'alter', 'truncate', 'create');
55    this.sqlListDiv = this.shadowRoot?.querySelector('#sqlList') as HTMLDivElement;
56    let htmlDivElement = this.queryTableEl.shadowRoot?.querySelector('.table') as HTMLDivElement;
57    htmlDivElement.style.overflowX = 'scroll';
58    window.addEventListener('resize', () => {
59      this.freshTableHeadResizeStyle();
60    });
61    let copyButtonEl = this.shadowRoot?.querySelector('#copy-button') as HTMLButtonElement;
62    copyButtonEl.addEventListener('click', () => {
63      this.copyTableData();
64    });
65    let closeButtonEl = this.shadowRoot?.querySelector('#close-button') as HTMLButtonElement;
66    closeButtonEl.addEventListener('click', () => {
67      this.pagination!.style.display = 'none';
68      this.querySize!.textContent = 'Query result - 0 counts.';
69      this.queryTableEl!.dataSource = [];
70      this.response!.innerHTML = '';
71    });
72    this.initCommonList();
73  }
74
75  private initCommonList(): void {
76    let commonSqlList = getAllSql();
77    if (commonSqlList.length > 0) {
78      for (let i = 0; i < commonSqlList.length; i++) {
79        let commonSqlDiv = document.createElement('div');
80        commonSqlDiv.className = 'sql-item';
81        let sql = document.createElement('div');
82        sql.className = 'sql';
83        sql.textContent = commonSqlList[i].sql;
84        let runButton = document.createElement('lit-icon');
85        runButton.className = 'runButton';
86        runButton.title = commonSqlList[i].title;
87        runButton.setAttribute('size', '20');
88        runButton.setAttribute('name', 'run-sql');
89        commonSqlDiv.appendChild(sql);
90        commonSqlDiv.appendChild(runButton);
91        this.sqlListDiv?.append(commonSqlDiv);
92      }
93    }
94  }
95
96  private freshTableHeadResizeStyle(): void {
97    let th = this.queryTableEl!.shadowRoot?.querySelector<HTMLDivElement>('.th');
98    if (th) {
99      let td = th.querySelectorAll<HTMLDivElement>('.td');
100      let firstChild = this.queryTableEl!.shadowRoot?.querySelector<HTMLDivElement>('.body')!.firstElementChild;
101      if (firstChild) {
102        let bodyList = firstChild.querySelectorAll<HTMLDivElement>('.td');
103        for (let index = 0; index < bodyList.length; index++) {
104          td[index].style.width = `${bodyList[index].offsetWidth}px`;
105          td[index].style.overflow = 'hidden';
106        }
107      }
108    }
109    let tableHeadStyle: HTMLDivElement | undefined | null = this.queryTableEl?.shadowRoot?.querySelector(
110      'div.th'
111    ) as HTMLDivElement;
112    if (tableHeadStyle && tableHeadStyle.hasChildNodes()) {
113      for (let index = 0; index < tableHeadStyle.children.length; index++) {
114        // @ts-ignore
115        tableHeadStyle.children[index].style.gridArea = null;
116      }
117    }
118    this.queryTableEl!.style.height = '100%';
119  }
120
121  private async copyTableData(): Promise<void> {
122    let copyResult = '';
123    for (let keyListKey of this.keyList!) {
124      copyResult += `${keyListKey}\t`;
125    }
126    copyResult += '\n';
127    let copyData: unknown[];
128    if (this.statDataArray.length > maxPageSize) {
129      copyData = this.sliceData;
130    } else {
131      copyData = this.statDataArray;
132    }
133    for (const value of copyData) {
134      this.keyList?.forEach((key) => {
135        // @ts-ignore
136        copyResult += `${value[key]}\t`;
137      });
138      copyResult += '\n';
139    }
140    await navigator.clipboard.writeText(copyResult);
141  }
142
143  selectEventListener = (event: KeyboardEvent): void => {
144    let enterKey = 13;
145    if (event.ctrlKey && event.keyCode === enterKey) {
146      SpStatisticsHttpUtil.addOrdinaryVisitAction({
147        event: 'query',
148        action: 'query',
149      });
150      this.statDataArray = [];
151      this.keyList = [];
152      this.response!.innerHTML = '';
153      this.queryTableEl!.innerHTML = '';
154      this.pagination!.style.display = 'none';
155      if (this.isSupportSql) {
156        this.executeSql(this.selector!.value);
157      } else {
158        this.querySize!.textContent = this.querySqlErrorText;
159        this.queryTableEl!.dataSource = [];
160        this.response!.innerHTML = '';
161        return;
162      }
163    }
164  };
165
166  private executeSql(sql: string): void {
167    this.progressLoad!.loading = true;
168    if (this.querySize) {
169      this.querySize!.title = `${sql}`;
170    }
171    queryCustomizeSelect(sql).then((resultList): void => {
172      if (resultList && resultList.length > 0) {
173        this.statDataArray = resultList;
174        this.keyList = Object.keys(resultList[0]);
175        this.querySize!.textContent = `Query result - ${this.statDataArray.length} counts.` + `(${sql})`;
176        this.initDataElement();
177        this.response!.appendChild(this.queryTableEl!);
178        this.setPageNationTableEl();
179        setTimeout(() => {
180          if (this.parentElement?.clientHeight !== 0) {
181            this.queryTableEl!.style.height = '100%';
182            this.queryTableEl!.reMeauseHeight();
183          }
184        }, 300);
185      } else {
186        this.querySize!.textContent = `Query result - ${this.statDataArray.length} counts.` + `(${sql})`;
187        this.progressLoad!.loading = false;
188      }
189    });
190  }
191
192  private setPageNationTableEl(): void {
193    let that = this;
194    let timeOutTs: number = 200;
195    let indexNumber = 1;
196    setTimeout(() => {
197      let total = this.statDataArray.length;
198      if (total > maxPageSize) {
199        that.pagination!.style.display = 'block';
200        that.pagination!.style.opacity = '1';
201        new PageNation(this.pagination, {
202          current: 1,
203          total: total,
204          pageSize: pageSize,
205          change(num: number): void {
206            that.sliceData = that.statDataArray!.slice((num - indexNumber) * pageSize, num * pageSize);
207            that.queryTableEl!.recycleDataSource = that.sliceData;
208          },
209        });
210      } else {
211        that.pagination!.style.opacity = '0';
212        this.queryTableEl!.recycleDataSource = this.statDataArray;
213      }
214      this.freshTableHeadResizeStyle();
215      this.progressLoad!.loading = false;
216    }, timeOutTs);
217  }
218
219  reset(): void {
220    this.pagination!.style.opacity = '0';
221    this.response!.innerHTML = '';
222    this.keyList = [];
223    this.statDataArray = [];
224    this.selector!.value = '';
225    this.querySize!.textContent = 'Please enter a query.';
226    this.resizeSqlHeight().then();
227  }
228
229  private checkSafetySelectSql(): boolean {
230    if (this.selector?.value.trim() === '') {
231      this.querySqlErrorText = 'Please enter a query.';
232      this.querySize!.textContent = this.querySqlErrorText;
233      return false;
234    } else {
235      let queryNormalLength = 15;
236      if (
237        this.selector!.value.length < queryNormalLength ||
238        !this.selector?.value.toLowerCase().trim().startsWith('select')
239      ) {
240        this.querySqlErrorText = `Query result - (Error):
241        ${this.selector!.value}.`;
242        return false;
243      }
244      if (this.notSupportList && this.notSupportList.length > 0) {
245        for (let index = 0; index < this.notSupportList.length; index++) {
246          let regexStr = new RegExp(this.notSupportList[index], 'i');
247          if (regexStr.test(this.selector!.value)) {
248            this.querySqlErrorText = `Query result - (Error):
249            ${this.selector!.value}.`;
250            return false;
251          }
252        }
253      }
254    }
255    return true;
256  }
257
258  private initDataElement(): void {
259    if (this.keyList) {
260      info('Metric query Table Colum size is: ', this.keyList.length);
261      this.keyList.forEach((item) => {
262        let htmlElement = document.createElement('lit-table-column') as LitTableColumn;
263        htmlElement.setAttribute('title', item);
264        htmlElement.setAttribute('data-index', item);
265        htmlElement.setAttribute('key', item);
266        htmlElement.setAttribute('align', 'flex-start');
267        htmlElement.setAttribute('height', '32px');
268        this.queryTableEl!.appendChild(htmlElement);
269      });
270    }
271  }
272
273  connectedCallback(): void {
274    // Listen to the sql execution of the query
275    this.addEventListener('keydown', this.selectEventListener);
276    this.selector!.addEventListener('input', this.inputSqlListener);
277    this.selector!.addEventListener('change', this.inputSqlListener);
278    this.selector!.addEventListener('keydown', this.deleteSqlListener);
279    this.shadowRoot
280      ?.querySelectorAll<LitIcon>('.runButton')
281      .forEach((it) => it.addEventListener('click', this.runSqlListener));
282  }
283
284  runSqlListener = (e: Event): void => {
285    this.scrollTo(0, 0);
286    this.statDataArray = [];
287    this.keyList = [];
288    this.response!.innerHTML = '';
289    this.queryTableEl!.innerHTML = '';
290    this.pagination!.style.display = 'none';
291    let previousSibling = (e.target as HTMLDivElement).previousElementSibling;
292    if (previousSibling && previousSibling instanceof HTMLDivElement) {
293      const content = previousSibling.textContent;
294      this.executeSql(content!);
295    }
296  };
297
298  private deleteSqlListener = (event: KeyboardEvent): void => {
299    if (event.key === 'Backspace') {
300      this.resizeSqlHeight().then();
301      this.isSupportSql = this.checkSafetySelectSql();
302    }
303  };
304
305  private async resizeSqlHeight(): Promise<void> {
306    let minRowNumber = 10;
307    let indexNumber = 1;
308    let multipleNumber = 1.2;
309    let paddingNumber = 2;
310    let valueLength = this.selector?.value.split('\n').length;
311    let rowNumber = Number(valueLength) - indexNumber;
312    let selectHeight = '3.2em';
313    if (rowNumber > 0) {
314      if (rowNumber <= minRowNumber) {
315        let allLength = multipleNumber * rowNumber + paddingNumber;
316        selectHeight = `${allLength}em`;
317      } else {
318        selectHeight = '14em';
319      }
320    }
321    // @ts-ignore
322    this.selector?.style.height = selectHeight;
323  }
324
325  private inputSqlListener = async (): Promise<void> => {
326    this.resizeSqlHeight().then();
327    this.isSupportSql = this.checkSafetySelectSql();
328  };
329
330  disconnectedCallback(): void {
331    this.removeEventListener('keydown', this.selectEventListener);
332    this.selector!.removeEventListener('input', this.inputSqlListener);
333    this.selector!.removeEventListener('change', this.inputSqlListener);
334    this.selector!.removeEventListener('keydown', this.deleteSqlListener);
335  }
336
337  attributeChangedCallback(name: string, oldValue: string, newValue: string): void {
338    let queryDataSty: HTMLDivElement | undefined | null = this.queryTableEl?.shadowRoot?.querySelector(
339      'div.tbody'
340    ) as HTMLDivElement;
341    if (queryDataSty && queryDataSty.hasChildNodes()) {
342      for (let index = 0; index < queryDataSty.children.length; index++) {
343        // @ts-ignore
344        queryDataSty.children[index].style.backgroundColor = 'var(--dark-background5,#F6F6F6)';
345      }
346    }
347  }
348
349  initHtml(): string {
350    return SpQuerySQLHtml;
351  }
352}
353
354const pageSize: number = 200000;
355const maxPageSize: number = 500000;
356