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.js"; 17import {queryCustomizeSelect, querySelectTraceStats} from "../database/SqlLite.js"; 18import {LitTable} from "../../base-ui/table/lit-table.js"; 19import "../../base-ui/table/lit-table.js"; 20import {LitTableColumn} from "../../base-ui/table/lit-table-column.js"; 21import {info} from "../../log/Log.js"; 22import {LitProgressBar} from "../../base-ui/progress-bar/LitProgressBar.js"; 23 24@element('sp-query-sql') 25export class SpQuerySQL extends BaseElement { 26 private queryTableEl: LitTable | undefined; 27 private queryText: string | undefined; 28 private resultText: string | undefined; 29 private notSupportList: Array<string> | undefined; 30 private querySize: HTMLElement | undefined; 31 private keyList: Array<string> | undefined; 32 private selector: HTMLTextAreaElement | undefined; 33 private isSupportSql: boolean = true; 34 private querySelectTables: string = ''; 35 private response: HTMLDivElement | undefined; 36 private statDataArray: any = [] 37 private querySqlErrorText: string = '' 38 private progressLoad: LitProgressBar | undefined; 39 40 initElements(): void { 41 this.progressLoad = this.shadowRoot?.querySelector(".load-metric") as LitProgressBar; 42 this.selector = this.shadowRoot?.querySelector('.sql-select') as HTMLTextAreaElement; 43 this.queryTableEl = new LitTable() 44 this.querySize = this.shadowRoot?.querySelector('.query_size') as HTMLElement; 45 this.response = this.shadowRoot?.querySelector('#dataResult') as HTMLDivElement; 46 this.notSupportList?.push('insert', 'delete', 'update', 'drop', 'alter', 'truncate', 'create'); 47 let htmlDivElement = this.queryTableEl.shadowRoot?.querySelector('.table') as HTMLDivElement; 48 htmlDivElement.style.overflowX = "scroll" 49 50 window.addEventListener('resize',()=>{ 51 this.freshTableHeadResizeStyle() 52 }) 53 54 let copyButtonEl = this.shadowRoot?.querySelector('#copy-button') as HTMLButtonElement; 55 copyButtonEl.addEventListener('click',()=>{ 56 this.copyTableData() 57 }) 58 59 let closeButtonEl = this.shadowRoot?.querySelector('#close-button') as HTMLButtonElement; 60 closeButtonEl.addEventListener('click',()=>{ 61 this.querySize!.textContent = 'Query result - 0 counts' 62 this.queryTableEl!.dataSource = [] 63 this.response!.innerHTML = '' 64 }) 65 } 66 67 freshTableHeadResizeStyle(){ 68 let th = this.queryTableEl!.shadowRoot?.querySelector<HTMLDivElement>(".th") 69 if (th) { 70 let td = th.querySelectorAll<HTMLDivElement>(".td") 71 let firstChild = this.queryTableEl!.shadowRoot?.querySelector<HTMLDivElement>(".body")!.firstElementChild 72 if(firstChild){ 73 let bodyList = firstChild.querySelectorAll<HTMLDivElement>('.td'); 74 for (let index = 0; index < bodyList.length; index++) { 75 td[index].style.width = bodyList[index].offsetWidth + 'px' 76 td[index].style.overflow = 'hidden' 77 } 78 } 79 } 80 } 81 82 copyTableData(){ 83 let copyResult = '' 84 for (let keyListKey of this.keyList!) { 85 copyResult += keyListKey + "\t" 86 } 87 copyResult += "\n" 88 for (const value of this.statDataArray) { 89 this.keyList?.forEach((key) =>{ 90 copyResult += value[key] + "\t"; 91 }) 92 copyResult += "\n" 93 } 94 navigator.clipboard.writeText(copyResult).then(()=>{ 95 }) 96 } 97 98 selectEventListener = (event: any) => { 99 if (event.ctrlKey && event.keyCode == 13) { 100 if (!this.isSupportSql) { 101 this.querySize!.textContent = this.querySqlErrorText 102 this.queryTableEl!.dataSource = [] 103 this.response!.innerHTML = '' 104 return 105 } 106 this.progressLoad!.loading = true 107 let startData = new Date().getTime(); 108 this.getInputSqlResult(this.selector!.value).then(resultList => { 109 let dur = new Date().getTime() - startData; 110 this.statDataArray = [] 111 this.keyList = []; 112 for (let index = 0; index < resultList.length; index++) { 113 const dataResult = resultList[index]; 114 let keys = Object.keys(dataResult); 115 let values = Object.values(dataResult); 116 let jsonText = '{'; 117 for (let keyIndex = 0; keyIndex < keys.length; keyIndex++) { 118 let key = keys[keyIndex]; 119 if (this.keyList.indexOf(key) <= -1) { 120 this.keyList.push(key) 121 } 122 let value = values[keyIndex]; 123 if (typeof value == "string") { 124 value = value.replace(/</gi, "<").replace(/>/gi, ">") 125 } 126 jsonText += '"' + key + '"' + ': ' + '"' + value + '"'; 127 if (keyIndex != keys.length - 1) { 128 jsonText += ',' 129 } else { 130 jsonText += '}'; 131 } 132 } 133 this.statDataArray.push(JSON.parse(jsonText)) 134 } 135 136 this.queryTableEl!.innerHTML = '' 137 this.queryText = this.selector!.value; 138 this.initDataElement(); 139 this.response!.appendChild(this.queryTableEl!); 140 141 setTimeout(() => { 142 this.queryTableEl!.recycleDataSource = this.statDataArray; 143 this.freshTableHeadResizeStyle() 144 new ResizeObserver(() => { 145 if (this.parentElement?.clientHeight != 0) { 146 this.queryTableEl!.style.height = '100%' 147 this.queryTableEl!.reMeauseHeight() 148 } 149 }).observe(this.parentElement!) 150 info("metric query Sql result Data size is: ", this.statDataArray!.length) 151 this.initData() 152 this.progressLoad!.loading = false 153 },200) 154 }) 155 } 156 } 157 158 reset() { 159 this.response!.innerHTML = '' 160 this.keyList = []; 161 this.statDataArray = []; 162 this.selector!.value = '' 163 this.querySize!.textContent = 'Query result - ' + ' 0 counts' 164 this.resizeSqlHeight().then(()=>{}); 165 } 166 167 initDataTableStyle(styleTable: HTMLDivElement): void { 168 for (let index = 0; index < styleTable.children.length; index++) { 169 // @ts-ignore 170 styleTable.children[index].style.backgroundColor = 'var(--dark-background5,#F6F6F6)' 171 } 172 } 173 174 async initMetricData(): Promise<any> { 175 if (!this.selector || this.selector.value == null) { 176 return []; 177 } 178 if (this.queryText == '' || this.queryText == null) { 179 let statList = await querySelectTraceStats(); 180 for (let index = 0; index < statList.length; index++) { 181 const statsResult = statList[index]; 182 let indexArray = { 183 event_name: statsResult.event_name, 184 start_type: statsResult.stat_type, 185 count: statsResult.count, 186 serverity: statsResult.serverity, 187 source: statsResult.source, 188 }; 189 } 190 if (this.querySize) { 191 this.querySize!.textContent = 'Query result - ' + statList.length + ' counts'; 192 } 193 this.resultText = 'Query result - ' + statList.length + ' counts'; 194 } else { 195 return this.statDataArray 196 } 197 } 198 199 checkSupportSqlAbility(): boolean { 200 let noSupportChart = ['insert', 'delete', 'update', 'drop', 'alter', 'truncate', 'create'] 201 let result = noSupportChart.filter(item => { 202 return this.selector!.value.indexOf(item) > -1; 203 }); 204 if(result.length > 0){ 205 this.querySqlErrorText = 'Error: Statement contains a change action keyword,The change operation is not supported.'; 206 this.isSupportSql = false; 207 return true 208 } else { 209 return false 210 } 211 } 212 213 checkSafetySelectSql(): boolean { 214 let split = this.selector?.value.trim().split(' '); 215 if (split) { 216 this.querySqlErrorText = 'Error: Incomplete query statement: ' + this.selector!.value; 217 this.isSupportSql = false; 218 return !split[0].toLowerCase().startsWith('select'); 219 } 220 return false; 221 } 222 223 getSelectSqlField(): string { 224 if (this.selector!.value.indexOf('from') < 0) { 225 return ''; 226 } 227 let splitSql = this.selector?.value.split('from'); 228 if (splitSql) { 229 if (splitSql[0].indexOf('*') > -1) { 230 return '*' 231 } else { 232 let fields = splitSql[0].split(',') 233 return fields[0]; 234 } 235 } 236 return ''; 237 } 238 239 getSelectSqlTableName(str: string): Array<string> { 240 if (this.selector!.value.indexOf(str) < 0) { 241 return []; 242 } 243 let tableNameList = []; 244 let splitSql = this.selector?.value.split(str); 245 if (splitSql) { 246 for (let index = 1; index < splitSql?.length; index++) { 247 let splitSqlItem = splitSql[index].trim(); 248 let tableItem = splitSqlItem.split(' '); 249 let tableName = tableItem[0].trim(); 250 tableNameList.push(tableName); 251 if (tableName.indexOf('(') >= 0) { 252 tableNameList.pop(); 253 } else if (tableName.indexOf(')') >= 0) { 254 tableNameList.pop(); 255 let unitTableName = tableName.split(')'); 256 let tableNewName = unitTableName[0]; 257 tableNameList.push(tableNewName); 258 } 259 } 260 } 261 return tableNameList 262 } 263 264 initDataElement() { 265 if (this.keyList) { 266 info("Metric query Table Colum size is: ", this.keyList.length) 267 this.keyList.forEach((item) => { 268 let htmlElement = document.createElement('lit-table-column') as LitTableColumn; 269 htmlElement.setAttribute('title', item); 270 htmlElement.setAttribute('data-index', item); 271 htmlElement.setAttribute('key', item); 272 htmlElement.setAttribute('align', 'flex-start'); 273 htmlElement.setAttribute('height', '32px'); 274 this.queryTableEl!.appendChild(htmlElement); 275 }) 276 } 277 } 278 279 connectedCallback() { 280 let selectQuery = this.shadowRoot?.querySelector('.query_select'); 281 if (selectQuery) { 282 let querySql = selectQuery.textContent; 283 } 284 // Listen to the sql execution of the query 285 this.addEventListener("keydown", this.selectEventListener); 286 this.selector!.addEventListener('input', this.inputSqlListener) 287 this.selector!.addEventListener('change', this.inputSqlListener) 288 this.selector!.addEventListener('keydown', this.deleteSqlListener) 289 } 290 291 deleteSqlListener = (event: any) => { 292 if (event.key == 'Backspace') { 293 this.resizeSqlHeight().then(()=>{}); 294 } 295 } 296 297 async resizeSqlHeight() { 298 let valueLength = this.selector?.value.split('\n').length; 299 let rowNumber = Number(valueLength) - 1; 300 let selectHeight = '3.2em' 301 if (rowNumber > 0) { 302 if (rowNumber <= 10) { 303 let allLength = 1.2 * rowNumber + 2; 304 selectHeight = allLength + 'em'; 305 } else { 306 selectHeight = '14em' 307 } 308 } 309 // @ts-ignore 310 this.selector?.style.height = selectHeight 311 } 312 313 inputSqlListener = async (event: any) => { 314 this.resizeSqlHeight().then(()=>{}); 315 let startData = new Date().getTime(); 316 if (this.selector!.value.trim() == '') { 317 this.querySqlErrorText = 'Please enter a query'; 318 this.querySize!.textContent = this.querySqlErrorText 319 return 320 } 321 this.checkSafetySelectSql() 322 this.checkSupportSqlAbility() 323 if (this.selector!.value.length < 15) { 324 return; 325 } 326 this.querySelectTables = this.getSelectSqlTableName('from').concat(this.getSelectSqlTableName('join')).toLocaleString(); 327 info("metric query sql table size is: ", this.querySelectTables.length) 328 this.isSupportSql = true; 329 } 330 331 async getInputSqlResult(sql: string): Promise<any> { 332 return await queryCustomizeSelect(sql); 333 } 334 335 disconnectedCallback() { 336 this.removeEventListener("keydown", this.selectEventListener); 337 this.selector!.removeEventListener('input', this.inputSqlListener) 338 this.selector!.removeEventListener('change', this.inputSqlListener) 339 this.selector!.removeEventListener('keydown', this.deleteSqlListener) 340 } 341 342 initData() { 343 if (this.statDataArray.length > 0) { 344 this.querySize!.textContent = 'Error: ' + this.selector?.value; 345 } 346 if (this.isSupportSql) { 347 let sqlField = this.keyList?.length == 0 ? '*' : this.keyList?.toLocaleString(); 348 this.querySize!.textContent = 'Query result - ' + this.statDataArray.length + ' counts'; 349 } else { 350 this.querySize!.textContent = this.querySqlErrorText; 351 } 352 353 let queryHeadStyle: HTMLDivElement | undefined | null = this.queryTableEl?.shadowRoot?.querySelector('div.th') as HTMLDivElement 354 if (queryHeadStyle && queryHeadStyle.hasChildNodes()) { 355 for (let index = 0; index < queryHeadStyle.children.length; index++) { 356 // @ts-ignore 357 queryHeadStyle.children[index].style.gridArea = null 358 } 359 } 360 361 this.queryTableEl!.style.height = '100%' 362 } 363 364 static get observedAttributes() { 365 return ["queryStr"] 366 } 367 368 attributeChangedCallback(name: string, oldValue: string, newValue: string) { 369 let queryDataSty: HTMLDivElement | undefined | null = this.queryTableEl?.shadowRoot?.querySelector('div.tbody') as HTMLDivElement 370 if (queryDataSty && queryDataSty.hasChildNodes()) { 371 for (let index = 0; index < queryDataSty.children.length; index++) { 372 // @ts-ignore 373 queryDataSty.children[index].style.backgroundColor = 'var(--dark-background5,#F6F6F6)' 374 } 375 } 376 } 377 378 private _queryStr?: string; 379 380 get queryStr(): string { 381 return this.queryStr; 382 } 383 384 set queryStr(value: string) { 385 this._queryStr = value; 386 } 387 388 initHtml(): string { 389 return ` 390 <style> 391 :host{ 392 width: 100%; 393 height: 100%; 394 font-size: 16px; 395 background-color: var(--dark-background5,#F6F6F6); 396 margin: 0; 397 padding: 0; 398 } 399 400 .sql-select{ 401 box-sizing: border-box; 402 width: 95%; 403 font-family: Helvetica,serif; 404 font-size: inherit; 405 color: var(--dark-color1,#212121); 406 text-align: left; 407 line-height: 1.2em; 408 font-weight: 400; 409 height: 3.2em; 410 margin-left: 10px; 411 resize: vertical; 412 border-width: 2px; 413 } 414 415 .query{ 416 display: flex; 417 flex-direction: column; 418 background-color: var(--dark-background5,#F6F6F6); 419 position: absolute; 420 top: 0; 421 bottom: 0; 422 left: 0; 423 right: 0; 424 } 425 426 .query-message{ 427 background-color: var(--dark-background3,#FFFFFF); 428 padding: 1% 2%; 429 margin: 2% 2.5% 0 2.5%; 430 border-radius: 16px; 431 width: 90%; 432 } 433 434 .request{ 435 display: flex; 436 flex-direction: column; 437 position: relative; 438 } 439 440 .response{ 441 flex-grow: 1; 442 margin-bottom: 1%; 443 display: flex; 444 flex-direction: column; 445 min-height: inherit; 446 max-height: 70vh; 447 } 448 449 #dataResult{ 450 flex-grow: 1; 451 overflow-y: auto; 452 overflow-x: visible; 453 margin-bottom: 1%; 454 border-radius: 16px; 455 } 456 457 p{ 458 display: table-cell; 459 padding: 7px 10px; 460 color: #999999; 461 font-size:0.875em; 462 line-height: 20px; 463 font-weight: 400; 464 text-align: left; 465 } 466 467 #response-json{ 468 margin-top: 20px; 469 background-color: var(--dark-background5,#F6F6F6); 470 margin-left: 10px; 471 flex-grow: 1; 472 scroll-y: visible; 473 } 474 475 .sql-select{ 476 background-color: var(--dark-background5, #F6F6F6); 477 } 478 479 /*Define the height, width and background of the scroll bar*/ 480 ::-webkit-scrollbar 481 { 482 width: 8px; 483 background-color: var(--dark-background3,#FFFFFF); 484 } 485 486 /*define slider*/ 487 ::-webkit-scrollbar-thumb 488 { 489 border-radius: 6px; 490 background-color: var(--dark-background7,rgba(0,0,0,0.1)); 491 } 492 493 .load-metric{ 494 width: 95%; 495 bottom: 0; 496 } 497 498 #copy-button{ 499 margin-right: 10%; 500 cursor:pointer 501 } 502 503 #close-button{ 504 margin-right: 5%; 505 cursor:pointer 506 } 507 508 .button-option{ 509 border-radius: 15px; 510 background-color: #0A59F7; 511 width: 120px; 512 height: 25px; 513 font-family: Helvetica-Bold; 514 color: var(--dark-background3,#FFFFFF); 515 text-align: center; 516 line-height: 20px; 517 font-weight: 400; 518 border:0 solid; 519 } 520 521 </style> 522 <div class="query"> 523 <div class="query-message request"> 524 <p class="query_select">Enter query and press cmd/ctrl + Enter</p> 525 <textarea class="sql-select"></textarea> 526 <lit-progress-bar class="load-metric"></lit-progress-bar> 527 </div> 528 <div class="query-message response"> 529 <div style="display: flex;justify-content: space-between"> 530 <p class="query_size">Query result - 0 counts</p> 531 <div style="display: flex; align-items: center"> 532 <button id="copy-button" class="button-option">Copy as.tsv</button> 533 <button id="close-button" class="button-option">Close</button> 534 </div> 535 </div> 536 <div id="dataResult"></div> 537 </div> 538 </div> 539 `; 540 } 541} 542