blob: a634210c63feccbf5b076e0ad9303e5424878c0e [file] [log] [blame]
// Copyright 2023 Google LLC
//
// 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 query
import (
"strings"
)
type qnode struct {
query string
children []*qnode
}
// HasOrderedResults checks if a given SQL query returns ordered results.
// This function uses a naive approach of checking the root level query
// ( ignoring subqueries, functions calls, etc ) and checking
// if it contains an ORDER BY clause.
func HasOrderedResults(sql string) bool {
cleanedQuery := strings.TrimSpace(sql)
if !strings.HasPrefix(cleanedQuery, "(") {
cleanedQuery = "(" + cleanedQuery + ")"
}
root := &qnode{query: cleanedQuery, children: []*qnode{}}
curNode := root
indexStack := []int{}
nodeStack := []*qnode{}
for i, c := range cleanedQuery {
if c == '(' {
indexStack = append(indexStack, i)
nextNode := &qnode{children: []*qnode{}}
curNode.children = append(curNode.children, nextNode)
nodeStack = append(nodeStack, curNode)
curNode = nextNode
}
if c == ')' {
if len(indexStack) == 0 {
// unbalanced
return false
}
start := indexStack[len(indexStack)-1]
indexStack = indexStack[:len(indexStack)-1]
curNode.query = cleanedQuery[start+1 : i]
curNode = nodeStack[len(nodeStack)-1]
nodeStack = nodeStack[:len(nodeStack)-1]
}
}
curNode = root.children[0]
q := curNode.query
for _, c := range curNode.children {
q = strings.Replace(q, c.query, "", 1)
}
return strings.Contains(strings.ToUpper(q), "ORDER BY")
}