Copilot commented on code in PR #114: URL: https://github.com/apache/iotdb-extras/pull/114#discussion_r3618972947
########## connectors/grafana-plugin/pkg/plugin/table_query.go: ########## @@ -0,0 +1,396 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package plugin + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "io" + "net/http" + "regexp" + "sort" + "strconv" + "strings" + "time" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/backend/log" + "github.com/grafana/grafana-plugin-sdk-go/data" +) + +// TableModelSqlType is the QueryEditor mode that sends standard table-model SQL +// straight to IoTDB's table REST interface, as opposed to the tree-model modes +// ("SQL: Full Customized" / "SQL: Drop-down List") that build root.* paths. +const TableModelSqlType = "SQL: Table Model" + +// Result formats for the table-model mode. Time series (the default) sorts +// rows ascending by the first TIMESTAMP column and pivots a long-shaped result +// (time + tag columns + value columns) into one labeled series per tag +// combination; Table returns the rows exactly as the server sent them. +const ( + tableFormatTimeSeries = "Time series" + tableFormatTable = "Table" +) + +// tableQueryPath is IoTDB's table-model query endpoint (see the /rest/table/v1 +// RestApi). It accepts a standard SQL statement plus an optional database +// context and returns a column-major QueryDataSet. Review Comment: The comment says the table REST endpoint returns a "column-major QueryDataSet", but this file (and the tests) treat the response as row-major (values[row][col]). The comment should be corrected to avoid misleading future maintainers. ########## connectors/grafana-plugin/src/QueryEditor.tsx: ########## @@ -247,14 +279,30 @@ export class QueryEditor extends PureComponent<Props, State> { control: '', }); onChange({ ...query, sqlType: value, isDropDownList: true }); + } else { + this.props.query.sqlType = selectType[2]; + this.props.query.expression = ['']; + this.props.query.prefixPath = ['']; + this.props.query.condition = ''; + this.props.query.control = ''; + this.props.query.isDropDownList = false; + this.setState({ + isDropDownList: false, + sqlType: selectType[2], + expression: [''], + prefixPath: [''], + condition: '', + control: '', + }); + onChange({ ...query, sqlType: value, isDropDownList: false }); } Review Comment: This branch mutates `this.props.query` directly (sqlType/expression/prefixPath/condition/control/isDropDownList). Mutating props can lead to stale renders and makes state/query synchronization harder; prefer constructing the next query object and passing it to `onChange` without writing into `this.props.query`. ########## connectors/grafana-plugin/pkg/plugin/table_query.go: ########## @@ -0,0 +1,396 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package plugin + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "io" + "net/http" + "regexp" + "sort" + "strconv" + "strings" + "time" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/backend/log" + "github.com/grafana/grafana-plugin-sdk-go/data" +) + +// TableModelSqlType is the QueryEditor mode that sends standard table-model SQL +// straight to IoTDB's table REST interface, as opposed to the tree-model modes +// ("SQL: Full Customized" / "SQL: Drop-down List") that build root.* paths. +const TableModelSqlType = "SQL: Table Model" + +// Result formats for the table-model mode. Time series (the default) sorts +// rows ascending by the first TIMESTAMP column and pivots a long-shaped result +// (time + tag columns + value columns) into one labeled series per tag +// combination; Table returns the rows exactly as the server sent them. +const ( + tableFormatTimeSeries = "Time series" + tableFormatTable = "Table" +) + +// tableQueryPath is IoTDB's table-model query endpoint (see the /rest/table/v1 +// RestApi). It accepts a standard SQL statement plus an optional database +// context and returns a column-major QueryDataSet. +const tableQueryPath = "/rest/table/v1/query" + +// tableQueryReq is the request body for tableQueryPath. Field names match the +// generated table/v1 SQL model (database / sql / row_limit). +type tableQueryReq struct { + Database string `json:"database,omitempty"` + Sql string `json:"sql"` + RowLimit *int `json:"row_limit,omitempty"` +} + +// tableQueryDataSet mirrors the table/v1 QueryDataSet response. Its values are +// ROW-major (values[row][col]) because the table endpoint transposes before +// serializing, unlike the tree-model endpoints. snake_case JSON. On failure +// IoTDB returns an ExecutionStatus instead, whose code/message are captured +// here so a non-zero code surfaces as an error. +type tableQueryDataSet struct { + ColumnNames []string `json:"column_names"` + DataTypes []string `json:"data_types"` + Values [][]interface{} `json:"values"` + Code int32 `json:"code"` + Message string `json:"message"` +} + +// timeFilterRe matches Grafana's $__timeFilter(column) macro; the column is +// optional and defaults to "time". One level of nested parentheses is allowed +// so expressions like $__timeFilter(cast(x)) survive intact. +var timeFilterRe = regexp.MustCompile(`\$__timeFilter\(\s*((?:[^()]|\([^()]*\))*?)\s*\)`) + +// timeFromRe / timeToRe match $__timeFrom / $__timeTo, with or without the +// trailing () that Grafana's SQL data sources use ($__timeFrom()). +var ( + timeFromRe = regexp.MustCompile(`\$__timeFrom\b(?:\s*\(\s*\))?`) + timeToRe = regexp.MustCompile(`\$__timeTo\b(?:\s*\(\s*\))?`) +) + +// timestampUnits maps the datasource's timestampPrecision option (which must +// match the server's timestamp_precision property) to conversion factors: +// unitsPerMs scales the panel's epoch-ms range into server units for the time +// macros, nsPerUnit scales raw TIMESTAMP values into nanoseconds for Grafana. +func timestampUnits(precision string) (unitsPerMs int64, nsPerUnit int64) { + switch strings.TrimSpace(strings.ToLower(precision)) { + case "us": + return 1000, int64(time.Microsecond) + case "ns": + return 1000000, 1 + default: // ms is IoTDB's default timestamp_precision + return 1, int64(time.Millisecond) + } +} + +// expandTableMacros rewrites the Grafana time macros a dashboard author can put +// in table-model SQL into concrete bounds for the panel's range: +// +// $__timeFilter(col) -> (col >= <from> AND col <= <to>) +// $__timeFrom[()] -> <from> +// $__timeTo[()] -> <to> +// +// Bounds are epoch values in the server's timestamp precision (milliseconds +// unless the datasource says otherwise), matching how IoTDB compares integer +// literals against TIMESTAMP columns. +func expandTableMacros(sql string, start int64, end int64) string { + from := strconv.FormatInt(start, 10) + to := strconv.FormatInt(end, 10) + sql = timeFilterRe.ReplaceAllStringFunc(sql, func(m string) string { + col := strings.TrimSpace(timeFilterRe.FindStringSubmatch(m)[1]) + if col == "" { + col = "time" + } + return "(" + col + " >= " + from + " AND " + col + " <= " + to + ")" + }) + sql = timeFromRe.ReplaceAllString(sql, from) + sql = timeToRe.ReplaceAllString(sql, to) + return sql +} + +// queryTableModel runs a table-model SQL query against IoTDB's table REST +// endpoint and turns the column-major QueryDataSet into a Grafana data frame. +func (d *IoTDBDataSource) queryTableModel(ctx context.Context, qp *queryParam, authorization string) backend.DataResponse { Review Comment: This comment refers to a "column-major QueryDataSet", but the table-model response is handled as row-major (values[row][col]) throughout this file. Align the wording with the actual orientation to prevent confusion. ########## connectors/grafana-plugin/src/datasource.ts: ########## @@ -126,6 +126,13 @@ export class DataSource extends DataSourceWithBackend<IoTDBQuery, IoTDBOptions> if (query.fillClauses) { query.fillClauses = getTemplateSrv().replace(query.fillClauses, scopedVars); } + } else if (query.sqlType === 'SQL: Table Model') { + if (query.sql) { + query.sql = getTemplateSrv().replace(query.sql, scopedVars); + } + if (query.database) { + query.database = getTemplateSrv().replace(query.database, scopedVars); + } Review Comment: The new `SQL: Table Model` branch in `applyTemplateVariables` adds templating behavior for `sql` and `database`, but there are no corresponding unit tests (existing tests cover the other modes). Adding a Jest test case would prevent regressions in macro/template expansion for table-model queries. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
