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 //@ts-ignore 175 this.keyList = Object.keys(resultList[0]); 176 this.querySize!.textContent = `Query result - ${this.statDataArray.length} counts.` + `(${sql})`; 177 this.initDataElement(); 178 this.response!.appendChild(this.queryTableEl!); 179 this.setPageNationTableEl(); 180 setTimeout(() => { 181 if (this.parentElement?.clientHeight !== 0) { 182 this.queryTableEl!.style.height = '100%'; 183 this.queryTableEl!.reMeauseHeight(); 184 } 185 }, 300); 186 } else { 187 this.querySize!.textContent = `Query result - ${this.statDataArray.length} counts.` + `(${sql})`; 188 this.progressLoad!.loading = false; 189 } 190 }); 191 } 192 193 private setPageNationTableEl(): void { 194 let timeOutTs: number = 200; 195 let indexNumber = 1; 196 setTimeout(() => { 197 let total = this.statDataArray.length; 198 if (total > maxPageSize) { 199 this.pagination!.style.display = 'block'; 200 this.pagination!.style.opacity = '1'; 201 const option = { 202 current: 1, 203 total: total, 204 pageSize: pageSize, 205 change: (num: number): void => { 206 this.sliceData = this.statDataArray!.slice((num - indexNumber) * pageSize, num * pageSize); 207 this.queryTableEl!.recycleDataSource = this.sliceData; 208 }, 209 }; 210 new PageNation(this.pagination, option); 211 } else { 212 this.pagination!.style.opacity = '0'; 213 this.queryTableEl!.recycleDataSource = this.statDataArray; 214 } 215 this.freshTableHeadResizeStyle(); 216 this.progressLoad!.loading = false; 217 }, timeOutTs); 218 } 219 220 reset(): void { 221 this.pagination!.style.opacity = '0'; 222 this.response!.innerHTML = ''; 223 this.keyList = []; 224 this.statDataArray = []; 225 this.selector!.value = ''; 226 this.querySize!.textContent = 'Please enter a query.'; 227 this.resizeSqlHeight().then(); 228 } 229 230 private checkSafetySelectSql(): boolean { 231 if (this.selector?.value.trim() === '') { 232 this.querySqlErrorText = 'Please enter a query.'; 233 this.querySize!.textContent = this.querySqlErrorText; 234 return false; 235 } else { 236 let queryNormalLength = 15; 237 if ( 238 this.selector!.value.length < queryNormalLength || 239 !this.selector?.value.toLowerCase().trim().startsWith('select') 240 ) { 241 this.querySqlErrorText = `Query result - (Error): 242 ${this.selector!.value}.`; 243 return false; 244 } 245 if (this.notSupportList && this.notSupportList.length > 0) { 246 for (let index = 0; index < this.notSupportList.length; index++) { 247 let regexStr = new RegExp(this.notSupportList[index], 'i'); 248 if (regexStr.test(this.selector!.value)) { 249 this.querySqlErrorText = `Query result - (Error): 250 ${this.selector!.value}.`; 251 return false; 252 } 253 } 254 } 255 } 256 return true; 257 } 258 259 private initDataElement(): void { 260 if (this.keyList) { 261 info('Metric query Table Colum size is: ', this.keyList.length); 262 this.keyList.forEach((item) => { 263 let htmlElement = document.createElement('lit-table-column') as LitTableColumn; 264 htmlElement.setAttribute('title', item); 265 htmlElement.setAttribute('data-index', item); 266 htmlElement.setAttribute('key', item); 267 htmlElement.setAttribute('align', 'flex-start'); 268 htmlElement.setAttribute('height', '32px'); 269 this.queryTableEl!.appendChild(htmlElement); 270 }); 271 } 272 } 273 274 connectedCallback(): void { 275 // Listen to the sql execution of the query 276 this.addEventListener('keydown', this.selectEventListener); 277 this.selector!.addEventListener('input', this.inputSqlListener); 278 this.selector!.addEventListener('change', this.inputSqlListener); 279 this.selector!.addEventListener('keydown', this.deleteSqlListener); 280 this.shadowRoot 281 ?.querySelectorAll<LitIcon>('.runButton') 282 .forEach((it) => it.addEventListener('click', this.runSqlListener)); 283 } 284 285 runSqlListener = (e: Event): void => { 286 this.scrollTo(0, 0); 287 this.statDataArray = []; 288 this.keyList = []; 289 this.response!.innerHTML = ''; 290 this.queryTableEl!.innerHTML = ''; 291 this.pagination!.style.display = 'none'; 292 let previousSibling = (e.target as HTMLDivElement).previousElementSibling; 293 if (previousSibling && previousSibling instanceof HTMLDivElement) { 294 const content = previousSibling.textContent; 295 this.executeSql(content!); 296 } 297 }; 298 299 private deleteSqlListener = (event: KeyboardEvent): void => { 300 if (event.key === 'Backspace') { 301 this.resizeSqlHeight().then(); 302 this.isSupportSql = this.checkSafetySelectSql(); 303 } 304 }; 305 306 private async resizeSqlHeight(): Promise<void> { 307 let minRowNumber = 10; 308 let indexNumber = 1; 309 let multipleNumber = 1.2; 310 let paddingNumber = 2; 311 let valueLength = this.selector?.value.split('\n').length; 312 let rowNumber = Number(valueLength) - indexNumber; 313 let selectHeight = '3.2em'; 314 if (rowNumber > 0) { 315 if (rowNumber <= minRowNumber) { 316 let allLength = multipleNumber * rowNumber + paddingNumber; 317 selectHeight = `${allLength}em`; 318 } else { 319 selectHeight = '14em'; 320 } 321 } 322 // @ts-ignore 323 this.selector?.style.height = selectHeight; 324 } 325 326 private inputSqlListener = async (): Promise<void> => { 327 this.resizeSqlHeight().then(); 328 this.isSupportSql = this.checkSafetySelectSql(); 329 }; 330 331 disconnectedCallback(): void { 332 this.removeEventListener('keydown', this.selectEventListener); 333 this.selector!.removeEventListener('input', this.inputSqlListener); 334 this.selector!.removeEventListener('change', this.inputSqlListener); 335 this.selector!.removeEventListener('keydown', this.deleteSqlListener); 336 } 337 338 attributeChangedCallback(name: string, oldValue: string, newValue: string): void { 339 let queryDataSty: HTMLDivElement | undefined | null = this.queryTableEl?.shadowRoot?.querySelector( 340 'div.tbody' 341 ) as HTMLDivElement; 342 if (queryDataSty && queryDataSty.hasChildNodes()) { 343 for (let index = 0; index < queryDataSty.children.length; index++) { 344 // @ts-ignore 345 queryDataSty.children[index].style.backgroundColor = 'var(--dark-background5,#F6F6F6)'; 346 } 347 } 348 } 349 350 initHtml(): string { 351 return SpQuerySQLHtml; 352 } 353} 354 355const pageSize: number = 200000; 356const maxPageSize: number = 500000; 357