* Copyright (C) 2022 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BaseElement, element } from '../../base-ui/BaseElement.js';
import { queryCustomizeSelect, querySelectTraceStats } from '../database/SqlLite.js';
import { LitTable } from '../../base-ui/table/lit-table.js';
import '../../base-ui/table/lit-table.js';
import { LitTableColumn } from '../../base-ui/table/lit-table-column.js';
import { info } from '../../log/Log.js';
import { LitProgressBar } from '../../base-ui/progress-bar/LitProgressBar.js';
import { PageNation } from '../../base-ui/chart/pagenation/PageNation.js';
import { PaginationBox } from '../../base-ui/chart/pagenation/pagination-box.js';
import { SpStatisticsHttpUtil } from '../../statistics/util/SpStatisticsHttpUtil.js';
@element('sp-query-sql')
export class SpQuerySQL extends BaseElement {
private queryTableEl: LitTable | undefined;
private queryText: string | undefined;
private resultText: string | undefined;
private notSupportList: Array<string> | undefined;
private querySize: HTMLElement | undefined;
private keyList: Array<string> | undefined;
private selector: HTMLTextAreaElement | undefined;
private isSupportSql: boolean = true;
private querySelectTables: string = '';
private response: HTMLDivElement | undefined;
private statDataArray: any = [];
private sliceData: any = [];
private querySqlErrorText: string = '';
private progressLoad: LitProgressBar | undefined;
private pagination: PaginationBox | undefined;
private pageSize: number = 200000;
private maxPageSize: number = 500000;
initElements(): void {
this.progressLoad = this.shadowRoot?.querySelector('.load-metric') as LitProgressBar;
this.selector = this.shadowRoot?.querySelector('.sql-select') as HTMLTextAreaElement;
this.queryTableEl = new LitTable();
this.querySize = this.shadowRoot?.querySelector('.query_size') as HTMLElement;
this.response = this.shadowRoot?.querySelector('#dataResult') as HTMLDivElement;
this.pagination = this.shadowRoot?.querySelector('.pagination-box') as PaginationBox;
this.notSupportList?.push('insert', 'delete', 'update', 'drop', 'alter', 'truncate', 'create');
let htmlDivElement = this.queryTableEl.shadowRoot?.querySelector('.table') as HTMLDivElement;
htmlDivElement.style.overflowX = 'scroll';
window.addEventListener('resize', () => {
this.freshTableHeadResizeStyle();
});
let copyButtonEl = this.shadowRoot?.querySelector('#copy-button') as HTMLButtonElement;
copyButtonEl.addEventListener('click', () => {
this.copyTableData();
});
let closeButtonEl = this.shadowRoot?.querySelector('#close-button') as HTMLButtonElement;
closeButtonEl.addEventListener('click', () => {
this.querySize!.textContent = 'Query result - 0 counts';
this.queryTableEl!.dataSource = [];
this.response!.innerHTML = '';
});
}
freshTableHeadResizeStyle() {
let th = this.queryTableEl!.shadowRoot?.querySelector<HTMLDivElement>('.th');
if (th) {
let td = th.querySelectorAll<HTMLDivElement>('.td');
let firstChild = this.queryTableEl!.shadowRoot?.querySelector<HTMLDivElement>('.body')!.firstElementChild;
if (firstChild) {
let bodyList = firstChild.querySelectorAll<HTMLDivElement>('.td');
for (let index = 0; index < bodyList.length; index++) {
td[index].style.width = bodyList[index].offsetWidth + 'px';
td[index].style.overflow = 'hidden';
}
}
}
}
async copyTableData() {
let copyResult = '';
for (let keyListKey of this.keyList!) {
copyResult += keyListKey + '\t';
}
copyResult += '\n';
let copyData: any = [];
if (this.statDataArray.length > this.maxPageSize) {
copyData = this.sliceData;
} else {
copyData = this.statDataArray;
}
for (const value of copyData) {
this.keyList?.forEach((key) => {
copyResult += value[key] + '\t';
});
copyResult += '\n';
}
await (navigator as any).clipboard.writeText(copyResult);
}
selectEventListener = (event: any) => {
let that = this;
if (event.ctrlKey && event.keyCode == 13) {
SpStatisticsHttpUtil.addOrdinaryVisitAction({
event: 'query',
action: 'query',
});
if (!this.isSupportSql) {
this.querySize!.textContent = this.querySqlErrorText;
this.queryTableEl!.dataSource = [];
this.response!.innerHTML = '';
return;
}
this.progressLoad!.loading = true;
let startData = new Date().getTime();
this.getInputSqlResult(this.selector!.value).then((resultList) => {
let dur = new Date().getTime() - startData;
this.statDataArray = [];
this.keyList = [];
for (let index = 0; index < resultList.length; index++) {
const dataResult = resultList[index];
let keys = Object.keys(dataResult);
let values = Object.values(dataResult);
let jsonText = '{';
for (let keyIndex = 0; keyIndex < keys.length; keyIndex++) {
let key = keys[keyIndex];
if (this.keyList.indexOf(key) <= -1) {
this.keyList.push(key);
}
let value = values[keyIndex];
if (typeof value == 'string') {
value = value.replace(/</gi, '<').replace(/>/gi, '>');
}
jsonText += '"' + key + '"' + ': ' + '"' + value + '"';
if (keyIndex != keys.length - 1) {
jsonText += ',';
} else {
jsonText += '}';
}
}
this.statDataArray.push(JSON.parse(jsonText));
}
this.queryTableEl!.innerHTML = '';
this.queryText = this.selector!.value;
this.initDataElement();
this.response!.appendChild(this.queryTableEl!);
setTimeout(() => {
let total = this.statDataArray.length;
if (total > this.maxPageSize) {
that.pagination!.style.opacity = '1';
new PageNation(this.pagination, {
current: 1,
total: total,
pageSize: this.pageSize,
change(num: number) {
that.sliceData = that.statDataArray!.slice((num - 1) * that.pageSize, num * that.pageSize);
that.queryTableEl!.recycleDataSource = that.sliceData;
},
});
} else {
that.pagination!.style.opacity = '0';
this.queryTableEl!.recycleDataSource = this.statDataArray;
}
this.freshTableHeadResizeStyle();
new ResizeObserver(() => {
if (this.parentElement?.clientHeight != 0) {
this.queryTableEl!.style.height = '100%';
this.queryTableEl!.reMeauseHeight();
}
}).observe(this.parentElement!);
info('metric query Sql result Data size is: ', this.statDataArray!.length);
this.initData();
this.progressLoad!.loading = false;
}, 200);
});
}
};
reset() {
this.pagination!.style.opacity = '0';
this.response!.innerHTML = '';
this.keyList = [];
this.statDataArray = [];
this.selector!.value = '';
this.querySize!.textContent = 'Query result - ' + ' 0 counts';
this.resizeSqlHeight().then(() => {});
}
initDataTableStyle(styleTable: HTMLDivElement): void {
for (let index = 0; index < styleTable.children.length; index++) {
styleTable.children[index].style.backgroundColor = 'var(--dark-background5,#F6F6F6)';
}
}
async initMetricData(): Promise<any> {
if (!this.selector || this.selector.value == null) {
return [];
}
if (this.queryText == '' || this.queryText == null) {
let statList = await querySelectTraceStats();
for (let index = 0; index < statList.length; index++) {
const statsResult = statList[index];
let indexArray = {
event_name: statsResult.event_name,
start_type: statsResult.stat_type,
count: statsResult.count,
serverity: statsResult.serverity,
source: statsResult.source,
};
}
if (this.querySize) {
this.querySize!.textContent = 'Query result - ' + statList.length + ' counts';
}
this.resultText = 'Query result - ' + statList.length + ' counts';
} else {
return this.statDataArray;
}
}
checkSupportSqlAbility(): boolean {
let noSupportChart = ['insert', 'delete', 'update', 'drop', 'alter', 'truncate', 'create'];
let result = noSupportChart.filter((item) => {
return this.selector!.value.indexOf(item) > -1;
});
if (result.length > 0) {
this.querySqlErrorText =
'Error: Statement contains a change action keyword,The change operation is not supported.';
this.isSupportSql = false;
return true;
} else {
return false;
}
}
checkSafetySelectSql(): boolean {
let split = this.selector?.value.trim().split(' ');
if (split) {
this.querySqlErrorText = 'Error: Incomplete query statement: ' + this.selector!.value;
this.isSupportSql = false;
return !split[0].toLowerCase().startsWith('select');
}
return false;
}
getSelectSqlField(): string {
if (this.selector!.value.indexOf('from') < 0) {
return '';
}
let splitSql = this.selector?.value.split('from');
if (splitSql) {
if (splitSql[0].indexOf('*') > -1) {
return '*';
} else {
let fields = splitSql[0].split(',');
return fields[0];
}
}
return '';
}
getSelectSqlTableName(str: string): Array<string> {
if (this.selector!.value.indexOf(str) < 0) {
return [];
}
let tableNameList = [];
let splitSql = this.selector?.value.split(str);
if (splitSql) {
for (let index = 1; index < splitSql?.length; index++) {
let splitSqlItem = splitSql[index].trim();
let tableItem = splitSqlItem.split(' ');
let tableName = tableItem[0].trim();
tableNameList.push(tableName);
if (tableName.indexOf('(') >= 0) {
tableNameList.pop();
} else if (tableName.indexOf(')') >= 0) {
tableNameList.pop();
let unitTableName = tableName.split(')');
let tableNewName = unitTableName[0];
tableNameList.push(tableNewName);
}
}
}
return tableNameList;
}
initDataElement() {
if (this.keyList) {
info('Metric query Table Colum size is: ', this.keyList.length);
this.keyList.forEach((item) => {
let htmlElement = document.createElement('lit-table-column') as LitTableColumn;
htmlElement.setAttribute('title', item);
htmlElement.setAttribute('data-index', item);
htmlElement.setAttribute('key', item);
htmlElement.setAttribute('align', 'flex-start');
htmlElement.setAttribute('height', '32px');
this.queryTableEl!.appendChild(htmlElement);
});
}
}
connectedCallback() {
let selectQuery = this.shadowRoot?.querySelector('.query_select');
if (selectQuery) {
let querySql = selectQuery.textContent;
}
this.addEventListener('keydown', this.selectEventListener);
this.selector!.addEventListener('input', this.inputSqlListener);
this.selector!.addEventListener('change', this.inputSqlListener);
this.selector!.addEventListener('keydown', this.deleteSqlListener);
}
deleteSqlListener = (event: any) => {
if (event.key == 'Backspace') {
this.resizeSqlHeight().then(() => {});
}
};
async resizeSqlHeight() {
let valueLength = this.selector?.value.split('\n').length;
let rowNumber = Number(valueLength) - 1;
let selectHeight = '3.2em';
if (rowNumber > 0) {
if (rowNumber <= 10) {
let allLength = 1.2 * rowNumber + 2;
selectHeight = allLength + 'em';
} else {
selectHeight = '14em';
}
}
this.selector?.style.height = selectHeight;
}
inputSqlListener = async (event: any) => {
this.resizeSqlHeight().then(() => {});
let startData = new Date().getTime();
if (this.selector!.value.trim() == '') {
this.querySqlErrorText = 'Please enter a query';
this.querySize!.textContent = this.querySqlErrorText;
return;
}
this.checkSafetySelectSql();
this.checkSupportSqlAbility();
if (this.selector!.value.length < 15) {
return;
}
this.querySelectTables = this.getSelectSqlTableName('from')
.concat(this.getSelectSqlTableName('join'))
.toLocaleString();
info('metric query sql table size is: ', this.querySelectTables.length);
this.isSupportSql = true;
};
async getInputSqlResult(sql: string): Promise<any> {
return await queryCustomizeSelect(sql);
}
disconnectedCallback() {
this.removeEventListener('keydown', this.selectEventListener);
this.selector!.removeEventListener('input', this.inputSqlListener);
this.selector!.removeEventListener('change', this.inputSqlListener);
this.selector!.removeEventListener('keydown', this.deleteSqlListener);
}
initData() {
if (this.statDataArray.length > 0) {
this.querySize!.textContent = 'Error: ' + this.selector?.value;
}
if (this.isSupportSql) {
let sqlField = this.keyList?.length == 0 ? '*' : this.keyList?.toLocaleString();
this.querySize!.textContent = 'Query result - ' + this.statDataArray.length + ' counts';
} else {
this.querySize!.textContent = this.querySqlErrorText;
}
let queryHeadStyle: HTMLDivElement | undefined | null = this.queryTableEl?.shadowRoot?.querySelector(
'div.th'
) as HTMLDivElement;
if (queryHeadStyle && queryHeadStyle.hasChildNodes()) {
for (let index = 0; index < queryHeadStyle.children.length; index++) {
queryHeadStyle.children[index].style.gridArea = null;
}
}
this.queryTableEl!.style.height = '100%';
}
static get observedAttributes() {
return ['queryStr'];
}
attributeChangedCallback(name: string, oldValue: string, newValue: string) {
let queryDataSty: HTMLDivElement | undefined | null = this.queryTableEl?.shadowRoot?.querySelector(
'div.tbody'
) as HTMLDivElement;
if (queryDataSty && queryDataSty.hasChildNodes()) {
for (let index = 0; index < queryDataSty.children.length; index++) {
queryDataSty.children[index].style.backgroundColor = 'var(--dark-background5,#F6F6F6)';
}
}
}
private _queryStr?: string;
get queryStr(): string {
return this.queryStr;
}
set queryStr(value: string) {
this._queryStr = value;
}
initHtml(): string {
return `
<style>
:host{
width: 100%;
height: 100%;
font-size: 16px;
background-color: var(--dark-background5,#F6F6F6);
margin: 0;
padding: 0;
}
.sql-select{
box-sizing: border-box;
width: 95%;
font-family: Helvetica,serif;
font-size: inherit;
color: var(--dark-color1,#212121);
text-align: left;
line-height: 1.2em;
font-weight: 400;
height: 3.2em;
margin-left: 10px;
resize: vertical;
border-width: 2px;
}
.query{
display: flex;
flex-direction: column;
background-color: var(--dark-background5,#F6F6F6);
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.query-message{
background-color: var(--dark-background3,#FFFFFF);
padding: 1% 2%;
margin: 2% 2.5% 0 2.5%;
border-radius: 16px;
width: 90%;
}
.request{
display: flex;
flex-direction: column;
position: relative;
}
.response{
flex-grow: 1;
margin-bottom: 1%;
display: flex;
flex-direction: column;
min-height: inherit;
max-height: 70vh;
}
#dataResult{
flex-grow: 1;
overflow-y: auto;
overflow-x: visible;
margin-bottom: 1%;
border-radius: 16px;
}
p{
display: table-cell;
padding: 7px 10px;
font-size:0.875em;
line-height: 20px;
font-weight: 400;
text-align: left;
}
#response-json{
margin-top: 20px;
background-color: var(--dark-background5,#F6F6F6);
margin-left: 10px;
flex-grow: 1;
scroll-y: visible;
}
.sql-select{
background-color: var(--dark-background5, #F6F6F6);
}
/*Define the height, width and background of the scroll bar*/
::-webkit-scrollbar
{
width: 8px;
background-color: var(--dark-background3,#FFFFFF);
}
/*define slider*/
::-webkit-scrollbar-thumb
{
border-radius: 6px;
background-color: var(--dark-background7,rgba(0,0,0,0.1));
}
.load-metric{
width: 95%;
bottom: 0;
}
#copy-button{
margin-right: 10%;
cursor:pointer;
opacity: 0.6;
}
#close-button{
margin-right: 5%;
cursor:pointer;
opacity: 0.6;
}
.button-option{
border-radius: 15px;
background-color: #0A59F7;
width: 120px;
height: 25px;
font-family: Helvetica-Bold;
color: var(--dark-background3,#FFFFFF);
text-align: center;
line-height: 20px;
font-weight: 400;
border:0 solid;
}
.pagination-box {
opacity: 0;
}
</style>
<div class="query">
<div class="query-message request">
<p class="query_select" style="color: #999999">Enter query and press cmd/ctrl + Enter</p>
<textarea class="sql-select"></textarea>
<lit-progress-bar class="load-metric"></lit-progress-bar>
</div>
<div class="query-message response">
<div style="display: flex;justify-content: space-between">
<p class="query_size" style="color: #999999">Query result - 0 counts</p>
<div style="display: flex; align-items: center">
<button id="copy-button" class="button-option">Copy as.tsv</button>
<button id="close-button" class="button-option">Close</button>
</div>
</div>
<div id="dataResult"></div>
<pagination-box class="pagination-box"></pagination-box>
</div>
</div>
`;
}
}