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