From 2f82c1241118634c49a271edf6c67bbef19b63de Mon Sep 17 00:00:00 2001 From: Justin Joyce Date: Mon, 24 Apr 2023 20:12:34 +0100 Subject: [PATCH 1/5] #1002 --- opteryx/__main__.py | 7 ++++--- opteryx/command.py | 15 +++++++++++++++ opteryx/connection.py | 17 +++++++++++++++-- opteryx/utils/__init__.py | 2 +- requirements.txt | 2 +- tests/misc/test_cli.py | 3 ++- tests/misc/test_connection_arrow.py | 13 +++++++++++++ 7 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 opteryx/command.py diff --git a/opteryx/__main__.py b/opteryx/__main__.py index a9dff7893..51af4b9eb 100644 --- a/opteryx/__main__.py +++ b/opteryx/__main__.py @@ -56,15 +56,16 @@ def main( print(f"Opteryx version {opteryx.__version__}") print(" Enter '.help' for usage hints") print(" Enter '.exit' to exit this program") - print() # Start the REPL loop while True: # pragma: no cover # Prompt the user for a SQL statement + print() statement = input('opteryx> ') - # If the user entered "quit", exit the loop - if statement == '.exit': + # If the user entered "exit", exit the loop + # forgive them for 'quit' + if statement in {'.exit', '.quit'}: break if statement == ".help": print(" .exit Exit this program") diff --git a/opteryx/command.py b/opteryx/command.py new file mode 100644 index 000000000..fce276d34 --- /dev/null +++ b/opteryx/command.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python + +# 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. + +from opteryx.__main__ import main \ No newline at end of file diff --git a/opteryx/connection.py b/opteryx/connection.py index 013061284..e433df827 100644 --- a/opteryx/connection.py +++ b/opteryx/connection.py @@ -20,6 +20,7 @@ import typing from uuid import uuid4 +import pyarrow from orso import DataFrame from orso import converters @@ -109,7 +110,7 @@ def id(self): """The unique internal reference for this query""" return self._qid - def execute(self, operation, params=None): + def _inner_execute(self, operation, params=None): if not operation: raise MissingSqlStatement("SQL statement not found") @@ -145,9 +146,21 @@ def execute(self, operation, params=None): results = self._query_planner.execute(self._plan) if results is not None: - self._rows, self._schema = converters.from_arrow(utils.arrow.rename_columns(results)) + return utils.arrow.rename_columns(results) + + def execute(self, operation, params=None): + results = self._inner_execute(operation, params) + if results is not None: + self._rows, self._schema = converters.from_arrow(results) self._cursor = iter(self._rows) + def execute_to_arrow(self, operation, params=None, limit=None): + results = self._inner_execute(operation, params) + if results is not None: + if limit is not None: + return utils.arrow.limit_records(results, limit) + return pyarrow.concat_tables(results, promote=True) + @property def stats(self): """execution statistics""" diff --git a/opteryx/utils/__init__.py b/opteryx/utils/__init__.py index 6092962e4..133edb0d6 100644 --- a/opteryx/utils/__init__.py +++ b/opteryx/utils/__init__.py @@ -24,7 +24,7 @@ def hasher(vals): This is roughly 2x faster than the previous implementation for lists of strings. Do note though, if you're micro-optimizing, this is faster to create but is - slower for some Python functions to handle, like 'sorted'. + slower for some Python functions to handle the result of, like 'sorted'. """ if numpy.issubdtype(vals.dtype, numpy.character): return numpy.array([CityHash64(s.encode()) for s in vals], numpy.uint64) diff --git a/requirements.txt b/requirements.txt index bcc3bb047..f9f2e9efd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ hadrodb numpy orjson -orso>=0.0.57 +orso>=0.0.61 pyarrow>=11.0.0 typer diff --git a/tests/misc/test_cli.py b/tests/misc/test_cli.py index d29c8cfb8..8f4da98a9 100644 --- a/tests/misc/test_cli.py +++ b/tests/misc/test_cli.py @@ -7,7 +7,7 @@ sys.path.insert(1, os.path.join(sys.path[0], "../..")) -from opteryx.__main__ import main +from opteryx.command import main def test_basic_cli(): @@ -16,6 +16,7 @@ def test_basic_cli(): main(sql="SELECT * FROM $planets;", o="temp.csv") main(sql="SELECT * FROM $planets;", o="temp.jsonl") main(sql="SELECT * FROM $planets;", o="temp.parquet") + main(sql="SELECT * FROM $planets;", o="temp.md") if __name__ == "__main__": # pragma: no cover diff --git a/tests/misc/test_connection_arrow.py b/tests/misc/test_connection_arrow.py index d484aaada..55adc62ff 100644 --- a/tests/misc/test_connection_arrow.py +++ b/tests/misc/test_connection_arrow.py @@ -33,8 +33,21 @@ def test_as_arrow_with_limit(): assert len(table.column_names) == 20 +def test_direct_as_arrow_no_limit(): + import opteryx + + conn = opteryx.connect() + cur = conn.cursor() + table = cur.execute_to_arrow("SELECT * FROM $planets") + + assert "name" in table.column_names + assert table.num_rows == 9 + assert len(table.column_names) == 20 + assert cur.stats["rows_read"] == 9, cur.stats + if __name__ == "__main__": # pragma: no cover test_as_arrow_no_limit() test_as_arrow_with_limit() + test_direct_as_arrow_no_limit() print("✅ okay") From c706456dfd0ad0c8ab848ee22cc7c77eec5fe51c Mon Sep 17 00:00:00 2001 From: Justin Joyce Date: Mon, 24 Apr 2023 21:49:41 +0100 Subject: [PATCH 2/5] #1002 --- README.md | 8 ++++---- opteryx/command.py | 2 +- tests/misc/test_connection_arrow.py | 1 + 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0f3c2bf78..d424d95d8 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@

-Opteryx is a SQL Engine designed for embedded and cloud-native environments, and with command-line skills. +Opteryx is an in-process SQL query engine for analysis of distributed datasets. [Documentation](https://opteryx.dev/latest) | [Examples](#examples) | @@ -26,7 +26,7 @@ Opteryx is a SQL Engine designed for embedded and cloud-native environments, and ## What is Opteryx? -Opteryx is a powerful Python library designed for data wrangling and analytics. With Opteryx, users can seamlessly interact with various data platforms, unlocking the full potential of their data. +Opteryx is a Python library designed for data wrangling and analytics. With Opteryx, users can seamlessly interact with various data platforms, unlocking the full potential of their data. Opteryx offers the following features: @@ -34,7 +34,7 @@ Opteryx offers the following features: - A command-line tool for filtering, transforming, and combining files in a flexible and intuitive manner. - Embeddable as a low-cost engine, allowing for hundreds of analysts to leverage ad hoc databases with ease. - Integration with familiar tools like pandas and Polars. -- Unified access to data on disk, in the Cloud and in on-prem databases, not only through the same interface, but in the same query. +- Unified and federated access to data on disk, in the Cloud and in on-prem databases, not only through the same interface, but in the same query. ## Why Use Opteryx? @@ -68,7 +68,7 @@ Opteryx is Open Source Python, it quickly and easily integrates into Python code ### __Time Travel__ -Designed for data analytics in environments where decisions need to be replayable, Opteryx allows you to query data as at a point in time in the past to replay decision algorithms against facts as they were known in the past. _(data must be structured to enable temporal queries)_ +Designed for data analytics in environments where decisions need to be replayable, Opteryx allows you to query data as at a point in time in the past to replay decision algorithms against facts as they were known in the past. You can even self-join tables historic data, great for finding deltas in datasets over time. _(data must be structured to enable temporal queries)_ ### __Fast__ diff --git a/opteryx/command.py b/opteryx/command.py index fce276d34..72235d66a 100644 --- a/opteryx/command.py +++ b/opteryx/command.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -from opteryx.__main__ import main \ No newline at end of file +from opteryx.__main__ import main diff --git a/tests/misc/test_connection_arrow.py b/tests/misc/test_connection_arrow.py index 55adc62ff..28424aa76 100644 --- a/tests/misc/test_connection_arrow.py +++ b/tests/misc/test_connection_arrow.py @@ -45,6 +45,7 @@ def test_direct_as_arrow_no_limit(): assert len(table.column_names) == 20 assert cur.stats["rows_read"] == 9, cur.stats + if __name__ == "__main__": # pragma: no cover test_as_arrow_no_limit() test_as_arrow_with_limit() From df4a5c9ea52b3c225d12de1e12515a3c2e20f143 Mon Sep 17 00:00:00 2001 From: Justin Joyce Date: Mon, 24 Apr 2023 22:33:35 +0100 Subject: [PATCH 3/5] #1003 --- opteryx/version.py | 2 +- tests/requirements.txt | 2 ++ tests/storage/test_sql_duckdb.py | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 tests/storage/test_sql_duckdb.py diff --git a/opteryx/version.py b/opteryx/version.py index 49f93a922..0847dfee4 100644 --- a/opteryx/version.py +++ b/opteryx/version.py @@ -17,4 +17,4 @@ """ # __version__ = "0.4.0-alpha.6" -__version__ = "0.10.0-alpha.5" +__version__ = "0.10.0-alpha.6" diff --git a/tests/requirements.txt b/tests/requirements.txt index 11e4412fa..8c6194cd8 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -25,5 +25,7 @@ sqlalchemy pymysql psycopg2-binary polars +duckdb +duckdb-engine setuptools_rust \ No newline at end of file diff --git a/tests/storage/test_sql_duckdb.py b/tests/storage/test_sql_duckdb.py new file mode 100644 index 000000000..372d0cc27 --- /dev/null +++ b/tests/storage/test_sql_duckdb.py @@ -0,0 +1,32 @@ +""" +Test we can read from DuckDB - this is a basic exercise of the SQL Connector +""" +import os +import sys + +sys.path.insert(1, os.path.join(sys.path[0], "../..")) + +import opteryx + +from opteryx.connectors import SqlConnector + + +def test_duckdb_storage(): + opteryx.register_store( + "duckdb", + SqlConnector, + remove_prefix=True, + connection="duckdb:///testdata/duckdb/planets.duckdb", + ) + + results = opteryx.query("SELECT * FROM duckdb.planets") + assert results.rowcount == 9, results.rowcount + + # PROCESS THE DATA IN SOME WAY + results = opteryx.query("SELECT COUNT(*) FROM duckdb.planets;") + assert results.rowcount == 1, results.rowcount + + +if __name__ == "__main__": # pragma: no cover + test_duckdb_storage() + print("✅ okay") From dd127a4c282552ef729d5f3a0b83394c68fc2ac6 Mon Sep 17 00:00:00 2001 From: Justin Joyce Date: Mon, 24 Apr 2023 22:47:21 +0100 Subject: [PATCH 4/5] #1003 --- testdata/duckdb/planets.duckdb | Bin 0 -> 1060864 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 testdata/duckdb/planets.duckdb diff --git a/testdata/duckdb/planets.duckdb b/testdata/duckdb/planets.duckdb new file mode 100644 index 0000000000000000000000000000000000000000..936d1eba3fb552cc957f5a6f3ddf4006028423e8 GIT binary patch literal 1060864 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 z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N 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 ICQab~1CZgvvj6}9 literal 0 HcmV?d00001 From 50f07a019ae5322f141b89437468674e1d122b5f Mon Sep 17 00:00:00 2001 From: Justin Joyce Date: Mon, 24 Apr 2023 23:06:22 +0100 Subject: [PATCH 5/5] #1003 --- tests/requirements_arm.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/requirements_arm.txt b/tests/requirements_arm.txt index a72f38f3a..3567a5b05 100644 --- a/tests/requirements_arm.txt +++ b/tests/requirements_arm.txt @@ -18,5 +18,7 @@ firebase-admin sqlalchemy pymysql psycopg2-binary +duckdb +duckdb-engine setuptools_rust \ No newline at end of file