featurebase/sql/mapper.go
2021-01-20 10:48:35 -06:00

115 lines
2.8 KiB
Go

// Copyright 2020 Pilosa Corp.
//
// 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.
package sql
import (
"strings"
"github.com/pilosa/pilosa/v2/logger"
"github.com/pkg/errors"
"vitess.io/vitess/go/vt/sqlparser"
)
const (
SQLTypeSelect = "select"
SQLTypeShow = "show"
SQLTypeEmpty = ""
)
// System errors.
var (
ErrMultipleSQLStatements = errors.New("statement contains multiple sql queries")
)
type Attributes map[string]interface{}
type MappedSQL struct {
SQLType string
Statement sqlparser.Statement
Mask QueryMask
Tables []string
SQL string
}
// Mapper is responsible for mapping a SQL query to structure representation
type Mapper struct {
Logger logger.Logger
}
func NewMapper() *Mapper {
return &Mapper{
Logger: logger.NopLogger,
}
}
// Parse parses SQL query
func (m *Mapper) Parse(sql string) (sqlparser.Statement, QueryMask, error) {
parsed, err := sqlparser.Parse(sql)
if err != nil {
return nil, QueryMask{}, errors.Wrap(err, "parsing sql")
}
qm := GenerateMask(parsed)
return parsed, qm, nil
}
// MapSQL converts a sql string into a MappedSQL object,
// which includes the parsed query and the query mask,
// among other information about the query.
func (m *Mapper) MapSQL(sql string) (*MappedSQL, error) {
// In the case where `sql` contains more than one query—since
// we don't support multiple return sets—we're going to just
// ignore everything and return a specific error type. This
// will allow the caller to handle it as needed (i.e. it can
// return the error, or return an empty result set).
if parts := strings.Split(sql, ";"); len(parts) > 1 {
var partCount int
for _, part := range parts {
if trimmed := strings.TrimSpace(part); trimmed != "" && trimmed != "\x00" {
partCount++
}
}
if partCount != 1 {
return nil, ErrMultipleSQLStatements
}
}
stmt, qm, err := m.Parse(sql)
if err != nil {
return nil, errors.Wrap(err, "parsing sql")
}
var sqlType string
var tableNames []string
switch slct := stmt.(type) {
case *sqlparser.Select:
sqlType = SQLTypeSelect
tableNames, err = extractTableNames(slct)
if err != nil {
return nil, errors.Wrap(err, "extracting table names")
}
case *sqlparser.Show:
sqlType = SQLTypeShow
}
return &MappedSQL{
SQLType: sqlType,
Statement: stmt,
Mask: qm,
Tables: tableNames,
SQL: sql,
}, nil
}