Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#1586 #1678

Merged
merged 2 commits into from
May 26, 2024
Merged

#1586 #1678

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion opteryx/__version__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__build__ = 517
__build__ = 518

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
1 change: 0 additions & 1 deletion opteryx/connectors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ def connector_factory(dataset, statistics, **config):
from opteryx.connectors import file_connector

return file_connector.FileConnector(dataset=dataset, statistics=statistics)

# fall back to the default connector (local disk if not set)
connector = _storage_prefixes.get("_default", DiskConnector)

Expand Down
24 changes: 24 additions & 0 deletions opteryx/planner/logical_planner/logical_planner.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@
from opteryx.managers.expression import get_all_nodes_of_type
from opteryx.models import Node
from opteryx.planner.logical_planner import logical_planner_builders
from opteryx.planner.views import is_view
from opteryx.planner.views import view_as_plan
from opteryx.third_party.travers import Graph


Expand Down Expand Up @@ -663,6 +665,28 @@ def create_node_relation(relation):
root_node = step_id
else: # pragma: no cover
raise NotImplementedError(relation["relation"]["Derived"])
elif is_view(relation["relation"]["Table"]["name"][0]["value"]):
# We're a view, we need to add it to the plan as a subquery
view_name = relation["relation"]["Table"]["name"][0]["value"]
sub_plan = view_as_plan(view_name=view_name)
plan_head = sub_plan.get_exit_points()[0]

# Replace the exit node with a subquery node
# We call the subquery the name of the view if we don't have an alias
# and we use the colums from the exit node
subquery_node = LogicalPlanNode(node_type=LogicalPlanStepType.Subquery)
subquery_node.alias = (
view_name
if relation["relation"]["Table"]["alias"] is None
else relation["relation"]["Table"]["alias"]["name"]["value"]
)
subquery_node.columns = sub_plan[plan_head].columns
sub_plan[plan_head] = subquery_node
root_node = plan_head

# DEBUG: log (f"VIEW PLAN")
# DEBUG: log (sub_plan)

elif relation["relation"]["Table"]["args"]:
function = relation["relation"]["Table"]
function_name = function["name"][0]["value"].upper()
Expand Down
47 changes: 47 additions & 0 deletions opteryx/planner/views/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# 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 orjson

from opteryx.managers.expression import NodeType
from opteryx.third_party.travers import Graph


def _load_views():
try:
with open("views.json", "rb") as defs:
return orjson.loads(defs.read())
except Exception as err:
print(f"[OPTERYX] Unable to open views definition file. {err}")
return {}


VIEWS = _load_views()


def is_view(view_name: str) -> bool:
return view_name in VIEWS


def view_as_plan(view_name: str) -> Graph:
from opteryx.planner.logical_planner import do_logical_planning_phase
from opteryx.third_party import sqloxide
from opteryx.utils.sql import clean_statement
from opteryx.utils.sql import remove_comments

operation = VIEWS.get(view_name)["statement"]

clean_sql = clean_statement(remove_comments(operation))
parsed_statements = sqloxide.parse_sql(clean_sql, dialect="mysql")
logical_plan, _, _ = next(do_logical_planning_phase(parsed_statements))

return logical_plan
15 changes: 15 additions & 0 deletions tests/sql_battery/test_shapes_and_errors_battery.py
Original file line number Diff line number Diff line change
Expand Up @@ -1346,6 +1346,7 @@
("SELECT * FROM $astronauts WHERE LIST_CONTAINS_ANY(missions, @@user_memberships)", 3, 19, None),
("SELECT $missions.* FROM $missions INNER JOIN $user ON Mission = value WHERE attribute = 'membership'", 1, 8, None),

# TEST FUNCTIONS
("EXECUTE PLANETS_BY_ID (id=1)", 1, 20, None), # simple case
("EXECUTE PLANETS_BY_ID (1)", None, None, ParameterError), # simple case)
("EXECUTE PLANETS_BY_ID (name=1)", None, None, ParameterError), # simple case)
Expand All @@ -1355,6 +1356,20 @@
("EXECUTE GET_SATELLITES_BY_PLANET_NAME(name='Jupiter')", 67, 1, SqlError), # string param
("EXECUTE multiply_two_numbers (one=1.0, two=9.9)", 1, 1, None), # multiple params

# TEST VIEWS
("SELECT * FROM mission_reports", 177, 1, None),
("SELECT * FROM mission_reports AS MR", 177, 1, None),
("SELECT MR.* FROM mission_reports AS MR", 177, 1, None),
("SELECT satellite_name FROM mission_reports", 177, 1, None),
("SELECT MR.satellite_name FROM mission_reports AS MR", 177, 1, None),
("SELECT satellite_name FROM mission_reports AS MR WHERE satellite_name ILIKE '%a%'", 90, 1, None),
("SELECT satellite_name FROM mission_reports AS MR WHERE satellite_name ILIKE '%a%'", 90, 1, None),
("SELECT * FROM mission_reports INNER JOIN $satellites ON satellite_name = name", 177, 9, None),
("SELECT * FROM my_mission_reports", 3, 19, None),
("SELECT * FROM my_mission_reports WHERE year = 1963", 2, 19, None),
("SELECT * FROM my_mission_reports ORDER BY name", 3, 19, None),
("SELECT name, status FROM my_mission_reports", 3, 2, None),

# These are queries which have been found to return the wrong result or not run correctly
# FILTERING ON FUNCTIONS
("SELECT DATE(birth_date) FROM $astronauts FOR TODAY WHERE DATE(birth_date) < '1930-01-01'", 14, 1, None),
Expand Down
8 changes: 8 additions & 0 deletions views.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"mission_reports": {
"statement": "/* A test case for VIEW functionality */ SELECT s.name AS satellite_name FROM $satellites AS s INNER JOIN $planets AS p ON p.id = s.planetId"
},
"my_mission_reports": {
"statement": "/* A test case for row-permissions functionality */ SELECT * FROM $astronauts WHERE LIST_CONTAINS_ANY(missions, @@user_memberships)"
}
}
Loading