From 532cda7bdb206cb6db7c44f5e7a1652b5418dd61 Mon Sep 17 00:00:00 2001
From: shaikhq
Date: Mon, 21 Apr 2025 19:33:53 -0700
Subject: [PATCH 1/5] Added shoe vector search demo
Signed-off-by: shaikhq
---
ai-vectors/product-recommendation/.env-sample | 8 +
ai-vectors/product-recommendation/.gitignore | 1 +
ai-vectors/product-recommendation/README.md | 97 +
.../__pycache__/utils.cpython-312.pyc | Bin 0 -> 8872 bytes
ai-vectors/product-recommendation/db2.ipynb | 3542 +++++++++++++++++
.../product-recommendation/db2connect.pickle | Bin 0 -> 215 bytes
.../product-recommendation/db2creds.pickle | Bin 0 -> 245 bytes
.../images/FOO-7361.jpeg | Bin 0 -> 35914 bytes
.../images/LOO-1396.jpeg | Bin 0 -> 56456 bytes
.../images/RUN-5480.jpeg | Bin 0 -> 136948 bytes
.../images/RUN-9444.jpeg | Bin 0 -> 52331 bytes
.../images/STR-1144.jpeg | Bin 0 -> 43245 bytes
.../images/STR-2196.jpeg | Bin 0 -> 161563 bytes
.../images/STR-2507.jpeg | Bin 0 -> 53812 bytes
.../images/STR-7223.jpeg | Bin 0 -> 37982 bytes
.../images/ZEN-2061.jpeg | Bin 0 -> 41432 bytes
.../product-recommendation/requirements.txt | 20 +
.../shoes-data-generation.ipynb | 764 ++++
.../shoes-data-partitioning.ipynb | 466 +++
.../product-recommendation/shoes-search.ipynb | 1566 ++++++++
.../product-recommendation/shoes-vectors.csv | 501 +++
ai-vectors/product-recommendation/shoes.csv | 501 +++
ai-vectors/product-recommendation/utils.py | 253 ++
23 files changed, 7719 insertions(+)
create mode 100644 ai-vectors/product-recommendation/.env-sample
create mode 100644 ai-vectors/product-recommendation/.gitignore
create mode 100644 ai-vectors/product-recommendation/README.md
create mode 100644 ai-vectors/product-recommendation/__pycache__/utils.cpython-312.pyc
create mode 100644 ai-vectors/product-recommendation/db2.ipynb
create mode 100644 ai-vectors/product-recommendation/db2connect.pickle
create mode 100644 ai-vectors/product-recommendation/db2creds.pickle
create mode 100644 ai-vectors/product-recommendation/images/FOO-7361.jpeg
create mode 100644 ai-vectors/product-recommendation/images/LOO-1396.jpeg
create mode 100644 ai-vectors/product-recommendation/images/RUN-5480.jpeg
create mode 100644 ai-vectors/product-recommendation/images/RUN-9444.jpeg
create mode 100644 ai-vectors/product-recommendation/images/STR-1144.jpeg
create mode 100644 ai-vectors/product-recommendation/images/STR-2196.jpeg
create mode 100644 ai-vectors/product-recommendation/images/STR-2507.jpeg
create mode 100644 ai-vectors/product-recommendation/images/STR-7223.jpeg
create mode 100644 ai-vectors/product-recommendation/images/ZEN-2061.jpeg
create mode 100644 ai-vectors/product-recommendation/requirements.txt
create mode 100644 ai-vectors/product-recommendation/shoes-data-generation.ipynb
create mode 100644 ai-vectors/product-recommendation/shoes-data-partitioning.ipynb
create mode 100644 ai-vectors/product-recommendation/shoes-search.ipynb
create mode 100644 ai-vectors/product-recommendation/shoes-vectors.csv
create mode 100644 ai-vectors/product-recommendation/shoes.csv
create mode 100644 ai-vectors/product-recommendation/utils.py
diff --git a/ai-vectors/product-recommendation/.env-sample b/ai-vectors/product-recommendation/.env-sample
new file mode 100644
index 0000000..c88cad3
--- /dev/null
+++ b/ai-vectors/product-recommendation/.env-sample
@@ -0,0 +1,8 @@
+WATSONX_PROJECT=4468f0f6-ce79-46aa-9897-d363b9c610ff
+WATSONX_APIKEY=kSHPOyVzLPJq3qeodlh_EtUKdGSN0Bwfj7xj5e-QfjGr
+database=tpcds
+hostname=localhost
+port=50000
+protocol=tcpip
+uid=shaikhq
+pwd=7?EBx|BcxV7.jgG
diff --git a/ai-vectors/product-recommendation/.gitignore b/ai-vectors/product-recommendation/.gitignore
new file mode 100644
index 0000000..4c49bd7
--- /dev/null
+++ b/ai-vectors/product-recommendation/.gitignore
@@ -0,0 +1 @@
+.env
diff --git a/ai-vectors/product-recommendation/README.md b/ai-vectors/product-recommendation/README.md
new file mode 100644
index 0000000..5fc8cb4
--- /dev/null
+++ b/ai-vectors/product-recommendation/README.md
@@ -0,0 +1,97 @@
+1. **Install Python 3.12**
+
+ RHEL 9.4 includes Python 3.12 in its package repositories. To install it, run:
+
+ ```bash
+ sudo dnf install python3.12
+ ```
+
+ Verify the installation:
+
+ ```bash
+ python3.12 --version
+ ```
+
+2. **Install `pip` for Python 3.12**
+
+ To manage Python packages, install `pip` for Python 3.12:
+
+ ```bash
+ sudo dnf install python3.12-pip
+ ```
+
+3. **Install `uv` for managing python dependencies
+```shell
+python3.12 -m pip install uv
+```
+
+4. **Add `uv` to the your shell profile
+Add this line to your ~/.kshrc, ~/.profile, or ~/.bashrc (depending on your shell setup). In my case, it is ~/.profile
+```shell
+vi ~/.profile
+```
+First, find the installation path of `uv`:
+```shell
+python3.12 -m site --user-base
+```
+
+This gave me:
+```
+/home/shaikhq/.local
+```
+
+Add the following line at the end of the file:
+```
+export PATH="$HOME/.local/bin:$PATH"
+```
+
+Saved changes and exited vi editor.
+
+Apply the changes:
+```
+. ~/.profile
+```
+
+Verify that `uv` is now on the path:
+```shell
+uv --version
+```
+
+uv --version
+uv 0.6.15
+
+5. **Create a python virtual environment
+```shell
+uv venv --python=python3.12
+```
+
+6. **Activate the python virtual env:
+```shell
+source .venv/bin/activate
+```
+
+7. **Install packages from `requirements.txt`
+uv pip install -r requirements.txt
+
+8. **Configure VS Code to Use the Virtual Environment**
+
+ - Open the Command Palette (`Ctrl+Shift+P` or `Cmd+Shift+P` on macOS).
+ - Type `Python: Select Interpreter` and select the interpreter located in your `.venv` directory.
+
+9. **Set the Notebook's Python Interpreter in VS Code**
+
+ - Open your Jupyter notebook in VS Code.
+ - Click on the kernel name at the top right corner.
+ - Select the interpreter from your `.venv`.
+
+10. Rename `.env-sample` to `.env` and populate the required API keys and metadata from your target AI platforms. Fill in the necessary values as shown below:
+
+```env
+WATSONX_PROJECT=
+WATSONX_APIKEY=
+database=
+hostname=
+port=
+protocol=
+uid=
+pwd=
\ No newline at end of file
diff --git a/ai-vectors/product-recommendation/__pycache__/utils.cpython-312.pyc b/ai-vectors/product-recommendation/__pycache__/utils.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..954cfc1711b4c7ff42287c9ae40f8686ba513a60
GIT binary patch
literal 8872
zcmeG>TTC2RmR0?t=x!d}fC+wt!Vlt<|kb+n7CHcZ@m6c*$4^DVed-B6v%}nRJc00KS~J-l50byzLG(R>s>QFXtVQ
zSMVi}SMm(x9=;TEFYgqpc$ZK$VdLF*Y-81Y8T8i7(Ef^DByQMGtF}mZI2hAu#a;&f
zR`|b|wIJOJjh&dX)ct%tYn_EQPtU^cp$>3!HU;O9G-bw?@5@?w%d8!sZCW{DS-0xg
zXi6!`gPGRorGEB^qOKus`aYnOw6r+!4)~Su49~2W-Uk_B+PB70tq|;X$XYduwH&PfU?jQQnHv%;fi^(eL`!zwV7>doyrLD
zCVv4%DjG{CWY#C96rgi>Iynxs6y{~1C8cj^@Vj^nckz^cO{*mnVq6wyg;;>rBt1n%)KHQ^s}I
zwdh*vTj^WvS!?*}qsL71b645j^NZ*2b}e?ToPO*&_0(H?uX(w7W%iNx^g_>5cg5Yo
z#la={ixI32EDkKiR=dA-H$8_||6>1Ac;&=a{h5s`4}4GDA3q2D%p$W?^G_9fwN#Do
zKP
zwZQJAw=nZVrh4Ln5(Pbpf(8q6RKAgrTA|+ue;JM1Jhf-ByBRX$Z?cQ)0Xt=8m~p`n
z&Ka%!EHt|FyfoSxQ;lYUyGB=@%|`nmej9`P8Enc=nnKKovBCK{Vei!lKYpTAiX7h=I=U?}b%?HL;A4F-md
zb^UB0%a>
zDoI90LcPHd+kLgLMV(%ty@!C9)wHunO*JJg($pH%R9Q@l2~IL;>lske4zso%(Y4ih
z_rWM0Y;w5q(FAW?@s{6N!<663xpX@n5|n8vCBwlRVsA_f(hRFiatbR6iX;j*
z!FZBW!j5Z}oS5cYqB6;9w=WBKtHPyXf=qTO2?@A-ZwjoEE@JpAO+FmIC_D`EMmT^*
zUrfc(jN)z1wa7w-Lrca3b!jZ{9RM~_A`}k{v#|s>Eei#!tm&3H*X;N4rlFCMmiEqf
z&ozND4@W{R=Q}z&n~oF0xqs35k=Jh0i%9-{jDI}G7^Ll+_4|oze!zJnX9}KIPBi?Q
zp2>JZSj`R`!_%efFFYmTh~=N4#3pnCWC;H9G04Csc&jTtdp^p0bjiI(S&!NG{Izao
zkE*r*P)qSAUB!ArU?YQUB+xw+EEv4PEsJ;Or+9S-LcVwW&4wzPZHNKT(P@Gp=EN9!
z*EFJCSUT3XO(SS>j49_$1G@1s2KV7ZYj2F0@qOPQ8DQ;aOz6$F$$&U(0w6bU-_S@P
zVrBun9oV8y&3qO2hQWjdqXWHc_rOO3!zLW$y4N{j-mqbhSj_HZ6=F#c4%7X
z{|S-Cfwwi$rx?7V6|iv~aaw9Ge!>2dXAdTkQs9sG*Z
zo=^uop{rJn$5X0aJ}+JfghN
zWqw4>(ODWFQo~&LtAy}_UJgI*r*`4u&C?ksAA9g5_8FHYd7B$8O?C&%m5A#Au@bZH
z&@NcbW?M9gSV3ws`T^Oz$*wj%h(%>4N*=7z`vCE8@Ru7P!>_lp3eVo*yrrhWQXeUI
zG4nlpZ&L1x`9bZwF7I8037R8-+_{6`NxKLBOrEZT?kTe(5I~AjjClunOz-Ds00zqi
zm{IVNP8t5lJR&F%imc-*^yxL-`=(=@vnYnYl(+IW-oEb8y`6K`taYkFpIIog7Gt(8
zi>sC3YFYX^eCd*PD+{oCjc0IV&JJExJE=Kd?+fKi*G*nU6YN3WP#gHisb5-nW`gV+
z{K{I}rL}qYdYQf_8t9!u0*aE(7w|Oyd*0JrGRFY5WN&k&Sw`FCoD*v0WCt#&RgjuH
z>xA9IeJjOGlpf$+1#c|jL$*{a;}+;~Ar9yvW!%D7@*dv1URChE7SAI7cIzxicdo2>
zzG_NXoi&ZP9nF?{&h$ALHN5R%9#J>kDgt@kj6=7yRT0$y?`Qo_9RUH}G}5
zb^Uk&iwVPVosvSypU`OnFLYt8lLZL9#-BV8vQZx|l&^q9?@v*R8?lpptE20%D7^x*
zxd;rJy8h02vYsjQIu)g<_M#4G>T^Knfd_Nmtal2(O^TA4S6|fZlg-&8P-ya}vNVaZ
zVzsvF(J
z$xJ#0j&ATaOI(UiC!^qA05`T1oax~Ay$zSXY6Zh8;cUM}LLcrf-mMb8swKn(F7E3y
zc>tSE+E0t2ErvL6a-dsQ0CdaNrVA
z;U*EvXEQO291Dp`_6dB%#ez%O*r6s(`R$_<~NVb}_{ZwoLj-o+PH^KwLUqjK%iD?=yDmdpE3NSur#j>e%1Lzq
zann$`WO!@e6lD>b311x;vvB9@8Hl7PDlLMjF;qgD65chcJ0FaQrnsb_GP-vGqAVgGy{#29
zs*CIkqeq~eE|hyz6TE8S07%;z-gn@CDo&d3q8eGKy9V(kgmg>4f+>H3zx)e09`n?*
zmcWCzaxKH#Ese$LuuF5|@1jy_RMpKq3?GD1>{vt+upXEx9wYR`=VokUg+GZJiOArT()4}
zDfcX$TAW(2?$jP$>B`lfUU2VJ9a@@Q{$PQ5<|7pChgXh1s%c!SS-bJ|8yjzLdj9avmhZxY8xJq<9BJJ6$^TmE(hupqdaA19Wg{{U
zndM%&v|O=zbdCF}exv6>-_8+s?GjLKKl7475Qy_EjNTbR)&iRsq&vfZyIShLTf12M
zdEI>Xj;HEg-E!Twry=KQ_||g*n9P)IGj%zpZspVyhTXGL-jkXT2R<9vsvLee_b|Ox
z{`1GSC@k~+fD?IX!;K0tMU4N>+DQa;7q1QfA=LxN5w$oWjzg4Ai%DuFxjocM(>Or@
zcMIZ?FnliI>5%>bGSv>T&diK7j`d3UdP3l&RBMt;i5MIvcNB^Ckr\n",
+ "%run db2.ipynb\n",
+ "\n",
+ "\n",
+ "### Install Db2 Extensions\n",
+ "\n",
+ "To load the Db2 magic commands into your notebook, run the following command in your Jupyter notebook:\n",
+ "```\n",
+ "!wget https://raw.githubusercontent.com/IBM/db2-jupyter/master/db2.ipynb -O db2.ipynb\n",
+ "```\n",
+ "\n",
+ "### Install Db2 Python Driver\n",
+ "If the ibm_db driver is not installed on your system, the subsequent Db2 commands will fail. In order to install the Db2 driver, issue the following command from a Jupyter notebook cell:\n",
+ "```\n",
+ "!pip install ibm_db --user\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Db2 Extensions Loaded.\n"
+ ]
+ }
+ ],
+ "source": [
+ "#\n",
+ "# Set up Jupyter MAGIC commands \"sql\". \n",
+ "# %sql will return results from a Db2 select statement or execute a DB2 command\n",
+ "#\n",
+ "# IBM 2024: George Baklarz\n",
+ "# Version 2024-09-16\n",
+ "#\n",
+ "\n",
+ "from __future__ import print_function\n",
+ "import multiprocessing\n",
+ "from IPython.display import HTML as pHTML, Image as pImage, display as pdisplay, Javascript as Javascript\n",
+ "from IPython.core.magic import (Magics, magics_class, line_magic,\n",
+ " cell_magic, line_cell_magic, needs_local_scope)\n",
+ "import ibm_db\n",
+ "import pandas\n",
+ "import ibm_db_dbi\n",
+ "import json\n",
+ "import getpass\n",
+ "import pickle\n",
+ "import time\n",
+ "import re\n",
+ "import warnings\n",
+ "import matplotlib\n",
+ "import matplotlib.pyplot as plt\n",
+ "import sys\n",
+ "import copy\n",
+ "\n",
+ "_db2magic_version = \"2024-09-16\"\n",
+ "\n",
+ "#\n",
+ "# Empty settings creates a structure that we will use for saving control information\n",
+ "# on the database. Needs to be at the front of the code to be found in a Python \n",
+ "# cell.\n",
+ "#\n",
+ "\n",
+ "def emptySettings():\n",
+ "\n",
+ " settings = {\n",
+ " \"maxrows\" : 10,\n",
+ " \"paging\" : \"ON\",\n",
+ " \"display\" : \"PANDAS\",\n",
+ " \"threads\" : 0,\n",
+ " \"database\" : \"\",\n",
+ " \"hostname\" : \"localhost\",\n",
+ " \"port\" : \"50000\",\n",
+ " \"protocol\" : \"TCPIP\", \n",
+ " \"uid\" : \"\",\n",
+ " \"pwd\" : \"\",\n",
+ " \"ssl\" : \"\",\n",
+ " \"dsn\" : \"\",\n",
+ " \"sslfile\" : \"\",\n",
+ " \"apikey\" : \"\",\n",
+ " \"accesstoken\" : \"\",\n",
+ " }\n",
+ "\n",
+ " return settings\n",
+ "\n",
+ "warnings.filterwarnings(\"ignore\")\n",
+ "\n",
+ "_settings = copy.deepcopy(emptySettings())\n",
+ "\n",
+ "_environment = {\n",
+ " \"jupyter\" : True,\n",
+ " \"grid\" : True,\n",
+ " \"gridinit\" : False\n",
+ "}\n",
+ "\n",
+ "# Db2 and Pandas data types\n",
+ "\n",
+ "\n",
+ "_db2types = [\"unknown\",\n",
+ " \"string\",\n",
+ " \"smallint\",\n",
+ " \"int\",\n",
+ " \"bigint\", \n",
+ " \"real\",\n",
+ " \"float\",\n",
+ " \"decfloat16\",\n",
+ " \"decfloat34\",\n",
+ " \"decimal\",\n",
+ " \"boolean\",\n",
+ " \"clob\",\n",
+ " \"blob\",\n",
+ " \"xml\",\n",
+ " \"date\",\n",
+ " \"time\",\n",
+ " \"timestamp\"]\n",
+ "\n",
+ "_pdtypes = [\"object\",\n",
+ " \"string\",\n",
+ " \"Int16\",\n",
+ " \"Int32\",\n",
+ " \"Int64\",\n",
+ " \"float32\",\n",
+ " \"float64\",\n",
+ " \"float64\",\n",
+ " \"float64\",\n",
+ " \"float64\",\n",
+ " \"boolean\", \n",
+ " \"string\", \n",
+ " \"object\",\n",
+ " \"string\",\n",
+ " \"string\",\n",
+ " \"string\",\n",
+ " \"datetime64[ns]\"] \n",
+ "\n",
+ "# Connection settings for statements \n",
+ "\n",
+ "_connected = False\n",
+ "_hdbc = None\n",
+ "_hdbi = None\n",
+ "_stmt = []\n",
+ "_stmtID = []\n",
+ "_stmtSQL = []\n",
+ "_vars = {}\n",
+ "_macros = {}\n",
+ "_flags = []\n",
+ "_debug = False\n",
+ "\n",
+ "# Db2 Error Messages and Codes\n",
+ "sqlcode = 0\n",
+ "sqlstate = \"0\"\n",
+ "sqlerror = \"\"\n",
+ "sqlelapsed = 0\n",
+ "\n",
+ "# Check to see if QGrid is installed\n",
+ "\n",
+ "try:\n",
+ " from itables import show, init_notebook_mode\n",
+ "except:\n",
+ " _environment['grid'] = False\n",
+ "\n",
+ "if (_environment['grid'] == False):\n",
+ " print(\"Warning: Grid display is unavailable for displaying results in scrollable windows.\")\n",
+ " print(\" Install itables if you want to enable scrolling of result sets.\")\n",
+ "\n",
+ "# Check if we are running in iPython or Jupyter\n",
+ "\n",
+ "_environment['jupyter'] = True\n",
+ "\n",
+ "try:\n",
+ " if (get_ipython().config == {}): \n",
+ " _environment['jupyter'] = False\n",
+ " _environment['grid'] = False\n",
+ " else:\n",
+ " _environment['jupyter'] = True\n",
+ "except:\n",
+ " _environment['jupyter'] = False\n",
+ " _environment['grid'] = False\n",
+ " \n",
+ "# Check if pandas supports data types in the data frame - Introduced in 1.3 of pandas\n",
+ "\n",
+ "_pandas_dtype = False\n",
+ "try:\n",
+ " _vrm = pandas.__version__.split(\".\")\n",
+ " _version = 0\n",
+ " _release = 0\n",
+ " _modlevel = 0\n",
+ " if (len(_vrm) >= 1):\n",
+ " _version = int(_vrm[0])\n",
+ " if (len(_vrm) >= 2):\n",
+ " _release = int(_vrm[1])\n",
+ " if (len(_vrm) >= 3):\n",
+ " _modlevel = int(_vrm[2])\n",
+ " if ((_version >= 1 and _release >= 3) or _version > 1):\n",
+ " _pandas_dtype = True\n",
+ " else:\n",
+ " _pandas_dtype = False\n",
+ "except:\n",
+ " _pandas_dtype = False\n",
+ "\n",
+ "if (_pandas_dtype == False):\n",
+ " print(\"Warning: PANDAS level does not support Db2 typing which will can increase memory usage.\")\n",
+ " print(\" Install PANDAS version 1.3+ for more efficient dataframe creation.\")\n",
+ " \n",
+ "# Check if we have parallism available\n",
+ "\n",
+ "if (sys.platform == \"darwin\"):\t\t\t\t\t# For the Mac we need multiprocess\n",
+ " try:\n",
+ " import multiprocess as mp\t\t\t\t# Check to see if it exists\n",
+ " from multiprocess.sharedctypes import Value, Array\n",
+ " _parallel = True\n",
+ " except:\n",
+ " print(\"Warning: On OSX you need to pip install the multiprocess package if you want\")\n",
+ " print(\" parallelism to work.\")\n",
+ " _parallel = False\n",
+ "else:\n",
+ " try:\n",
+ " import multiprocessing as mp\n",
+ " from multiprocessing.sharedctypes import Value, Array\n",
+ " _parallel = True\n",
+ " except:\n",
+ " _parallel = False\n",
+ " \n",
+ "if (_parallel == False):\n",
+ " print(\"Warning: Parallelism is unavailable and THREADS option will be ignored.\")\n",
+ " print(\" Install MULTIPROCESSING/MULTIPROCESS(OSX) if you want allow\")\n",
+ " print(\" multiple SQL threads to run in parallel.\") \n",
+ " _settings[\"threads\"] = 0\n",
+ "\n",
+ "#\n",
+ "# Set Options for the Db2 Magic Commands\n",
+ "#\n",
+ "\n",
+ "def setOptions(inSQL):\n",
+ "\n",
+ " global _settings\n",
+ "\n",
+ " cParms = inSQL.split()\n",
+ " cnt = 0\n",
+ " \n",
+ " if (len(cParms) == 1):\n",
+ " print(\"(MAXROWS) Maximum number of rows displayed: \" + str(_settings.get(\"maxrows\",10)))\n",
+ " print(\"(DISPLAY) Use PANDAS or GRID display format for output: \" + _settings.get(\"display\",\"PANDAS\")) \n",
+ " print(\"(PAGING) Use paging when displaying a grid: \" + _settings.get(\"paging\",\"ON\")) \n",
+ " print(\"(THREADS) Maximum number of threads to use when running SQL: \" + str(_settings.get(\"threads\",0))) \n",
+ " return\n",
+ "\n",
+ " while cnt < len(cParms):\n",
+ " \n",
+ " if cParms[cnt][0] == \"?\":\n",
+ " print(\"%sql OPTION MAXROWS n PAGING n DISPLAY n THREADS n\")\n",
+ " print(\"LIST - List the current option settings\")\n",
+ " print(\"MAXROWS n - The maximum number of rows displayed when returning results\")\n",
+ " print(\"THREADS n - Maximum number of parallel threads to use when running SQL\")\n",
+ " print(\"PAGING ON|OFF - Use paging when displaying a grid\")\n",
+ " return\n",
+ " \n",
+ " if cParms[cnt].upper() == 'MAXROWS':\n",
+ " \n",
+ " if cnt+1 < len(cParms):\n",
+ " try:\n",
+ " _settings[\"maxrows\"] = int(cParms[cnt+1])\n",
+ " if (_settings[\"maxrows\"] > 100 or _settings[\"maxrows\"] <= 0):\n",
+ " _settings[\"maxrows\"] = 100\n",
+ " except Exception as err:\n",
+ " errormsg(\"Invalid MAXROWS value provided.\")\n",
+ " pass\n",
+ " cnt = cnt + 1\n",
+ " else:\n",
+ " errormsg(\"No maximum rows specified for the MAXROWS option.\")\n",
+ " return\n",
+ " \n",
+ " elif cParms[cnt].upper() == 'PAGING':\n",
+ " \n",
+ " if cnt+1 < len(cParms):\n",
+ " paging = cParms[cnt+1]\n",
+ " paging = paging.upper().strip()\n",
+ " if (paging in [\"OFF\",\"FALSE\"]): # Minimum window size is 100px\n",
+ " paging = \"OFF\"\n",
+ " elif (paging in [\"ON\",\"TRUE\"]):\n",
+ " paging = \"ON\"\n",
+ " else: \n",
+ " errormsg(\"Invalid paging [ON|OFF] option provided with the PAGING keyword.\")\n",
+ " return\n",
+ " _settings[\"paging\"] = paging\n",
+ " cnt = cnt + 1\n",
+ " else:\n",
+ " errormsg(\"No paging [ON|OFF] option provided with the PAGING keyword.\")\n",
+ " return \n",
+ " \n",
+ " elif cParms[cnt].upper() == 'DISPLAY':\n",
+ " if cnt+1 < len(cParms):\n",
+ " if (cParms[cnt+1].upper() == 'GRID'):\n",
+ " _settings[\"display\"] = 'GRID'\n",
+ " elif (cParms[cnt+1].upper() == 'PANDAS'):\n",
+ " _settings[\"display\"] = 'PANDAS'\n",
+ " else:\n",
+ " errormsg(\"Invalid DISPLAY value provided.\")\n",
+ " cnt = cnt + 1\n",
+ " else:\n",
+ " errormsg(\"No value provided for the DISPLAY option.\")\n",
+ " return \n",
+ " \n",
+ " elif cParms[cnt].upper() == 'THREADS':\n",
+ " if cnt+1 < len(cParms):\n",
+ " try:\n",
+ " threads = int(cParms[cnt+1])\n",
+ " if (threads < 0):\n",
+ " threads = 0\n",
+ " elif (threads > 12):\n",
+ " threads = 12\n",
+ " else:\n",
+ " pass\n",
+ " _settings[\"threads\"] = threads\n",
+ " except Exception as err:\n",
+ " errormsg(\"Invalid THREADS value provided.\")\n",
+ " pass\n",
+ " cnt = cnt + 1\n",
+ " else:\n",
+ " errormsg(\"No thread count specified for the THREADS option.\")\n",
+ " return\n",
+ " \n",
+ " elif (cParms[cnt].upper() == 'LIST'):\n",
+ " print(\"(MAXROWS) Maximum number of rows displayed: \" + str(_settings.get(\"maxrows\",10)))\n",
+ " print(\"(PAGING) Use paging when displaying a grid: \" + _settings.get(\"paging\",\"ON\"))\n",
+ " print(\"(DISPLAY) Use PANDAS or GRID display format for output: \" + _settings.get(\"display\",\"PANDAS\"))\n",
+ " print(\"(THREADS) Maximum number of threads to use when running SQL: \" + str(_settings.get(\"threads\",0)))\n",
+ " return\n",
+ " else:\n",
+ " cnt = cnt + 1\n",
+ " \n",
+ " save_settings(_settings)\n",
+ "\n",
+ " print(\"(MAXROWS) Maximum number of rows displayed: \" + str(_settings.get(\"maxrows\",10)))\n",
+ " print(\"(PAGING) Use paging when displaying a grid: \" + _settings.get(\"paging\",\"ON\"))\n",
+ " print(\"(DISPLAY) Use PANDAS or GRID display format for output: \" + _settings.get(\"display\",\"PANDAS\"))\n",
+ " print(\"(THREADS) Maximum number of threads to use when running SQL: \" + str(_settings.get(\"threads\",0)))\n",
+ " \n",
+ " return\t\n",
+ "\n",
+ "#\n",
+ "# Display help (link to documentation)\n",
+ "#\n",
+ "\n",
+ "def sqlhelp():\n",
+ " \n",
+ " print(\"Db2 Magic Documentation: https://ibm.github.io/db2-jupyter/\")\n",
+ " return\n",
+ " \n",
+ "# Split port and IP addresses\n",
+ "\n",
+ "def split_string(in_port,splitter=\":\"):\n",
+ " \n",
+ " # Split input into an IP address and Port number\n",
+ "\n",
+ " checkports = in_port.split(splitter)\n",
+ " ip = checkports[0]\n",
+ " if (len(checkports) > 1):\n",
+ " port = checkports[1]\n",
+ " else:\n",
+ " port = None\n",
+ "\n",
+ " return ip, port\n",
+ "\n",
+ "# Parse the CONNECT statement and execute if possible \n",
+ "\n",
+ "def parseConnect(inSQL,local_ns):\n",
+ "\n",
+ " import shlex\n",
+ " \n",
+ " global _settings, _connected\n",
+ "\n",
+ " _connected = False\n",
+ " \n",
+ " cParms = shlex.split(inSQL) # inSQL.split()\n",
+ " cnt = 0\n",
+ "\n",
+ " connection = copy.deepcopy(emptySettings())\n",
+ "\n",
+ " # CONNECT {no arguments}\n",
+ "\n",
+ " if (len(cParms) == 1):\n",
+ " connection = copy.deepcopy(_settings)\n",
+ " _ = db2_doConnect(connection)\n",
+ " return\n",
+ " \n",
+ " # Check CONNECT arguments\n",
+ " \n",
+ " while cnt < len(cParms):\n",
+ "\n",
+ " if cParms[cnt].upper() == 'TO':\n",
+ " if cnt+1 < len(cParms):\n",
+ " connection[\"database\"] = cParms[cnt+1].upper()\n",
+ " cnt = cnt + 1\n",
+ " else:\n",
+ " errormsg(\"No database specified in the CONNECT statement\")\n",
+ " return\n",
+ " \n",
+ " elif cParms[cnt].upper() == \"SSL\":\n",
+ " connection[\"ssl\"] = \"SSL\"\n",
+ "\n",
+ " elif cParms[cnt].upper() == \"SSLFILE\":\n",
+ " if cnt+1 < len(cParms):\n",
+ " cert = cParms[cnt+1]\n",
+ " connection[\"sslfile\"] = cert\n",
+ " connection[\"ssl\"] = \"SSL\"\n",
+ " cnt = cnt + 1\n",
+ " else:\n",
+ " errormsg(\"No filename provided for the SSLFILE option\")\n",
+ " return\n",
+ " \n",
+ " elif cParms[cnt].upper() == 'CREDENTIALS':\n",
+ " if cnt+1 < len(cParms):\n",
+ " credentials = cParms[cnt+1]\n",
+ " if (credentials in local_ns):\n",
+ " tempid = eval(credentials,local_ns)\n",
+ " if (isinstance(tempid,dict) == False):\n",
+ " errormsg(\"The CREDENTIALS variable (\" + credentials + \") does not contain a valid Python dictionary (JSON object)\")\n",
+ " return\n",
+ " else:\n",
+ " tempid = None\n",
+ " \n",
+ " if (tempid == None):\n",
+ " fname = credentials + \".pickle\"\n",
+ " try:\n",
+ " with open(fname,'rb') as f:\n",
+ " _id = pickle.load(f)\n",
+ " except:\n",
+ " errormsg(\"Unable to find credential variable or file.\")\n",
+ " return\n",
+ " else:\n",
+ " _id = tempid\n",
+ " \n",
+ " try:\n",
+ " connection[\"database\"] = _id.get(\"database\",\"\")\n",
+ " connection[\"hostname\"] = _id.get(\"hostname\",\"\")\n",
+ " connection[\"port\"] = _id.get(\"port\",\"50000\")\n",
+ " connection[\"uid\"] = _id.get(\"uid\",\"\")\n",
+ " connection[\"pwd\"] = _id.get(\"pwd\",\"\")\n",
+ " connection[\"ssl\"] = _id.get(\"ssl\",\"\")\n",
+ " connection[\"sslfile\"] = _id.get(\"sslfile\",\"\")\n",
+ " connection[\"apikey\"] = _id.get(\"apikey\",\"\")\n",
+ " connection[\"accesstoken\"] = _id.get(\"accesstoken\",\"\")\n",
+ " connection[\"dsn\"] = _id.get(\"dsn\",\"\")\n",
+ " connection[\"protocol\"] = _id.get(\"protocol\",\"TCPIP\")\n",
+ " connection[\"paging\"] = _id.get(\"paging\",\"ON\")\n",
+ " connection[\"display\"] = _id.get(\"display\",\"PANDAS\")\n",
+ " connection[\"threads\"] = _id.get(\"threads\",0)\n",
+ " connection[\"maxrows\"] = _id.get(\"maxrows\",250)\n",
+ " \n",
+ " try:\n",
+ " fname = credentials + \".pickle\"\n",
+ " with open(fname,'wb') as f:\n",
+ " pickle.dump(connection,f)\n",
+ " \n",
+ " except:\n",
+ " errormsg(\"Failed trying to write Db2 Credentials.\")\n",
+ " return\n",
+ " except:\n",
+ " errormsg(\"Credentials file is missing information.\")\n",
+ " return\n",
+ " \n",
+ " else:\n",
+ " errormsg(\"No Credentials name supplied\")\n",
+ " return\n",
+ " \n",
+ " cnt = cnt + 1\n",
+ " \n",
+ " elif cParms[cnt].upper() == 'USER':\n",
+ " if cnt+1 < len(cParms):\n",
+ " connection[\"uid\"] = cParms[cnt+1]\n",
+ " cnt = cnt + 1\n",
+ " else:\n",
+ " errormsg(\"No userid specified in the CONNECT statement\")\n",
+ " return\n",
+ " \n",
+ " elif cParms[cnt].upper() == 'USING':\n",
+ " if cnt+1 < len(cParms):\n",
+ " connection[\"pwd\"] = cParms[cnt+1] \n",
+ " if (connection.get(\"pwd\",\"?\") == '?'):\n",
+ " connection[\"pwd\"] = getpass.getpass(\"Password [password]: \") or \"password\"\n",
+ " cnt = cnt + 1\n",
+ " else:\n",
+ " errormsg(\"No password specified in the CONNECT statement\")\n",
+ " return\n",
+ " \n",
+ " elif cParms[cnt].upper() == 'HOST':\n",
+ " if cnt+1 < len(cParms):\n",
+ " hostport = cParms[cnt+1]\n",
+ " ip, port = split_string(hostport)\n",
+ " if (port == None): connection[\"port\"] = \"50000\"\n",
+ " connection[\"hostname\"] = ip\n",
+ " cnt = cnt + 1\n",
+ " else:\n",
+ " errormsg(\"No hostname specified in the CONNECT statement\")\n",
+ " return\n",
+ " \n",
+ " elif cParms[cnt].upper() == 'PORT': \n",
+ " if cnt+1 < len(cParms):\n",
+ " connection[\"port\"] = cParms[cnt+1]\n",
+ " cnt = cnt + 1\n",
+ " else:\n",
+ " errormsg(\"No port specified in the CONNECT statement\")\n",
+ " return\n",
+ " \n",
+ " elif cParms[cnt].upper() == 'DSN': \n",
+ " if cnt+1 < len(cParms):\n",
+ " connection[\"dsn\"] = cParms[cnt+1]\n",
+ " cnt = cnt + 1\n",
+ " else:\n",
+ " errormsg(\"No DSN string provided in the CONNECT statement\")\n",
+ " return\t\n",
+ " \n",
+ " elif cParms[cnt].upper() == 'APIKEY': \n",
+ " if cnt+1 < len(cParms):\n",
+ " connection[\"apikey\"] = cParms[cnt+1]\n",
+ " cnt = cnt + 1\n",
+ " else:\n",
+ " errormsg(\"No APIKEY string provided in the CONNECT statement\")\n",
+ " return\t \n",
+ " \n",
+ " elif cParms[cnt].upper() == 'ACCESSTOKEN': \n",
+ " if cnt+1 < len(cParms):\n",
+ " connection[\"accesstoken\"] = cParms[cnt+1]\n",
+ " cnt = cnt + 1\n",
+ " else:\n",
+ " errormsg(\"No ACCESSTOKEN provided in the CONNECT statement\")\n",
+ " return\n",
+ " \n",
+ " elif cParms[cnt].upper() in ('CLOSE','RESET') :\n",
+ " try:\n",
+ " result = ibm_db.close(_hdbc)\n",
+ " _hdbi.close()\n",
+ " except:\n",
+ " pass\n",
+ " success(\"Connection closed.\") \n",
+ " if cParms[cnt].upper() == 'RESET': \n",
+ " _settings = copy.deepcopy(emptySettings())\n",
+ " save_settings(_settings)\n",
+ " return\n",
+ " else:\n",
+ " pass\n",
+ " \n",
+ " cnt = cnt + 1\n",
+ " \n",
+ " _ = db2_doConnect(connection)\n",
+ "\n",
+ "# Build the DSN string based on the connection information\n",
+ "\n",
+ "def buildDSN(connection,reconnect=False):\n",
+ " \n",
+ " database = connection.get(\"database\",None)\n",
+ " hostname = connection.get(\"hostname\",None)\n",
+ " port = connection.get(\"port\",\"50000\")\n",
+ " uid = connection.get(\"uid\",None)\n",
+ " pwd = connection.get(\"pwd\",None)\n",
+ " ssl = connection.get(\"ssl\",None)\n",
+ " sslfile = connection.get(\"sslfile\",None)\n",
+ " apikey = connection.get(\"apikey\",None)\n",
+ " accesstoken = connection.get(\"accesstoken\",None)\n",
+ " user_dsn = connection.get(\"dsn\",None)\n",
+ "\n",
+ " dsn = None\n",
+ "\n",
+ " while True:\n",
+ "\n",
+ " # CONNECT TO HOST PORT APIKEY \n",
+ "\n",
+ " if (apikey not in [\"\",None]): # APIKEY does not require a userid/password \n",
+ "\n",
+ " if (hostname in [None,\"\"] or port in [None,\"\"] or database in [None,\"\"]):\n",
+ " if (reconnect == False):\n",
+ " errormsg(\"Connect using an APIKEY requires HOST, PORT, and DATABASE name\")\n",
+ " return None \n",
+ "\n",
+ " if (uid not in [\"\",None]):\n",
+ " \n",
+ " dsn = (\"DRIVER={IBM DB2 ODBC DRIVER};\"\n",
+ " f\"DATABASE={database};\"\n",
+ " f\"HOSTNAME={hostname};\"\n",
+ " f\"PORT={port};\"\n",
+ " f\"PROTOCOL=TCPIP;ConnectTimeout=15;loginTimeout=15;LONGDATACOMPAT=1;DeferredPrepare=1;\"\n",
+ " f\"UID={uid};\"\n",
+ " f\"AUTHENTICATION=GSSplugin;SECURITY=SSL;\"\n",
+ " f\"APIKEY={apikey};\")\n",
+ " \n",
+ " else:\n",
+ "\n",
+ " dsn = (\"DRIVER={IBM DB2 ODBC DRIVER};\"\n",
+ " f\"DATABASE={database};\"\n",
+ " f\"HOSTNAME={hostname};\"\n",
+ " f\"PORT={port};\"\n",
+ " f\"PROTOCOL=TCPIP;ConnectTimeout=15;loginTimeout=15;LONGDATACOMPAT=1;DeferredPrepare=1;\"\n",
+ " f\"AUTHENTICATION=GSSplugin;SECURITY=SSL;\"\n",
+ " f\"APIKEY={apikey};\")\n",
+ "\n",
+ " break\n",
+ "\n",
+ " # CONNECT TO HOST PORT ACCESSTOKEN \n",
+ "\n",
+ " if (accesstoken not in [\"\",None]): # ACCESSTOKEN does not require a userid/password\n",
+ "\n",
+ " if (hostname in [None,\"\"] or port in [None,\"\"] or database in [None,\"\"]):\n",
+ " if (reconnect == False):\n",
+ " errormsg(\"Connect using an ACCESSTOKEN requires HOST, PORT, and DATABASE name\")\n",
+ " return None \n",
+ "\n",
+ " dsn = (\"DRIVER={IBM DB2 ODBC DRIVER};\"\n",
+ " f\"DATABASE={database};\"\n",
+ " f\"HOSTNAME={hostname};\"\n",
+ " f\"PORT={port};\"\n",
+ " f\"PROTOCOL=TCPIP;ConnectTimeout=15;loginTimeout=15;LONGDATACOMPAT=1;DeferredPrepare=1;\"\n",
+ " f\"AUTHENTICATION=GSSplugin;SECURITY=SSL;\"\n",
+ " f\"ACCESSTOKEN={accesstoken};\")\n",
+ "\n",
+ " break\n",
+ "\n",
+ " # CONNECT DSN \n",
+ "\n",
+ " if (user_dsn not in [\"\",None]): # Absolutely no checking is done on your DSN Setting\n",
+ "\n",
+ " dsn = user_dsn\n",
+ " break\n",
+ "\n",
+ " # CONNECT TO HOST PORT SSLFILE \n",
+ "\n",
+ " if (hostname in [None,\"\"] or port in [None,\"\"] or database in [None,\"\"]):\n",
+ " if (reconnect == False):\n",
+ " errormsg(\"Connect requires a HOST, PORT, and DATABASE name\")\n",
+ " return None \n",
+ " \n",
+ " if (uid in [\"\",None] or pwd in [\"\",None]): \n",
+ " if (reconnect == False):\n",
+ " errormsg(\"Connect requires a userid and password\")\n",
+ " return None\n",
+ "\n",
+ " if (ssl not in [\"\",None] or sslfile not in [\"\",None]): # See if you have specified SSL connection\n",
+ "\n",
+ " if (sslfile not in [\"\",None]):\n",
+ "\n",
+ " dsn = (\"DRIVER={IBM DB2 ODBC DRIVER};\"\n",
+ " f\"DATABASE={database};\"\n",
+ " f\"HOSTNAME={hostname};\"\n",
+ " f\"PORT={port};\"\n",
+ " f\"UID={uid};\"\n",
+ " f\"PWD={pwd};\"\n",
+ " f\"PROTOCOL=TCPIP;ConnectTimeout=15;loginTimeout=15;LONGDATACOMPAT=1;DeferredPrepare=1;\"\n",
+ " f\"SECURITY=SSL;SSLServerCertificate={sslfile};\") \n",
+ "\n",
+ " else:\n",
+ " \n",
+ " dsn = (\"DRIVER={IBM DB2 ODBC DRIVER};\"\n",
+ " f\"DATABASE={database};\"\n",
+ " f\"HOSTNAME={hostname};\"\n",
+ " f\"PORT={port};\"\n",
+ " f\"UID={uid};\"\n",
+ " f\"PWD={pwd};\"\n",
+ " f\"PROTOCOL=TCPIP;ConnectTimeout=15;loginTimeout=15;LONGDATACOMPAT=1;DeferredPrepare=1;\"\n",
+ " f\"SECURITY=SSL;\") \n",
+ "\n",
+ " else:\n",
+ " dsn = (\"DRIVER={IBM DB2 ODBC DRIVER};\"\n",
+ " f\"DATABASE={database};\"\n",
+ " f\"HOSTNAME={hostname};\"\n",
+ " f\"PORT={port};\"\n",
+ " f\"UID={uid};\"\n",
+ " f\"PWD={pwd};\"\n",
+ " f\"PROTOCOL=TCPIP;ConnectTimeout=15;loginTimeout=15;LONGDATACOMPAT=1;DeferredPrepare=1;\")\n",
+ "\n",
+ " break\n",
+ " \n",
+ " return dsn\n",
+ "\n",
+ "def db2_doConnect(connection):\n",
+ " \n",
+ " global _hdbc, _hdbi, _connected\n",
+ " global _settings \n",
+ "\n",
+ " reconnect = False\n",
+ "\n",
+ " if (connection == None):\n",
+ " reconnect = True\n",
+ " connection = copy.deepcopy(_settings)\n",
+ "\n",
+ " database = connection.get(\"database\",None)\n",
+ " hostname = connection.get(\"hostname\",None) \n",
+ "\n",
+ " dsn = buildDSN(connection,reconnect=reconnect)\n",
+ "\n",
+ " if flag([\"-e\",\"-echo\"]) and dsn not in [None, \"\"]:\n",
+ " print(dsn)\n",
+ " return False\n",
+ "\n",
+ " if (dsn in [None,\"\"]):\n",
+ " if (reconnect == False):\n",
+ " errormsg(\"Empty connection string\")\n",
+ " return False\n",
+ " \n",
+ " # Get a database handle (hdbc) and a statement handle (hstmt) for subsequent access to DB2\n",
+ "\n",
+ " try:\n",
+ " _hdbc = ibm_db.connect(dsn, \"\", \"\")\n",
+ " except Exception as err:\n",
+ " print(repr(err))\n",
+ " # db2_error(False,True) # errormsg(str(err))\n",
+ " _connected = False\n",
+ " _settings[\"database\"] = ''\n",
+ " return False\n",
+ " \n",
+ " try:\n",
+ " _hdbi = ibm_db_dbi.Connection(_hdbc)\n",
+ " except Exception as err:\n",
+ " print(repr(err))\n",
+ " # db2_error(False,True) # errormsg(str(err))\n",
+ " _connected = False\n",
+ " _settings[\"database\"] = ''\n",
+ " return False \n",
+ " \n",
+ " _connected = True\n",
+ " \n",
+ " # Save the values for future use\n",
+ " \n",
+ " save_settings(connection)\n",
+ " \n",
+ " success(f\"Connection successful. {database} @ {hostname} \")\n",
+ " return True\n",
+ " \n",
+ "\n",
+ "def load_settings():\n",
+ "\n",
+ " # This routine will load the settings from the previous session if they exist\n",
+ " \n",
+ " global _settings\n",
+ " \n",
+ " fname = \"db2connect.pickle\"\n",
+ "\n",
+ " try:\n",
+ " with open(fname,'rb') as f: \n",
+ " _settings = pickle.load(f) \n",
+ " \n",
+ " except: \n",
+ " pass\n",
+ " \n",
+ " return\n",
+ "\n",
+ "def save_settings(connection):\n",
+ "\n",
+ " # This routine will save the current settings if they exist\n",
+ " \n",
+ " global _settings\n",
+ " \n",
+ " fname = \"db2connect.pickle\"\n",
+ " \n",
+ " try:\n",
+ " with open(fname,'wb') as f:\n",
+ " pickle.dump(connection,f)\n",
+ " _settings = copy.deepcopy(connection)\n",
+ " \n",
+ " except:\n",
+ " errormsg(\"Failed trying to write Db2 Configuration Information.\")\n",
+ " \n",
+ " return \n",
+ "\n",
+ "def db2_error(quiet,connect=False):\n",
+ " \n",
+ " global sqlerror, sqlcode, sqlstate, _environment\n",
+ " \n",
+ " try:\n",
+ " if (connect == False):\n",
+ " errmsg = ibm_db.stmt_errormsg().replace('\\r',' ')\n",
+ " errmsg = errmsg[errmsg.rfind(\"]\")+1:].strip()\n",
+ " else:\n",
+ " errmsg = ibm_db.conn_errormsg().replace('\\r',' ')\n",
+ " errmsg = errmsg[errmsg.rfind(\"]\")+1:].strip()\n",
+ " \n",
+ " sqlerror = errmsg\n",
+ " \n",
+ " msg_start = errmsg.find(\"SQLSTATE=\")\n",
+ " if (msg_start != -1):\n",
+ " msg_end = errmsg.find(\" \",msg_start)\n",
+ " if (msg_end == -1):\n",
+ " msg_end = len(errmsg)\n",
+ " sqlstate = errmsg[msg_start+9:msg_end]\n",
+ " else:\n",
+ " sqlstate = \"0\"\n",
+ " \n",
+ " msg_start = errmsg.find(\"SQLCODE=\")\n",
+ " if (msg_start != -1):\n",
+ " msg_end = errmsg.find(\" \",msg_start)\n",
+ " if (msg_end == -1):\n",
+ " msg_end = len(errmsg)\n",
+ " sqlcode = errmsg[msg_start+8:msg_end]\n",
+ " try:\n",
+ " sqlcode = int(sqlcode)\n",
+ " except:\n",
+ " pass\n",
+ " else: \n",
+ " sqlcode = 0\n",
+ " \n",
+ " except:\n",
+ " errmsg = \"Unknown error.\"\n",
+ " sqlcode = -99999\n",
+ " sqlstate = \"-99999\"\n",
+ " sqlerror = errmsg\n",
+ " return\n",
+ " \n",
+ " \n",
+ " msg_start = errmsg.find(\"SQLSTATE=\")\n",
+ " if (msg_start != -1):\n",
+ " msg_end = errmsg.find(\" \",msg_start)\n",
+ " if (msg_end == -1):\n",
+ " msg_end = len(errmsg)\n",
+ " sqlstate = errmsg[msg_start+9:msg_end]\n",
+ " else:\n",
+ " sqlstate = \"0\"\n",
+ " \n",
+ " \n",
+ " msg_start = errmsg.find(\"SQLCODE=\")\n",
+ " if (msg_start != -1):\n",
+ " msg_end = errmsg.find(\" \",msg_start)\n",
+ " if (msg_end == -1):\n",
+ " msg_end = len(errmsg)\n",
+ " sqlcode = errmsg[msg_start+8:msg_end]\n",
+ " try:\n",
+ " sqlcode = int(sqlcode)\n",
+ " except:\n",
+ " pass\n",
+ " else:\n",
+ " sqlcode = 0\n",
+ " \n",
+ " if quiet == True: return\n",
+ " \n",
+ " if (errmsg == \"\"): return\n",
+ "\n",
+ " html = ''\n",
+ " \n",
+ " if (_environment[\"jupyter\"] == True):\n",
+ " pdisplay(pHTML(html+errmsg+\"
\"))\n",
+ " else:\n",
+ " print(errmsg)\n",
+ " \n",
+ "# Print out an error message\n",
+ "\n",
+ "def errormsg(message):\n",
+ " \n",
+ " global _environment\n",
+ " \n",
+ " if (message != \"\"):\n",
+ " html = ''\n",
+ " if (_environment[\"jupyter\"] == True):\n",
+ " pdisplay(pHTML(html + message + \"
\")) \n",
+ " else:\n",
+ " print(message)\n",
+ " \n",
+ "def success(message):\n",
+ " \n",
+ " if (message not in (None,\"\")):\n",
+ " print(message)\n",
+ " return \n",
+ "\n",
+ "def debug(message,error=False):\n",
+ " \n",
+ " global _environment\n",
+ " \n",
+ " if (message in (None,\"\")):\n",
+ " return\n",
+ " \n",
+ " if (_environment[\"jupyter\"] == True):\n",
+ " spacer = \"
\" + \" \"\n",
+ " else:\n",
+ " spacer = \"\\n \"\n",
+ " \n",
+ " lines = message.split('\\n')\n",
+ " msg = \"\"\n",
+ " indent = 0\n",
+ " for line in lines:\n",
+ " delta = line.count(\"(\") - line.count(\")\")\n",
+ " if (msg == \"\"):\n",
+ " msg = line\n",
+ " indent = indent + delta\n",
+ " else:\n",
+ " if (delta < 0): indent = indent + delta\n",
+ " msg = msg + spacer * (indent*2) + line\n",
+ " if (delta > 0): indent = indent + delta \n",
+ "\n",
+ " if (indent < 0): indent = 0\n",
+ " if (error == True): \n",
+ " html = '' \n",
+ " else:\n",
+ " html = ''\n",
+ "\n",
+ " if (_environment[\"jupyter\"] == True):\n",
+ " pdisplay(pHTML(html + msg + \"
\"))\n",
+ " else:\n",
+ " print(msg)\n",
+ " \n",
+ " return \n",
+ "\n",
+ "def setMacro(inSQL,parms):\n",
+ " \n",
+ " global _macros\n",
+ " \n",
+ " names = parms.split()\n",
+ " if (len(names) < 2):\n",
+ " errormsg(\"No command name supplied.\")\n",
+ " return None\n",
+ " \n",
+ " macroName = names[1].upper()\n",
+ " _macros[macroName] = inSQL # inSQL.replace(\"\\t\",\" \")\n",
+ "\n",
+ " return\n",
+ "\n",
+ "def checkMacro(in_sql):\n",
+ " \n",
+ " global _macros\n",
+ " \n",
+ " if (len(in_sql) == 0): return(in_sql) # Nothing to do \n",
+ " \n",
+ " tokens = parseArgs(in_sql,None) # Take the string and reduce into tokens\n",
+ " \n",
+ " macro_name = tokens[0].upper() # Uppercase the name of the token\n",
+ " \n",
+ " if (macro_name not in _macros): \n",
+ " return(in_sql) # No macro by this name so just return the string\n",
+ "\n",
+ " result = runMacro(_macros[macro_name],in_sql,tokens) # Execute the macro using the tokens we found\n",
+ "\n",
+ " return(result) # Runmacro will either return the original SQL or the new one\n",
+ "\n",
+ "def splitassign(arg):\n",
+ " \n",
+ " var_name = \"null\"\n",
+ " var_value = \"null\"\n",
+ " \n",
+ " arg = arg.strip()\n",
+ " eq = arg.find(\"=\")\n",
+ " if (eq != -1):\n",
+ " var_name = arg[:eq].strip()\n",
+ " temp_value = arg[eq+1:].strip()\n",
+ " if (temp_value != \"\"):\n",
+ " ch = temp_value[0]\n",
+ " if (ch in [\"'\",'\"']):\n",
+ " if (temp_value[-1:] == ch):\n",
+ " var_value = temp_value[1:-1]\n",
+ " else:\n",
+ " var_value = temp_value\n",
+ " else:\n",
+ " var_value = temp_value\n",
+ " else:\n",
+ " var_value = arg\n",
+ "\n",
+ " return var_name, var_value\n",
+ "\n",
+ "def parseArgs(argin,_vars):\n",
+ "\n",
+ " quoteChar = \"\"\n",
+ " blockChar = \"\"\n",
+ " inQuote = False\n",
+ " inBlock = False\n",
+ " inArg = True\n",
+ " args = []\n",
+ " arg = ''\n",
+ "\n",
+ " for ch in argin.lstrip():\n",
+ " if (inBlock == True):\n",
+ " if (ch == \")\"):\n",
+ " inBlock = False\n",
+ " arg = arg + ch\n",
+ " else:\n",
+ " arg = arg + ch\n",
+ " elif (inQuote == True):\n",
+ " if (ch == quoteChar):\n",
+ " inQuote = False \n",
+ " arg = arg + ch #z\n",
+ " else:\n",
+ " arg = arg + ch\n",
+ " elif (ch == \"(\"): # Do we have a block\n",
+ " arg = arg + ch\n",
+ " inBlock = True\n",
+ " elif (ch == \"\\\"\" or ch == \"\\'\"): # Do we have a quote\n",
+ " quoteChar = ch\n",
+ " arg = arg + ch #z\n",
+ " inQuote = True\n",
+ " elif (ch == \" \"):\n",
+ " if (arg != \"\"):\n",
+ " arg = subvars(arg,_vars)\n",
+ " args.append(arg)\n",
+ " else:\n",
+ " args.append(\"null\")\n",
+ " arg = \"\"\n",
+ " else:\n",
+ " arg = arg + ch\n",
+ "\n",
+ " if (arg != \"\"):\n",
+ " arg = subvars(arg,_vars)\n",
+ " args.append(arg) \n",
+ "\n",
+ " return(args)\n",
+ "\n",
+ "def runMacro(script,in_sql,tokens):\n",
+ "\n",
+ " result = \"\"\n",
+ " runIT = True \n",
+ " code = script.split(\"\\n\")\n",
+ " level = 0\n",
+ " runlevel = [True,False,False,False,False,False,False,False,False,False]\n",
+ " ifcount = 0\n",
+ " flags = \"\"\n",
+ " _vars = {}\n",
+ "\n",
+ " for i in range(0,len(tokens)):\n",
+ " vstr = str(i)\n",
+ " _vars[vstr] = tokens[i]\n",
+ "\n",
+ " if (len(tokens) == 0):\n",
+ " _vars[\"argc\"] = \"0\"\n",
+ " else:\n",
+ " _vars[\"argc\"] = str(len(tokens)-1)\n",
+ "\n",
+ " for line in code:\n",
+ " line = line.strip()\n",
+ " if (line == \"\" or line == \"\\n\"): continue\n",
+ " if (line[0] == \"#\"): continue # A comment line starts with a # in the first position of the line\n",
+ " args = parseArgs(line,_vars) # Get all of the arguments\n",
+ " if (args[0] == \"if\"):\n",
+ " ifcount = ifcount + 1\n",
+ " if (runlevel[level] == False): # You can't execute this statement\n",
+ " continue\n",
+ " level = level + 1 \n",
+ " if (len(args) < 4):\n",
+ " print(\"Macro: Incorrect number of arguments for the if clause.\")\n",
+ " return in_sql\n",
+ " arg1 = args[1]\n",
+ " arg2 = args[3]\n",
+ " if (len(arg2) > 2):\n",
+ " ch1 = arg2[0]\n",
+ " ch2 = arg2[-1:]\n",
+ " if (ch1 in ['\"',\"'\"] and ch1 == ch2):\n",
+ " arg2 = arg2[1:-1].strip()\n",
+ "\n",
+ " op = args[2]\n",
+ " if (op in [\"=\",\"==\"]):\n",
+ " if (arg1 == arg2):\n",
+ " runlevel[level] = True\n",
+ " else:\n",
+ " runlevel[level] = False \n",
+ " elif (op in [\"<=\",\"=<\"]):\n",
+ " if (arg1 <= arg2):\n",
+ " runlevel[level] = True\n",
+ " else:\n",
+ " runlevel[level] = False \n",
+ " elif (op in [\">=\",\"=>\"]): \n",
+ " if (arg1 >= arg2):\n",
+ " runlevel[level] = True\n",
+ " else:\n",
+ " runlevel[level] = False \n",
+ " elif (op in [\"<>\",\"!=\"]): \n",
+ " if (arg1 != arg2):\n",
+ " runlevel[level] = True\n",
+ " else:\n",
+ " runlevel[level] = False \n",
+ " elif (op in [\"<\"]):\n",
+ " if (arg1 < arg2):\n",
+ " runlevel[level] = True\n",
+ " else:\n",
+ " runlevel[level] = False \n",
+ " elif (op in [\">\"]):\n",
+ " if (arg1 > arg2):\n",
+ " runlevel[level] = True\n",
+ " else:\n",
+ " runlevel[level] = False \n",
+ " else:\n",
+ " print(\"Macro: Unknown comparison operator in the if statement:\" + op)\n",
+ "\n",
+ " continue\n",
+ "\n",
+ " elif (args[0] in [\"exit\",\"echo\"] and runlevel[level] == True):\n",
+ " msg = \"\"\n",
+ " for msgline in args[1:]:\n",
+ " if (msg == \"\"):\n",
+ " msg = subvars(msgline,_vars)\n",
+ " else:\n",
+ " msg = msg + \" \" + subvars(msgline,_vars)\n",
+ " if (msg != \"\"): \n",
+ " if (args[0] == \"echo\"):\n",
+ " debug(msg,error=False)\n",
+ " else:\n",
+ " debug(msg,error=True)\n",
+ " if (args[0] == \"exit\"): return ''\n",
+ "\n",
+ " elif (args[0] == \"pass\" and runlevel[level] == True):\n",
+ " pass\n",
+ "\n",
+ " elif (args[0] == \"flags\" and runlevel[level] == True):\n",
+ " if (len(args) > 1):\n",
+ " for i in range(1,len(args)):\n",
+ " flags = flags + \" \" + args[i]\n",
+ " flags = flags.strip()\n",
+ "\n",
+ " elif (args[0] == \"var\" and runlevel[level] == True):\n",
+ " value = \"\"\n",
+ " for val in args[2:]:\n",
+ " if (value == \"\"):\n",
+ " value = subvars(val,_vars)\n",
+ " else:\n",
+ " value = value + \" \" + subvars(val,_vars)\n",
+ " value.strip()\n",
+ " _vars[args[1]] = value \n",
+ "\n",
+ " elif (args[0] == 'else'):\n",
+ "\n",
+ " if (ifcount == level):\n",
+ " runlevel[level] = not runlevel[level]\n",
+ "\n",
+ " elif (args[0] == 'return' and runlevel[level] == True):\n",
+ " return(f\"{flags} {result}\")\n",
+ "\n",
+ " elif (args[0] == \"endif\"):\n",
+ " ifcount = ifcount - 1\n",
+ " if (ifcount < level):\n",
+ " level = level - 1\n",
+ " if (level < 0):\n",
+ " print(\"Macro: Unmatched if/endif pairs.\")\n",
+ " return ''\n",
+ "\n",
+ " else:\n",
+ " if (runlevel[level] == True):\n",
+ " if (result == \"\"):\n",
+ " result = subvars(line,_vars)\n",
+ " else:\n",
+ " result = result + \"\\n\" + subvars(line,_vars)\n",
+ "\n",
+ " return(f\"{flags} {result}\") \n",
+ "\n",
+ "def subvars(script,_vars):\n",
+ " \n",
+ " if (_vars == None): return script\n",
+ " \n",
+ " remainder = script\n",
+ " result = \"\"\n",
+ " done = False\n",
+ " \n",
+ " while done == False:\n",
+ " bv = remainder.find(\"{\")\n",
+ " if (bv == -1):\n",
+ " done = True\n",
+ " continue\n",
+ " ev = remainder.find(\"}\")\n",
+ " if (ev == -1):\n",
+ " done = True\n",
+ " continue\n",
+ " result = result + remainder[:bv]\n",
+ " vvar = remainder[bv+1:ev].strip()\n",
+ " remainder = remainder[ev+1:]\n",
+ " \n",
+ " modifier = \"\"\n",
+ " \n",
+ " if (len(vvar) == 0):\n",
+ " errormsg(f\"No variable name supplied in the braces {{}}.\")\n",
+ " return script\n",
+ " \n",
+ " upper = False\n",
+ " allvars = False\n",
+ " concat = \" \"\n",
+ " \n",
+ " if (len(vvar) > 1):\n",
+ " modifier = vvar[0]\n",
+ " if (modifier == \"^\"):\n",
+ " upper = True\n",
+ " vvar = vvar[1:]\n",
+ " elif (modifier == \"*\"):\n",
+ " vvar = vvar[1:]\n",
+ " allvars = True\n",
+ " concat = \" \"\n",
+ " elif (vvar[0] == \",\"):\n",
+ " vvar = vvar[1:]\n",
+ " allvars = True\n",
+ " concat = \",\"\n",
+ " else:\n",
+ " pass\n",
+ " \n",
+ " if (vvar in _vars):\n",
+ " if (upper == True):\n",
+ " items = _vars[vvar].upper()\n",
+ " elif (allvars == True):\n",
+ " try:\n",
+ " iVar = int(vvar)\n",
+ " except:\n",
+ " return(script)\n",
+ " items = \"\"\n",
+ " sVar = str(iVar)\n",
+ " while sVar in _vars:\n",
+ " if (items == \"\"):\n",
+ " items = _vars[sVar]\n",
+ " else:\n",
+ " items = items + concat + _vars[sVar]\n",
+ " iVar = iVar + 1\n",
+ " sVar = str(iVar)\n",
+ " else:\n",
+ " items = _vars[vvar]\n",
+ " else:\n",
+ " if (allvars == True):\n",
+ " items = \"\"\n",
+ " else:\n",
+ " items = \"null\" \n",
+ " \n",
+ " result = result + items\n",
+ " \n",
+ " if (remainder != \"\"):\n",
+ " result = result + remainder\n",
+ " \n",
+ " return(result)\n",
+ "\n",
+ "def splitargs(arguments):\n",
+ " \n",
+ " import types\n",
+ " \n",
+ " # String the string and remove the ( and ) characters if they at the beginning and end of the string\n",
+ " \n",
+ " results = []\n",
+ " \n",
+ " step1 = arguments.strip()\n",
+ " if (len(step1) == 0): return(results) # Not much to do here - no args found\n",
+ " \n",
+ " if (step1[0] == '('):\n",
+ " if (step1[-1:] == ')'):\n",
+ " step2 = step1[1:-1]\n",
+ " step2 = step2.strip()\n",
+ " else:\n",
+ " step2 = step1\n",
+ " else:\n",
+ " step2 = step1\n",
+ " \n",
+ " # Now we have a string without brackets. Start scanning for commas\n",
+ " \n",
+ " quoteCH = \"\"\n",
+ " pos = 0\n",
+ " arg = \"\"\n",
+ " args = []\n",
+ " \n",
+ " while pos < len(step2):\n",
+ " ch = step2[pos]\n",
+ " if (quoteCH == \"\"): # Are we in a quote?\n",
+ " if (ch in ('\"',\"'\")): # Check to see if we are starting a quote\n",
+ " quoteCH = ch\n",
+ " arg = arg + ch\n",
+ " pos += 1\n",
+ " elif (ch == \",\"): # Are we at the end of a parameter?\n",
+ " arg = arg.strip()\n",
+ " args.append(arg)\n",
+ " arg = \"\"\n",
+ " inarg = False \n",
+ " pos += 1\n",
+ " else: # Continue collecting the string\n",
+ " arg = arg + ch\n",
+ " pos += 1\n",
+ " else:\n",
+ " if (ch == quoteCH): # Are we at the end of a quote?\n",
+ " arg = arg + ch # Add the quote to the string\n",
+ " pos += 1 # Increment past the quote\n",
+ " quoteCH = \"\" # Stop quote checking (maybe!)\n",
+ " else:\n",
+ " pos += 1\n",
+ " arg = arg + ch\n",
+ "\n",
+ " if (quoteCH != \"\"): # So we didn't end our string\n",
+ " arg = arg.strip()\n",
+ " args.append(arg)\n",
+ " elif (arg != \"\"): # Something left over as an argument\n",
+ " arg = arg.strip()\n",
+ " args.append(arg)\n",
+ " else:\n",
+ " pass\n",
+ " \n",
+ " results = []\n",
+ " \n",
+ " for arg in args:\n",
+ " result = []\n",
+ " if (len(arg) > 0):\n",
+ " if (arg[0] in ('\"',\"'\")):\n",
+ " value = arg[1:-1]\n",
+ " isString = True\n",
+ " isNumber = False\n",
+ " else:\n",
+ " isString = False \n",
+ " isNumber = False \n",
+ " try:\n",
+ " value = eval(arg)\n",
+ " if (type(value) == int):\n",
+ " isNumber = True\n",
+ " elif (isinstance(value,float) == True):\n",
+ " isNumber = True\n",
+ " else:\n",
+ " value = arg\n",
+ " except:\n",
+ " value = arg\n",
+ "\n",
+ " else:\n",
+ " value = \"\"\n",
+ " isString = False\n",
+ " isNumber = False\n",
+ " \n",
+ " result = [value,isString,isNumber]\n",
+ " results.append(result)\n",
+ " \n",
+ " return results\n",
+ "\n",
+ "def createDF(hdbc,hdbi,sqlin,local_ns):\n",
+ " \n",
+ " import datetime\n",
+ " import ibm_db \n",
+ " import shlex\n",
+ " \n",
+ " global sqlcode, _settings, _parallel\n",
+ " \n",
+ " NoDF = False\n",
+ " YesDF = True\n",
+ " \n",
+ " if (hdbc == None or hdbi == None):\n",
+ " errormsg(\"You need to connect to a database before issuing this command.\")\n",
+ " return NoDF, None\n",
+ " \n",
+ " # Strip apart the command into tokens based on spaces\n",
+ "\n",
+ " tokens = shlex.split(sqlin)\n",
+ "\n",
+ " token_count = len(tokens)\n",
+ " \n",
+ " if (token_count < 5): # Not enough parameters\n",
+ " errormsg(\"Insufficient arguments for USING command.\")\n",
+ " return NoDF, None\n",
+ " \n",
+ " keyword_command = tokens[0].upper()\n",
+ " dfName = tokens[1]\n",
+ " keyword_create = tokens[2].upper()\n",
+ " keyword_table = tokens[3].upper()\n",
+ " table = tokens[4] \n",
+ " \n",
+ " if (dfName not in local_ns):\n",
+ " errormsg(f'The variable \"{dfName}\" does not exist in the local variable list.')\n",
+ " return NoDF, None \n",
+ "\n",
+ " try:\n",
+ " dfValue = eval(dfName,None,local_ns) # globals()[varName] # eval(varName)\n",
+ " except:\n",
+ " errormsg(f'The variable \"{dfName}\" does not contain a value.')\n",
+ " return NoDF, None \n",
+ " \n",
+ " if (keyword_create in (\"SELECT\",\"WITH\")):\n",
+ " \n",
+ " if (_parallel == False):\n",
+ " errormsg(\"Parallelism is not availble on this system.\")\n",
+ " return NoDF, None\n",
+ " \n",
+ " thread_count = _settings.get(\"threads\",0)\n",
+ " if (thread_count in (0,1)):\n",
+ " errormsg(\"The THREADS option is currently set to 0 or 1 which disables parallelism.\")\n",
+ " return NoDF, None \n",
+ " \n",
+ " ok, df = dfSQL(hdbc,hdbi,sqlin,dfName,dfValue,thread_count)\n",
+ " \n",
+ " if (ok == False):\n",
+ " return NoDF, None\n",
+ " else:\n",
+ " return YesDF, df\n",
+ " \n",
+ " if (isinstance(dfValue,pandas.DataFrame) == False): # Not a Pandas dataframe\n",
+ " errormsg(f'The variable \"{dfName}\" is not a Pandas dataframe.')\n",
+ " return NoDF, None\n",
+ " \n",
+ " if (keyword_create not in (\"CREATE\",\"REPLACE\",\"APPEND\",\"DECLARE\") or keyword_table != \"TABLE\"):\n",
+ " errormsg(\"Incorrect syntax: %sql using [create | replace | append | declare] table [options]\")\n",
+ " return NoDF, None\n",
+ " \n",
+ " flag_withdata = False\n",
+ " flag_asis = False\n",
+ " flag_export = False\n",
+ " flag_ddl = False\n",
+ " clob_type = \"CLOB\"\n",
+ " blob_type = \"BLOB\"\n",
+ " char_type = \"VARCHAR\"\n",
+ " limit = -1\n",
+ " column_list = []\n",
+ " column_index = []\n",
+ " padding = 1.0\n",
+ " \n",
+ " token_idx = 5\n",
+ " while (token_idx < token_count):\n",
+ " option_key = tokens[token_idx].upper()\n",
+ " if (token_idx + 1 >= token_count):\n",
+ " errormsg(f\"Insufficient arguments for the {option_key} option.\")\n",
+ " return NoDF, None\n",
+ " option_val = tokens[token_idx+1].upper()\n",
+ " if (option_key == \"WITH\" and option_val == \"DATA\"):\n",
+ " flag_withdata = True\n",
+ " token_idx += 2\n",
+ " elif (option_key == \"NAMES\" and option_val == \"ASIS\"):\n",
+ " flag_asis = True\n",
+ " token_idx += 2\n",
+ " elif (option_key == \"DDL\"):\n",
+ " if (option_val == \"EXPORT\"):\n",
+ " flag_export = True\n",
+ " elif (option_val == \"ONLY\"):\n",
+ " flag_ddl = True\n",
+ " else:\n",
+ " errormsg(\"DDL option requires EXPORT or ONLY keyword\")\n",
+ " return NoDF, None\n",
+ " token_idx += 2 \n",
+ " elif (option_key in [\"NCHAR\",\"PADDING\"]):\n",
+ " try:\n",
+ " padding = float(option_val)\n",
+ " except:\n",
+ " errormsg(\"The NCHAR/PADDING value must be a valid decimal value greater than 1.0\")\n",
+ " return NoDF, None\n",
+ " if (padding <= 1.0 or padding > 5.0):\n",
+ " padding = 1.0 \n",
+ " token_idx += 2\n",
+ " elif (option_key == \"LIMIT\"):\n",
+ " if (option_val.isnumeric() == False):\n",
+ " errormsg(\"The LIMIT must be a valid number from -1 (unlimited) to the maximun number of rows to insert\")\n",
+ " return NoDF, None\n",
+ " limit = int(option_val)\n",
+ " token_idx += 2\n",
+ " elif (option_key == \"COLUMNS\"):\n",
+ " column_list == []\n",
+ " for i in range (token_idx + 1, token_count):\n",
+ " column_list.append(tokens[i].upper())\n",
+ " token_idx = token_count\n",
+ " else:\n",
+ " errormsg(\"Invalid option. [WITH DATA] [DDL (ONLY | EXPORT)] [NAMES ASIS] [PADDING x] [LIMIT count] [COLUMNS list]\")\n",
+ " return NoDF, None \n",
+ "\n",
+ " if (keyword_create == \"REPLACE\" and (flag_ddl == False and flag_export == False)):\n",
+ " sql = f\"DROP TABLE {table}\"\n",
+ " ok = execSQL(hdbc,sql,quiet=True) \n",
+ "\n",
+ " sql = [] \n",
+ " columns = dict(dfValue.dtypes)\n",
+ " if (keyword_create == \"DECLARE\"):\n",
+ " sql.append(f'CREATE GLOBAL TEMPORARY TABLE {table} (')\n",
+ " else:\n",
+ " sql.append(f'CREATE TABLE {table} (')\n",
+ " datatypes = []\n",
+ " sql_types = []\n",
+ " column_names = []\n",
+ " comma = \"\"\n",
+ "\n",
+ " idx = -1\n",
+ "\n",
+ " temp_column_list = []\n",
+ " for column in columns:\n",
+ " temp_column_list.append(column.upper())\n",
+ "\n",
+ " for column_requested in column_list:\n",
+ " if (column_requested not in temp_column_list):\n",
+ " errormsg(f'Column \"{column_requested}\" was not found in the dataframe.')\n",
+ " return NoDF, None \n",
+ "\n",
+ " for column in columns:\n",
+ "\n",
+ " if (len(column_list) != 0):\n",
+ " if (column.upper() not in column_list):\n",
+ " idx += 1\n",
+ " continue\n",
+ " column_found = True\n",
+ "\n",
+ " idx += 1\n",
+ " datatype = str(columns[column])\n",
+ " datatype = datatype.upper()\n",
+ " column_index.append(idx)\n",
+ " \n",
+ " if (datatype == \"OBJECT\"):\n",
+ " maxlength = dfValue[column].apply(str).apply(lambda x: len(x.encode('utf-8'))).max() \n",
+ " if (maxlength <= 0): maxlength = 1\n",
+ " adjusted = int(float(maxlength) * padding) \n",
+ " if (adjusted < 32000):\n",
+ " type = f\"{char_type}({adjusted})\" #f\"VARCHAR({adjusted})\"\n",
+ " sql_type = ibm_db.SQL_VARCHAR\n",
+ " else:\n",
+ " type = f\"{clob_type}({adjusted})\" # \"CLOB({adjusted})\"\n",
+ " if (\"DBCLOB\" in clob_type):\n",
+ " sql_type = ibm_db.SQL_DBCLOB \n",
+ " else: \n",
+ " sql_type = ibm_db.SQL_CLOB # lob_sqltype\n",
+ " elif (datatype in [\"INT64\",\"INT32\",\"INT16\",\"INT8\"]):\n",
+ " count = dfValue[column].count()\n",
+ " if (count == 0):\n",
+ " maxlength = dfValue[column].apply(str).apply(lambda x: len(x.encode('utf-8'))).max() \n",
+ " if (maxlength <= 0): maxlength = 1\n",
+ " adjusted = int(float(maxlength) * padding) \n",
+ " type = f\"{char_type}({adjusted})\" \n",
+ " sql_type = ibm_db.SQL_VARCHAR\n",
+ " else:\n",
+ " if (datatype == \"INT64\"):\n",
+ " sql_type = ibm_db.SQL_BIGINT\n",
+ " type = \"BIGINT\"\n",
+ " elif (datatype == \"INT32\"):\n",
+ " sql_type = ibm_db.SQL_INTEGER\n",
+ " type = \"INT\"\n",
+ " elif (datatype == \"INT16\"):\n",
+ " sql_type = ibm_db.SQL_SMALLINT\n",
+ " type = \"SMALLINT\" \n",
+ " else:\n",
+ " sql_type = ibm_db.SQL_SMALLINT\n",
+ " type = \"SMALLINT\"\n",
+ " elif (datatype in [\"FLOAT64\",\"FLOAT32\",\"FLOAT16\"]):\n",
+ " count = dfValue[column].count()\n",
+ " if (count == 0):\n",
+ " maxlength = dfValue[column].apply(str).apply(lambda x: len(x.encode('utf-8'))).max() \n",
+ " if (maxlength <= 0): maxlength = 1\n",
+ " adjusted = int(float(maxlength) * padding) \n",
+ " type = f\"{char_type}({adjusted})\" \n",
+ " sql_type = ibm_db.SQL_VARCHAR\n",
+ " else:\n",
+ " if (datatype == \"FLOAT64\"):\n",
+ " sql_type = ibm_db.SQL_FLOAT\n",
+ " type = \"FLOAT\"\n",
+ " elif (datatype == \"FLOAT32\"):\n",
+ " sql_type = ibm_db.SQL_REAL\n",
+ " type = \"REAL\"\n",
+ " else:\n",
+ " sql_type = ibm_db.SQL_REAL\n",
+ " type = \"REAL\"\n",
+ " elif (\"DATETIME64\" in datatype):\n",
+ " type = \"TIMESTAMP\"\n",
+ " sql_type = ibm_db.SQL_VARCHAR\n",
+ " elif (datatype == \"BOOL\"):\n",
+ " type = \"BINARY\"\n",
+ " sql_type = ibm_db.SQL_BINARY\n",
+ " maxlength = dfValue[column].apply(str).apply(len).max() \n",
+ " if (maxlength <= 0): maxlength = 1\n",
+ " adjusted = int(float(maxlength) * padding) \n",
+ " if (adjusted < 32000):\n",
+ " type = f\"{char_type}({adjusted})\" # f\"VARCHAR({adjusted})\"\n",
+ " sql_type = ibm_db.SQL_VARCHAR\n",
+ " else:\n",
+ " type = f\"{blob_type}({adjusted})\" # \"CLOB({adjusted})\"\n",
+ " sql_type = ibm_db.SQL_BLOB # lob_sqltype\n",
+ " else:\n",
+ " maxlength = dfValue[column].apply(str).apply(lambda x: len(x.encode('utf-8'))).max() \n",
+ " if (maxlength <= 0): maxlength = 1\n",
+ " adjusted = int(float(maxlength) * padding) \n",
+ " if (adjusted < 32000):\n",
+ " type = f\"{char_type}({adjusted})\" # \"VARCHAR({adjusted})\"\n",
+ " sql_type = ibm_db.SQL_VARCHAR\n",
+ " else:\n",
+ " type = f\"{clob_type}({adjusted})\" # \"CLOB({adjusted})\"\n",
+ " sql_type = ibm_db.SQL_CLOB # lob_sqltype\n",
+ "\n",
+ " datatypes.append(type) \n",
+ " sql_types.append(sql_type) \n",
+ "\n",
+ " if (flag_asis == False):\n",
+ " if (isinstance(column,str) == False):\n",
+ " column = str(column)\n",
+ " identifier = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_\"\n",
+ " column_name = column.strip().upper()\n",
+ " new_name = \"\"\n",
+ " for ch in column_name:\n",
+ " if (ch not in identifier):\n",
+ " new_name = new_name + \"_\"\n",
+ " else:\n",
+ " new_name = new_name + ch\n",
+ " \n",
+ " new_name = new_name.lstrip('_').rstrip('_')\n",
+ " \n",
+ " if (new_name == \"\" or new_name[0] not in \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"):\n",
+ " new_name = f'\"{column}\"'\n",
+ " else:\n",
+ " new_name = f'\"{column}\"'\n",
+ "\n",
+ " column_names.append(new_name)\n",
+ " \n",
+ " sql.append(f\" {new_name} {type}\")\n",
+ " sql.append(\")\")\n",
+ "\n",
+ " sqlcmd = \"\"\n",
+ " for i in range(0,len(sql)):\n",
+ " if (i > 0 and i < len(sql)-2):\n",
+ " comma = \",\"\n",
+ " else:\n",
+ " comma = \"\"\n",
+ " if (i == 0):\n",
+ " sqlcmd = \"{}{}\".format(sql[i],comma)\n",
+ " else:\n",
+ " sqlcmd = \"{}\\n{}{}\".format(sqlcmd,sql[i],comma)\n",
+ "\n",
+ " if (keyword_create == \"DECLARE\"):\n",
+ " sqlcmd = \"{}\\n{}\".format(sqlcmd,\"ON COMMIT PRESERVE ROWS NOT LOGGED\")\n",
+ "\n",
+ " if (flag_export == True or flag_ddl == True):\n",
+ " if (flag_ddl == True):\n",
+ " print(sqlcmd)\n",
+ " return NoDF, None\n",
+ " else:\n",
+ " return NoDF, sqlcmd\n",
+ " \n",
+ " if (keyword_create != \"APPEND\"):\n",
+ " print(sqlcmd)\n",
+ " ok = execSQL(hdbc,sqlcmd,quiet=False)\n",
+ " if (ok == False):\n",
+ " return NoDF, None\n",
+ "\n",
+ " if (flag_withdata == True or keyword_create == \"APPEND\"):\n",
+ " \n",
+ " autocommit = ibm_db.autocommit(hdbc)\n",
+ " ibm_db.autocommit(hdbc,False)\n",
+ "\n",
+ " row_count = 0\n",
+ " insert_sql = \"\"\n",
+ " rows, cols = dfValue.shape\n",
+ "\n",
+ " markers = \"\"\n",
+ " column_limit = len(sql_types)\n",
+ "\n",
+ " # column_list = \"\"\n",
+ " for i in range(0, column_limit):\n",
+ " if (markers == \"\"):\n",
+ " markers = \"?\"\n",
+ " else:\n",
+ " markers = f\"{markers},?\"\n",
+ "\n",
+ " insert_sql = f\"INSERT INTO {table} VALUES ({markers})\"\n",
+ "\n",
+ " try:\n",
+ " stmt = ibm_db.prepare(hdbc,insert_sql) # Check error code here\n",
+ " if (stmt in [False,None]): \n",
+ " db2_error(False)\n",
+ " return NoDF, None\n",
+ " except:\n",
+ " db2_error(False)\n",
+ " return NoDF, None\n",
+ "\n",
+ " row_count = 0\n",
+ "\n",
+ " parms = [None] * column_limit\n",
+ "\n",
+ " convert_table = []\n",
+ "\n",
+ " for col in range(0, column_limit):\n",
+ "\n",
+ " coltype = sql_types[col]\n",
+ " \n",
+ " if (coltype in [ibm_db.SQL_BIGINT, ibm_db.SQL_SMALLINT, ibm_db.SQL_INTEGER]):\t\t\t# Big Integer -> Python int\n",
+ " conversion = \"int\"\n",
+ " elif (coltype in [ibm_db.SQL_DECFLOAT,ibm_db.SQL_FLOAT,ibm_db.SQL_REAL,ibm_db.SQL_DOUBLE]):\t\t# Decfloat -> convert to character\n",
+ " conversion = \"float\"\n",
+ " elif (coltype == ibm_db.SQL_VARCHAR):\t\t# Varchar -> convert to character\n",
+ " conversion = \"str\"\n",
+ " elif (coltype == ibm_db.SQL_CLOB):\t\t\t# CLOB -> convert to character\n",
+ " conversion = \"str\"\n",
+ " elif (coltype == ibm_db.SQL_BINARY):\t\t# Binary -> Integer\n",
+ " conversion = \"int\"\n",
+ " else:\n",
+ " conversion = \"str\"\n",
+ " convert_table.append(conversion)\n",
+ "\n",
+ " try:\n",
+ " result = ibm_db.bind_param(stmt, col+1, parms[col], ibm_db.SQL_PARAM_INPUT, sql_types[col]) \n",
+ " except:\n",
+ " db2_error(False)\n",
+ " return NoDF, None\n",
+ " \n",
+ " if (result in [False,None]):\n",
+ " errormsg(\"SQL Prepare failed.\") \n",
+ " return NoDF, None\n",
+ "\n",
+ " for row in range(0,rows):\n",
+ " \n",
+ " for col in range(0, column_limit):\n",
+ "\n",
+ " colno = column_index[col]\n",
+ " checkValue = dfValue.iat[row,colno]\n",
+ "\n",
+ " conversion = convert_table[col]\n",
+ " try:\n",
+ " if (conversion == None):\t\t\t# Nothing to do\n",
+ " converted = checkValue\n",
+ " elif (conversion == \"int\"):\t\t\t# Set it to integer\n",
+ " try:\n",
+ " converted: int = int(checkValue) \n",
+ " except ValueError:\n",
+ " converted: int = None\n",
+ " elif (conversion == \"float\"):\t\t# Set to float\n",
+ " try:\n",
+ " converted: float = float(checkValue)\n",
+ " except ValueError:\n",
+ " converted: float = None\n",
+ " elif (conversion == \"str\"):\t\t\t# Set to string\n",
+ " try:\n",
+ " converted: str = str(checkValue)\n",
+ " except ValueError:\n",
+ " converted: str = None\n",
+ " else:\n",
+ " converted = checkValue\t\t\t# Hope for the best\n",
+ " except:\n",
+ " converted = checkValue\n",
+ "\n",
+ " parms[col] = converted\n",
+ "\n",
+ " try:\n",
+ " result = ibm_db.execute(stmt,(*parms,)) \n",
+ " except Exception as e:\n",
+ " print(\"SQL Execution error due to possible datatype mismatch or column too small\")\n",
+ " print(repr(e))\n",
+ " db2_error(True)\n",
+ " ibm_db.free_result(stmt)\n",
+ " return NoDF, None\n",
+ " \n",
+ " if (result == False): \n",
+ " errormsg(\"SQL Execute failed.\") \n",
+ " return NoDF, None\n",
+ "\n",
+ " row_count += 1\n",
+ " if (row_count % 1000 == 0 or row_count == limit):\n",
+ " ibm_db.commit(hdbc)\n",
+ " print(f\"\\r{row_count} of {rows} rows inserted.\",end=\"\")\n",
+ " \n",
+ " if (row_count == limit):\n",
+ " break\n",
+ "\n",
+ " print(f\"\\r{row_count} of {rows} rows inserted.\",end=\"\")\n",
+ " \n",
+ " ibm_db.commit(hdbc)\n",
+ " ibm_db.autocommit(hdbc,autocommit)\n",
+ "\n",
+ " print(\"\\nInsert completed.\")\n",
+ " \n",
+ " return NoDF, None\n",
+ "\n",
+ "\n",
+ "def sqlParser(sqlin,local_ns):\n",
+ " \n",
+ " sql_cmd = \"\"\n",
+ " encoded_sql = sqlin\n",
+ " \n",
+ " firstCommand = \"(?:^\\s*)([a-zA-Z]+)(?:\\s+.*|$)\"\n",
+ " \n",
+ " findFirst = re.match(firstCommand,sqlin)\n",
+ " \n",
+ " if (findFirst == None): # We did not find a match so we just return the empty string\n",
+ " return sql_cmd, encoded_sql\n",
+ " \n",
+ " cmd = findFirst.group(1)\n",
+ " sql_cmd = cmd.upper()\n",
+ "\n",
+ " #\n",
+ " # Scan the input string looking for variables in the format :var. If no : is found just return.\n",
+ " # Var must be alpha+number+_ to be valid\n",
+ " #\n",
+ " \n",
+ " if (':' not in sqlin): # A quick check to see if parameters are in here, but not fool-proof! \n",
+ " return sql_cmd, encoded_sql \n",
+ " \n",
+ " inVar = False \n",
+ " inQuote = \"\" \n",
+ " varName = \"\"\n",
+ " encoded_sql = \"\"\n",
+ " \n",
+ " STRING = 0\n",
+ " NUMBER = 1\n",
+ " LIST = 2\n",
+ " RAW = 3\n",
+ " PANDAS = 5\n",
+ " \n",
+ " for ch in sqlin:\n",
+ " if (inVar == True): # We are collecting the name of a variable\n",
+ " if (ch.upper() in \"@_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789[]\"):\n",
+ " varName = varName + ch\n",
+ " continue\n",
+ " else:\n",
+ " if (varName == \"\"):\n",
+ " encoded_sql = encoded_sql + \":\"\n",
+ " elif (varName[0] in ('[',']')):\n",
+ " encoded_sql = encoded_sql + \":\" + varName\n",
+ " else:\n",
+ " if (ch == '.'): # If the variable name is stopped by a period, assume no quotes are used\n",
+ " flag_quotes = False\n",
+ " else:\n",
+ " flag_quotes = True\n",
+ " varValue, varType = getContents(varName,flag_quotes,local_ns)\n",
+ " if (varType != PANDAS and varValue == None): \n",
+ " encoded_sql = encoded_sql + \":\" + varName\n",
+ " else:\n",
+ " if (varType == STRING):\n",
+ " encoded_sql = encoded_sql + varValue\n",
+ " elif (varType == NUMBER):\n",
+ " encoded_sql = encoded_sql + str(varValue)\n",
+ " elif (varType == RAW):\n",
+ " encoded_sql = encoded_sql + varValue\n",
+ " elif (varType == PANDAS):\n",
+ " insertsql = \"\"\n",
+ " coltypes = varValue.dtypes\n",
+ " rows, cols = varValue.shape\n",
+ " for row in range(0,rows):\n",
+ " insertrow = \"\"\n",
+ " for col in range(0, cols):\n",
+ " value = varValue.iloc[row][col]\n",
+ " if (coltypes[col] == \"object\"):\n",
+ " value = str(value)\n",
+ " value = addquotes(value,True)\n",
+ " else:\n",
+ " strvalue = str(value)\n",
+ " if (\"NAN\" in strvalue.upper()):\n",
+ " value = \"NULL\" \n",
+ " if (insertrow == \"\"):\n",
+ " insertrow = f\"{value}\"\n",
+ " else:\n",
+ " insertrow = f\"{insertrow},{value}\"\n",
+ " if (insertsql == \"\"):\n",
+ " insertsql = f\"({insertrow})\"\n",
+ " else:\n",
+ " insertsql = f\"{insertsql},({insertrow})\" \n",
+ " encoded_sql = encoded_sql + insertsql\n",
+ " elif (varType == LIST):\n",
+ " start = True\n",
+ " for v in varValue:\n",
+ " if (start == False):\n",
+ " encoded_sql = encoded_sql + \",\"\n",
+ " if (isinstance(v,int) == True): # Integer value \n",
+ " encoded_sql = encoded_sql + str(v)\n",
+ " elif (isinstance(v,float) == True):\n",
+ " encoded_sql = encoded_sql + str(v)\n",
+ " else:\n",
+ " flag_quotes = True\n",
+ " try:\n",
+ " if (v.find('0x') == 0): # Just guessing this is a hex value at beginning\n",
+ " encoded_sql = encoded_sql + v\n",
+ " else:\n",
+ " encoded_sql = encoded_sql + addquotes(v,flag_quotes) # String\n",
+ " except:\n",
+ " encoded_sql = encoded_sql + addquotes(str(v),flag_quotes) \n",
+ " start = False\n",
+ "\n",
+ " encoded_sql = encoded_sql + ch\n",
+ " varName = \"\"\n",
+ " inVar = False \n",
+ " elif (inQuote != \"\"):\n",
+ " encoded_sql = encoded_sql + ch\n",
+ " if (ch == inQuote): inQuote = \"\"\n",
+ " elif (ch in (\"'\",'\"')):\n",
+ " encoded_sql = encoded_sql + ch\n",
+ " inQuote = ch\n",
+ " elif (ch == \":\"): # This might be a variable\n",
+ " varName = \"\"\n",
+ " inVar = True\n",
+ " else:\n",
+ " encoded_sql = encoded_sql + ch\n",
+ " \n",
+ " if (inVar == True):\n",
+ " varValue, varType = getContents(varName,True,local_ns) # We assume the end of a line is quoted\n",
+ " if (varType != PANDAS and varValue == None): \n",
+ " encoded_sql = encoded_sql + \":\" + varName \n",
+ " else:\n",
+ " if (varType == STRING):\n",
+ " encoded_sql = encoded_sql + varValue\n",
+ " elif (varType == RAW):\n",
+ " encoded_sql = encoded_sql + varValue \n",
+ " elif (varType == NUMBER):\n",
+ " encoded_sql = encoded_sql + str(varValue)\n",
+ " elif (varType == PANDAS):\n",
+ " insertsql = \"\"\n",
+ " coltypes = varValue.dtypes\n",
+ " rows, cols = varValue.shape\n",
+ " for row in range(0,rows):\n",
+ " insertrow = \"\"\n",
+ " for col in range(0, cols):\n",
+ " value = varValue.iloc[row][col]\n",
+ " if (coltypes[col] == \"object\"):\n",
+ " value = str(value)\n",
+ " value = addquotes(value,True)\n",
+ " else:\n",
+ " strvalue = str(value)\n",
+ " if (\"NAN\" in strvalue.upper()):\n",
+ " value = \"NULL\" \n",
+ " if (insertrow == \"\"):\n",
+ " insertrow = f\"{value}\"\n",
+ " else:\n",
+ " insertrow = f\"{insertrow},{value}\"\n",
+ " if (insertsql == \"\"):\n",
+ " insertsql = f\"({insertrow})\"\n",
+ " else:\n",
+ " insertsql = f\"{insertsql},({insertrow})\" \n",
+ " encoded_sql = encoded_sql + insertsql \n",
+ " elif (varType == LIST):\n",
+ " flag_quotes = True\n",
+ " start = True\n",
+ " for v in varValue:\n",
+ " if (start == False):\n",
+ " encoded_sql = encoded_sql + \",\"\n",
+ " if (isinstance(v,int) == True): # Integer value \n",
+ " encoded_sql = encoded_sql + str(v)\n",
+ " elif (isinstance(v,float) == True):\n",
+ " encoded_sql = encoded_sql + str(v)\n",
+ " else:\n",
+ " try:\n",
+ " if (v.find('0x') == 0): # Just guessing this is a hex value\n",
+ " encoded_sql = encoded_sql + v\n",
+ " else:\n",
+ " encoded_sql = encoded_sql + addquotes(v,flag_quotes) # String\n",
+ " except:\n",
+ " encoded_sql = encoded_sql + addquotes(str(v),flag_quotes) \n",
+ " start = False\n",
+ "\n",
+ " return sql_cmd, encoded_sql\n",
+ "\n",
+ "def plotData(hdbi, sql):\n",
+ " \n",
+ " try:\n",
+ " df = pandas.read_sql(sql,hdbi)\n",
+ " \n",
+ " except Exception as err:\n",
+ " db2_error(False)\n",
+ " return\n",
+ " \n",
+ " \n",
+ " if df.empty:\n",
+ " errormsg(\"No results returned\")\n",
+ " return\n",
+ " \n",
+ " col_count = len(df.columns)\n",
+ "\n",
+ " if flag([\"-pb\",\"-bar\"]): # Plot 1 = bar chart\n",
+ " \n",
+ " if (col_count in (1,2,3)):\n",
+ " \n",
+ " if (col_count == 1):\n",
+ " \n",
+ " df.index = df.index + 1\n",
+ " _ = df.plot(kind='bar');\n",
+ " _ = plt.plot();\n",
+ " \n",
+ " elif (col_count == 2):\n",
+ " \n",
+ " xlabel = df.columns.values[0]\n",
+ " ylabel = df.columns.values[1]\n",
+ " df.plot(kind='bar',x=xlabel,y=ylabel);\n",
+ " _ = plt.plot();\n",
+ " \n",
+ " else:\n",
+ " \n",
+ " values = df.columns.values[2]\n",
+ " columns = df.columns.values[0]\n",
+ " index = df.columns.values[1]\n",
+ " pivoted = pandas.pivot_table(df, values=values, columns=columns, index=index) \n",
+ " _ = pivoted.plot.bar(); \n",
+ " \n",
+ " else:\n",
+ " errormsg(\"Can't determine what columns to plot\")\n",
+ " return\n",
+ " \n",
+ " elif flag([\"-pp\",\"-pie\"]): # Plot 2 = pie chart\n",
+ " \n",
+ " if (col_count in (1,2)): \n",
+ " \n",
+ " if (col_count == 1):\n",
+ " df.index = df.index + 1\n",
+ " yname = df.columns.values[0]\n",
+ " _ = df.plot(kind='pie',y=yname); \n",
+ " else: \n",
+ " xlabel = df.columns.values[0]\n",
+ " xname = df[xlabel].tolist()\n",
+ " yname = df.columns.values[1]\n",
+ " _ = df.plot(kind='pie',y=yname,labels=xname);\n",
+ " \n",
+ " plt.show();\n",
+ " \n",
+ " else:\n",
+ " errormsg(\"Can't determine what columns to plot\")\n",
+ " return\n",
+ " \n",
+ " elif flag([\"-pl\",\"-line\"]): # Plot 3 = line chart\n",
+ " \n",
+ " if (col_count in (1,2,3)): \n",
+ " \n",
+ " if (col_count == 1):\n",
+ " df.index = df.index + 1 \n",
+ " _ = df.plot(kind='line'); \n",
+ " elif (col_count == 2): \n",
+ " xlabel = df.columns.values[0]\n",
+ " ylabel = df.columns.values[1]\n",
+ " _ = df.plot(kind='line',x=xlabel,y=ylabel) ; \n",
+ " else: \n",
+ " values = df.columns.values[2]\n",
+ " columns = df.columns.values[0]\n",
+ " index = df.columns.values[1]\n",
+ " pivoted = pandas.pivot_table(df, values=values, columns=columns, index=index)\n",
+ " _ = pivoted.plot();\n",
+ " \n",
+ " plt.show();\n",
+ " \n",
+ " else:\n",
+ " errormsg(\"Can't determine what columns to plot\")\n",
+ " return\n",
+ " else:\n",
+ " return\n",
+ "\n",
+ "def getContents(varName,flag_quotes,local_ns):\n",
+ " \n",
+ " #\n",
+ " # Get the contents of the variable name that is passed to the routine. Only simple\n",
+ " # variables are checked, i.e. arrays and lists are not parsed\n",
+ " #\n",
+ " \n",
+ " STRING = 0\n",
+ " NUMBER = 1\n",
+ " LIST = 2\n",
+ " RAW = 3\n",
+ " DICT = 4\n",
+ " PANDAS = 5\n",
+ " \n",
+ " try:\n",
+ " value = eval(varName,None,local_ns) # globals()[varName] # eval(varName)\n",
+ " except:\n",
+ " return(None,STRING)\n",
+ " \n",
+ " if (isinstance(value,dict) == True): # Check to see if this is JSON dictionary\n",
+ " return(addquotes(value,flag_quotes),STRING)\n",
+ "\n",
+ " elif(isinstance(value,list) == True or isinstance(value,tuple) == True): # List - tricky \n",
+ " return(value,LIST)\n",
+ " \n",
+ " elif (isinstance(value,pandas.DataFrame) == True): # Pandas dataframe\n",
+ " return(value,PANDAS)\n",
+ "\n",
+ " elif (isinstance(value,int) == True): # Integer value \n",
+ " return(value,NUMBER)\n",
+ "\n",
+ " elif (isinstance(value,float) == True): # Float value\n",
+ " return(value,NUMBER)\n",
+ "\n",
+ " else:\n",
+ " try:\n",
+ " # The pattern needs to be in the first position (0 in Python terms)\n",
+ " if (value.find('0x') == 0): # Just guessing this is a hex value\n",
+ " return(value,RAW)\n",
+ " else:\n",
+ " return(addquotes(value,flag_quotes),STRING) # String\n",
+ " except:\n",
+ " return(addquotes(str(value),flag_quotes),RAW)\n",
+ "\n",
+ "def addquotes(inString,flag_quotes):\n",
+ " \n",
+ " if (isinstance(inString,dict) == True): # Check to see if this is JSON dictionary\n",
+ " serialized = json.dumps(inString) \n",
+ " else:\n",
+ " serialized = inString\n",
+ "\n",
+ " # Replace single quotes with '' (two quotes) and wrap everything in single quotes\n",
+ " if (flag_quotes == False):\n",
+ " return(serialized)\n",
+ " else:\n",
+ " return(\"'\"+serialized.replace(\"'\",\"''\")+\"'\") # Convert single quotes to two single quotes\n",
+ " \n",
+ "def checkOption(args_in, option, vFalse=False, vTrue=True):\n",
+ " \n",
+ " args_out = args_in.strip()\n",
+ " found = vFalse\n",
+ " \n",
+ " if (args_out != \"\"):\n",
+ " if (args_out.find(option) >= 0):\n",
+ " args_out = args_out.replace(option,\" \")\n",
+ " args_out = args_out.strip()\n",
+ " found = vTrue\n",
+ "\n",
+ " return args_out, found\n",
+ "\n",
+ "def findProc(procname):\n",
+ " \n",
+ " global _hdbc, _hdbi, _connected\n",
+ " \n",
+ " # Split the procedure name into schema.procname if appropriate\n",
+ " upper_procname = procname.upper()\n",
+ " schema, proc = split_string(upper_procname,\".\") # Expect schema.procname\n",
+ " if (proc == None):\n",
+ " proc = schema\n",
+ "\n",
+ " # Call ibm_db.procedures to see if the procedure does exist\n",
+ " schema = \"%\"\n",
+ "\n",
+ " try:\n",
+ " stmt = ibm_db.procedures(_hdbc, None, schema, proc) \n",
+ " if (stmt == False): # Error executing the code\n",
+ " errormsg(\"Procedure \" + procname + \" not found in the system catalog.\")\n",
+ " return None\n",
+ "\n",
+ " result = ibm_db.fetch_tuple(stmt)\n",
+ " resultsets = result[5]\n",
+ " if (resultsets >= 1): resultsets = 1\n",
+ " return resultsets\n",
+ " \n",
+ " except Exception as err:\n",
+ " errormsg(\"Procedure \" + procname + \" not found in the system catalog.\")\n",
+ " return None\n",
+ "\n",
+ "def parseCallArgs(macro):\n",
+ " \n",
+ " quoteChar = \"\"\n",
+ " inQuote = False\n",
+ " inParm = False\n",
+ " ignore = False\n",
+ " name = \"\"\n",
+ " parms = []\n",
+ " parm = ''\n",
+ " \n",
+ " sqlin = macro.replace(\"\\n\",\"\")\n",
+ " sqlin.lstrip()\n",
+ " \n",
+ " for ch in sqlin:\n",
+ " if (inParm == False):\n",
+ " # We hit a blank in the name, so ignore everything after the procedure name until a ( is found\n",
+ " if (ch == \" \"): \n",
+ " ignore == True\n",
+ " elif (ch == \"(\"): # Now we have parameters to send to the stored procedure\n",
+ " inParm = True\n",
+ " else:\n",
+ " if (ignore == False): name = name + ch # The name of the procedure (and no blanks)\n",
+ " else:\n",
+ " if (inQuote == True):\n",
+ " if (ch == quoteChar):\n",
+ " inQuote = False \n",
+ " else:\n",
+ " parm = parm + ch\n",
+ " elif (ch in (\"\\\"\",\"\\'\",\"[\")): # Do we have a quote\n",
+ " if (ch == \"[\"):\n",
+ " quoteChar = \"]\"\n",
+ " else:\n",
+ " quoteChar = ch\n",
+ " inQuote = True\n",
+ " elif (ch == \")\"):\n",
+ " if (parm != \"\"):\n",
+ " parms.append(parm)\n",
+ " parm = \"\"\n",
+ " break\n",
+ " elif (ch == \",\"):\n",
+ " if (parm != \"\"):\n",
+ " parms.append(parm) \n",
+ " else:\n",
+ " parms.append(\"null\")\n",
+ " \n",
+ " parm = \"\"\n",
+ "\n",
+ " else:\n",
+ " parm = parm + ch\n",
+ " \n",
+ " if (inParm == True):\n",
+ " if (parm != \"\"):\n",
+ " parms.append(parm) \n",
+ " \n",
+ " return(name,parms)\n",
+ "\n",
+ "def getColumns(stmt):\n",
+ " \n",
+ " columns = []\n",
+ " types = []\n",
+ " colcount = 0\n",
+ " try:\n",
+ " colname = ibm_db.field_name(stmt,colcount)\n",
+ " coltype = ibm_db.field_type(stmt,colcount)\n",
+ " precision = ibm_db.field_precision(stmt,colcount)\n",
+ " while (colname != False):\n",
+ " if (coltype == \"real\"):\n",
+ " if (precision == 7):\n",
+ " coltype = \"real\"\n",
+ " elif (precision == 15):\n",
+ " coltype = \"float\"\n",
+ " elif (precision == 16):\n",
+ " coltype = \"decfloat16\"\n",
+ " elif (precision == 34):\n",
+ " coltype = \"decfloat34\"\n",
+ " else:\n",
+ " coltype = \"real\"\n",
+ " elif (coltype == \"int\"):\n",
+ " if (precision == 1):\n",
+ " coltype = \"boolean\"\n",
+ " elif (precision == 5):\n",
+ " coltype = \"smallint\"\n",
+ " elif (precision == 10):\n",
+ " coltype = \"int\"\n",
+ " else:\n",
+ " coltype = \"int\"\n",
+ " columns.append(colname)\n",
+ " types.append(coltype)\n",
+ " colcount += 1\n",
+ " colname = ibm_db.field_name(stmt,colcount)\n",
+ " coltype = ibm_db.field_type(stmt,colcount) \n",
+ " precision = ibm_db.field_precision(stmt,colcount) \n",
+ " \n",
+ " return columns,types \n",
+ " \n",
+ " except Exception as err:\n",
+ " db2_error(False)\n",
+ " return None\n",
+ "\n",
+ "def parseCall(hdbc, inSQL, local_ns):\n",
+ " \n",
+ " global _hdbc, _hdbi, _connected, _environment\n",
+ " \n",
+ " # Check to see if we are connected first\n",
+ " if (_connected == False): # Check if you are connected \n",
+ " db2_doConnect(None)\n",
+ " if _connected == False: return None\n",
+ " \n",
+ " remainder = inSQL.strip()\n",
+ " procName, procArgs = parseCallArgs(remainder[5:]) # Assume that CALL ... is the format\n",
+ " \n",
+ " resultsets = findProc(procName)\n",
+ " if (resultsets == None): return None\n",
+ " \n",
+ " argvalues = []\n",
+ " \n",
+ " if (len(procArgs) > 0): # We have arguments to consider\n",
+ " for arg in procArgs:\n",
+ " varname = arg\n",
+ " if (len(varname) > 0):\n",
+ " if (varname[0] == \":\"):\n",
+ " checkvar = varname[1:]\n",
+ " varvalue = getContents(checkvar,True,local_ns)\n",
+ " if (varvalue == None):\n",
+ " errormsg(\"Variable \" + checkvar + \" is not defined.\")\n",
+ " return None\n",
+ " argvalues.append(varvalue)\n",
+ " else:\n",
+ " if (varname.upper() == \"NULL\"):\n",
+ " argvalues.append(None)\n",
+ " else:\n",
+ " argvalues.append(varname)\n",
+ " else:\n",
+ " argvalues.append(None)\n",
+ "\n",
+ " \n",
+ " try:\n",
+ "\n",
+ " if (len(procArgs) > 0):\n",
+ " argtuple = tuple(argvalues)\n",
+ " result = ibm_db.callproc(_hdbc,procName,argtuple)\n",
+ " stmt = result[0]\n",
+ " else:\n",
+ " result = ibm_db.callproc(_hdbc,procName)\n",
+ " stmt = result\n",
+ " \n",
+ " if (resultsets != 0 and stmt != None): \n",
+ "\n",
+ " columns, types = getColumns(stmt)\n",
+ " if (columns == None): return None\n",
+ " \n",
+ " rows = []\n",
+ " rowlist = ibm_db.fetch_tuple(stmt)\n",
+ " while ( rowlist ) :\n",
+ " row = []\n",
+ " colcount = 0\n",
+ " for col in rowlist:\n",
+ " try:\n",
+ " if (types[colcount] in [\"int\",\"bigint\"]):\n",
+ " row.append(int(col))\n",
+ " elif (types[colcount] in [\"decimal\",\"real\"]):\n",
+ " row.append(float(col))\n",
+ " elif (types[colcount] in [\"date\",\"time\",\"timestamp\"]):\n",
+ " row.append(str(col))\n",
+ " else:\n",
+ " row.append(col)\n",
+ " except:\n",
+ " row.append(col)\n",
+ " colcount += 1\n",
+ " rows.append(row)\n",
+ " rowlist = ibm_db.fetch_tuple(stmt)\n",
+ " \n",
+ " if flag([\"-r\",\"-array\"]):\n",
+ " rows.insert(0,columns)\n",
+ " if len(procArgs) > 0:\n",
+ " allresults = []\n",
+ " allresults.append(rows)\n",
+ " for x in result[1:]:\n",
+ " allresults.append(x)\n",
+ " return allresults # rows,returned_results\n",
+ " else:\n",
+ " return rows\n",
+ " else:\n",
+ " df = pandas.DataFrame.from_records(rows,columns=columns)\n",
+ " if flag(\"-grid\") or _settings.get('display',\"PANDAS\") == 'GRID':\n",
+ " if (_environment['grid'] == False):\n",
+ " with pandas.option_context('display.max_rows', None, 'display.max_columns', None): \n",
+ " pdisplay(df)\n",
+ " else:\n",
+ " try:\n",
+ " setDisplay(True)\n",
+ " if (_settings[\"paging\"] == \"OFF\"):\n",
+ " show(df, scrollY=\"300px\", scrollCollapse=True, paging=False)\n",
+ " else:\n",
+ " show(df)\n",
+ " setDisplay(False)\n",
+ " except Exception as err:\n",
+ " print(repr(err))\n",
+ " errormsg(\"Grid control failed to diplay data. Use option -a or %sql OPTION DISPLAY PANDAS instead.\")\n",
+ " \n",
+ " return \n",
+ " else:\n",
+ " if flag([\"-a\",\"-all\"]) or _settings.get(\"maxrows\",10) == -1 : # All of the rows\n",
+ " with pandas.option_context('display.max_rows', 100, 'display.max_columns', None): \n",
+ " pdisplay(df)\n",
+ " else:\n",
+ " return df\n",
+ " \n",
+ " else:\n",
+ " if len(procArgs) > 0:\n",
+ " allresults = []\n",
+ " for x in result[1:]:\n",
+ " allresults.append(x)\n",
+ " return allresults # rows,returned_results\n",
+ " else:\n",
+ " return None\n",
+ " \n",
+ " except Exception as err:\n",
+ " db2_error(False)\n",
+ " return None\n",
+ "\n",
+ "def parsePExec(hdbc, inSQL):\n",
+ " \n",
+ " import ibm_db \n",
+ " global _stmt, _stmtID, _stmtSQL, sqlcode\n",
+ " \n",
+ " cParms = inSQL.split()\n",
+ " parmCount = len(cParms)\n",
+ " if (parmCount == 0): return(None) # Nothing to do but this shouldn't happen\n",
+ " \n",
+ " keyword = cParms[0].upper() # Upper case the keyword\n",
+ " \n",
+ " if (keyword == \"PREPARE\"): # Prepare the following SQL\n",
+ " uSQL = inSQL.upper()\n",
+ " found = uSQL.find(\"PREPARE\")\n",
+ " sql = inSQL[found+7:].strip()\n",
+ "\n",
+ " try:\n",
+ " pattern = \"\\?\\*[0-9]+\"\n",
+ " findparm = re.search(pattern,sql)\n",
+ " while findparm != None:\n",
+ " found = findparm.group(0)\n",
+ " count = int(found[2:])\n",
+ " markers = ('?,' * count)[:-1]\n",
+ " sql = sql.replace(found,markers)\n",
+ " findparm = re.search(pattern,sql)\n",
+ " \n",
+ " stmt = ibm_db.prepare(hdbc,sql) # Check error code here\n",
+ " if (stmt == False): \n",
+ " db2_error(False)\n",
+ " return(False)\n",
+ " \n",
+ " stmttext = str(stmt).strip()\n",
+ " stmtID = stmttext[33:48].strip()\n",
+ " \n",
+ " if (stmtID in _stmtID) == False:\n",
+ " _stmt.append(stmt) # Prepare and return STMT to caller\n",
+ " _stmtID.append(stmtID)\n",
+ " else:\n",
+ " stmtIX = _stmtID.index(stmtID)\n",
+ " _stmt[stmtIX] = stmt\n",
+ " \n",
+ " return(stmtID)\n",
+ " \n",
+ " except Exception as err:\n",
+ " print(err)\n",
+ " db2_error(False)\n",
+ " return(False)\n",
+ "\n",
+ " if (keyword == \"EXECUTE\"): # Execute the prepare statement\n",
+ " if (parmCount < 2): return(False) # No stmtID available\n",
+ " \n",
+ " stmtID = cParms[1].strip()\n",
+ " if (stmtID in _stmtID) == False:\n",
+ " errormsg(\"Prepared statement not found or invalid.\")\n",
+ " return(False)\n",
+ "\n",
+ " stmtIX = _stmtID.index(stmtID)\n",
+ " stmt = _stmt[stmtIX]\n",
+ "\n",
+ " try: \n",
+ "\n",
+ " if (parmCount == 2): # Only the statement handle available\n",
+ " result = ibm_db.execute(stmt) # Run it\n",
+ " elif (parmCount == 3): # Not quite enough arguments\n",
+ " errormsg(\"Missing or invalid USING clause on EXECUTE statement.\")\n",
+ " sqlcode = -99999\n",
+ " return(False)\n",
+ " else:\n",
+ " using = cParms[2].upper()\n",
+ " if (using != \"USING\"): # Bad syntax again\n",
+ " errormsg(\"Missing USING clause on EXECUTE statement.\")\n",
+ " sqlcode = -99999\n",
+ " return(False)\n",
+ " \n",
+ " uSQL = inSQL.upper()\n",
+ " found = uSQL.find(\"USING\")\n",
+ " parmString = inSQL[found+5:].strip()\n",
+ " parmset = splitargs(parmString)\n",
+ " \n",
+ " if (len(parmset) == 0):\n",
+ " errormsg(\"Missing parameters after the USING clause.\")\n",
+ " sqlcode = -99999\n",
+ " return(False)\n",
+ " \n",
+ " parm_count = 0\n",
+ " parms = []\n",
+ " parms.append(None)\n",
+ " \n",
+ " CONSTANT = 0\n",
+ " VARIABLE = 1\n",
+ " const = [0]\n",
+ " const_cnt = 0\n",
+ " \n",
+ " for v in parmset:\n",
+ " \n",
+ " parm_count = parm_count + 1\n",
+ " parms.append(None)\n",
+ " \n",
+ " if (v[1] == True or v[2] == True): # v[1] true if string, v[2] true if num\n",
+ " \n",
+ " parm_type = CONSTANT \n",
+ " const_cnt = const_cnt + 1\n",
+ " if (v[2] == True):\n",
+ " if (isinstance(v[0],int) == True): # Integer value \n",
+ " sql_type = ibm_db.SQL_INTEGER\n",
+ " elif (isinstance(v[0],float) == True): # Float value\n",
+ " sql_type = ibm_db.SQL_DOUBLE\n",
+ " else:\n",
+ " sql_type = ibm_db.SQL_INTEGER\n",
+ " else:\n",
+ " sql_type = ibm_db.SQL_CHAR\n",
+ " \n",
+ " const.append(v[0])\n",
+ "\n",
+ " \n",
+ " else:\n",
+ " \n",
+ " parm_type = VARIABLE\n",
+ " \n",
+ " # See if the variable has a type associated with it varname@type\n",
+ " \n",
+ " varset = v[0].split(\"@\")\n",
+ " parm_name = varset[0]\n",
+ " \n",
+ " parm_datatype = \"char\"\n",
+ "\n",
+ " if (parm_name[0] == \":\"):\n",
+ " parm_name = parm_name[1:]\n",
+ "\n",
+ " # Does the variable exist?\n",
+ " if (parm_name not in globals()):\n",
+ " errormsg(\"SQL Execute parameter \" + parm_name + \" not found\")\n",
+ " sqlcode = -99999\n",
+ " return(False) \n",
+ " \n",
+ " parms[parm_count] = globals()[parm_name]\n",
+ " \n",
+ " if (len(varset) > 1): # Type provided\n",
+ " parm_datatype = varset[1]\n",
+ "\n",
+ " if (parm_datatype == \"dec\" or parm_datatype == \"decimal\"):\n",
+ " sql_type = ibm_db.SQL_DOUBLE\n",
+ " elif (parm_datatype == \"bin\" or parm_datatype == \"binary\"):\n",
+ " sql_type = ibm_db.SQL_BINARY\n",
+ " elif (parm_datatype == \"int\" or parm_datatype == \"integer\"):\n",
+ " sql_type = ibm_db.SQL_INTEGER\n",
+ " elif (parm_datatype == \"file\"):\n",
+ " sql_type = ibm_db.SQL_CHAR\n",
+ " else:\n",
+ " sql_type = ibm_db.SQL_CHAR\n",
+ " parms[parm_count] = addquotes(parms[parm_count],False)\n",
+ " \n",
+ " try:\n",
+ " if (parm_type == VARIABLE):\n",
+ " if (parm_datatype == \"file\"):\n",
+ " result = ibm_db.bind_param(stmt, parm_count, parms[parm_count], ibm_db.PARAM_FILE,ibm_db.SQL_CLOB) \t\t\t\t\t\t\t\t\n",
+ " else:\n",
+ " result = ibm_db.bind_param(stmt, parm_count, parms[parm_count], ibm_db.SQL_PARAM_INPUT, sql_type) \n",
+ " # result = ibm_db.bind_param(stmt, parm_count, globals()[parm_name], ibm_db.SQL_PARAM_INPUT, sql_type)\n",
+ " else:\n",
+ " result = ibm_db.bind_param(stmt, parm_count, const[const_cnt], ibm_db.SQL_PARAM_INPUT, sql_type)\n",
+ " \n",
+ " except Exception as e:\n",
+ " print(repr(e))\n",
+ " result = False\n",
+ " \n",
+ " if (result == False):\n",
+ " errormsg(\"SQL Bind on variable \" + parm_name + \" failed.\")\n",
+ " sqlcode = -99999\n",
+ " return(False) \n",
+ " \n",
+ " result = ibm_db.execute(stmt) # ,tuple(parms))\n",
+ " \n",
+ " if (result == False): \n",
+ " errormsg(\"SQL Execute failed.\") \n",
+ " return(False)\n",
+ " \n",
+ " if (ibm_db.num_fields(stmt) == 0): return(True) # Command successfully completed\n",
+ " \n",
+ " return(fetchResults(stmt))\n",
+ " \n",
+ " except Exception as err:\n",
+ " db2_error(False)\n",
+ " return(False)\n",
+ " \n",
+ " return(False)\n",
+ " \n",
+ " return(False) \n",
+ "\n",
+ "def fetchResults(stmt):\n",
+ " \n",
+ " global sqlcode\n",
+ " \n",
+ " rows = []\n",
+ " columns, types = getColumns(stmt)\n",
+ " \n",
+ " # By default we assume that the data will be an array\n",
+ " is_array = True\n",
+ " \n",
+ " # Check what type of data we want returned - array or json\n",
+ " if (flag([\"-r\",\"-array\"]) == False):\n",
+ " # See if we want it in JSON format, if not it remains as an array\n",
+ " if (flag(\"-json\") == True):\n",
+ " is_array = False\n",
+ " \n",
+ " # Set column names to lowercase for JSON records\n",
+ " if (is_array == False):\n",
+ " columns = [col.lower() for col in columns] # Convert to lowercase for each of access\n",
+ " \n",
+ " # First row of an array has the column names in it\n",
+ " if (is_array == True):\n",
+ " rows.append(columns)\n",
+ " \n",
+ " result = ibm_db.fetch_tuple(stmt)\n",
+ " rowcount = 0\n",
+ " while (result):\n",
+ " \n",
+ " rowcount += 1\n",
+ " \n",
+ " if (is_array == True):\n",
+ " row = []\n",
+ " else:\n",
+ " row = {}\n",
+ " \n",
+ " colcount = 0\n",
+ " for col in result:\n",
+ " try:\n",
+ " if (types[colcount] in [\"int\",\"bigint\"]):\n",
+ " if (is_array == True):\n",
+ " row.append(int(col))\n",
+ " else:\n",
+ " row[columns[colcount]] = int(col)\n",
+ " elif (types[colcount] in [\"decimal\",\"real\"]):\n",
+ " if (is_array == True):\n",
+ " row.append(float(col))\n",
+ " else:\n",
+ " row[columns[colcount]] = float(col)\n",
+ " elif (types[colcount] in [\"date\",\"time\",\"timestamp\"]):\n",
+ " if (is_array == True):\n",
+ " row.append(str(col))\n",
+ " else:\n",
+ " row[columns[colcount]] = str(col)\n",
+ " else:\n",
+ " if (is_array == True):\n",
+ " row.append(col)\n",
+ " else:\n",
+ " row[columns[colcount]] = col\n",
+ " \n",
+ " except:\n",
+ " if (is_array == True):\n",
+ " row.append(col)\n",
+ " else:\n",
+ " row[columns[colcount]] = col\n",
+ " \n",
+ " colcount += 1\n",
+ " \n",
+ " rows.append(row)\n",
+ " result = ibm_db.fetch_tuple(stmt)\n",
+ " \n",
+ " if (rowcount == 0): \n",
+ " sqlcode = 100 \n",
+ " else:\n",
+ " sqlcode = 0\n",
+ " \n",
+ " return rows\n",
+ " \n",
+ "\n",
+ "def parseCommit(sql):\n",
+ " \n",
+ " global _hdbc, _hdbi, _connected, _stmt, _stmtID, _stmtSQL\n",
+ "\n",
+ " if (_connected == False): return # Nothing to do if we are not connected\n",
+ " \n",
+ " cParms = sql.split()\n",
+ " if (len(cParms) == 0): return # Nothing to do but this shouldn't happen\n",
+ " \n",
+ " keyword = cParms[0].upper() # Upper case the keyword\n",
+ " \n",
+ " if (keyword == \"COMMIT\"): # Commit the work that was done\n",
+ " try:\n",
+ " result = ibm_db.commit (_hdbc) # Commit the connection\n",
+ " if (len(cParms) > 1):\n",
+ " keyword = cParms[1].upper()\n",
+ " if (keyword == \"HOLD\"):\n",
+ " return\n",
+ " \n",
+ " del _stmt[:]\n",
+ " del _stmtID[:]\n",
+ "\n",
+ " except Exception as err:\n",
+ " db2_error(False)\n",
+ " \n",
+ " return\n",
+ " \n",
+ " if (keyword == \"ROLLBACK\"): # Rollback the work that was done\n",
+ " try:\n",
+ " result = ibm_db.rollback(_hdbc) # Rollback the connection\n",
+ " del _stmt[:]\n",
+ " del _stmtID[:] \n",
+ "\n",
+ " except Exception as err:\n",
+ " db2_error(False)\n",
+ " \n",
+ " return\n",
+ " \n",
+ " if (keyword == \"AUTOCOMMIT\"): # Is autocommit on or off\n",
+ " if (len(cParms) > 1): \n",
+ " op = cParms[1].upper() # Need ON or OFF value\n",
+ " else:\n",
+ " return\n",
+ " \n",
+ " try:\n",
+ " if (op == \"OFF\"):\n",
+ " ibm_db.autocommit(_hdbc, False)\n",
+ " elif (op == \"ON\"):\n",
+ " ibm_db.autocommit (_hdbc, True)\n",
+ " return \n",
+ " \n",
+ " except Exception as err:\n",
+ " db2_error(False)\n",
+ " return \n",
+ " \n",
+ " return\n",
+ "\n",
+ "def setFlags(inSQL,reset=False):\n",
+ "\n",
+ " global _flags\n",
+ "\n",
+ " if (reset == True):\n",
+ " _flags = [] # Delete all of the current flag settings\n",
+ "\n",
+ " pos = 0\n",
+ " end = len(inSQL)-1\n",
+ " inFlag = False\n",
+ " ignore = False\n",
+ " outSQL = \"\"\n",
+ " flag = \"\"\n",
+ "\n",
+ " while (pos <= end):\n",
+ " ch = inSQL[pos]\n",
+ " if (ignore == True): \n",
+ " outSQL = outSQL + ch\n",
+ " else:\n",
+ " if (inFlag == True):\n",
+ " if (ch != \" \"):\n",
+ " flag = flag + ch\n",
+ " else:\n",
+ " _flags.append(flag)\n",
+ " inFlag = False\n",
+ " else:\n",
+ " if (ch == \"-\"):\n",
+ " flag = \"-\"\n",
+ " inFlag = True\n",
+ " elif (ch == ' '):\n",
+ " outSQL = outSQL + ch\n",
+ " else:\n",
+ " outSQL = outSQL + ch\n",
+ " ignore = True\n",
+ " pos += 1\n",
+ "\n",
+ " if (inFlag == True):\n",
+ " _flags.append(flag)\n",
+ "\n",
+ " return outSQL\n",
+ "\n",
+ "def flag(inflag):\n",
+ " \n",
+ " global _flags\n",
+ "\n",
+ " if isinstance(inflag,list):\n",
+ " for x in inflag:\n",
+ " if (x in _flags):\n",
+ " return True\n",
+ " return False\n",
+ " else:\n",
+ " if (inflag in _flags):\n",
+ " return True\n",
+ " else:\n",
+ " return False\n",
+ "\n",
+ "def execSQL(hdbc,sql,quiet=True):\n",
+ "\n",
+ " success = True\n",
+ " try: # See if we have an answer set\n",
+ " stmt = ibm_db.prepare(hdbc,sql)\n",
+ " result = ibm_db.execute(stmt) # Run it \n",
+ " if (result == False): # Error executing the code\n",
+ " db2_error(quiet)\n",
+ " success = False\n",
+ " except:\n",
+ " db2_error(quiet) \n",
+ " success = False\n",
+ "\n",
+ " return success\t \n",
+ "\n",
+ "def splitSQL(inputString, delimiter):\n",
+ " \n",
+ " pos = 0\n",
+ " arg = \"\"\n",
+ " results = []\n",
+ " quoteCH = \"\"\n",
+ " \n",
+ " inSQL = inputString.strip()\n",
+ " if (len(inSQL) == 0): return(results) # Not much to do here - no args found\n",
+ " \n",
+ " while pos < len(inSQL):\n",
+ " ch = inSQL[pos]\n",
+ " pos += 1\n",
+ " if (ch in ('\"',\"'\")): # Is this a quote characters?\n",
+ " arg = arg + ch # Keep appending the characters to the current arg\n",
+ " if (ch == quoteCH): # Is this quote character we are in\n",
+ " quoteCH = \"\"\n",
+ " elif (quoteCH == \"\"): # Create the quote\n",
+ " quoteCH = ch\n",
+ " else:\n",
+ " None\n",
+ " elif (quoteCH != \"\"): # Still in a quote\n",
+ " arg = arg + ch\n",
+ " elif (ch == delimiter): # Is there a delimiter?\n",
+ " results.append(arg)\n",
+ " arg = \"\"\n",
+ " else:\n",
+ " arg = arg + ch\n",
+ " \n",
+ " if (arg != \"\"):\n",
+ " results.append(arg)\n",
+ " \n",
+ " return(results)\n",
+ "\n",
+ "def process_slice(connection, dfName, dfValue, pd_dtypes, sql, q, s):\n",
+ " \n",
+ " import numpy as np \n",
+ " import pandas as pd\n",
+ "\n",
+ " if (q.empty() == False): return None\n",
+ "\n",
+ " if (isinstance(dfValue,list) == True or isinstance(dfValue,tuple) == True):\n",
+ " encoded_sql = \"\"\n",
+ " start = True\n",
+ " for v in dfValue:\n",
+ " if (start == False):\n",
+ " encoded_sql = encoded_sql + \",\"\n",
+ " if (isinstance(v,str) == True):\n",
+ " encoded_sql = encoded_sql + addquotes(v,True)\n",
+ " else:\n",
+ " encoded_sql = encoded_sql + str(v)\n",
+ " start = False\n",
+ "\n",
+ " dfValue = encoded_sql\n",
+ " elif (isinstance(dfValue,str) == True):\n",
+ " dfValue = addquotes(dfValue,True)\n",
+ " else:\n",
+ " dfValue = str(dfValue)\n",
+ "\n",
+ " if (q.empty() == False): return None\n",
+ "\n",
+ " dsn = buildDSN(connection)\n",
+ "\n",
+ " if (dsn in [None,\"\"]):\n",
+ " return None\n",
+ " \n",
+ " # Get a database handle (hdbc) and a statement handle (hstmt) for subsequent access to Db2\n",
+ "\n",
+ " try:\n",
+ " hdbc = ibm_db.connect(dsn, \"\", \"\")\n",
+ " except Exception as err:\n",
+ " try:\n",
+ " errmsg = ibm_db.conn_errormsg().replace('\\r',' ')\n",
+ " errmsg = errmsg[errmsg.rfind(\"]\")+1:].strip()\n",
+ " except:\n",
+ " errmsg = \"Error attempting to retrieve error message\"\n",
+ " q.put(errmsg) \n",
+ " return None\n",
+ " \n",
+ " try:\n",
+ " hdbi = ibm_db_dbi.Connection(hdbc)\n",
+ " except Exception as err:\n",
+ " errmsg = \"Connection error when connecting through DBI adapter.\"\n",
+ " q.put(errmsg)\n",
+ " return None\n",
+ "\n",
+ " if (q.empty() == False): return None\n",
+ " \n",
+ " # if (isinstance(dfValue,str) == True):\n",
+ " # \tdfValue = addquotes(dfValue,True)\n",
+ " # else:\n",
+ " # \tdfValue = str(dfValue)\n",
+ " \n",
+ " protoSQL = sql.replace(f\":{dfName}\",dfValue)\n",
+ "\n",
+ " s.put(protoSQL)\n",
+ "\n",
+ " if (q.empty() == False): return None\n",
+ "\n",
+ " try:\n",
+ " if (pd_dtypes != None):\n",
+ " df = pd.read_sql_query(protoSQL,hdbi,dtype=pd_dtypes) \n",
+ " else:\n",
+ " df = pd.read_sql_query(protoSQL,hdbi) \n",
+ " except:\n",
+ " try:\n",
+ " errmsg = ibm_db.stmt_errormsg().replace('\\r',' ')\n",
+ " errmsg = errmsg[errmsg.rfind(\"]\")+1:].strip()\t\t\n",
+ " ibm_db.close(hdbc)\n",
+ " except:\n",
+ " errmsg = \"Error attempting to retrieve statement error message.\"\n",
+ " q.put(errmsg)\n",
+ " return None\n",
+ "\n",
+ " if (q.empty() == False): return None\n",
+ "\n",
+ " try:\n",
+ " ibm_db.close(hdbc)\n",
+ " except:\n",
+ " pass\n",
+ " \n",
+ " return df\n",
+ "\n",
+ "def dfSQL(hdbc,hdbi,sqlin,dfName,dfValue,thread_count):\n",
+ " \n",
+ " import shlex\n",
+ "\n",
+ " NoDF = False\n",
+ " YesDF = True\n",
+ "\n",
+ " sqlin = \" \".join(shlex.split(sqlin)) \n",
+ "\n",
+ " if (hdbc == None or hdbi == None or sqlin in (None, \"\")):\n",
+ " return NoDF,None\n",
+ " \n",
+ " uSQLin = sqlin.upper()\n",
+ " \n",
+ " select_location = uSQLin.find(\"SELECT\")\n",
+ " with_location = uSQLin.find(\"WITH\")\n",
+ "\n",
+ " if (select_location == -1):\n",
+ " errormsg(\"SQL statement does not contain a SELECT statement.\")\n",
+ " return NoDF, None \n",
+ " \n",
+ " if (with_location != -1 and (with_location < select_location)):\n",
+ " keyword_location = with_location\n",
+ " else:\n",
+ " keyword_location = select_location\n",
+ " \n",
+ " sql = sqlin[keyword_location:]\n",
+ "\n",
+ " keyword_location = sql.find(f\":{dfName}\")\n",
+ " \n",
+ " if (keyword_location == -1):\n",
+ " errormsg(f'The parallelism value \"{dfName}\" was not found in the SQL statement.')\n",
+ " return NoDF, None\n",
+ " \n",
+ " if (isinstance(dfValue,list) == False):\n",
+ " errormsg(f'The variable \"{dfName}\" is not an array or a list of values.')\n",
+ " return NoDF, None\n",
+ "\n",
+ " #\tCreate a prototype statement to make sure the SQL will run\n",
+ " \n",
+ " protoValue = dfValue[0]\n",
+ "\n",
+ " if (isinstance(protoValue,list) == True or isinstance(protoValue,tuple) == True):\n",
+ " if (len(protoValue) == 0):\n",
+ " errormsg(f'The variable \"{dfName}\" contains array values that are empty.')\n",
+ " return NoDF, None\n",
+ " protoValue = protoValue[0]\n",
+ "\n",
+ " if (isinstance(protoValue,str) == True):\n",
+ " protoValue = addquotes(protoValue,True)\n",
+ " else:\n",
+ " protoValue = str(protoValue)\n",
+ " \n",
+ " protoSQL = sql.replace(f\":{dfName}\",protoValue)\n",
+ " \n",
+ " try:\n",
+ " stmt = ibm_db.prepare(hdbc,protoSQL)\n",
+ "\n",
+ " if (ibm_db.num_fields(stmt) == 0): \n",
+ " errormsg(\"The SQL statement does not return an answer set.\")\n",
+ " return NoDF, None\n",
+ " \n",
+ " except Exception as err:\n",
+ " db2_error(False)\n",
+ " return NoDF, None\n",
+ "\n",
+ " #\tDetermine the datatypes for a Pandas dataframe if it is supported\n",
+ "\n",
+ " pd_dtypes = None\n",
+ "\n",
+ " if (_pandas_dtype == True):\n",
+ " pd_dtypes = None\n",
+ " columns, types = getColumns(stmt)\n",
+ " pd_dtypes={}\n",
+ " for idx, col in enumerate(columns):\n",
+ " try:\n",
+ " _dindex = _db2types.index(types[idx])\n",
+ " except:\n",
+ " _dindex = 0\n",
+ "\n",
+ " pd_dtypes[col] = _pdtypes[_dindex]\n",
+ "\n",
+ " if len(pd_dtypes.keys()) == 0:\n",
+ " pd_dtypes = None\n",
+ " \n",
+ " pool \t = mp.Pool(processes=thread_count)\n",
+ " m \t\t = mp.Manager()\t\t\t# multiprocessing.Manager()\n",
+ " q\t\t = m.Queue()\n",
+ " tracesql = m.Queue()\n",
+ " \n",
+ " try:\n",
+ " results = [pool.apply_async(process_slice, args=(_settings,dfName,x,pd_dtypes,sql,q,tracesql,)) for x in dfValue]\n",
+ " except Exception as err:\n",
+ " print(repr(err))\n",
+ " return NoDF, None \n",
+ "\n",
+ " output=[]\n",
+ "\n",
+ " badresults = False\n",
+ " \n",
+ " for p in results:\n",
+ " try:\n",
+ " df = p.get()\n",
+ " if (isinstance(df,pandas.DataFrame) == True):\n",
+ " output.append(df)\n",
+ " else:\n",
+ " badresults = True\n",
+ " except Exception as err:\n",
+ " print(repr(err))\n",
+ " badresults = True \n",
+ "\n",
+ " if flag([\"-e\",\"-echo\"]): \n",
+ " while (tracesql.empty() == False):\n",
+ " debug(tracesql.get(),False)\n",
+ "\n",
+ " if (badresults == True):\n",
+ " if (q.empty() == False):\n",
+ " errormsg(q.get())\n",
+ " return NoDF, None \n",
+ "\n",
+ " finaldf = pandas.concat(output)\n",
+ " finaldf.reset_index(drop=True, inplace=True)\n",
+ " \n",
+ " if (len(finaldf) == 0):\n",
+ " sqlcode = 100\n",
+ " errormsg(\"No rows found\")\n",
+ " return NoDF, None \n",
+ "\n",
+ " return YesDF, finaldf\n",
+ "\n",
+ "def stripComments(sql):\n",
+ "\n",
+ " import re\n",
+ "\n",
+ " if (sql in [\"\",None]):\n",
+ " return(sql)\n",
+ "\n",
+ " sql = re.sub(r\"(.*)--.*\\n\",r\"\\1 \",sql)\n",
+ " sql = sql.strip()\n",
+ " return(sql) \n",
+ "\n",
+ "def setDisplay(onoff):\n",
+ " global _environment, _settings\n",
+ " # Set the display type\n",
+ " if (_environment['grid'] == False): # Nothing to do since no grid control\n",
+ " return\n",
+ " \n",
+ " if (onoff == True): # Turn on grid \n",
+ " if flag('-grid') or _settings.get('display',\"PANDAS\") == 'GRID':\n",
+ " if (_environment[\"gridinit\"] == False):\n",
+ " init_notebook_mode(all_interactive=True)\n",
+ " _environment[\"gridinit\"] = True\n",
+ " else:\n",
+ " if (_environment[\"gridinit\"] == True):\n",
+ " init_notebook_mode(all_interactive=False)\n",
+ " _environment[\"gridinit\"] = False\n",
+ " else:\n",
+ " if (_environment[\"gridinit\"] == True):\n",
+ " init_notebook_mode(all_interactive=False)\n",
+ " _environment[\"gridinit\"] = False\n",
+ "\n",
+ "@magics_class\n",
+ "class DB2(Magics):\n",
+ " \n",
+ " @needs_local_scope \n",
+ " @line_cell_magic\n",
+ " def sql(self, line, cell=None, local_ns=None):\n",
+ " \n",
+ " # Before we event get started, check to see if you have connected yet. Without a connection we \n",
+ " # can't do anything. You may have a connection request in the code, so if that is true, we run those,\n",
+ " # otherwise we connect immediately\n",
+ " \n",
+ " # If your statement is not a connect, and you haven't connected, we need to do it for you\n",
+ " \n",
+ " global _settings, _environment\n",
+ " global _hdbc, _hdbi, _connected, sqlstate, sqlerror, sqlcode, sqlelapsed\n",
+ " \n",
+ " # If you use %sql (line) we just run the SQL. If you use %%SQL the entire cell is run.\n",
+ " \n",
+ " flag_cell = False\n",
+ " flag_output = False\n",
+ " sqlstate = \"0\"\n",
+ " sqlerror = \"\"\n",
+ " sqlcode = 0\n",
+ " sqlelapsed = 0\n",
+ " start_time = time.time() \n",
+ " \n",
+ " # Macros gets expanded before anything is done\n",
+ "\n",
+ " SQL1 = stripComments(line)\n",
+ " \n",
+ " SQL1 = line.replace(\"\\n\",\" \").strip()\n",
+ " SQL1 = setFlags(SQL1,reset=True) \n",
+ " SQL1 = checkMacro(SQL1) # Update the SQL if any macros are in there\n",
+ " SQL1 = setFlags(SQL1)\n",
+ " SQL2 = stripComments(cell) \n",
+ " \n",
+ " if SQL1 == \"?\" or flag([\"-h\",\"-help\"]): # Are you asking for help\n",
+ " sqlhelp()\n",
+ " return\n",
+ " \n",
+ " if len(SQL1) == 0 and SQL2 == None: return # Nothing to do here\n",
+ " \n",
+ " # Check for help \n",
+ " \n",
+ " sqlType,remainder = sqlParser(SQL1,local_ns) # What type of command do you have?\n",
+ "\n",
+ " if (sqlType == \"CONNECT\"): # A connect request \n",
+ " parseConnect(SQL1,local_ns)\n",
+ " return \n",
+ " elif (sqlType == \"USING\"): # You want to use a dataframe to create a table?\n",
+ " pdReturn, df = createDF(_hdbc,_hdbi, SQL1,local_ns)\n",
+ " if (pdReturn == True):\n",
+ " if flag(\"-grid\") or _settings.get('display',\"PANDAS\") == 'GRID': # Check to see if we can display the results\n",
+ " if (_environment['grid'] == False):\n",
+ " with pandas.option_context('display.max_rows', 100, 'display.max_columns', None): \n",
+ " print(df.to_string())\n",
+ " else:\n",
+ " try:\n",
+ " setDisplay(True)\n",
+ " if (_settings[\"paging\"] == \"OFF\"):\n",
+ " show(df, scrollY=\"300px\", scrollCollapse=True, paging=False)\n",
+ " else:\n",
+ " show(df)\n",
+ " setDisplay(False)\n",
+ " except Exception as err:\n",
+ " print(repr(err))\n",
+ " errormsg(\"Grid control failed to diplay data. Use option -a or %sql OPTION DISPLAY PANDAS instead.\")\n",
+ " return \n",
+ " else:\n",
+ " if flag([\"-a\",\"-all\"]) or _settings.get(\"maxrows\",10) == -1 : # All of the rows\n",
+ " pandas.options.display.max_rows = 100\n",
+ " pandas.options.display.max_columns = None\n",
+ " return df # print(df.to_string())\n",
+ " else:\n",
+ " pandas.options.display.max_rows = _settings.get(\"maxrows\",10)\n",
+ " pandas.options.display.max_columns = None\n",
+ " return df # pdisplay(df) # print(df.to_string())\n",
+ " else:\n",
+ " if (df != None):\n",
+ " return df\n",
+ " else:\n",
+ " return\n",
+ " elif (sqlType == \"DEFINE\"): # Create a macro from the body\n",
+ " result = setMacro(SQL2,remainder)\n",
+ " return\n",
+ " elif (sqlType in (\"OPTION\",\"OPTIONS\")):\n",
+ " setOptions(SQL1)\n",
+ " return \n",
+ " elif (sqlType == 'COMMIT' or sqlType == 'ROLLBACK' or sqlType == 'AUTOCOMMIT'):\n",
+ " parseCommit(remainder)\n",
+ " return\n",
+ " elif (sqlType == \"PREPARE\"):\n",
+ " pstmt = parsePExec(_hdbc, remainder)\n",
+ " return(pstmt)\n",
+ " elif (sqlType == \"EXECUTE\"):\n",
+ " result = parsePExec(_hdbc, remainder)\n",
+ " return(result) \n",
+ " elif (sqlType == \"CALL\"):\n",
+ " result = parseCall(_hdbc, remainder, local_ns)\n",
+ " return(result)\n",
+ " else:\n",
+ " pass \n",
+ " \n",
+ " sql = SQL1\n",
+ " \n",
+ " if (sql == \"\"): sql = SQL2\n",
+ " \n",
+ " if (sql == \"\"): return # Nothing to do here\n",
+ " \n",
+ " if (_connected == False):\n",
+ " if (db2_doConnect(None) == False):\n",
+ " errormsg('A CONNECT statement must be issued before running SQL statements.')\n",
+ " return \n",
+ " \n",
+ " if _settings.get(\"maxrows\",10) == -1: # Set the return result size\n",
+ " pandas.reset_option('display.max_rows')\n",
+ " else:\n",
+ " pandas.options.display.max_rows = _settings.get(\"maxrows\",10)\n",
+ " \n",
+ " runSQL = re.sub('.*?--.*$',\"\",sql,flags=re.M)\n",
+ " remainder = runSQL.replace(\"\\n\",\" \") \n",
+ "\n",
+ " if flag([\"-d\",\"-delim\"]):\n",
+ " sqlLines = splitSQL(remainder,\"@\")\n",
+ " else:\n",
+ " sqlLines = splitSQL(remainder,\";\")\n",
+ " flag_cell = True\n",
+ " \n",
+ " # For each line figure out if you run it as a command (db2) or select (sql)\n",
+ "\n",
+ " for sqlin in sqlLines: # Run each command\n",
+ " \n",
+ " sqlin = checkMacro(sqlin) # Update based on any macros\n",
+ "\n",
+ " sqlType, sql = sqlParser(sqlin,local_ns) # Parse the SQL \n",
+ " if (sql.strip() == \"\"): continue\n",
+ "\n",
+ " if flag([\"-e\",\"-echo\"]): \n",
+ " debug(sql,False)\n",
+ " \n",
+ " if flag([\"-pb\",\"-bar\",\"-pp\",\"-pie\",\"-pl\",\"-line\"]): # We are plotting some results \n",
+ " plotData(_hdbi, sql) # Plot the data and return\n",
+ " return \n",
+ "\n",
+ " try: # See if we have an answer set\n",
+ " stmt = ibm_db.prepare(_hdbc,sql)\n",
+ " if (ibm_db.num_fields(stmt) == 0): # No, so we just execute the code\n",
+ " start_time = time.time()\n",
+ " result = ibm_db.execute(stmt) # Run it \n",
+ " sqlelapsed = time.time() - start_time\n",
+ " if (result == False): # Error executing the code\n",
+ " db2_error(flag([\"-q\",\"-quiet\"])) \n",
+ " continue\n",
+ " \n",
+ " rowcount = ibm_db.num_rows(stmt) \n",
+ " \n",
+ " if (rowcount == 0 and flag([\"-q\",\"-quiet\"]) == False):\n",
+ " errormsg(\"No rows found.\") \n",
+ " \n",
+ " continue # Continue running\n",
+ " \n",
+ " elif flag([\"-r\",\"-array\",\"-j\",\"-json\"]): # raw, json, format json\n",
+ " row_count = 0\n",
+ " resultSet = []\n",
+ " try:\n",
+ " start_time = time.time() \n",
+ " result = ibm_db.execute(stmt) # Run it\n",
+ " sqlelapsed = time.time() - start_time \n",
+ " if (result == False): # Error executing the code\n",
+ " db2_error(flag([\"-q\",\"-quiet\"])) \n",
+ " return\n",
+ " \n",
+ " if flag(\"-j\"): # JSON single output\n",
+ " row_count = 0\n",
+ " json_results = []\n",
+ " while( ibm_db.fetch_row(stmt) ):\n",
+ " row_count = row_count + 1\n",
+ " jsonVal = ibm_db.result(stmt,0)\n",
+ " jsonDict = json.loads(jsonVal)\n",
+ " json_results.append(jsonDict)\n",
+ " flag_output = True \n",
+ " \n",
+ " if (row_count == 0): sqlcode = 100\n",
+ " return(json_results)\n",
+ " \n",
+ " else:\n",
+ " return(fetchResults(stmt))\n",
+ " \n",
+ " except Exception as err:\n",
+ " db2_error(flag([\"-q\",\"-quiet\"]))\n",
+ " return\n",
+ " \n",
+ " else:\n",
+ "\n",
+ " # New for pandas 1.3. We can coerce the PD datatypes to mimic those of Db2\n",
+ " \n",
+ " pd_dtypes = None\n",
+ " \n",
+ " if (_pandas_dtype == True):\n",
+ " pd_dtypes = None\n",
+ " columns, types = getColumns(stmt)\n",
+ " pd_dtypes={}\n",
+ " for idx, col in enumerate(columns):\n",
+ " try:\n",
+ " _dindex = _db2types.index(types[idx])\n",
+ " except:\n",
+ " _dindex = 0\n",
+ "\n",
+ " pd_dtypes[col] = _pdtypes[_dindex]\n",
+ "\n",
+ " if len(pd_dtypes.keys()) == 0:\n",
+ " pd_dtypes = None\n",
+ " try:\n",
+ " \n",
+ " start_time = time.time() \n",
+ " if (_pandas_dtype == True):\n",
+ " df = pandas.read_sql_query(sql,_hdbi,dtype=pd_dtypes) \n",
+ " else:\n",
+ " df = pandas.read_sql_query(sql,_hdbi) \n",
+ " sqlelapsed = time.time() - start_time \n",
+ " \n",
+ " except Exception as err:\n",
+ " print(repr(err))\n",
+ " sqlelapsed = 0\n",
+ " db2_error(False)\n",
+ " return\n",
+ " \n",
+ " if (len(df) == 0):\n",
+ " sqlcode = 100\n",
+ " if (flag([\"-q\",\"-quiet\"]) == False): \n",
+ " errormsg(\"No rows found\")\n",
+ " continue \n",
+ " \n",
+ " flag_output = True\n",
+ " if flag(\"-grid\") or _settings.get('display',\"PANDAS\") == 'GRID': # Check to see if we can display the results\n",
+ " if (_environment['grid'] == False):\n",
+ " with pandas.option_context('display.max_rows', None, 'display.max_columns', None): \n",
+ " print(df.to_string())\n",
+ " else:\n",
+ " try:\n",
+ " setDisplay(True)\n",
+ " if (_settings[\"paging\"] == \"OFF\"):\n",
+ " show(df, scrollY=\"300px\", scrollCollapse=True, paging=False)\n",
+ " else:\n",
+ " show(df)\n",
+ " setDisplay(False)\n",
+ " except Exception as err:\n",
+ " print(repr(err))\n",
+ " errormsg(\"Grid control failed to diplay data. Use option -a or %sql OPTION DISPLAY PANDAS instead.\")\n",
+ " return \n",
+ " else:\n",
+ " if flag([\"-a\",\"-all\"]) or _settings.get(\"maxrows\",10) == -1 : # All of the rows\n",
+ " pandas.options.display.max_rows = 100\n",
+ " pandas.options.display.max_columns = None\n",
+ " return df # print(df.to_string())\n",
+ " else:\n",
+ " pandas.options.display.max_rows = _settings.get(\"maxrows\",10)\n",
+ " pandas.options.display.max_columns = None\n",
+ " return df # pdisplay(df) # print(df.to_string())\n",
+ "\n",
+ " except:\n",
+ " db2_error(flag([\"-q\",\"-quiet\"]))\n",
+ " continue # return\n",
+ " \n",
+ " sqlelapsed = time.time() - start_time\n",
+ " if (flag_output == False and flag([\"-q\",\"-quiet\"]) == False): print(\"Command completed.\")\n",
+ " \n",
+ "# Register the Magic extension in Jupyter \n",
+ "ip = get_ipython() \n",
+ "ip.register_magics(DB2)\n",
+ "load_settings()\n",
+ "\n",
+ "macro_list = '''\n",
+ "#\n",
+ "# The LIST macro is used to list all of the tables in the current schema or for all schemas\n",
+ "#\n",
+ "var syntax Syntax: LIST TABLES [FOR ALL | FOR SCHEMA name]\n",
+ "# \n",
+ "# Only LIST TABLES is supported by this macro\n",
+ "#\n",
+ "flags -a\n",
+ "if {^1} <> 'TABLES'\n",
+ " exit {syntax}\n",
+ "endif\n",
+ "\n",
+ "#\n",
+ "# This SQL is a temporary table that contains the description of the different table types\n",
+ "#\n",
+ "WITH TYPES(TYPE,DESCRIPTION) AS (\n",
+ " VALUES\n",
+ " ('A','Alias'),\n",
+ " ('G','Created temporary table'),\n",
+ " ('H','Hierarchy table'),\n",
+ " ('L','Detached table'),\n",
+ " ('N','Nickname'),\n",
+ " ('S','Materialized query table'),\n",
+ " ('T','Table'),\n",
+ " ('U','Typed table'),\n",
+ " ('V','View'),\n",
+ " ('W','Typed view')\n",
+ ")\n",
+ "SELECT TABNAME, TABSCHEMA, T.DESCRIPTION FROM SYSCAT.TABLES S, TYPES T\n",
+ " WHERE T.TYPE = S.TYPE \n",
+ "\n",
+ "#\n",
+ "# Case 1: No arguments - LIST TABLES\n",
+ "#\n",
+ "if {argc} == 1\n",
+ " AND OWNER = CURRENT USER\n",
+ " ORDER BY TABNAME, TABSCHEMA\n",
+ " return\n",
+ "endif \n",
+ "\n",
+ "#\n",
+ "# Case 2: Need 3 arguments - LIST TABLES FOR ALL\n",
+ "#\n",
+ "if {argc} == 3\n",
+ " if {^2}&{^3} == 'FOR&ALL'\n",
+ " ORDER BY TABNAME, TABSCHEMA\n",
+ " return\n",
+ " endif\n",
+ " exit {syntax}\n",
+ "endif\n",
+ "\n",
+ "#\n",
+ "# Case 3: Need FOR SCHEMA something here\n",
+ "#\n",
+ "if {argc} == 4\n",
+ " if {^2}&{^3} == 'FOR&SCHEMA'\n",
+ " AND TABSCHEMA = '{^4}'\n",
+ " ORDER BY TABNAME, TABSCHEMA\n",
+ " return\n",
+ " else\n",
+ " exit {syntax}\n",
+ " endif\n",
+ "endif\n",
+ "\n",
+ "#\n",
+ "# Nothing matched - Error\n",
+ "#\n",
+ "exit {syntax}\n",
+ "'''\n",
+ "DB2.sql(None, \"define LIST\", cell=macro_list, local_ns=locals())\n",
+ "\n",
+ "macro_describe = '''\n",
+ "#\n",
+ "# The DESCRIBE command can either use the syntax DESCRIBE TABLE or DESCRIBE TABLE SELECT ...\n",
+ "#\n",
+ "var syntax Syntax: DESCRIBE [TABLE name | SELECT statement] \n",
+ "#\n",
+ "# Check to see what count of variables is... Must be at least 2 items DESCRIBE TABLE x or SELECT x\n",
+ "#\n",
+ "flags -a\n",
+ "if {argc} < 2\n",
+ " exit {syntax}\n",
+ "endif\n",
+ "\n",
+ "CALL ADMIN_CMD('{*0}');\n",
+ "'''\n",
+ "\n",
+ "DB2.sql(None,\"define describe\", cell=macro_describe, local_ns=locals())\n",
+ "\n",
+ "create_sample = \"\"\"\n",
+ "flags -d\n",
+ "BEGIN\n",
+ "DECLARE FOUND INTEGER;\n",
+ "SET FOUND = (SELECT COUNT(*) FROM SYSIBM.SYSTABLES WHERE NAME='DEPARTMENT' AND CREATOR=CURRENT USER);\n",
+ "IF FOUND = 0 THEN\n",
+ " EXECUTE IMMEDIATE('CREATE TABLE DEPARTMENT(DEPTNO CHAR(3) NOT NULL, DEPTNAME VARCHAR(36) NOT NULL,\n",
+ " MGRNO CHAR(6),ADMRDEPT CHAR(3) NOT NULL)');\n",
+ " EXECUTE IMMEDIATE('INSERT INTO DEPARTMENT VALUES\n",
+ " (''A00'',''SPIFFY COMPUTER SERVICE DIV.'',''000010'',''A00''),\n",
+ " (''B01'',''PLANNING'',''000020'',''A00''),\n",
+ " (''C01'',''INFORMATION CENTER'',''000030'',''A00''),\n",
+ " (''D01'',''DEVELOPMENT CENTER'',NULL,''A00''),\n",
+ " (''D11'',''MANUFACTURING SYSTEMS'',''000060'',''D01''),\n",
+ " (''D21'',''ADMINISTRATION SYSTEMS'',''000070'',''D01''),\n",
+ " (''E01'',''SUPPORT SERVICES'',''000050'',''A00''),\n",
+ " (''E11'',''OPERATIONS'',''000090'',''E01''),\n",
+ " (''E21'',''SOFTWARE SUPPORT'',''000100'',''E01''),\n",
+ " (''F22'',''BRANCH OFFICE F2'',NULL,''E01''),\n",
+ " (''G22'',''BRANCH OFFICE G2'',NULL,''E01''),\n",
+ " (''H22'',''BRANCH OFFICE H2'',NULL,''E01''),\n",
+ " (''I22'',''BRANCH OFFICE I2'',NULL,''E01''),\n",
+ " (''J22'',''BRANCH OFFICE J2'',NULL,''E01'')');\n",
+ "END IF;\n",
+ "\n",
+ "SET FOUND = (SELECT COUNT(*) FROM SYSIBM.SYSTABLES WHERE NAME='EMPLOYEE' AND CREATOR=CURRENT USER);\n",
+ "IF FOUND = 0 THEN\n",
+ " EXECUTE IMMEDIATE('CREATE TABLE EMPLOYEE(\n",
+ " EMPNO CHAR(6) NOT NULL,\n",
+ " FIRSTNME VARCHAR(12) NOT NULL,\n",
+ " MIDINIT CHAR(1),\n",
+ " LASTNAME VARCHAR(15) NOT NULL,\n",
+ " WORKDEPT CHAR(3),\n",
+ " PHONENO CHAR(4),\n",
+ " HIREDATE DATE,\n",
+ " JOB CHAR(8),\n",
+ " EDLEVEL SMALLINT NOT NULL,\n",
+ " SEX CHAR(1),\n",
+ " BIRTHDATE DATE,\n",
+ " SALARY DECIMAL(9,2),\n",
+ " BONUS DECIMAL(9,2),\n",
+ " COMM DECIMAL(9,2)\n",
+ " )');\n",
+ " EXECUTE IMMEDIATE('INSERT INTO EMPLOYEE VALUES\n",
+ " (''000010'',''CHRISTINE'',''I'',''HAAS'' ,''A00'',''3978'',''1995-01-01'',''PRES '',18,''F'',''1963-08-24'',152750.00,1000.00,4220.00),\n",
+ " (''000020'',''MICHAEL'' ,''L'',''THOMPSON'' ,''B01'',''3476'',''2003-10-10'',''MANAGER '',18,''M'',''1978-02-02'',94250.00,800.00,3300.00),\n",
+ " (''000030'',''SALLY'' ,''A'',''KWAN'' ,''C01'',''4738'',''2005-04-05'',''MANAGER '',20,''F'',''1971-05-11'',98250.00,800.00,3060.00),\n",
+ " (''000050'',''JOHN'' ,''B'',''GEYER'' ,''E01'',''6789'',''1979-08-17'',''MANAGER '',16,''M'',''1955-09-15'',80175.00,800.00,3214.00),\n",
+ " (''000060'',''IRVING'' ,''F'',''STERN'' ,''D11'',''6423'',''2003-09-14'',''MANAGER '',16,''M'',''1975-07-07'',72250.00,500.00,2580.00),\n",
+ " (''000070'',''EVA'' ,''D'',''PULASKI'' ,''D21'',''7831'',''2005-09-30'',''MANAGER '',16,''F'',''2003-05-26'',96170.00,700.00,2893.00),\n",
+ " (''000090'',''EILEEN'' ,''W'',''HENDERSON'' ,''E11'',''5498'',''2000-08-15'',''MANAGER '',16,''F'',''1971-05-15'',89750.00,600.00,2380.00),\n",
+ " (''000100'',''THEODORE'' ,''Q'',''SPENSER'' ,''E21'',''0972'',''2000-06-19'',''MANAGER '',14,''M'',''1980-12-18'',86150.00,500.00,2092.00),\n",
+ " (''000110'',''VINCENZO'' ,''G'',''LUCCHESSI'' ,''A00'',''3490'',''1988-05-16'',''SALESREP'',19,''M'',''1959-11-05'',66500.00,900.00,3720.00),\n",
+ " (''000120'',''SEAN'' ,'' '',''O`CONNELL'' ,''A00'',''2167'',''1993-12-05'',''CLERK '',14,''M'',''1972-10-18'',49250.00,600.00,2340.00),\n",
+ " (''000130'',''DELORES'' ,''M'',''QUINTANA'' ,''C01'',''4578'',''2001-07-28'',''ANALYST '',16,''F'',''1955-09-15'',73800.00,500.00,1904.00),\n",
+ " (''000140'',''HEATHER'' ,''A'',''NICHOLLS'' ,''C01'',''1793'',''2006-12-15'',''ANALYST '',18,''F'',''1976-01-19'',68420.00,600.00,2274.00),\n",
+ " (''000150'',''BRUCE'' ,'' '',''ADAMSON'' ,''D11'',''4510'',''2002-02-12'',''DESIGNER'',16,''M'',''1977-05-17'',55280.00,500.00,2022.00),\n",
+ " (''000160'',''ELIZABETH'',''R'',''PIANKA'' ,''D11'',''3782'',''2006-10-11'',''DESIGNER'',17,''F'',''1980-04-12'',62250.00,400.00,1780.00),\n",
+ " (''000170'',''MASATOSHI'',''J'',''YOSHIMURA'' ,''D11'',''2890'',''1999-09-15'',''DESIGNER'',16,''M'',''1981-01-05'',44680.00,500.00,1974.00),\n",
+ " (''000180'',''MARILYN'' ,''S'',''SCOUTTEN'' ,''D11'',''1682'',''2003-07-07'',''DESIGNER'',17,''F'',''1979-02-21'',51340.00,500.00,1707.00),\n",
+ " (''000190'',''JAMES'' ,''H'',''WALKER'' ,''D11'',''2986'',''2004-07-26'',''DESIGNER'',16,''M'',''1982-06-25'',50450.00,400.00,1636.00),\n",
+ " (''000200'',''DAVID'' ,'' '',''BROWN'' ,''D11'',''4501'',''2002-03-03'',''DESIGNER'',16,''M'',''1971-05-29'',57740.00,600.00,2217.00),\n",
+ " (''000210'',''WILLIAM'' ,''T'',''JONES'' ,''D11'',''0942'',''1998-04-11'',''DESIGNER'',17,''M'',''2003-02-23'',68270.00,400.00,1462.00),\n",
+ " (''000220'',''JENNIFER'' ,''K'',''LUTZ'' ,''D11'',''0672'',''1998-08-29'',''DESIGNER'',18,''F'',''1978-03-19'',49840.00,600.00,2387.00),\n",
+ " (''000230'',''JAMES'' ,''J'',''JEFFERSON'' ,''D21'',''2094'',''1996-11-21'',''CLERK '',14,''M'',''1980-05-30'',42180.00,400.00,1774.00),\n",
+ " (''000240'',''SALVATORE'',''M'',''MARINO'' ,''D21'',''3780'',''2004-12-05'',''CLERK '',17,''M'',''2002-03-31'',48760.00,600.00,2301.00),\n",
+ " (''000250'',''DANIEL'' ,''S'',''SMITH'' ,''D21'',''0961'',''1999-10-30'',''CLERK '',15,''M'',''1969-11-12'',49180.00,400.00,1534.00),\n",
+ " (''000260'',''SYBIL'' ,''P'',''JOHNSON'' ,''D21'',''8953'',''2005-09-11'',''CLERK '',16,''F'',''1976-10-05'',47250.00,300.00,1380.00),\n",
+ " (''000270'',''MARIA'' ,''L'',''PEREZ'' ,''D21'',''9001'',''2006-09-30'',''CLERK '',15,''F'',''2003-05-26'',37380.00,500.00,2190.00),\n",
+ " (''000280'',''ETHEL'' ,''R'',''SCHNEIDER'' ,''E11'',''8997'',''1997-03-24'',''OPERATOR'',17,''F'',''1976-03-28'',36250.00,500.00,2100.00),\n",
+ " (''000290'',''JOHN'' ,''R'',''PARKER'' ,''E11'',''4502'',''2006-05-30'',''OPERATOR'',12,''M'',''1985-07-09'',35340.00,300.00,1227.00),\n",
+ " (''000300'',''PHILIP'' ,''X'',''SMITH'' ,''E11'',''2095'',''2002-06-19'',''OPERATOR'',14,''M'',''1976-10-27'',37750.00,400.00,1420.00),\n",
+ " (''000310'',''MAUDE'' ,''F'',''SETRIGHT'' ,''E11'',''3332'',''1994-09-12'',''OPERATOR'',12,''F'',''1961-04-21'',35900.00,300.00,1272.00),\n",
+ " (''000320'',''RAMLAL'' ,''V'',''MEHTA'' ,''E21'',''9990'',''1995-07-07'',''FIELDREP'',16,''M'',''1962-08-11'',39950.00,400.00,1596.00),\n",
+ " (''000330'',''WING'' ,'' '',''LEE'' ,''E21'',''2103'',''2006-02-23'',''FIELDREP'',14,''M'',''1971-07-18'',45370.00,500.00,2030.00),\n",
+ " (''000340'',''JASON'' ,''R'',''GOUNOT'' ,''E21'',''5698'',''1977-05-05'',''FIELDREP'',16,''M'',''1956-05-17'',43840.00,500.00,1907.00),\n",
+ " (''200010'',''DIAN'' ,''J'',''HEMMINGER'' ,''A00'',''3978'',''1995-01-01'',''SALESREP'',18,''F'',''1973-08-14'',46500.00,1000.00,4220.00),\n",
+ " (''200120'',''GREG'' ,'' '',''ORLANDO'' ,''A00'',''2167'',''2002-05-05'',''CLERK '',14,''M'',''1972-10-18'',39250.00,600.00,2340.00),\n",
+ " (''200140'',''KIM'' ,''N'',''NATZ'' ,''C01'',''1793'',''2006-12-15'',''ANALYST '',18,''F'',''1976-01-19'',68420.00,600.00,2274.00),\n",
+ " (''200170'',''KIYOSHI'' ,'' '',''YAMAMOTO'' ,''D11'',''2890'',''2005-09-15'',''DESIGNER'',16,''M'',''1981-01-05'',64680.00,500.00,1974.00),\n",
+ " (''200220'',''REBA'' ,''K'',''JOHN'' ,''D11'',''0672'',''2005-08-29'',''DESIGNER'',18,''F'',''1978-03-19'',69840.00,600.00,2387.00),\n",
+ " (''200240'',''ROBERT'' ,''M'',''MONTEVERDE'',''D21'',''3780'',''2004-12-05'',''CLERK '',17,''M'',''1984-03-31'',37760.00,600.00,2301.00),\n",
+ " (''200280'',''EILEEN'' ,''R'',''SCHWARTZ'' ,''E11'',''8997'',''1997-03-24'',''OPERATOR'',17,''F'',''1966-03-28'',46250.00,500.00,2100.00),\n",
+ " (''200310'',''MICHELLE'' ,''F'',''SPRINGER'' ,''E11'',''3332'',''1994-09-12'',''OPERATOR'',12,''F'',''1961-04-21'',35900.00,300.00,1272.00),\n",
+ " (''200330'',''HELENA'' ,'' '',''WONG'' ,''E21'',''2103'',''2006-02-23'',''FIELDREP'',14,''F'',''1971-07-18'',35370.00,500.00,2030.00),\n",
+ " (''200340'',''ROY'' ,''R'',''ALONZO'' ,''E21'',''5698'',''1997-07-05'',''FIELDREP'',16,''M'',''1956-05-17'',31840.00,500.00,1907.00)'); \n",
+ "END IF;\n",
+ "END\"\"\"\n",
+ "\n",
+ "DB2.sql(None,\"define sampledata\", cell=create_sample, local_ns=locals())\n",
+ "\n",
+ "create_set = '''\n",
+ "#\n",
+ "# Convert a SET statement into an OPTION statement\n",
+ "#\n",
+ "\n",
+ "# Display settings\n",
+ "if {^1} == 'DISPLAY'\n",
+ " if {^2} == \"PANDAS\" \n",
+ " OPTION DISPLAY PANDAS\n",
+ " return\n",
+ " else\n",
+ " if {^2} == \"GRID\"\n",
+ " OPTION DISPLAY GRID\n",
+ " return\n",
+ " endif\n",
+ " endif\n",
+ "endif\n",
+ "\n",
+ "# Multithreading\n",
+ "if {^1} == 'THREADS'\n",
+ " OPTION THREADS {2}\n",
+ " return\n",
+ "endif\n",
+ " \n",
+ "# Maximum number of rows displayed\n",
+ "if {^1} == 'MAXROWS'\n",
+ " OPTION MAXROWS {2}\n",
+ " return\n",
+ "endif\n",
+ "\n",
+ "# Is paging on or off?\n",
+ "if {^1} == 'PAGING'\n",
+ " OPTION PAGING {2}\n",
+ " return\n",
+ "endif\n",
+ " \n",
+ "{*0}\n",
+ "return\n",
+ "'''\n",
+ "\n",
+ "DB2.sql(None,\"define set\", cell=create_set, local_ns=locals())\n",
+ " \n",
+ "success(f\"Db2 Extensions Loaded. Version: {_db2magic_version}\")\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Credits: IBM 2024, George Baklarz [baklarz@ca.ibm.com]"
+ ]
+ }
+ ],
+ "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.8.10"
+ },
+ "vscode": {
+ "interpreter": {
+ "hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1"
+ }
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/ai-vectors/product-recommendation/db2connect.pickle b/ai-vectors/product-recommendation/db2connect.pickle
new file mode 100644
index 0000000000000000000000000000000000000000..465ee75fcc45a500cfe9c642960cdf192b092c7f
GIT binary patch
literal 215
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
J-BjND=|98}{y6{u
literal 0
HcmV?d00001
diff --git a/ai-vectors/product-recommendation/images/LOO-1396.jpeg b/ai-vectors/product-recommendation/images/LOO-1396.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..c20941af2d367dc5a8705f3f603157ad87edb329
GIT binary patch
literal 56456
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 |