Skip to content

Commit

Permalink
Merge pull request #446 from mabel-dev/FEATURE/#351
Browse files Browse the repository at this point in the history
Feature/#351 Support `SHOW FUNCTIONS`
  • Loading branch information
joocer committed Aug 29, 2022
2 parents f1d1ce7 + df93953 commit eec7fa0
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 6 deletions.
1 change: 1 addition & 0 deletions docs/Release Notes/Change Log.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

- [[#366](https://github.com/mabel-dev/opteryx/issues/336)] Implement 'function not found' suggestions. ([@joocer](https://github.com/joocer))
- [[#443](https://github.com/mabel-dev/opteryx/issues/443)] Introduce a CLI. ([@joocer](https://github.com/joocer))
- [[#351](https://github.com/mabel-dev/opteryx/issues/351)] Support `SHOW FUNCTIONS`. ([@joocer](https://github.com/joocer))

**Changed**

Expand Down
18 changes: 13 additions & 5 deletions docs/SQL Reference/02 Statements.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,10 @@ LIMIT count
List the columns in a relation along with their data type. Without any modifiers, `SHOW COLUMNS` only reads a single page of data before returning.

~~~sql
SHOW [EXTENDED] [FULL] COLUMNS
FROM relation
LIKE pattern
FOR period
SHOW [EXTENDED] [FULL] COLUMNS
FROM relation
LIKE pattern
FOR period
~~~

### EXTENDED modifier
Expand Down Expand Up @@ -175,4 +175,12 @@ FOR DATES IN range

The `FOR` clause specifies the date to review data for. Although this supports the full syntax as per the `SELECT` statements.

See [Temporality](https://mabel-dev.github.io/opteryx/SQL%20Reference/09%20Temporality/) for more information on `FOR` syntax and functionality.
See [Temporality](https://mabel-dev.github.io/opteryx/SQL%20Reference/09%20Temporality/) for more information on `FOR` syntax and functionality.

## SHOW FUNCTIONS

List the functions and aggregators supported by the engine.

~~~sql
SHOW FUNCTIONS
~~~
36 changes: 36 additions & 0 deletions opteryx/managers/query/planner/planner.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ def create_plan(self, sql: str = None, ast: dict = None):
self._explain_planner(self._ast, self._statistics)
elif "ShowColumns" in self._ast[0]:
self._show_columns_planner(self._ast, self._statistics)
elif "ShowVariable" in self._ast[0]:
self._show_variable_planner(self._ast, self._statistics)
else: # pragma: no cover
raise SqlError("Unknown or unsupported Query type.")

Expand Down Expand Up @@ -720,6 +722,40 @@ def _extract_identifiers(self, ast):

return list(set(identifiers))

def _show_variable_planner(self, ast, statistics):
"""
SHOW <variable> only really has a single node.
All of the keywords should up as a 'values' list in the variable in the ast.
The last word is the variable, preceeding words are modifiers.
"""

directives = self._extract_directives(ast)

keywords = [
value["value"].upper() for value in ast[0]["ShowVariable"]["variable"]
]
if keywords[-1] == "FUNCTIONS":
show_node = "show_functions"
node = operators.ShowFunctionsNode(
directives=directives,
statistics=statistics,
)
self.add_operator(show_node, operator=node)
else: # pragma: no cover
raise SqlError(f"SHOW statement type not supported for `{keywords[-1]}`.")

name_column = ExpressionTreeNode(NodeType.IDENTIFIER, value="name")

order_by_node = operators.SortNode(
directives=directives,
statistics=statistics,
order=[([name_column], "ascending")],
)
self.add_operator("order", operator=order_by_node)
self.link_operators(show_node, "order")

def _naive_select_planner(self, ast, statistics):
"""
The naive planner only works on single tables and always puts operations in
Expand Down
1 change: 1 addition & 0 deletions opteryx/managers/query/planner/temporal.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
r"FULL\sOUTER\sJOIN",
r"JOIN",
r"WITH",
r"SHOW",
]

COMBINE_WHITESPACE_REGEX = re.compile(r"\s+")
Expand Down
3 changes: 2 additions & 1 deletion opteryx/operators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
from .outer_join_node import OuterJoinNode # LEFT/RIGHT/FULL OUTER JOIN
from .projection_node import ProjectionNode # remove unwanted columns including renames
from .selection_node import SelectionNode # filter unwanted rows
from .show_columns import ShowColumnsNode # column details
from .show_columns_node import ShowColumnsNode # column details
from .show_functions_node import ShowFunctionsNode # supported functions
from .sort_node import SortNode # order by selected columns


Expand Down
File renamed without changes.
60 changes: 60 additions & 0 deletions opteryx/operators/show_functions_node.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# 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.

"""
Show Functions Node
This is a SQL Query Execution Plan Node.
"""
from typing import Iterable
import pyarrow

from opteryx import operators, functions
from opteryx.models import Columns, QueryDirectives, QueryStatistics
from opteryx.operators import BasePlanNode


class ShowFunctionsNode(BasePlanNode):
def __init__(
self, directives: QueryDirectives, statistics: QueryStatistics, **config
):
super().__init__(directives=directives, statistics=statistics)
self._full = config.get("full")
self._extended = config.get("extended")

@property
def name(self): # pragma: no cover
return "Show Functions"

@property
def config(self): # pragma: no cover
return ""

def execute(self) -> Iterable:

buffer = []

for function in functions.functions():
buffer.append({"name": function, "type": "function"})
for aggregate in operators.aggregators():
buffer.append({"name": aggregate, "type": "aggregator"})

table = pyarrow.Table.from_pylist(buffer)
table = Columns.create_table_metadata(
table=table,
expected_rows=len(buffer),
name="show_columns",
table_aliases=[],
)

yield table
return
30 changes: 30 additions & 0 deletions tests/query_execution/test_show_functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""
Test show functions works; the number of functions is constantly changing so test it's
more than it was when we last reviewed this test.
"""
import os
import sys

sys.path.insert(1, os.path.join(sys.path[0], "../.."))


def test_documentation_connect_example():

import opteryx

conn = opteryx.connect()
cur = conn.cursor()
cur.execute("SHOW FUNCTIONS")
rows = cur.fetchall()

# below here is not in the documentation
rows = list(rows)
assert len(rows) > 85, len(rows)
conn.close()


if __name__ == "__main__": # pragma: no cover

test_documentation_connect_example()

print("✅ okay")

0 comments on commit eec7fa0

Please sign in to comment.