diff --git a/CHANGELOG.md b/CHANGELOG.md index bfefa57d2..a15174f7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## 0.10.7dev +* [Feature] Add Spark Connection as a dialect for Jupysql ([#965](https://github.com/ploomber/jupysql/issues/965)) (by [@gilandose](https://github.com/gilandose)) + ## 0.10.6 (2023-12-21) * [Fix] Fix error when `%sql` includes a query with negative numbers ([#958](https://github.com/ploomber/jupysql/issues/958)) diff --git a/doc/_toc.yml b/doc/_toc.yml index 2d9850b10..f667e807b 100644 --- a/doc/_toc.yml +++ b/doc/_toc.yml @@ -43,6 +43,7 @@ parts: - file: integrations/duckdb-native - file: integrations/compatibility - file: integrations/chdb + - file: integrations/spark - caption: API Reference chapters: diff --git a/doc/api/configuration.md b/doc/api/configuration.md index e2bb114a5..254ea712a 100644 --- a/doc/api/configuration.md +++ b/doc/api/configuration.md @@ -234,6 +234,26 @@ value enables the ones from previous values plus new ones: - `2`: All feedback - Footer to distinguish pandas/polars data frames from JupySQL's result sets +## `lazy_execution` + +```{versionadded} 0.10.7 +This option only works when connecting to Spark +``` + +Default: `False` + +Return lazy relation to dataset rather than executing through JupySql. + +```{code-cell} ipython3 +%config SqlMagic.lazy_execution = True +df = %sql SELECT * FROM languages +``` + +```{code-cell} ipython3 +%config SqlMagic.lazy_execution = False +res = %sql SELECT * FROM languages +``` + ## `named_parameters` ```{versionadded} 0.9 diff --git a/doc/conf.py b/doc/conf.py index 5e1792154..39080d77d 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -27,6 +27,7 @@ "integrations/oracle.ipynb", "integrations/snowflake.ipynb", "integrations/redshift.ipynb", + "integrations/spark.ipynb", ] nb_execution_in_temp = True nb_execution_show_tb = True diff --git a/doc/integrations/compatibility.md b/doc/integrations/compatibility.md index 4e6b36432..d59760a98 100644 --- a/doc/integrations/compatibility.md +++ b/doc/integrations/compatibility.md @@ -114,4 +114,20 @@ These table reflects the compatibility status of JupySQL `>=0.7` - Listing tables with `%sqlcmd tables` ✅ - Listing columns with `%sqlcmd columns` ✅ - Parametrized SQL queries via `{{parameter}}` ✅ -- Interactive SQL queries via `--interact` ✅ \ No newline at end of file +- Interactive SQL queries via `--interact` ✅ + +## Spark + +- Running queries with `%%sql` ✅ +- CTEs with `%%sql --save NAME` ✅ +- Plotting with `%%sqlplot boxplot` ❓ +- Plotting with `%%sqlplot bar` ✅ +- Plotting with `%%sqlplot pie` ✅ +- Plotting with `%%sqlplot histogram` ✅ +- Plotting with `ggplot` ✅ +- Profiling tables with `%sqlcmd profile` ✅ +- Listing tables with `%sqlcmd tables` ❌ +- Listing columns with `%sqlcmd columns` ❌ +- Parametrized SQL queries via `{{parameter}}` ✅ +- Interactive SQL queries via `--interact` ✅ +- Persisting Dataframes via `--persist` ✅ \ No newline at end of file diff --git a/doc/integrations/spark.ipynb b/doc/integrations/spark.ipynb new file mode 100644 index 000000000..4f150500d --- /dev/null +++ b/doc/integrations/spark.ipynb @@ -0,0 +1,1399 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Spark\n", + "\n", + "This tutorial will show you how to get a Spark instance up and running locally to integrate with JupySQL. You can run this in a Jupyter notebook. We'll use [Spark Connect](https://spark.apache.org/docs/latest/api/python/getting_started/quickstart_connect.html) which is the new thin client for Spark" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Pre-requisites\n", + "\n", + "To run this tutorial, you need to install following Python packages:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Note: you may need to restart the kernel to use updated packages.\n" + ] + } + ], + "source": [ + "%pip install jupysql pyspark==3.4.1 arrow pyarrow==12.0.1 pandas grpcio-status --quiet" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Start Spark instance\n", + "\n", + "We fetch the official image, create a new database, and user (this will take a few seconds)." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "12f699ee8e8e35ab10186f3c39024a7e443691bb4213e56ca3c2e90cd80daf1b\n" + ] + } + ], + "source": [ + "%%bash\n", + "docker run -p 15002:15002 -p 4040:4040 -d --name spark wh1isper/sparglim-server" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Our database is running, let's load some data!" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load sample data\n", + "\n", + "Now, let's fetch some sample data. We'll be using the [NYC taxi dataset](https://www.nyc.gov/site/tlc/about/tlc-trip-record-data.page):" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "import pandas as pd\n", + "from pyspark.sql.connect.session import SparkSession\n", + "\n", + "spark = SparkSession.builder.remote(\"sc://localhost\").getOrCreate()\n", + "\n", + "df = pd.read_parquet(\n", + " \"https://d37ci6vzurychx.cloudfront.net/trip-data/yellow_tripdata_2021-01.parquet\"\n", + ")\n", + "sparkDf = spark.createDataFrame(df.head(10000))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Set [eagerEval](https://spark.apache.org/docs/latest/api/python/getting_started/quickstart_df.html#Viewing-Data) on to print dataframes, This makes Spark print dataframes eagerly in notebook environments, rather than it's default lazy execution which requires .show() to see the data. In Spark 3.4.1 we need to override, as below, but in 3.5.0 it will print in html. " + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "def __pretty_(self, p, cycle):\n", + " self.show(truncate=False)\n", + "\n", + "\n", + "from pyspark.sql.connect.dataframe import DataFrame\n", + "\n", + "DataFrame._repr_pretty_ = __pretty_\n", + "spark.conf.set(\"spark.sql.repl.eagerEval.enabled\", True)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Add dataset to temporary view to allow querying:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "sparkDf.createOrReplaceTempView(\"taxi\")" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Query\n", + "\n", + "Now, let's start JupySQL, authenticate, and query the data!" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The sql extension is already loaded. To reload it, use:\n", + " %reload_ext sql\n" + ] + } + ], + "source": [ + "%load_ext sql" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "%sql spark" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```{important}\n", + "If the cell above fails, you might have some missing packages. Message us on [Slack](https://ploomber.io/community) and we'll help you!\n", + "```" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "List the tables in the database:" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "Running query in 'SparkSession'" + ], + "text/plain": [ + "Running query in 'SparkSession'" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
namespaceviewNameisTemporary
taxiTrue
" + ], + "text/plain": [ + "+-----------+----------+-------------+\n", + "| namespace | viewName | isTemporary |\n", + "+-----------+----------+-------------+\n", + "| | taxi | True |\n", + "+-----------+----------+-------------+" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%sql show views in default" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can turn on `lazy_spark` to avoid executing spark plan and return a Spark Dataframe" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [], + "source": [ + "%config SqlMagic.lazy_execution = True" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "Running query in 'SparkSession'" + ], + "text/plain": [ + "Running query in 'SparkSession'" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+---------+--------+-----------+\n", + "|namespace|viewName|isTemporary|\n", + "+---------+--------+-----------+\n", + "| |taxi |true |\n", + "+---------+--------+-----------+\n", + "\n" + ] + }, + { + "data": { + "text/plain": [] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%sql show views in default" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [], + "source": [ + "%config SqlMagic.lazy_execution = False" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "List columns in the taxi table:" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "Running query in 'SparkSession'" + ], + "text/plain": [ + "Running query in 'SparkSession'" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "root\n", + " |-- VendorID: long (nullable = true)\n", + " |-- tpep_pickup_datetime: timestamp (nullable = true)\n", + " |-- tpep_dropoff_datetime: timestamp (nullable = true)\n", + " |-- passenger_count: double (nullable = true)\n", + " |-- trip_distance: double (nullable = true)\n", + " |-- RatecodeID: double (nullable = true)\n", + " |-- store_and_fwd_flag: string (nullable = true)\n", + " |-- PULocationID: long (nullable = true)\n", + " |-- DOLocationID: long (nullable = true)\n", + " |-- payment_type: long (nullable = true)\n", + " |-- fare_amount: double (nullable = true)\n", + " |-- extra: double (nullable = true)\n", + " |-- mta_tax: double (nullable = true)\n", + " |-- tip_amount: double (nullable = true)\n", + " |-- tolls_amount: double (nullable = true)\n", + " |-- improvement_surcharge: double (nullable = true)\n", + " |-- total_amount: double (nullable = true)\n", + " |-- congestion_surcharge: double (nullable = true)\n", + " |-- airport_fee: double (nullable = true)\n", + "\n" + ] + } + ], + "source": [ + "df = %sql select * from taxi\n", + "df.sqlaproxy.dataframe.printSchema()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Query our data:" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "Running query in 'SparkSession'" + ], + "text/plain": [ + "Running query in 'SparkSession'" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
count(1)
10000
" + ], + "text/plain": [ + "+----------+\n", + "| count(1) |\n", + "+----------+\n", + "| 10000 |\n", + "+----------+" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%sql\n", + "SELECT COUNT(*) FROM taxi" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Parameterize queries" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "threshold = 10" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "Running query in 'SparkSession'" + ], + "text/plain": [ + "Running query in 'SparkSession'" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
count(1)
9476
" + ], + "text/plain": [ + "+----------+\n", + "| count(1) |\n", + "+----------+\n", + "| 9476 |\n", + "+----------+" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%sql\n", + "SELECT COUNT(*) FROM taxi\n", + "WHERE trip_distance < {{threshold}}" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "threshold = 0.5" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "Running query in 'SparkSession'" + ], + "text/plain": [ + "Running query in 'SparkSession'" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
count(1)
642
" + ], + "text/plain": [ + "+----------+\n", + "| count(1) |\n", + "+----------+\n", + "| 642 |\n", + "+----------+" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%sql\n", + "SELECT COUNT(*) FROM taxi\n", + "WHERE trip_distance < {{threshold}}" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## CTEs" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "Running query in 'SparkSession'" + ], + "text/plain": [ + "Running query in 'SparkSession'" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "Skipping execution..." + ], + "text/plain": [ + "Skipping execution..." + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%sql --save many_passengers --no-execute\n", + "SELECT *\n", + "FROM taxi\n", + "WHERE passenger_count > 3\n", + "-- remove top 1% outliers for better visualization\n", + "AND trip_distance < 18.93" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "Running query in 'SparkSession'" + ], + "text/plain": [ + "Running query in 'SparkSession'" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
min(trip_distance)avg(trip_distance)max(trip_distance)
0.03.109138187221396318.46
" + ], + "text/plain": [ + "+--------------------+--------------------+--------------------+\n", + "| min(trip_distance) | avg(trip_distance) | max(trip_distance) |\n", + "+--------------------+--------------------+--------------------+\n", + "| 0.0 | 3.1091381872213963 | 18.46 |\n", + "+--------------------+--------------------+--------------------+" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%sql --save trip_stats --with many_passengers\n", + "SELECT MIN(trip_distance), AVG(trip_distance), MAX(trip_distance)\n", + "FROM many_passengers" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is what JupySQL executes:" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "scrolled": true, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WITH `many_passengers` AS (\n", + "SELECT *\n", + "FROM taxi\n", + "WHERE passenger_count > 3\n", + "\n", + "AND trip_distance < 18.93)\n", + "SELECT MIN(trip_distance), AVG(trip_distance), MAX(trip_distance)\n", + "FROM many_passengers\n" + ] + } + ], + "source": [ + "query = %sqlcmd snippets trip_stats\n", + "print(query)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Profiling" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
Following statistics are not available in\n", + " SparkSession: STD, 25%, 50%, 75%
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
VendorIDtpep_pickup_datetimetpep_dropoff_datetimepassenger_counttrip_distanceRatecodeIDstore_and_fwd_flagPULocationIDDOLocationIDpayment_typefare_amountextramta_taxtip_amounttolls_amountimprovement_surchargetotal_amountcongestion_surchargeairport_fee
count1000010000100001000010000100001000010000100001000010000100001000010000100001000010000100000
unique287668745712436217323042288350418395930
topnan2021-01-01 00:41:192021-01-02 00:00:00nannannanNnannannannannannannannannannannanNone
freqnan47nannannan9808nannannannannannannannannannannan0
mean1.6901nannan1.50803.10021.0712nan158.5551154.72961.381911.88220.82590.48641.78460.22460.294516.96962.1063nan
std0.4625nannan1.13543.59701.0755nan70.928875.25040.555210.84201.11670.10412.43511.27300.057012.50230.9562nan
min1nannan0.00.01.0nan111-100.0-0.5-0.5-1.07-6.12-0.3-100.3-2.5nan
25%1.0000nannan1.00001.04001.0000nan100.000083.00001.00006.00000.00000.50000.00000.00000.300010.30002.5000nan
50%2.0000nannan1.00001.93001.0000nan152.0000151.00001.00008.50000.50000.50001.54000.00000.300013.55002.5000nan
75%2.0000nannan2.00003.60001.0000nan234.0000234.00002.000013.50002.50000.50002.65000.00000.300019.30002.5000nan
max2nannan6.045.9299.0nan2652654121.03.50.580.025.50.3137.762.5nan
" + ], + "text/plain": [ + "+--------+----------+----------------------+-----------------------+-----------------+---------------+------------+--------------------+--------------+--------------+--------------+-------------+--------+---------+------------+--------------+-----------------------+--------------+----------------------+-------------+\n", + "| | VendorID | tpep_pickup_datetime | tpep_dropoff_datetime | passenger_count | trip_distance | RatecodeID | store_and_fwd_flag | PULocationID | DOLocationID | payment_type | fare_amount | extra | mta_tax | tip_amount | tolls_amount | improvement_surcharge | total_amount | congestion_surcharge | airport_fee |\n", + "+--------+----------+----------------------+-----------------------+-----------------+---------------+------------+--------------------+--------------+--------------+--------------+-------------+--------+---------+------------+--------------+-----------------------+--------------+----------------------+-------------+\n", + "| count | 10000 | 10000 | 10000 | 10000 | 10000 | 10000 | 10000 | 10000 | 10000 | 10000 | 10000 | 10000 | 10000 | 10000 | 10000 | 10000 | 10000 | 10000 | 0 |\n", + "| unique | 2 | 8766 | 8745 | 7 | 1243 | 6 | 2 | 173 | 230 | 4 | 228 | 8 | 3 | 504 | 18 | 3 | 959 | 3 | 0 |\n", + "| top | nan | 2021-01-01 00:41:19 | 2021-01-02 00:00:00 | nan | nan | nan | N | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | None |\n", + "| freq | nan | 4 | 7 | nan | nan | nan | 9808 | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | 0 |\n", + "| mean | 1.6901 | nan | nan | 1.5080 | 3.1002 | 1.0712 | nan | 158.5551 | 154.7296 | 1.3819 | 11.8822 | 0.8259 | 0.4864 | 1.7846 | 0.2246 | 0.2945 | 16.9696 | 2.1063 | nan |\n", + "| std | 0.4625 | nan | nan | 1.1354 | 3.5970 | 1.0755 | nan | 70.9288 | 75.2504 | 0.5552 | 10.8420 | 1.1167 | 0.1041 | 2.4351 | 1.2730 | 0.0570 | 12.5023 | 0.9562 | nan |\n", + "| min | 1 | nan | nan | 0.0 | 0.0 | 1.0 | nan | 1 | 1 | 1 | -100.0 | -0.5 | -0.5 | -1.07 | -6.12 | -0.3 | -100.3 | -2.5 | nan |\n", + "| 25% | 1.0000 | nan | nan | 1.0000 | 1.0400 | 1.0000 | nan | 100.0000 | 83.0000 | 1.0000 | 6.0000 | 0.0000 | 0.5000 | 0.0000 | 0.0000 | 0.3000 | 10.3000 | 2.5000 | nan |\n", + "| 50% | 2.0000 | nan | nan | 1.0000 | 1.9300 | 1.0000 | nan | 152.0000 | 151.0000 | 1.0000 | 8.5000 | 0.5000 | 0.5000 | 1.5400 | 0.0000 | 0.3000 | 13.5500 | 2.5000 | nan |\n", + "| 75% | 2.0000 | nan | nan | 2.0000 | 3.6000 | 1.0000 | nan | 234.0000 | 234.0000 | 2.0000 | 13.5000 | 2.5000 | 0.5000 | 2.6500 | 0.0000 | 0.3000 | 19.3000 | 2.5000 | nan |\n", + "| max | 2 | nan | nan | 6.0 | 45.92 | 99.0 | nan | 265 | 265 | 4 | 121.0 | 3.5 | 0.5 | 80.0 | 25.5 | 0.3 | 137.76 | 2.5 | nan |\n", + "+--------+----------+----------------------+-----------------------+-----------------+---------------+------------+--------------------+--------------+--------------+--------------+-------------+--------+---------+------------+--------------+-----------------------+--------------+----------------------+-------------+" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%sqlcmd profile -t taxi" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Plotting" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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VtOmTTVkyBBt27ZNISEheuedd5Sdna2FCxeWehtt27ZVv3799PrrrysvL0+33367kpOTdfDgwSu+d9GiRXr99dfVu3dvNW7cWCdPntRbb70lu92ue+65R9Lvl4wiIyP1/vvvq2nTpqpdu7Zatmypli1bqkuXLpo5c6bOnTunG264QevWrVNGRsZVzYX0+z1VL7zwgl588UV17txZDz74oHx8fLRt2zaFhYVp+vTpstvtmj9/vgYMGKB27dqpb9++CgoKUmZmplatWqU77rhDr7766lX3UBoTJkzQe++9p549e2rMmDGqXbu2Fi1apIyMDH3wwQclbgivaOXxGb300ktKT09XcnKyatasqdatW2vy5MmaNGmSHnroIfPnAagy3PeAGwDD+P3x7tjYWKNmzZqGJPMR/OJH2i/2CPulHruPjY011q5da7Ru3drw8fExmjdvbixbtqzMPZ05c8YYM2aMUadOHaNGjRrGfffdZxw+fPiKj93v2LHD6Nevn9GwYUPDx8fHCA4ONu69915j+/btLtvfvHmzERUVZXh7e7ts86effjJ69+5tBAYGGgEBAcbDDz9sHDlypMR+L/VY+8XmxTAM45133jFuueUWw8fHx6hVq5bRtWtXIykpyaVmw4YNRkxMjBEQEGD4+voajRs3NgYNGlSi99K43GP3sbGxF33P999/bzz00ENGYGCg4evra9x2223GypUrS/QoqcRneqmflUv1URp/5jNKS0szPD09jdGjR7ts8/z588att95qhIWFGSdOnHDpEXA3m2GU4912ANwmPDxcLVu21MqVK93dCgBcc7iHCAAAWB73EAEWcqUvZ/Tz8+MJIACWRCACLKRevXqXHR84cKASExMrpxkAqEIIRMB1ojTfRJ2UlHTZ8bCwsHLqBgCuLdxUDQAALI+bqgEAgOVxyawUioqKdOTIEdWsWZM/TAgAwDXCMAydPHlSYWFhV/yCUwJRKRw5cqTEX5EGAADXhsOHD6t+/fqXrSEQlULNmjUl/T6hdrvdzd0AAIDScDqdatCggfl7/HIIRKVQfJnMbrcTiAAAuMaU5nYXbqoGAACWRyACAACWRyACAACWRyACAACWRyACAACWRyACAACWRyACAACWRyACAACWRyACAACWRyACAACWRyACAACWRyACAACWRyACAACWRyACAACWRyACAACW5+nu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FAzD0KhRo7R8+XKtX79eERERLuNRUVHy8vJymd/9+/crMzOT+f0TioqKlJ+fz/yWg+7du2vXrl1KT083l/bt26t///7mv5nj8nXq1Cl9//33qlevXvn+DP+JG79RRS1ZssTw8fExEhMTjb179xrDhw83AgMDjaysLHe3dk06efKk8fXXXxtff/21Icl45ZVXjK+//tr48ccfDcMwjBkzZhiBgYHGRx99ZOzcudN44IEHjIiICOPMmTNu7rzqGzlypBEQEGBs3LjROHr0qLn89ttvZs2IESOMhg0bGuvXrze2b99uOBwOw+FwuLHra8uECROMlJQUIyMjw9i5c6cxYcIEw2azGevWrTMMg/mtCBc+ZWYYzPGf9cwzzxgbN240MjIyjC+//NKIjo426tata+Tk5BiGUX7zSyC6Tv3zn/80GjZsaHh7exu33XabsWXLFne3dM3asGGDIanEMnDgQMMwfn/0/m9/+5sREhJi+Pj4GN27dzf279/v3qavERebV0nGwoULzZozZ84YTz31lFGrVi2jevXqRu/evY2jR4+6r+lrzODBg41GjRoZ3t7eRlBQkNG9e3czDBkG81sR/hiImOM/55FHHjHq1atneHt7GzfccIPxyCOPGAcPHjTHy2t+bYZhGOVwBgsAAOCaxT1EAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEANwqISFBbdu2rdB93HnnnYqPjzdfh4eHa86cORW6TwDXFgIRgArxxxByKc8++6zL3yGqDNu2bdPw4cNLVUt4AqzB090NALAmwzBUWFgof39/+fv7V+q+g4KCKnV/AKo+zhABKHeDBg1SSkqK5s6dK5vNJpvNpsTERNlsNq1evVpRUVHy8fHRF198UeKS2aBBg9SrVy9NnTpVQUFBstvtGjFihAoKCkq179OnT+vxxx+Xv7+/6tWrp1mzZpWoufCsj2EYSkhIUMOGDeXj46OwsDCNGTNG0u9nuX788UeNHTvWPA5J+vXXX9WvXz/dcMMNql69ulq1aqX33nvPZR933nmnxowZo/Hjx6t27doKDQ1VQkKCS01ubq6efPJJhYSEyNfXVy1bttTKlSvN8S+++EKdO3eWn5+fGjRooDFjxuj06dOlmgcAZUMgAlDu5s6dK4fDoWHDhuno0aM6evSoGjRoIEmaMGGCZsyYoW+//VatW7e+6PuTk5P17bffauPGjXrvvff04YcfaurUqaXa93PPPaeUlBR99NFHWrdunTZu3KgdO3Zcsv6DDz7Q7Nmz9cYbb+jAgQNasWKFWrVqJUn68MMPVb9+fU2bNs08Dkk6e/asoqKitGrVKu3evVvDhw/XgAED9NVXX7lse9GiRapRo4a2bt2qmTNnatq0aUpKSpIkFRUVqWfPnvryyy/17rvvau/evZoxY4aqVasmSfr+++919913q0+fPtq5c6fef/99ffHFFxo1alSp5gFAGZXXX6MFgAv98S9+b9iwwZBkrFixwqVuypQpRps2bczXAwcONGrXrm2cPn3aXDd//nzD39/fKCwsvOw+T548aXh7extLly411/3666+Gn5+fSy+NGjUyZs+ebRiGYcyaNcto2rSpUVBQcNFtXlh7ObGxscYzzzxjvu7atavRqVMnl5pbb73VeP755w3DMIy1a9caHh4exv79+y+6vSFDhhjDhw93Wff5558bHh4expkzZ67YD4Cy4QwRgErVvn37K9a0adNG1atXN187HA6dOnVKhw8fvuz7vv/+exUUFKhDhw7mutq1a6tZs2aXfM/DDz+sM2fO6MYbb9SwYcO0fPlynT9//rL7KSws1IsvvqhWrVqpdu3a8vf319q1a5WZmelS98czYPXq1VNOTo4kKT09XfXr11fTpk0vuo9vvvlGiYmJ5j1W/v7+iomJUVFRkTIyMi7bH4Cy46ZqAJWqRo0a7m7BRYMGDbR//3599tlnSkpK0lNPPaWXX35ZKSkp8vLyuuh7Xn75Zc2dO1dz5sxRq1atVKNGDcXHx5e4z+mP77fZbCoqKpIk+fn5XbavU6dO6cknnzTvZ7pQw4YNy3KIAEqBQASgQnh7e6uwsPCq3vvNN9/ozJkzZmjYsmWL/P39zfuQLqVx48by8vLS1q1bzdBw4sQJfffdd+ratesl3+fn56f77rtP9913n+Li4tS8eXPt2rVL7dq1u+hxfPnll3rggQf02GOPSfr9fqDvvvtOkZGRpT7G1q1b66efftJ333130bNE7dq10969e9WkSZNSbxPA1eOSGYAKER4erq1bt+rQoUP65ZdfzDMjpVFQUKAhQ4Zo7969+vTTTzVlyhSNGjVKHh6X/0+Wv7+/hgwZoueee07r16/X7t27NWjQoMu+LzExUf/617+0e/du/fDDD3r33Xfl5+enRo0amcexadMm/fzzz/rll18kSTfddJOSkpK0efNmffvtt3ryySeVnZ1d6uOTpK5du6pLly7q06ePkpKSlJGRodWrV2vNmjWSpOeff16bN2/WqFGjlJ6ergMHDuijjz7ipmqgghCIAFSIZ599VtWqVVNkZKSCgoJK3F9zOd27d9dNN92kLl266JFHHtH9999f4pH1S3n55ZfVuXNn3XfffYqOjlanTp0UFRV1yfrAwEC99dZbuuOOO9S6dWt99tln+uSTT1SnTh1J0rRp03To0CE1btzY/P6iSZMmqV27doqJidGdd96p0NBQ9erVq9THV+yDDz7Qrbfeqn79+ikyMlLjx483z0a1bt1aKSkp+u6779S5c2fdcsstmjx5ssLCwsq8HwBXZjMMw3B3EwBQbNCgQcrNzdWKFSvc3QoAC+EMEQAAsDwCEYBrRmZmpstj6H9cynJZDgAuxCUzANeM8+fP69ChQ5ccDw8Pl6cnD88CKDsCEQAAsDwumQEAAMsjEAEAAMsjEAEAAMsjEAEAAMsjEAEAAMsjEAEAAMsjEAEAAMsjEAEAAMv7f+dZjQYV06HwAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%sqlplot histogram --table taxi --column trip_distance --bins 10" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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p379+mnEiBHq16+f4uLilJmZ6enSAHgAYQaAqWRmZmro0KFKTExUbm6uSktLlZubq8TERA0dOpRAAzRDFsMwDE8X0ZBKSkoUEhKi4uJi7s0EmJzValVcXJwSExO1dOlSeXn97/uYzWZTamqq8vLytGfPHk45ASZXn89vRmYAmEZOTo4KCgr01FNPOQQZSfLy8lJaWpry8/OVk5PjoQoBeAJhBoBpFBYWSpISEhLq3F7TXtMPQPNAmAFgGpGRkZKkvLy8OrfXtNf0A9A8EGYAmEZKSopiYmL04osvymazOWyz2WxKT09XbGysUlJSPFQhAE8gzAAwDW9vb82aNUvLly9Xamqqw9VMqampWr58uV577TUm/wLNDIvmATCVwYMHa/HixXrkkUfUq1cve3tsbKwWL16swYMHe7A6AJ7ApdkATIkVgIGmrT6f34zMADAlb29v9e3b19NlAGgEmDMDAABMjTADAABMjTADAABMjTADAABMjTADAABMjTADAABMjTADAABMjTADAABMjTADAABMjTADAABMjTADAABMjTADAABMjTADAABMjTADAABMjTADAABMjTADAABMjTADAABMjTADAABMjTADAABMjTADAABMjTADAABMjTADAABMjTADAABMjTADAABMjTADAABMjTADAABMjTADAABMjTADAABMjTADAABMjTADAABMjTADAABMjTADAABMjTADAABMrVGFmZdeekkWi0VTpkyxt508eVITJkxQaGioAgMDNWTIEB06dMhzRQIAgEal0YSZDRs26E9/+pOSkpIc2qdOnaply5bp448/1po1a3Tw4EENHjzYQ1UCAIDGplGEmRMnTuiuu+7SggUL1KZNG3t7cXGx3n77bc2ePVvXX3+9kpOTtXDhQq1du1br1q3zYMUAAKCxaBRhZsKECRo0aJD69+/v0L5p0yadOnXKoT0+Pl4dOnRQbm5unfuqrKxUSUmJww8AAGi6Wni6gA8//FCbN2/Whg0bam0rKiqSj4+PWrdu7dAeHh6uoqKiOveXnp6uGTNmNESpAACgEfLoyMz+/fv18MMP629/+5v8/Pzcss+0tDQVFxfbf/bv3++W/QIAgMbJo2Fm06ZNOnz4sLp3764WLVqoRYsWWrNmjV5//XW1aNFC4eHhqqqq0vHjxx2ed+jQIUVERNS5T19fXwUHBzv8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pvP4449rzpw5DmtctWjRQlOnTtUrr7ziwcoAuEt9Pr9dGpn5/PPPtXLlylrtK1eu1BdffOHKLgHAKY8//rheffVVhYaGasGCBSosLNSCBQsUGhqqV199VY8//rinSwRwgbk0MpOUlKSXXnpJN998s0P7ihUr9MQTT+j77793W4Hni5EZoOmoqqpSq1atFBoaqgMHDqhFi/9N+6uurlb79u119OhRlZWVycfHx4OVAjhfDT4ys2fPHnXt2rVWe3x8vH744QdXdgkA55SRkaHq6mo9//zzslgsys7O1gcffKDs7GxZLBbNnDlT1dXVysjI8HSpAC4gl65mCgkJ0Y8//qiYmBiH9h9++EGtWrVyR10AUEvNopwWi0VxcXEqKCiwb4uJidHTTz/t0A9A8+BSmLnttts0ZcoULVmyRJ06dZJ0Osg88sgjuvXWW91aIADUqPl7M3bsWA0aNEiPPfaY/P39VVFRoS+++EIPPPCAQz8AzYNLc2aKi4t10003aePGjWrfvr0k6cCBA0pJSVFmZqZat27t7jpdxpwZoOmoqKhQQECAWrRooaioKPtNbiWpQ4cOOnjwoKqrq1VeXi5/f38PVgrgfNXn89vl00xr167V6tWr9f3338vf319JSUnq3bu3SwUDgDPWr18v6fRk36KiIj3xxBMaM2aM3n77bYdLtdevX6++fft6sFIAF5JLYUY6fc76xhtv1I033ujOegDgjP79739LkmJjY7Vv3z69/PLLevnllyWdXmcmNjZW+fn59n4AmgeXw0xWVpaysrJ0+PBh2Ww2h23vvPPOeRcGAL925MgRSdJTTz2le++9VxkZGdq7d686deqk8ePHa9GiRRo3bpy9H4DmwaUwM2PGDM2cOVNXXnmlIiMjuVM2gAsiLCxMkpSZman77rtPU6ZMsW+z2WxaunSpQz8AzYNLYebNN9/UokWLdM8997i7HgA4o4svvljS6QU6U1NTlZaWpoSEBOXl5Sk9PV0rVqxw6AegeXApzFRVValXr17urgUAziolJUUxMTG66KKLtHXrVoe/Q7GxsUpOTtbRo0eVkpLiwSoBXGgurQB8//336/3333d3LQBwVt7e3po1a5Y2btyowsJCh20HDx7Uxo0b9dprr8nb29tDFQLwBJdGZk6ePKm33npL//jHP5SUlKSWLVs6bJ89e7ZbigOAM6msrDzr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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%sqlplot boxplot --table taxi --column trip_distance" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "Removing NULLs, if there exists any from payment_type" + ], + "text/plain": [ + "Removing NULLs, if there exists any from payment_type" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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t6/VqFEja9KkSdahQ4esv//971aDBg2sV155xbIsy3r99det9957zzp69KiVkpJiORwOa/Dgwa7z/+d//seKjIx0m/Pxxx+37rjjDtdjSdZPfvITa/Xq1VZGRoY1dOhQKywszBowYIC1adMm68CBA1afPn2sO++803XOjh07LLvdbiUmJlpHjx61tmzZYoWFhVlz5sxxm7d169bWypUrrcOHD1uPP/641ahRI+u7776zLl68aP3jH/+wJFkZGRnWyZMnrby8vGv+LPLy8iyHw2E9+uij1smTJ62TJ09aFy9eLHOPzZo1s1599VUrIyPDmjlzpuXt7W0dOHDAsizLKioqsiIiIqxHHnnE+vLLL60DBw5YDzzwgNW+fXursLCwrMsF4CoIRABuWN++fa2IiAirpKTEte/JJ5+0IiIirjp+z549liTrzJkzlmVZ1jfffGN5e3tbu3fvtizrP3/4mzdvbiUmJrrOkWTNnDnT9TglJcWSZL3++uuufW+99Zbl7+/vejxw4EDrz3/+s9tz/+1vf7NatWr1o/OePXvWkmS9//77lmVZ1ocffmhJsr7//vty/Twu/Xbu8vQ4fvx4t/N69+5tTZgwwVV7+/bt3X7OhYWFVv369a3NmzeXuT4AV+IlMwCVok+fPrLZbK7HDodDhw8fVnFxsVJTU/WrX/1Kbdq0UePGjdW3b19JUlZWliQpJCREMTExeuONNyRJ69evV2FhoX7961+7PUfnzp1d/w4KCpIkderUyW1fQUGBnE6nJOmLL77QvHnz1KhRI9f26KOP6uTJk/rhhx+uOm/Dhg1lt9tdL+lVlrL26HA4rnh88OBBVz9HjhxR48aNXf00bdpUBQUFV7xUCKB86nm6AAB1W0FBgaKjoxUdHa0333xTLVq0UFZWlqKjo1VUVOQaN3bsWI0aNUoJCQlavny57rvvPjVo0MBtLh8fH9e/S8PX1faVlJRIks6ePau5c+dq2LBhV9Tl7+9/1XlL5ymdozKVpcdrOXv2rLp3764333zzimMtWrSozFIB4xCIAFSK3bt3uz3etWuXbr75Zh06dEjfffedFixYoNDQUEnSZ599dsX5Q4YMUcOGDbV06VJt2rRJO3bsuOGaunXrpoyMDLVr167Cc/j6+kqSiouLy3XO1caXpcddu3bpwQcfdHt86623SvpPP6tXr1bLli1lt9vL2wqAa+AlMwCVIisrS1OmTFFGRobeeustvfDCC5o0aZLatGkjX19fvfDCC/rqq6+0bt06PfXUU1ec7+3trYceekgzZszQzTfffMVLRxUxa9Ys/fWvf9XcuXO1f/9+HTx4UKtWrdLMmTPLPEfbtm1ls9m0YcMGnTp1SmfPnr3uOWFhYdq9e7eOHTumb7/91nW1qSw9rlmzRm+88Yb+9a9/afbs2fr0008VFxcnSRo5cqSaN2+uu+++Wx999JEyMzO1fft2Pf744/r666/L3BOAKxGIAFSKBx98UOfPn1evXr0UGxurSZMmady4cWrRooUSExO1Zs0aRUZGasGCBVe8/bzUmDFjVFRUpIcffrhSaoqOjtaGDRu0ZcsW9ezZU3369FFCQoLatm1b5jl+8pOfaO7cuZo+fbqCgoJc4eRannjiCXl7eysyMtL1EmGp6/U4d+5crVq1Sp07d9Zf//pXvfXWW4qMjJQkNWjQQDt27FCbNm00bNgwRUREaMyYMSooKOCKEXCDbJb1/z4oBAAqqF+/furatauee+65G5rno48+0sCBA3X8+HHXTdN1zbV6tNlsWrt2rYYOHeqZ4gCDcQ8RAI8rLCzUqVOnNGfOHP3617+uk2HIhB6B2oyXzAB43FtvvaW2bdsqLy9PCxcu9HQ515SVleX2Nv7Lt0tfHrtUbeoRMBEvmQFAOVy8ePGaX5URFhamevW4+A7UNgQiAABgPF4yAwAAxiMQAQAA4xGIAACA8QhEAADAeAQiAABgPAIRAAAwHoEIAAAYj0AEAACM939faPJBMq+8vAAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%sqlplot bar --table taxi --column payment_type" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "Removing NULLs, if there exists any from payment_type" + ], + "text/plain": [ + "Removing NULLs, if there exists any from payment_type" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%sqlplot pie --table taxi --column payment_type" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [], + "source": [ + "from sql.ggplot import ggplot, aes, geom_histogram" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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VtOmTTVkyBBt27ZNISEheuedd5Sdna2FCxeWehtt27ZVv3799PrrrysvL0+33367kpOTdfDgwSu+d9GiRXr99dfVu3dvNW7cWCdPntRbb70lu92ue+65R9Lvl4wiIyP1/vvvq2nTpqpdu7Zatmypli1bqkuXLpo5c6bOnTunG264QevWrVNGRsZVzYX0+z1VL7zwgl588UV17txZDz74oHx8fLRt2zaFhYVp+vTpstvtmj9/vgYMGKB27dqpb9++CgoKUmZmplatWqU77rhDr7766lX3UBoTJkzQe++9p549e2rMmDGqXbu2Fi1apIyMDH3wwQclbgivaOXxGb300ktKT09XcnKyatasqdatW2vy5MmaNGmSHnroIfPnAagy3PeAGwDD+P3x7tjYWKNmzZqGJPMR/OJH2i/2CPulHruPjY011q5da7Ru3drw8fExmjdvbixbtqzMPZ05c8YYM2aMUadOHaNGjRrGfffdZxw+fPiKj93v2LHD6Nevn9GwYUPDx8fHCA4ONu69915j+/btLtvfvHmzERUVZXh7e7ts86effjJ69+5tBAYGGgEBAcbDDz9sHDlypMR+L/VY+8XmxTAM45133jFuueUWw8fHx6hVq5bRtWtXIykpyaVmw4YNRkxMjBEQEGD4+voajRs3NgYNGlSi99K43GP3sbGxF33P999/bzz00ENGYGCg4evra9x2223GypUrS/QoqcRneqmflUv1URp/5jNKS0szPD09jdGjR7ts8/z588att95qhIWFGSdOnHDpEXA3m2GU4912ANwmPDxcLVu21MqVK93dCgBcc7iHCAAAWB73EAEWcqUvZ/Tz8+MJIACWRCACLKRevXqXHR84cKASExMrpxkAqEIIRMB1ojTfRJ2UlHTZ8bCwsHLqBgCuLdxUDQAALI+bqgEAgOVxyawUioqKdOTIEdWsWZM/TAgAwDXCMAydPHlSYWFhV/yCUwJRKRw5cqTEX5EGAADXhsOHD6t+/fqXrSEQlULNmjUl/T6hdrvdzd0AAIDScDqdatCggfl7/HIIRKVQfJnMbrcTiAAAuMaU5nYXbqoGAACWRyACAACWRyACAACWRyACAACWRyACAACWRyACAACWRyACAACWRyACAACWRyACAACWRyACAACWRyACAACWRyACAACWRyACAACWRyACAACWRyACAACW5+nu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FAzD0KhRo7R8+XKtX79eERERLuNRUVHy8vJymd/9+/crMzOT+f0TioqKlJ+fz/yWg+7du2vXrl1KT083l/bt26t///7mv5nj8nXq1Cl9//33qlevXvn+DP+JG79RRS1ZssTw8fExEhMTjb179xrDhw83AgMDjaysLHe3dk06efKk8fXXXxtff/21Icl45ZVXjK+//tr48ccfDcMwjBkzZhiBgYHGRx99ZOzcudN44IEHjIiICOPMmTNu7rzqGzlypBEQEGBs3LjROHr0qLn89ttvZs2IESOMhg0bGuvXrze2b99uOBwOw+FwuLHra8uECROMlJQUIyMjw9i5c6cxYcIEw2azGevWrTMMg/mtCBc+ZWYYzPGf9cwzzxgbN240MjIyjC+//NKIjo426tata+Tk5BiGUX7zSyC6Tv3zn/80GjZsaHh7exu33XabsWXLFne3dM3asGGDIanEMnDgQMMwfn/0/m9/+5sREhJi+Pj4GN27dzf279/v3qavERebV0nGwoULzZozZ84YTz31lFGrVi2jevXqRu/evY2jR4+6r+lrzODBg41GjRoZ3t7eRlBQkNG9e3czDBkG81sR/hiImOM/55FHHjHq1atneHt7GzfccIPxyCOPGAcPHjTHy2t+bYZhGOVwBgsAAOCaxT1EAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEANwqISFBbdu2rdB93HnnnYqPjzdfh4eHa86cORW6TwDXFgIRgArxxxByKc8++6zL3yGqDNu2bdPw4cNLVUt4AqzB090NALAmwzBUWFgof39/+fv7V+q+g4KCKnV/AKo+zhABKHeDBg1SSkqK5s6dK5vNJpvNpsTERNlsNq1evVpRUVHy8fHRF198UeKS2aBBg9SrVy9NnTpVQUFBstvtGjFihAoKCkq179OnT+vxxx+Xv7+/6tWrp1mzZpWoufCsj2EYSkhIUMOGDeXj46OwsDCNGTNG0u9nuX788UeNHTvWPA5J+vXXX9WvXz/dcMMNql69ulq1aqX33nvPZR933nmnxowZo/Hjx6t27doKDQ1VQkKCS01ubq6efPJJhYSEyNfXVy1bttTKlSvN8S+++EKdO3eWn5+fGjRooDFjxuj06dOlmgcAZUMgAlDu5s6dK4fDoWHDhuno0aM6evSoGjRoIEmaMGGCZsyYoW+//VatW7e+6PuTk5P17bffauPGjXrvvff04YcfaurUqaXa93PPPaeUlBR99NFHWrdunTZu3KgdO3Zcsv6DDz7Q7Nmz9cYbb+jAgQNasWKFWrVqJUn68MMPVb9+fU2bNs08Dkk6e/asoqKitGrVKu3evVvDhw/XgAED9NVXX7lse9GiRapRo4a2bt2qmTNnatq0aUpKSpIkFRUVqWfPnvryyy/17rvvau/evZoxY4aqVasmSfr+++919913q0+fPtq5c6fef/99ffHFFxo1alSp5gFAGZXXX6MFgAv98S9+b9iwwZBkrFixwqVuypQpRps2bczXAwcONGrXrm2cPn3aXDd//nzD39/fKCwsvOw+T548aXh7extLly411/3666+Gn5+fSy+NGjUyZs+ebRiGYcyaNcto2rSpUVBQcNFtXlh7ObGxscYzzzxjvu7atavRqVMnl5pbb73VeP755w3DMIy1a9caHh4exv79+y+6vSFDhhjDhw93Wff5558bHh4expkzZ67YD4Cy4QwRgErVvn37K9a0adNG1atXN187HA6dOnVKhw8fvuz7vv/+exUUFKhDhw7mutq1a6tZs2aXfM/DDz+sM2fO6MYbb9SwYcO0fPlynT9//rL7KSws1IsvvqhWrVqpdu3a8vf319q1a5WZmelS98czYPXq1VNOTo4kKT09XfXr11fTpk0vuo9vvvlGiYmJ5j1W/v7+iomJUVFRkTIyMi7bH4Cy46ZqAJWqRo0a7m7BRYMGDbR//3599tlnSkpK0lNPPaWXX35ZKSkp8vLyuuh7Xn75Zc2dO1dz5sxRq1atVKNGDcXHx5e4z+mP77fZbCoqKpIk+fn5XbavU6dO6cknnzTvZ7pQw4YNy3KIAEqBQASgQnh7e6uwsPCq3vvNN9/ozJkzZmjYsmWL/P39zfuQLqVx48by8vLS1q1bzdBw4sQJfffdd+ratesl3+fn56f77rtP9913n+Li4tS8eXPt2rVL7dq1u+hxfPnll3rggQf02GOPSfr9fqDvvvtOkZGRpT7G1q1b66efftJ333130bNE7dq10969e9WkSZNSbxPA1eOSGYAKER4erq1bt+rQoUP65ZdfzDMjpVFQUKAhQ4Zo7969+vTTTzVlyhSNGjVKHh6X/0+Wv7+/hgwZoueee07r16/X7t27NWjQoMu+LzExUf/617+0e/du/fDDD3r33Xfl5+enRo0amcexadMm/fzzz/rll18kSTfddJOSkpK0efNmffvtt3ryySeVnZ1d6uOTpK5du6pLly7q06ePkpKSlJGRodWrV2vNmjWSpOeff16bN2/WqFGjlJ6ergMHDuijjz7ipmqgghCIAFSIZ599VtWqVVNkZKSCgoJK3F9zOd27d9dNN92kLl266JFHHtH9999f4pH1S3n55ZfVuXNn3XfffYqOjlanTp0UFRV1yfrAwEC99dZbuuOOO9S6dWt99tln+uSTT1SnTh1J0rRp03To0CE1btzY/P6iSZMmqV27doqJidGdd96p0NBQ9erVq9THV+yDDz7Qrbfeqn79+ikyMlLjx483z0a1bt1aKSkp+u6779S5c2fdcsstmjx5ssLCwsq8HwBXZjMMw3B3EwBQbNCgQcrNzdWKFSvc3QoAC+EMEQAAsDwCEYBrRmZmpstj6H9cynJZDgAuxCUzANeM8+fP69ChQ5ccDw8Pl6cnD88CKDsCEQAAsDwumQEAAMsjEAEAAMsjEAEAAMsjEAEAAMsjEAEAAMsjEAEAAMsjEAEAAMsjEAEAAMv7f+dZjQYV06HwAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "(ggplot(table=\"taxi\", mapping=aes(x=\"trip_distance\")) + geom_histogram(bins=10))" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Clean up\n", + "\n", + "To stop and remove the container:" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES\n", + "12f699ee8e8e wh1isper/sparglim-server \"tini -- sparglim-se…\" About a minute ago Up About a minute 0.0.0.0:4040->4040/tcp, 0.0.0.0:15002->15002/tcp spark\n", + "f019407c6426 docker.dev.slicelife.com/onelogin-aws-assume-role:stable \"onelogin-aws-assume…\" 2 weeks ago Up 2 weeks heuristic_tu\n" + ] + } + ], + "source": [ + "! docker container ls" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "%%capture out\n", + "! docker container ls --filter ancestor=wh1isper/sparglim-server --quiet" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Container id: 12f699ee8e8e\n" + ] + } + ], + "source": [ + "container_id = out.stdout.strip()\n", + "print(f\"Container id: {container_id}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "12f699ee8e8e\n" + ] + } + ], + "source": [ + "! docker container stop {container_id}" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "12f699ee8e8e\n" + ] + } + ], + "source": [ + "! docker container rm {container_id}" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.13" + }, + "myst": { + "html_meta": { + "description lang=en": "Query using Spark SQL from Jupyter via JupySQL", + "keywords": "jupyter, sql, jupysql, spark", + "property=og:locale": "en_US" + } + }, + "vscode": { + "interpreter": { + "hash": "8de7291ac4f217ed756f77e1d71d41823fff9c4ffb13df0a183e9309929ad9aa" + } + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "state": {}, + "version_major": 2, + "version_minor": 0 + } + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/noxfile.py b/noxfile.py index 92c2b8fb6..5a62c0f26 100644 --- a/noxfile.py +++ b/noxfile.py @@ -35,6 +35,8 @@ "pyodbc==4.0.34", "sqlalchemy-pytds", "python-tds", + "pyspark>=3.4.1", + "grpcio-status", ] diff --git a/setup.py b/setup.py index e1ba5911a..1c03419f8 100644 --- a/setup.py +++ b/setup.py @@ -71,6 +71,9 @@ "redshift-connector", "sqlalchemy-redshift", "clickhouse-sqlalchemy", + # following two dependencies required for spark + "pyspark", + "grpcio-status", ] setup( diff --git a/src/sql/_testing.py b/src/sql/_testing.py index 14a5e9675..041994e28 100644 --- a/src/sql/_testing.py +++ b/src/sql/_testing.py @@ -210,6 +210,10 @@ def get_tmp_dir(): "docker_ct": None, "query": {}, }, + "spark": { + "alias": "SparkSession", + "drivername": "SparkSession", + }, "clickhouse": { "drivername": "clickhouse+native", "username": "username", diff --git a/src/sql/command.py b/src/sql/command.py index 7b7bd168b..675420d74 100644 --- a/src/sql/command.py +++ b/src/sql/command.py @@ -5,7 +5,7 @@ from sql import parse, exceptions from sql.store import store -from sql.connection import ConnectionManager, is_pep249_compliant +from sql.connection import ConnectionManager, is_pep249_compliant, is_spark from sql.util import validate_nonidentifier_connection @@ -49,7 +49,11 @@ def __init__(self, magic, user_ns, line, cell) -> None: if ( one_arg and self.args.line[0] in user_ns - and (isinstance(user_ns[self.args.line[0]], Engine) or is_dbapi_connection_) + and ( + isinstance(user_ns[self.args.line[0]], Engine) + or is_dbapi_connection_ + or is_spark(user_ns[self.args.line[0]]) + ) ): line_for_command = [] add_conn = True diff --git a/src/sql/connection/__init__.py b/src/sql/connection/__init__.py index 7c48e624b..4d9dfb10a 100644 --- a/src/sql/connection/__init__.py +++ b/src/sql/connection/__init__.py @@ -2,7 +2,9 @@ ConnectionManager, SQLAlchemyConnection, DBAPIConnection, + SparkConnectConnection, is_pep249_compliant, + is_spark, PLOOMBER_DOCS_LINK_STR, default_alias_for_engine, ResultSetCollection, @@ -14,7 +16,9 @@ "ConnectionManager", "SQLAlchemyConnection", "DBAPIConnection", + "SparkConnectConnection", "is_pep249_compliant", + "is_spark", "PLOOMBER_DOCS_LINK_STR", "default_alias_for_engine", "ResultSetCollection", diff --git a/src/sql/connection/connection.py b/src/sql/connection/connection.py index 678ca30de..fc4552aaa 100644 --- a/src/sql/connection/connection.py +++ b/src/sql/connection/connection.py @@ -16,6 +16,9 @@ InternalError, ProgrammingError, ) + +from sql.run.sparkdataframe import handle_spark_dataframe + from IPython.core.error import UsageError import sqlglot import sqlparse @@ -257,6 +260,10 @@ def set( ) elif is_pep249_compliant(descriptor): cls.current = DBAPIConnection(descriptor, config=config, alias=alias) + elif is_spark(descriptor): + cls.current = SparkConnectConnection( + descriptor, config=config, alias=alias + ) else: existing = rough_dict_get(cls.connections, descriptor) if existing and existing.alias == alias: @@ -1060,6 +1067,82 @@ def to_table(self, table_name, data_frame, if_exists, index, schema=None): ) +class SparkConnectConnection(AbstractConnection): + is_dbapi_connection = False + + @telemetry.log_call("SparkConnectConnection", payload=True) + def __init__(self, payload, connection, alias=None, config=None): + try: + payload["engine"] = type(connection) + except Exception as e: + payload["engine_parsing_error"] = str(e) + self._driver = None + + # TODO: implement the dialect blacklist and add unit tests + self._requires_manual_commit = True if config is None else config.autocommit + + self._connection = connection + self._connection_class_name = type(connection).__name__ + + # calling init from AbstractConnection must be the last thing we do as it + # register the connection + super().__init__(alias=alias or self._connection_class_name) + + self.name = self._connection_class_name + + @property + def dialect(self): + """Returns a string with the SQL dialect name""" + return "spark2" + + def raw_execute(self, query, parameters=None): + """Run the query without any pre-processing""" + return handle_spark_dataframe(self._connection.sql(query)) + + def _get_database_information(self): + """ + Get the dialect, driver, and database server version info of current + connection + """ + return { + "dialect": self.dialect, + "driver": self._connection_class_name, + "server_version_info": self._connection.version, + } + + @property + def url(self): + """Returns None since Spark connections don't have a url""" + return None + + @property + def connection_sqlalchemy(self): + """ + Raises NotImplementedError since Spark connections don't have a SQLAlchemy + connection object + """ + raise NotImplementedError( + "This feature is only available for SQLAlchemy connections" + ) + + def to_table(self, table_name, data_frame, if_exists, index, schema=None): + mode = ( + "overwrite" + if if_exists == "replace" + else "append" + if if_exists == "append" + else "error" + ) + self._connection.createDataFrame(data_frame).write.mode(mode).saveAsTable( + f"{schema}.{table_name}" if schema else table_name + ) + + def close(self): + """Override of the abstract close as SparkSession is usually + shared with pyspark""" + pass + + def _check_if_duckdb_dbapi_connection(conn): """Check if the connection is a native duckdb connection""" # NOTE: duckdb defines df and pl to efficiently convert results to @@ -1154,6 +1237,26 @@ def is_pep249_compliant(conn): return True +def is_spark(conn): + """Check if it is a SparkSession by checking for available methods""" + + sparksession_methods = [ + "table", + "read", + "createDataFrame", + "sql", + "stop", + "catalog", + "version", + ] + for method_name in sparksession_methods: + # Checking whether the connection object has the method + if not hasattr(conn, method_name): + return False + + return True + + def default_alias_for_engine(engine): if not engine.url.username: # keeping this for compatibility diff --git a/src/sql/error_handler.py b/src/sql/error_handler.py index 0eac9292a..cd1dfb3cd 100644 --- a/src/sql/error_handler.py +++ b/src/sql/error_handler.py @@ -50,6 +50,7 @@ def _detailed_message_with_error_type(error, query): "error in your sql syntax", "incorrect syntax", "invalid sql", + "syntax_error", ] not_found_substrings = [ r"(\btable with name\b).+(\bdoes not exist\b)", diff --git a/src/sql/magic.py b/src/sql/magic.py index d34d32e2c..17a2a2a49 100644 --- a/src/sql/magic.py +++ b/src/sql/magic.py @@ -147,6 +147,15 @@ class SqlMagic(Magics, Configurable): config=True, help="Verbosity level. 0=minimal, 1=normal, 2=all", ) + lazy_execution = Bool( + default_value=False, + config=True, + help="Whether to evaluate using ResultSet which will " + "cause the plan to execute or just return a lazily " + "executed plan allowing validating schemas, " + "without expensive compute." + "Currently only supported for Spark Connection.", + ) named_parameters = Bool( default_value=False, config=True, diff --git a/src/sql/plot.py b/src/sql/plot.py index 10e7e7895..d3cecb759 100644 --- a/src/sql/plot.py +++ b/src/sql/plot.py @@ -268,10 +268,9 @@ def _min_max(conn, table, column, with_=None, use_backticks=False): """ if use_backticks: template_ = template_.replace('"', "`") - + table = table.replace('"', "`") template = Template(template_) query = template.render(table=table, column=column) - min_, max_ = conn.execute(query, with_).fetchone() return min_, max_ @@ -628,6 +627,7 @@ def _histogram( if use_backticks: template_ = template_.replace('"', "`") + table = table.replace('"', "`") template = Template(template_) @@ -663,6 +663,7 @@ def _histogram( if use_backticks: template_ = template_.replace('"', "`") + table = table.replace('"', "`") template = Template(template_) @@ -681,6 +682,7 @@ def _histogram( if use_backticks: template_ = template_.replace('"', "`") + table = table.replace('"', "`") template = Template(template_) @@ -835,6 +837,7 @@ def _bar(table, column, with_=None, conn=None): if use_backticks: template_ = template_.replace('"', "`") + table = table.replace('"', "`") template = Template(template_) query = template.render(table=table, x_=x_, height_=height_) @@ -854,6 +857,7 @@ def _bar(table, column, with_=None, conn=None): if use_backticks: template_ = template_.replace('"', "`") + table = table.replace('"', "`") template = Template(template_) query = template.render(table=table, column=column) @@ -1022,6 +1026,7 @@ def _pie(table, column, with_=None, conn=None): """ if use_backticks: template_ = template_.replace('"', "`") + table = table.replace('"', "`") template = Template(template_) query = template.render(table=table, labels_=labels_, size_=size_) @@ -1037,6 +1042,7 @@ def _pie(table, column, with_=None, conn=None): """ if use_backticks: template_ = template_.replace('"', "`") + table = table.replace('"', "`") template = Template(template_) query = template.render(table=table, column=column) diff --git a/src/sql/run/resultset.py b/src/sql/run/resultset.py index 8451150aa..4b977a8e2 100644 --- a/src/sql/run/resultset.py +++ b/src/sql/run/resultset.py @@ -434,7 +434,10 @@ def fetchmany(self, size): raise RuntimeError(f"Error running the query: {str(e)}") from e self.mark_fetching_as_done() return - + # spark doesn't support cursor + if hasattr(self._sqlaproxy, "dataframe"): + self._results = [] + self._pretty_table.clear() self._extend_results(returned) if len(returned) < size: @@ -458,6 +461,9 @@ def fetch_for_repr_if_needed(self): def fetchall(self): if not self._done_fetching(): + if hasattr(self._sqlaproxy, "dataframe"): + self._results = [] + self._pretty_table.clear() self._extend_results(self.sqlaproxy.fetchall()) self.mark_fetching_as_done() @@ -500,6 +506,8 @@ def _convert_to_data_frame( # maybe create accessors in the connection objects? if result_set._conn.is_dbapi_connection: native_connection = result_set.sqlaproxy + elif hasattr(result_set.sqlaproxy, "dataframe"): + return result_set.sqlaproxy.dataframe.toPandas() else: native_connection = result_set._conn._connection.connection diff --git a/src/sql/run/run.py b/src/sql/run/run.py index 11312c450..a1e34aa7d 100644 --- a/src/sql/run/run.py +++ b/src/sql/run/run.py @@ -52,6 +52,8 @@ def run_statements(conn, sql, config, parameters=None): # regular query else: result = conn.raw_execute(statement, parameters=parameters) + if is_spark(conn.dialect) and config.lazy_execution: + return result.dataframe if ( config.feedback >= 1 @@ -69,6 +71,10 @@ def is_postgres_or_redshift(dialect): return "postgres" in str(dialect) or "redshift" in str(dialect) +def is_spark(dialect): + return "spark" in str(dialect) + + def select_df_type(resultset, config): """ Converts the input resultset to either a Pandas DataFrame diff --git a/src/sql/run/sparkdataframe.py b/src/sql/run/sparkdataframe.py new file mode 100644 index 000000000..81644b1e2 --- /dev/null +++ b/src/sql/run/sparkdataframe.py @@ -0,0 +1,52 @@ +try: + from pyspark.sql import DataFrame + from pyspark.sql.connect.dataframe import DataFrame as CDataFrame +except ModuleNotFoundError: + DataFrame = None + CDataFrame = None + +from sql import exceptions + + +def handle_spark_dataframe(dataframe, should_cache=False): + """Execute a ResultSet sqlaproxy using pysark module.""" + if not DataFrame and not CDataFrame: + raise exceptions.MissingPackageError("pysark not installed") + + return SparkResultProxy(dataframe, dataframe.columns, should_cache) + + +class SparkResultProxy(object): + """A fake class that pretends to behave like the ResultProxy from + SqlAlchemy. + """ + + dataframe = None + + def __init__(self, dataframe, headers, should_cache): + self.dataframe = dataframe + self.fetchall = dataframe.collect + self.rowcount = dataframe.count() + self.keys = lambda: headers + self.cursor = SparkCursor(headers) + self.returns_rows = True + if should_cache: + self.dataframe.cache() + + def fetchmany(self, size): + return self.dataframe.take(size) + + def fetchone(self): + return self.dataframe.head() + + def close(self): + self.dataframe.unpersist() + + +class SparkCursor(object): + """Class to extend to give SqlAlchemy Cursor like behaviour""" + + description = None + + def __init__(self, headers) -> None: + self.description = headers diff --git a/src/sql/stats.py b/src/sql/stats.py index 0fc12e87f..b03252154 100644 --- a/src/sql/stats.py +++ b/src/sql/stats.py @@ -45,7 +45,6 @@ def _summary_stats_one_by_one(conn, table, column, with_=None): other = list(conn.execute(query, with_).fetchone()) keys = ["q1", "med", "q3", "mean", "N"] - return {k: float(v) for k, v in zip(keys, percentiles + other)} diff --git a/src/sql/util.py b/src/sql/util.py index b0d9c0c3b..4f7fd6d4f 100644 --- a/src/sql/util.py +++ b/src/sql/util.py @@ -543,6 +543,9 @@ def is_non_sqlalchemy_error(error): "pyodbc.ProgrammingError", # Clickhouse errors "DB::Exception:", + # Pyspark + "UNRESOLVED_ROUTINE", + "PARSE_SYNTAX_ERROR", ] return any(msg in str(error) for msg in specific_db_errors) diff --git a/src/tests/integration/conftest.py b/src/tests/integration/conftest.py index e0a0b5e7f..cad899583 100644 --- a/src/tests/integration/conftest.py +++ b/src/tests/integration/conftest.py @@ -2,6 +2,7 @@ from pathlib import Path import shutil import pandas as pd +from pyspark.sql import SparkSession import pytest from sqlalchemy import MetaData, Table, create_engine, text import uuid @@ -288,6 +289,49 @@ def setup_duckDB_native(test_table_name_dict): conn.close() +@pytest.fixture(scope="session") +def setup_spark(test_table_name_dict): + import os + import shutil + import sys + + os.environ["PYSPARK_PYTHON"] = sys.executable + os.environ["PYSPARK_DRIVER_PYTHON"] = sys.executable + spark = SparkSession.builder.master("local[1]").enableHiveSupport().getOrCreate() + load_generic_testing_data_spark(spark, test_table_name_dict) + yield spark + spark.stop() + shutil.rmtree("metastore_db", ignore_errors=True) + shutil.rmtree("spark-warehouse", ignore_errors=True) + os.remove("derby.log") + + +def load_generic_testing_data_spark(spark: SparkSession, test_table_name_dict): + spark.createDataFrame( + pd.DataFrame( + {"taxi_driver_name": ["Eric Ken", "John Smith", "Kevin Kelly"] * 15} + ) + ).createOrReplaceTempView(test_table_name_dict["taxi"]) + spark.createDataFrame( + pd.DataFrame({"x": range(0, 5), "y": range(5, 10)}) + ).createOrReplaceTempView(test_table_name_dict["plot_something"]) + spark.createDataFrame( + pd.DataFrame({"numbers_elements": [1, 2, 3] * 20}) + ).createOrReplaceTempView(test_table_name_dict["numbers"]) + + +@pytest.fixture +def ip_with_spark(ip_empty, setup_spark): + alias = "SparkSession" + + ip_empty.push({"conn": setup_spark}) + # Select database engine, use different sqlite database endpoint + ip_empty.run_cell("%sql " + "conn" + " --alias " + alias) + yield ip_empty + # Disconnect database + ip_empty.run_cell("%sql -x " + alias) + + def load_generic_testing_data_duckdb_native(ip, test_table_name_dict): ip.run_cell("import pandas as pd") ip.run_cell( diff --git a/src/tests/integration/test_connection.py b/src/tests/integration/test_connection.py index 5d867b6e6..63dc71ac9 100644 --- a/src/tests/integration/test_connection.py +++ b/src/tests/integration/test_connection.py @@ -8,7 +8,12 @@ import pytest -from sql.connection import SQLAlchemyConnection, DBAPIConnection, ConnectionManager +from sql.connection import ( + SQLAlchemyConnection, + DBAPIConnection, + ConnectionManager, + SparkConnectConnection, +) from sql import _testing from sql.connection import connection @@ -92,6 +97,7 @@ def test_connection_properties(dynamic_db, request, Constructor, alias, dialect) partial(DBAPIConnection, alias="another-alias"), "another-alias", ], + ["setup_spark", SparkConnectConnection, "SparkSession"], ], ) def test_connection_identifiers( diff --git a/src/tests/integration/test_generic_db_operations.py b/src/tests/integration/test_generic_db_operations.py index 47325ed16..95722758a 100644 --- a/src/tests/integration/test_generic_db_operations.py +++ b/src/tests/integration/test_generic_db_operations.py @@ -21,6 +21,7 @@ "ip_with_Snowflake", "ip_with_oracle", "ip_with_clickhouse", + "ip_with_spark", ] @@ -54,6 +55,7 @@ def mock_log_api(monkeypatch): ("ip_with_clickhouse", "", "LIMIT 3"), ("ip_with_oracle", "", "FETCH FIRST 3 ROWS ONLY"), ("ip_with_MSSQL", "TOP 3", ""), + ("ip_with_spark", "", "LIMIT 3"), ], ) def test_run_query( @@ -93,6 +95,7 @@ def test_run_query( "ip_with_Snowflake", "ip_with_redshift", "ip_with_clickhouse", + "ip_with_spark", ], ) def test_handle_multiple_open_result_sets( @@ -151,6 +154,7 @@ def test_handle_multiple_open_result_sets( "No engine for table " ), ), + ("ip_with_spark", "--no-index"), ], ) def test_create_table_with_indexed_df( @@ -218,6 +222,7 @@ def get_connection_count(ip_with_dynamic_db): ("ip_with_MSSQL", 1), ("ip_with_Snowflake", 1), ("ip_with_clickhouse", 1), + ("ip_with_spark", 1), ], ) def test_active_connection_number(ip_with_dynamic_db, expected, request): @@ -273,6 +278,7 @@ def test_close_and_connect( ("ip_with_Snowflake", "snowflake", "snowflake"), ("ip_with_oracle", "oracle", "oracledb"), ("ip_with_clickhouse", "clickhouse", "native"), + ("ip_with_spark", "spark2", "SparkSession"), ], ) def test_telemetry_execute_command_has_connection_info( @@ -337,6 +343,7 @@ def test_telemetry_execute_command_has_connection_info( ("ip_with_Snowflake"), ("ip_with_duckDB_native"), ("ip_with_redshift"), + ("ip_with_spark"), pytest.param( "ip_with_MSSQL", marks=pytest.mark.xfail(reason="sqlglot does not support SQL server"), @@ -419,6 +426,9 @@ def test_sqlplot_histogram(ip_with_dynamic_db, cell, request, test_table_name_di reason="Plotting from snippet not working in clickhouse" ), ), + pytest.param( + "ip_with_spark", marks=pytest.mark.xfail(reason=BOX_PLOT_FAIL_REASON) + ), ], ) def test_sqlplot_boxplot(ip_with_dynamic_db, cell, request, test_table_name_dict): @@ -442,6 +452,7 @@ def test_sqlplot_boxplot(ip_with_dynamic_db, cell, request, test_table_name_dict "ip_with_duckDB", "ip_with_redshift", "ip_with_MSSQL", + "ip_with_spark", ], ) def test_sqlplot_bar(ip_with_dynamic_db, request, test_table_name_dict): @@ -464,7 +475,13 @@ def test_sqlplot_bar(ip_with_dynamic_db, request, test_table_name_dict): @pytest.mark.parametrize( "ip_with_dynamic_db", - ["ip_with_postgreSQL", "ip_with_duckDB", "ip_with_redshift", "ip_with_MSSQL"], + [ + "ip_with_postgreSQL", + "ip_with_duckDB", + "ip_with_redshift", + "ip_with_MSSQL", + "ip_with_spark", + ], ) def test_sqlplot_pie(ip_with_dynamic_db, request, test_table_name_dict): plt.cla() @@ -517,6 +534,7 @@ def test_sqlplot_pie(ip_with_dynamic_db, request, test_table_name_dict): reason="Plotting from snippet not working in clickhouse" ), ), + "ip_with_spark", ], ) def test_sqlplot_using_schema(ip_with_dynamic_db, request): @@ -569,6 +587,7 @@ def test_sqlplot_using_schema(ip_with_dynamic_db, request): ("ip_with_Snowflake"), ("ip_with_oracle"), ("ip_with_clickhouse"), + ("ip_with_spark"), ], ) def test_sqlcmd_test(ip_with_dynamic_db, request, test_table_name_dict): @@ -604,6 +623,7 @@ def test_sqlcmd_test(ip_with_dynamic_db, request, test_table_name_dict): ), ("ip_with_oracle"), ("ip_with_clickhouse"), + ("ip_with_spark"), ], ) def test_profile_data_mismatch(ip_with_dynamic_db, request, capsys): @@ -786,6 +806,25 @@ def test_profile_data_mismatch(ip_with_dynamic_db, request, capsys): }, "Following statistics are not available in", ), + ( + "ip_with_spark", + "taxi", + ["taxi_driver_name"], + { + "count": [45], + "mean": [math.nan], + "min": ["Eric Ken"], + "max": ["Kevin Kelly"], + "unique": [3], + "freq": [15], + "top": ["Eric Ken"], + "std": [math.nan], + "25%": [math.nan], + "50%": [math.nan], + "75%": [math.nan], + }, + None, + ), ], ) def test_sqlcmd_profile( @@ -847,6 +886,10 @@ def test_sqlcmd_profile( ("ip_with_MSSQL"), ("ip_with_Snowflake"), ("ip_with_clickhouse"), + pytest.param( + "ip_with_spark", + marks=pytest.mark.xfail(reason="Not Implemented"), + ), ], ) def test_sqlcmd_columns(ip_with_dynamic_db, table, request, test_table_name_dict): @@ -873,6 +916,10 @@ def test_sqlcmd_columns(ip_with_dynamic_db, table, request, test_table_name_dict ("ip_with_MSSQL"), ("ip_with_Snowflake"), ("ip_with_clickhouse"), + pytest.param( + "ip_with_spark", + marks=pytest.mark.xfail(reason="Not Implemented"), + ), ], ) def test_sqlcmd_tables(ip_with_dynamic_db, request): @@ -927,6 +974,7 @@ def test_sql_query(ip_with_dynamic_db, cell, request, test_table_name_dict): "ip_with_Snowflake", "ip_with_oracle", "ip_with_clickhouse", + "ip_with_spark", ], ) def test_sql_query_cte(ip_with_dynamic_db, request, test_table_name_dict, cell): @@ -957,6 +1005,7 @@ def test_sql_query_cte(ip_with_dynamic_db, request, test_table_name_dict, cell): "ip_with_clickhouse", marks=pytest.mark.xfail(reason="Not yet implemented"), ), + "ip_with_spark", ], ) def test_sql_error_suggests_using_cte(ip_with_dynamic_db, request): @@ -987,6 +1036,7 @@ def test_sql_error_suggests_using_cte(ip_with_dynamic_db, request): "ip_with_MSSQL", "ip_with_oracle", "ip_with_clickhouse", + "ip_with_spark", ], ) def test_results_sets_are_closed(ip_with_dynamic_db, request, test_table_name_dict): @@ -1024,6 +1074,7 @@ def test_results_sets_are_closed(ip_with_dynamic_db, request, test_table_name_di "ip_with_MSSQL", "ip_with_oracle", "ip_with_clickhouse", + "ip_with_spark", ], ) @pytest.mark.parametrize( @@ -1150,6 +1201,7 @@ def test_autocommit_retrieve_existing_resultssets_duckdb_from( CREATE_TABLE, marks=pytest.mark.xfail(reason="Not working yet"), ), + ("ip_with_spark", CREATE_TABLE), ], ) def test_autocommit_create_table_single_cell( @@ -1222,6 +1274,7 @@ def test_autocommit_create_table_single_cell( CREATE_TABLE, marks=pytest.mark.xfail(reason="Not working yet"), ), + ("ip_with_spark", CREATE_TABLE), ], ) def test_autocommit_create_table_multiple_cells( @@ -1408,6 +1461,20 @@ def test_autocommit_create_table_multiple_cells( ["Table with name mysnip does not exist!"], "RuntimeError", ), + ( + "ip_with_spark", + "mysnippet", + [ + "Cannot resolve function `not_a_function` on search path", + ], + "RuntimeError", + ), + ( + "ip_with_spark", + "mysnip", + ["Cannot resolve function `not_a_function` on search path"], + "RuntimeError", + ), ], ids=[ "no-typo-postgreSQL", @@ -1428,6 +1495,8 @@ def test_autocommit_create_table_multiple_cells( "with-typo-redshift", "no-typo-duckDB-native", "with-typo-duckDB-native", + "no-typo-spark", + "with-typo-spark", ], ) def test_query_snippet_invalid_function_error_message( @@ -1456,7 +1525,7 @@ def test_query_snippet_invalid_function_error_message( # Save result and test error message result_error = excinfo.value.error_type result_msg = str(excinfo.value) - + print(result_msg) assert error_type == result_error assert all(msg in result_msg for msg in error_msgs) @@ -1502,6 +1571,7 @@ def test_query_snippet_invalid_function_error_message( "No engine for table " ), ), + ("ip_with_spark", "--no-index"), ], ) def test_persist_in_schema(ip_with_dynamic_db, args, request, test_table_name_dict): diff --git a/src/tests/integration/test_stats.py b/src/tests/integration/test_stats.py index d8f93439d..fe33b18f5 100644 --- a/src/tests/integration/test_stats.py +++ b/src/tests/integration/test_stats.py @@ -1,7 +1,7 @@ import pytest from sql.stats import _summary_stats -from sql.connection import SQLAlchemyConnection +from sql.connection import SQLAlchemyConnection, SparkConnectConnection @pytest.mark.parametrize( @@ -26,3 +26,23 @@ def test_summary_stats(fixture_name, request, test_table_name_dict): "mean": 2.0, "N": 5.0, } + + +@pytest.mark.parametrize( + "fixture_name", + [ + "setup_spark", + ], +) +def test_summary_stats_spark(fixture_name, request, test_table_name_dict): + conn = SparkConnectConnection(request.getfixturevalue(fixture_name)) + table = test_table_name_dict["plot_something"] + column = "x" + + assert _summary_stats(conn, table, column) == { + "q1": 1.0, + "med": 2.0, + "q3": 3.0, + "mean": 2.0, + "N": 5.0, + } diff --git a/src/tests/test_connection.py b/src/tests/test_connection.py index 9a9a0a399..8369ff690 100644 --- a/src/tests/test_connection.py +++ b/src/tests/test_connection.py @@ -13,6 +13,7 @@ from sqlalchemy.engine import Engine from sqlalchemy import exc + from sql.connection import connection as connection_module import sql.connection from sql.connection import ( @@ -21,6 +22,7 @@ ConnectionManager, is_pep249_compliant, default_alias_for_engine, + is_spark, ResultSetCollection, detect_duckdb_summarize_or_select, ) @@ -41,6 +43,34 @@ def mock_database(monkeypatch, cleanup): monkeypatch.setattr(sqlalchemy, "create_engine", Mock()) +def mock_sparksession(): + mock = Mock( + spec=[ + "table", + "read", + "createDataFrame", + "sql", + "stop", + "catalog", + "version", + ] + ) + return mock + + +def mock_not_sparksession(): + mock = Mock( + spec=[ + "read", + "readStream", + "createDataFrame", + "sql", + "version", + ] + ) + return mock + + @pytest.fixture def mock_postgres(monkeypatch, cleanup): monkeypatch.setitem(sys.modules, "psycopg2", Mock()) @@ -457,6 +487,24 @@ def test_is_pep249_compliant(conn, expected): assert is_pep249_compliant(conn) is expected +@pytest.mark.parametrize( + "descriptor, expected", + [ + [sqlite3.connect(""), False], + [duckdb.connect(""), False], + [create_engine("sqlite://"), False], + [mock_sparksession(), True], + [mock_not_sparksession(), False], + [None, False], + [object(), False], + ["not_a_valid_connection", False], + [0, False], + ], +) +def test_is_spark(descriptor, expected): + assert is_spark(descriptor) is expected + + def test_close_all(ip_empty, monkeypatch): connections = {} monkeypatch.setattr(ConnectionManager, "connections", connections) @@ -590,6 +638,22 @@ def test_set_dbapi(monkeypatch, callable_, key): assert ConnectionManager.current == conn +@pytest.mark.parametrize( + "spark, key", + [ + [mock_sparksession(), "Mock"], + ], +) +def test_set_spark(monkeypatch, spark, key): + connections = {} + monkeypatch.setattr(ConnectionManager, "connections", connections) + + conn = ConnectionManager.set(spark, displaycon=False) + + assert connections == {key: conn} + assert ConnectionManager.current == conn + + def test_set_with_alias(monkeypatch): connections = {} monkeypatch.setattr(ConnectionManager, "connections", connections)