├── .DS_Store ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml └── workflows │ ├── node-ci.yaml │ └── python-ci.yaml ├── .gitignore ├── .pre-commit-config.yaml ├── Dremio ├── Dockerfile ├── README.md ├── build_image.sh ├── build_jar.sh ├── pom.xml └── src │ └── main │ ├── checkstyle │ ├── checkstyle-config.xml │ └── checkstyle-suppresions.xml │ ├── java │ └── com │ │ └── dremio │ │ └── exec │ │ └── store │ │ └── jdbc │ │ └── conf │ │ └── IBMSQLConf.java │ └── resources │ ├── IBMSQL-layout.json │ ├── IBMSQL.svg │ ├── arp │ └── implementation │ │ └── IBMSQL-arp.yaml │ ├── log4j.properties │ └── sabot-module.conf ├── Grafana ├── README.md ├── back-end │ ├── .gitignore │ ├── Dockerfile.stages │ ├── app.py │ ├── environment.yml │ └── howto_docker.txt └── front-end │ └── cloudsql-cos-ts │ ├── .gitignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── build │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── img │ │ ├── cloudsql.png │ │ ├── cos-logo.png │ │ ├── cos.png │ │ ├── ibm-cloud-logo.png │ │ ├── logo.png │ │ ├── logo.svg │ │ └── logo.xcf │ ├── module.js │ ├── module.js.LICENSE.txt │ ├── module.js.map │ └── plugin.json │ ├── jest.config.js │ ├── package.json │ ├── src │ ├── CheatSheet.tsx │ ├── ConfigEditor.tsx │ ├── DataSource.ts │ ├── ExploreQueryEditor.tsx │ ├── QueryEditor.tsx │ ├── configuration │ │ ├── CloudSQLSettings.tsx │ │ ├── Dialog.tsx │ │ └── QueryFields.tsx │ ├── img │ │ └── logo.png │ ├── module.ts │ ├── plugin.json │ ├── sql │ │ ├── add_label_to_query.ts │ │ ├── cloudsql.ts │ │ ├── help.tsx │ │ ├── language_provider.ts │ │ └── language_utils.ts │ ├── types.ts │ └── utils │ │ └── CancelablePromise.ts │ └── tsconfig.json ├── LICENSE ├── Node ├── .gitignore ├── README.md ├── lib │ └── sqlQuery.js ├── package.json └── test │ └── sqlquery_spec.js ├── Python ├── .gitignore ├── Cloud SQL Query Notebook.ipynb ├── MANIFEST.in ├── Pipfile ├── Pipfile.lock ├── README.md ├── _install.sh ├── cloud_function │ ├── Dockerfile │ ├── README.md │ ├── __main__.py │ ├── bind.sh │ ├── bind_as_python_function.sh │ ├── build.sh │ ├── call.sh │ ├── call_as_python_function.sh │ ├── ibmcloudsql_cloudfunction.zip │ ├── invoke.py │ ├── register.sh │ ├── register_as_python_function.sh │ └── sqlfunction.py ├── ibmcloudsql │ ├── SQLQuery.py │ ├── __init__.py │ ├── catalog_table.py │ ├── cos.py │ ├── exceptions.py │ ├── sql_magic.py │ ├── sql_query_ts.py │ ├── test.py │ ├── test_credentials.py.template │ └── utilities.py ├── package.sh ├── setup.py ├── setup_env.sh └── tests │ ├── test_cos.py │ ├── test_sql_magic.py │ └── test_sqlquery.py ├── README.md ├── Samples ├── meteorites.json ├── nobelprizes.json ├── sample-payload-iotmessages.avro └── sample-payload-iotmessages.json ├── cosaccess ├── .gitignore ├── COS FGAC Demo.ipynb ├── Data Engine FGAC Demo.ipynb ├── README.md ├── _install.sh ├── cosaccess.png ├── cosaccess.pptx ├── cosaccess │ ├── __init__.py │ ├── cosaccess.py │ ├── pyvenv.cfg │ ├── test.py │ └── test_credentials.py.template ├── package.sh ├── setup.py └── setup_env.sh └── docs ├── Makefile └── source ├── _static └── css │ └── custom.css ├── conf.py ├── cos.rst ├── ibmcloudsql.rst ├── includeme.rst ├── index.rst ├── intro.rst ├── modules.rst ├── sql_magic.rst ├── sql_query.rst └── utilities.rst /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM-Cloud/sql-query-clients/1963cca543dccd2734aa66bdb0229c862516cfdb/.DS_Store -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "pip" # See documentation for possible values 9 | directory: "/Python" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/workflows/node-ci.yaml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | paths: 6 | - 'Node/**' 7 | pull_request: 8 | paths: 9 | - 'Node/**' 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | defaults: 15 | run: 16 | working-directory: Node 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions/setup-node@v1 20 | with: 21 | node-version: 8.x 22 | - run: npm install 23 | - run: npm audit 24 | - run: npm test -------------------------------------------------------------------------------- /.github/workflows/python-ci.yaml: -------------------------------------------------------------------------------- 1 | name: Python CI 2 | 3 | on: 4 | push: 5 | paths: 6 | - 'Python/**' 7 | pull_request: 8 | paths: 9 | - 'Python/**' 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | env: 15 | PIPENV_PYUP_API_KEY: "" 16 | defaults: 17 | run: 18 | working-directory: Python 19 | steps: 20 | - uses: actions/checkout@v2 21 | - uses: actions/setup-python@v2 22 | with: 23 | python-version: '3.8' 24 | - run: pip install pipenv 25 | - run: pipenv sync --dev 26 | - run: pipenv check -i 39253 27 | - run: PYTHONPATH=. pipenv run ci 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *DS_Store 2 | ./Dremio/target 3 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v3.2.0 6 | hooks: 7 | - id: trailing-whitespace 8 | - id: end-of-file-fixer 9 | - id: check-yaml 10 | - id: check-added-large-files 11 | - repo: https://github.com/asottile/reorder_python_imports 12 | rev: v2.4.0 13 | hooks: 14 | - id: reorder-python-imports 15 | - repo: https://gitlab.com/pycqa/flake8 16 | rev: '3.7.9' # pick a git hash / tag to point to 17 | hooks: 18 | - id: flake8 19 | - repo: https://github.com/ambv/black 20 | rev: 19.10b0 21 | hooks: 22 | - id: black 23 | language_version: python3.8 24 | additional_dependencies: ['click==8.0.2'] 25 | - repo: https://github.com/pre-commit/pre-commit-hooks 26 | rev: v1.2.3 27 | hooks: 28 | - id: flake8 29 | additional_dependencies: [flake8-docstrings] 30 | -------------------------------------------------------------------------------- /Dremio/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM dremio/dremio-oss 2 | 3 | ARG DREMIO_VERSION 4 | 5 | USER root 6 | 7 | # Download JDBC driver and Dremio connector for SQL Query 8 | RUN cd /tmp \ 9 | && wget --output-document=/opt/dremio/jars/3rdparty/ibmcloudsql-jdbc.jar https://us.sql-query.cloud.ibm.com/download/jdbc/ibmcloudsql-jdbc-2.5.46.jar \ 10 | && wget --output-document=/opt/dremio/jars/dremio-ibm-sql-query-plugin-$DREMIO_VERSION.jar https://github.com/IBM-Cloud/sql-query-clients/releases/download/Dremio_$DREMIO_VERSION/dremio-ibm-sql-query-plugin-$DREMIO_VERSION.jar 11 | 12 | -------------------------------------------------------------------------------- /Dremio/README.md: -------------------------------------------------------------------------------- 1 | # Dremio IBM SQL Query Connector 2 | 3 | 4 | 5 | 6 | ## Overview 7 | 8 | This is a community based IBM SQL Query Dremio connector made using the ARP framework. Check [Dremio Hub](https://github.com/dremio-hub) for more examples and [ARP Docs](https://github.com/dremio-hub/dremio-sqllite-connector#arp-file-format) for documentation. 9 | 10 | ### What is Dremio? 11 | 12 | Dremio delivers lightning fast query speed and a self-service semantic layer operating directly against your data lake storage and other sources. No moving data to proprietary data warehouses or creating cubes, aggregation tables and BI extracts. Just flexibility and control for Data Architects, and self-service for Data Consumers. 13 | 14 | ## Installation 15 | 16 | ### Run Dremio with SQL Query connector locally 17 | 18 | You can launch the ready-to-use Dremio image with the latest release of the IBM SQL Query connector and the IBM SQL Query JDBC driver like follows: 19 | * `docker run -d -p 9047:9047 -p 31010:31010 -p 45678:45678 torsstei/dremio-ibm-sql-query` 20 | 21 | Now you can open the Dremio console at this URL: http://localhost:9047 and proceed as descibed in Usage below. 22 | 23 | ### Run Dremio with SQL Query connector in IBM Cloud 24 | 25 | You can use helm charts to deploy an entire Dremio cluster on Kubernetes in IBM Cloud's managed [Kubernetes Service](https://www.ibm.com/cloud/kubernetes-service). 26 | 27 | * Make sure you have helm installed on your local machine. For instance use `brew install kubernetes-helm` on Mac OS. 28 | * Make sure you have Kubernetes cluster created in your IBM Cloud account. 29 | * Install the IBM Cloud [CLI tool](https://cloud.ibm.com/docs/cli?topic=cli-install-ibmcloud-cli). 30 | * Install and Set up the Kubernetes container service plugin for the IBM Cloud CLI and the kubectl CLI tool for your Kubernetes cluster as described [here](https://cloud.ibm.com/docs/containers?topic=containers-cs_cli_install). 31 | * Clone the github repository with the Dremio helm charts for IBM Cloud: 32 | * `git clone https://github.com/IBM-Cloud/dremio-cloud-tools.git` 33 | * Go to the directoy with the desired configuration 34 | * `cd dremio-cloud-tools/charts/dremio_v2` 35 | * If you want to start with a smaller cluster with less resources use `cd dremio-cloud-tools/charts/dremio_v2_small` instead. 36 | * Submit the helm chart deployment `helm install --wait . --generate-name` 37 | * You can list the deployed helm charts with `helm list` 38 | * You can use the Kubernetes dashboard (can be launched from the IBM Cloud dashboard UI for your Kubernetes cluster) or the `kubectl` CLI to observe the status of the individual assets that helm has deployed in your Kubernetes cluster. 39 | * Identify the load balancer ingress host name of the Dremio server in your Kubernetes cluster 40 | * `kubectl describe service dremio-client | grep LoadBalancer` 41 | * Now open URL `http://{hostname}:9047` in your browser to get the Dremio web console. 42 | * Alternatively you can also go to the Kubernetes dashboard and click on the second Endpoint in `Service->Services->"dremio-client"->External Endpoints` (this is the endpoint with port 9047 for the Dremio console). 43 | 44 | ## Usage 45 | 46 | ### Creating a new IBM SQL Query Source 47 | 48 | In the Dremio UI click the `+` option for `External Sources` in the bottom left corner. Then select `IBMSQL` and specify the following parameters: 49 | 50 | * JDBC URL 51 | * Ex: `jdbc:ibmcloudsql:{instance-crn}` 52 | * The `{instance-crn}` is the unique identifier of your instance of SQL Query in your IBM Cloud account. 53 | * Make sure that the URL does not include the paramter `?targetcosurl=...`. This is specified in a separate parameter. See below. 54 | * See also `Connect` menu in your IBM SQL Query web console. 55 | * API Key (IBM Cloud IAM API Key) 56 | * Target COS URL 57 | * Ex: `cos://{IBM cloud region alias}/{bucket name}/{prefix}` 58 | * This is the IBM cloud object storage location where results for each SQL query are written. You can configure automatic [expiration](https://cloud.ibm.com/docs/cloud-object-storage?topic=cloud-object-storage-expiry) if you want an automatic cleanup. 59 | * This is the same value as in the field `Targert location` in your IBM SQL Query web console. 60 | 61 | Learn about IBM SQL Query JDBC client properties [here](https://cloud.ibm.com/docs/sql-query?topic=sql-query-jdbc) 62 | 63 | ## Development 64 | 65 | ### Manual Build and Installation of the connector 66 | 67 | 0. Change the pom's dremio.version to suit your Dremio's version. 68 | ` 4.9.1-202010230218060541-2e764ed0` 69 | 1. In root directory with the pom.xml file run `mvn clean install -DskipTests` (or run the `build_jar.sh` script). If you want to run the tests, add the JDBC jar to your local maven repo along with environment variables that are required. Check the basic test example for more details. 70 | 2. Take the resulting `.jar` file in the target folder and put it in the /jars folder in Dremio 71 | 3. Download the IBM SQL Query JDBC driver from [here](https://cloud.ibm.com/docs/sql-query?topic=sql-query-jdbc) and put in in the /jars/3rdparty folder 72 | 4. Restart Dremio 73 | 74 | ### Building the docker image 75 | 76 | Run `build_image.sh`. 77 | -------------------------------------------------------------------------------- /Dremio/build_image.sh: -------------------------------------------------------------------------------- 1 | version=$(grep "" pom.xml | head -1 | sed 's/^.*//g' | sed 's/<\/version>.*$//g') 2 | echo Make sure that you have a release created with the SQL Query connector jar for Dremio versio $version. 3 | echo Building image for Dremio version $version... 4 | 5 | docker build --no-cache --build-arg DREMIO_VERSION=$version --tag dremio-ibm-sql-query . 6 | docker tag dremio-ibm-sql-query torsstei/dremio-ibm-sql-query:dremio$version 7 | docker tag dremio-ibm-sql-query torsstei/dremio-ibm-sql-query:latest 8 | docker push torsstei/dremio-ibm-sql-query:dremio$version 9 | docker push torsstei/dremio-ibm-sql-query:latest 10 | 11 | -------------------------------------------------------------------------------- /Dremio/build_jar.sh: -------------------------------------------------------------------------------- 1 | mvn clean install -DskipTests 2 | 3 | -------------------------------------------------------------------------------- /Dremio/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 22 | 4.0.0 23 | 24 | com.dremio.plugins 25 | 20.1.0 26 | dremio-ibm-sql-query-plugin 27 | Dremio IBM SQL Query Community Connector 28 | 29 | 30 | 20.1.0-202202061055110045-36733c65 31 | 32 | 33 | 34 | 35 | com.dremio.community.plugins 36 | dremio-ce-jdbc-plugin 37 | ${dremio.version} 38 | 39 | 40 | 41 | 42 | 43 | 44 | org.apache.maven.plugins 45 | maven-checkstyle-plugin 46 | 3.0.0 47 | 48 | src/main/checkstyle/checkstyle-config.xml 49 | src/main/checkstyle/checkstyle-suppressions.xml 50 | 51 | 52 | 53 | maven-compiler-plugin 54 | 3.0 55 | 56 | 1.8 57 | 1.8 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | dremio-public 66 | http://maven.dremio.com/public/ 67 | 68 | 69 | dremio-free 70 | http://maven.dremio.com/free/ 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /Dremio/src/main/checkstyle/checkstyle-config.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Dremio/src/main/checkstyle/checkstyle-suppresions.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Dremio/src/main/java/com/dremio/exec/store/jdbc/conf/IBMSQLConf.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2019 Dremio Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.dremio.exec.store.jdbc.conf; 17 | import static com.google.common.base.Preconditions.checkNotNull; 18 | import com.dremio.exec.store.jdbc.*; 19 | import com.dremio.options.OptionManager; 20 | import com.dremio.security.CredentialsService; 21 | import org.apache.log4j.Logger; 22 | import com.dremio.exec.catalog.conf.DisplayMetadata; 23 | import com.dremio.exec.catalog.conf.NotMetadataImpacting; 24 | import com.dremio.exec.catalog.conf.Secret; 25 | import com.dremio.exec.catalog.conf.SourceType; 26 | import com.dremio.exec.store.jdbc.JdbcPluginConfig; 27 | import com.dremio.exec.store.jdbc.dialect.arp.ArpDialect; 28 | import com.dremio.exec.store.jdbc.dialect.arp.ArpYaml; 29 | import com.google.common.annotations.VisibleForTesting; 30 | import io.protostuff.Tag; 31 | import java.util.Properties; 32 | /** 33 | * Configuration for IBMSQL. 34 | */ 35 | @SourceType(value = "IBMSQL", label = "IBMSQL" , uiConfig = "IBMSQL-layout.json") 36 | public class IBMSQLConf extends AbstractArpConf { 37 | private static final String ARP_FILENAME = "arp/implementation/IBMSQL-arp.yaml"; 38 | private static final ArpDialect ARP_DIALECT = 39 | AbstractArpConf.loadArpFile(ARP_FILENAME, (ArpDialect::new)); 40 | private static final String DRIVER = "com.ibm.cloud.sql.jdbc.Driver"; 41 | /* 42 | https://cloud.ibm.com/docs/sql-query?topic=sql-query-jdbc 43 | */ 44 | @Tag(1) 45 | @DisplayMetadata(label = "JDBC URL (Ex: jdbc:ibmcloudsql:{instance-crn}[?{key1}={value1}&{key2}={value2}...]") 46 | public String jdbcURL; 47 | 48 | 49 | @Tag(2) 50 | @Secret 51 | @DisplayMetadata(label = "API Key") 52 | public String apikey; 53 | 54 | 55 | @Tag(3) 56 | @NotMetadataImpacting 57 | @DisplayMetadata(label = "Target COS URL") 58 | public String targetcosurl; 59 | 60 | @Tag(4) 61 | @DisplayMetadata(label = "Maximum idle connections") 62 | @NotMetadataImpacting 63 | public int maxIdleConns = 8; 64 | 65 | @Tag(5) 66 | @DisplayMetadata(label = "Connection idle time (s)") 67 | @NotMetadataImpacting 68 | public int idleTimeSec = 60; 69 | 70 | @VisibleForTesting 71 | public String toJdbcConnectionString() { 72 | checkNotNull(this.targetcosurl, "Target COS URL is required"); 73 | checkNotNull(this.jdbcURL, "JDBC URL is required"); 74 | return jdbcURL + "?targetcosurl=" + targetcosurl; 75 | } 76 | 77 | 78 | @Tag(6) 79 | @DisplayMetadata(label = "Encrypt connection") 80 | public boolean useSsl = false; 81 | 82 | public IBMSQLConf(){} 83 | 84 | 85 | @Override 86 | @VisibleForTesting 87 | public JdbcPluginConfig buildPluginConfig( 88 | JdbcPluginConfig.Builder configBuilder, 89 | CredentialsService credentialsService, 90 | OptionManager optionManager 91 | ){ 92 | return configBuilder.withDialect(getDialect()) 93 | .withDatasourceFactory(this::newDataSource) 94 | .withAllowExternalQuery(false) 95 | .build(); 96 | } 97 | 98 | 99 | private CloseableDataSource newDataSource() { 100 | final Properties properties = new Properties(); 101 | 102 | if (useSsl) { 103 | properties.setProperty("SSL", "1"); 104 | } 105 | 106 | return DataSources.newGenericConnectionPoolDataSource(DRIVER, 107 | toJdbcConnectionString(), null, apikey, properties, 108 | DataSources.CommitMode.DRIVER_SPECIFIED_COMMIT_MODE, 109 | maxIdleConns, idleTimeSec); 110 | } 111 | 112 | @Override 113 | public ArpDialect getDialect() { 114 | return ARP_DIALECT; 115 | } 116 | @VisibleForTesting 117 | public static ArpDialect getDialectSingleton() { 118 | return ARP_DIALECT; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Dremio/src/main/resources/IBMSQL-layout.json: -------------------------------------------------------------------------------- 1 | { 2 | "sourceType": "IBMSQL", 3 | "metadataRefresh": { 4 | "datasetDiscovery": true 5 | }, 6 | "form": { 7 | "tabs": [ 8 | { 9 | "name": "General", 10 | "isGeneral": true, 11 | "sections": [ 12 | { 13 | "name": "Connection", 14 | "layout": "row", 15 | "elements": [ 16 | { 17 | "propName": "config.jdbcURL", 18 | "validate": { 19 | "isRequired": true 20 | } 21 | }, 22 | { 23 | "propName": "config.apikey", 24 | "validate": { 25 | "isRequired": true 26 | } 27 | }, 28 | { 29 | "propName": "config.targetcosurl", 30 | "validate": { 31 | "isRequired": true 32 | } 33 | }, 34 | { 35 | "propName": "config.maxIdleConns", 36 | "validate": { 37 | "isRequired": true 38 | } 39 | }, 40 | { 41 | "propName": "config.idleTimeSec", 42 | "validate": { 43 | "isRequired": true 44 | } 45 | } 46 | ] 47 | } 48 | ] 49 | } 50 | ] 51 | } 52 | } -------------------------------------------------------------------------------- /Dremio/src/main/resources/IBMSQL.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 75 | 76 | -------------------------------------------------------------------------------- /Dremio/src/main/resources/arp/implementation/IBMSQL-arp.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2017-2019 Dremio Corporation 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | metadata: 18 | # Manually Configured Metadata Section. 19 | name: IBMSQL 20 | apiname: IBMSQL 21 | spec_version: '1' 22 | 23 | syntax: 24 | # Manually Configured Syntax Section. 25 | identifier_length_limit: 128 26 | allows_boolean_literal: false 27 | map_boolean_literal_to_bit: false 28 | supports_catalogs: false 29 | supports_schemas: false 30 | 31 | data_types: 32 | mappings: 33 | # Manually Configured Data Types Mappings Section. 34 | - source: 35 | name: "BOOLEAN" 36 | dremio: 37 | name: "boolean" 38 | - source: 39 | name: "INTEGER" 40 | dremio: 41 | name: "integer" 42 | - source: 43 | name: "TIMESTAMP" 44 | dremio: 45 | name: "timestamp" 46 | default_cast_spec: true 47 | - source: 48 | name: "BIGINT" 49 | dremio: 50 | name: "bigint" 51 | - source: 52 | name: "DOUBLE" 53 | dremio: 54 | name: "double" 55 | - source: 56 | name: "FLOAT" 57 | dremio: 58 | name: "double" 59 | - source: 60 | name: "DECIMAL" 61 | dremio: 62 | name: "decimal" 63 | - source: 64 | name: "DATE" 65 | dremio: 66 | name: "date" 67 | - source: 68 | name: "VARCHAR" 69 | dremio: 70 | name: "varchar" 71 | 72 | relational_algebra: 73 | aggregation: 74 | enable: true 75 | group_by_ordinal: true 76 | distinct: true 77 | count_functions: 78 | count_star: 79 | enable: true 80 | count: 81 | enable: true 82 | count_multi: 83 | enable: true 84 | variable_rewrite: 85 | separator_sequence: 86 | - ' IS NULL OR ' 87 | rewrite_format: 'SUM(CASE WHEN {separator[0]} IS NULL THEN 0 ELSE 1 END)' 88 | count_distinct: 89 | enable: true 90 | count_distinct_multi: 91 | enable: true 92 | variable_rewrite: 93 | separator_sequence: 94 | - ' IS NULL OR ' 95 | - ', ' 96 | rewrite_format: 'COUNT(DISTINCT CASE WHEN {separator[0]} IS NULL THEN NULL ELSE CONCAT({separator[1]}) END)' 97 | functions: 98 | - names: 99 | - "avg" 100 | signatures: 101 | - args: 102 | - "double" 103 | return: "double" 104 | - args: 105 | - "integer" 106 | return: "double" 107 | - names: 108 | - "max" 109 | - "min" 110 | signatures: 111 | - args: 112 | - "integer" 113 | return: "integer" 114 | - args: 115 | - "double" 116 | return: "double" 117 | - args: 118 | - "varchar" 119 | return: "varchar" 120 | - names: 121 | - "sum" 122 | signatures: 123 | - args: 124 | - "double" 125 | return: "double" 126 | - args: 127 | - "integer" 128 | return: "bigint" 129 | 130 | except: 131 | enable: true 132 | project: 133 | enable: true 134 | join: 135 | enable: true 136 | cross: 137 | enable: true 138 | inner: 139 | enable: true 140 | inequality: true 141 | left: 142 | enable: true 143 | inequality: true 144 | right: 145 | enable: true 146 | inequality: true 147 | full: 148 | enable: true 149 | inequality: true 150 | sort: 151 | enable: true 152 | order_by: 153 | enable: true 154 | default_nulls_ordering: high 155 | fetch_offset: 156 | offset_fetch: 157 | enable: false 158 | format: 'LIMIT {1}' 159 | offset_only: 160 | enable: false 161 | fetch_only: 162 | enable: true 163 | format: 'LIMIT {0}' 164 | union: 165 | enable: false 166 | union_all: 167 | enable: false 168 | values: 169 | enable: false 170 | method: values 171 | 172 | # Describe the set of function signatures that are internally supported. 173 | expressions: 174 | subqueries: 175 | correlated: true 176 | scalar: true 177 | in_clause: true 178 | supports_case: true 179 | supports_over: false 180 | operators: 181 | - names: 182 | - "=" 183 | - "!=" 184 | - "<>" 185 | - ">" 186 | - ">=" 187 | - "<" 188 | - "<=" 189 | signatures: 190 | - args: 191 | - "double" 192 | - "double" 193 | return: "boolean" 194 | - args: 195 | - "double" 196 | - "integer" 197 | return: "boolean" 198 | - args: 199 | - "double" 200 | - "varchar" 201 | return: "boolean" 202 | - args: 203 | - "integer" 204 | - "double" 205 | return: "boolean" 206 | - args: 207 | - "integer" 208 | - "integer" 209 | return: "boolean" 210 | - args: 211 | - "integer" 212 | - "varchar" 213 | return: "boolean" 214 | - args: 215 | - "varchar" 216 | - "varchar" 217 | return: "boolean" 218 | - names: 219 | - "not" 220 | signatures: 221 | - args: 222 | - "boolean" 223 | return: "boolean" 224 | # Example scalar function 225 | - names: 226 | - "sign" 227 | signatures: 228 | - args: 229 | - "double" 230 | return: "double" 231 | - args: 232 | - "integer" 233 | return: "integer" 234 | # Example rewrite (although this is not necessary here as the default is the same) 235 | rewrite: "SIGN({0})" 236 | 237 | variable_length_operators: 238 | - names: 239 | - and 240 | variable_signatures: 241 | - return: boolean 242 | arg_type: boolean 243 | - names: 244 | - or 245 | variable_signatures: 246 | - return: boolean 247 | arg_type: boolean 248 | -------------------------------------------------------------------------------- /Dremio/src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 2 | log4j.appender.stdout.Target=System.out 3 | log4j.appender.stdout.layout=org.apache.log4j.SimpleLayout 4 | log4j.rootLogger=info, stdout -------------------------------------------------------------------------------- /Dremio/src/main/resources/sabot-module.conf: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2017-2019 Dremio Corporation 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | // This file tells Dremio to consider this module when class path scanning. 18 | // This file can also include any supplementary configuration information. 19 | // This file is in HOCON format, see https://github.com/typesafehub/config/blob/master/HOCON.md for more information. 20 | dremio.classpath.scanning.packages += "com.dremio.exec.store.jdbc" 21 | 22 | -------------------------------------------------------------------------------- /Grafana/README.md: -------------------------------------------------------------------------------- 1 | # Grafana IBM SQL Query Connector 2 | 3 | 4 | Overview 5 | ----------- 6 | 7 | This is a community based IBM SQL Query Grafana connector. The connector has 2 parts: a front-end part and back-end part. 8 | The plugin provides the capability to launch one or many SQL-based queries to the CloudSQL service instance. The returned data can be categorized into either: TimeSeries or Table. 9 | With TimeSeries, there must be a column holding timestamp data, and a column holding observation/measurement data. With Multi-TimeSeries, there must be a third column as metrics. 10 | 11 | A tutorial is available at [IBM Cloud's Blog](https://www.ibm.com/cloud/blog/time-series-analytics-for-ibm-virtual-private-cloud-flows-using-grafana) 12 | 13 | What is Grafana? 14 | ----------- 15 | 16 | Grafana is a multi-platform open source analytics and interactive visualization web application. It provides charts, graphs, and alerts for the web when connected to supported data sources. 17 | 18 | 19 | Usage 20 | ----------- 21 | 22 | ### Setup 23 | 24 | 1. The plugin can be built using 25 | 26 | ```console 27 | yarn 28 | yarn dev 29 | ``` 30 | 31 | 2. Grafana 7.4+ should be downloaded and we put the the built code into the designated plugin location of Grafana 32 | 33 | Suppose you download Grafana to 34 | ```console 35 | export GRAFANA_DIR = $HOME/go/src/github.com/grafana/grafana 36 | ``` 37 | then 38 | 39 | ```console 40 | cp -R /sql-query-clients/Grafana/front-end/cloudsql-cos-ts/dist $GRAFANA_DIR/plugins-bundled/cloudsql-cos-ts 41 | ``` 42 | 43 | ### Launch Grafana 44 | 45 | Open a new console and run 46 | 47 | ```console 48 | $GRAFANA_DIR/bin/grafana-server 49 | ``` 50 | 51 | ### Launch back-end 52 | 53 | Open a new console and run 54 | ```console 55 | # ONLY ONCE 56 | cd /sql-query-clients/Grafana/back-end/ 57 | conda env create -f environment.yml 58 | 59 | # EACH TIME the back-end is run 60 | conda activate cloudsql_cos_ts 61 | python app.py 62 | ``` 63 | 64 | 65 | ### Creating a new IBM SQL Query Source 66 | 67 | The plugin is named `cloudsql-cos-ts`. There, we need to configure the IP:port of the backend webapp, and the credential required to connect to your 68 | CloudSQL service instance. 69 | 70 | 71 | ## Development 72 | 73 | Building and Installation 74 | ----------- 75 | 76 | 0. Download Grafana 7.4+ 77 | 78 | ```console 79 | wget https://dl.grafana.com/oss/release/grafana-7.4.1.linux-amd64.tar.gz 80 | tar -zxvf grafana-7.4.1.linux-amd64.tar.gz 81 | ln -s grafana grafana-7.4.1 82 | ``` 83 | 1. Edit the file 84 | 85 | ```console 86 | vi $GRAFANA_DIR/conf/custom.ini 87 | ``` 88 | and update the `app_mode` to 89 | ```console 90 | app_mode = development 91 | ``` 92 | and chosoe the location to your plugin via the `plugins` line 93 | ```console 94 | plugins = /sql-query-clients/Grafana/front-end 95 | ``` 96 | 97 | 2. Compile the plugin after changed 98 | 99 | ```console 100 | yarn dev 101 | ``` 102 | 103 | 3. Reload the web-browser for the change to plugin is updated. 104 | -------------------------------------------------------------------------------- /Grafana/back-end/.gitignore: -------------------------------------------------------------------------------- 1 | _cache_dir 2 | -------------------------------------------------------------------------------- /Grafana/back-end/environment.yml: -------------------------------------------------------------------------------- 1 | name: cloudsql_cos_ts 2 | channels: 3 | - anaconda 4 | - brian-team 5 | - conda-forge 6 | - defaults 7 | dependencies: 8 | - alabaster 9 | - appnope 10 | - babel 11 | - backcall 12 | - bottle 13 | - ca-certificates 14 | - cffi 15 | - chardet 16 | - click 17 | - commonmark 18 | - cryptography 19 | - cycler 20 | - decorator 21 | - entrypoints 22 | - flask 23 | - flask-cors 24 | - freetype 25 | - future 26 | - imagesize 27 | - ipykernel 28 | - ipython 29 | - ipython_genutils 30 | - itsdangerous 31 | - jedi 32 | - jinja2 33 | - jupyter_client 34 | - jupyter_core 35 | - kiwisolver 36 | - libblas 37 | - libcblas 38 | - libcxx 39 | - libffi 40 | - libgfortran 41 | - liblapack 42 | - libopenblas 43 | - libpng 44 | - libsodium 45 | - llvm-openmp 46 | - m2r 47 | - markupsafe 48 | - matplotlib 49 | - matplotlib-base 50 | - mistune 51 | - ncurses 52 | - openssl 53 | - packaging 54 | - pandas 55 | - parso 56 | - patsy 57 | - pexpect 58 | - pickleshare 59 | - pip 60 | - prompt-toolkit 61 | - ptyprocess 62 | - pycparser 63 | - pygments 64 | - pyopenssl 65 | - pyparsing 66 | - pysocks 67 | - python 68 | - python-dateutil 69 | - python_abi 70 | - pytz 71 | - pyzmq 72 | - readline 73 | - recommonmark 74 | - scipy 75 | - seaborn 76 | - snowballstemmer 77 | - sphinx 78 | - sphinxcontrib-applehelp 79 | - sphinxcontrib-devhelp 80 | - sphinxcontrib-htmlhelp 81 | - sphinxcontrib-jsmath 82 | - sphinxcontrib-qthelp 83 | - sphinxcontrib-serializinghtml 84 | - sqlite 85 | - statsmodels 86 | - tk 87 | - tornado 88 | - traitlets 89 | - ujson 90 | - wcwidth 91 | - werkzeug 92 | - xz 93 | - zeromq 94 | - zlib 95 | - pip: 96 | - absl-py 97 | - astunparse 98 | - attrs 99 | - backoff 100 | - cachetools 101 | - certifi 102 | - docutils 103 | - gast 104 | - gevent 105 | - google-auth 106 | - google-auth-oauthlib 107 | - google-pasta 108 | - greenlet 109 | - grpcio 110 | - h5py 111 | - ibm-cos-sdk-core 112 | - ibm-cos-sdk-s3transfer 113 | - idna 114 | - importlib-metadata 115 | - isodate 116 | - jmespath 117 | - joblib 118 | - jsonschema 119 | - keras 120 | - keras-preprocessing 121 | - markdown 122 | - more-itertools 123 | - nbformat 124 | - numpy 125 | - numpydoc 126 | - oauthlib 127 | - opt-einsum 128 | - pathlib2 129 | - pluggy 130 | - protobuf 131 | - py 132 | - pyarrow 133 | - pyasn1 134 | - pyasn1-modules 135 | - pycalver 136 | - pyrsistent 137 | - pytest 138 | - pyyaml 139 | - requests 140 | - requests-oauthlib 141 | - responses 142 | - rsa 143 | - rstcheck 144 | - scikit-learn 145 | - setuptools 146 | - six 147 | - sklearn 148 | - sql-metadata 149 | - tensorboard 150 | - tensorboard-plugin-wit 151 | - tensorflow 152 | - tensorflow-estimator 153 | - termcolor 154 | - threadpoolctl 155 | - toml 156 | - urllib3 157 | - wheel 158 | - wrapt 159 | - xlrd 160 | - zipp 161 | - zope-event 162 | - zope-interface 163 | - regex 164 | - ibm_cos_sdk 165 | -------------------------------------------------------------------------------- /Grafana/back-end/howto_docker.txt: -------------------------------------------------------------------------------- 1 | 2 | ``` 3 | conda activate cloud_cos_sql 4 | conda env export -n cloud_cos_sql| cut -f 1 -d '=' | grep -v "prefix" > requirements.txt 5 | ``` 6 | 7 | Use Dockerfile (python:3-alpine) is better as it has a much smaller footprint. Delete the `prefix` field, if present. 8 | ``` 9 | python:3-alpine 45MB 10 | 11 | ``` 12 | 13 | Build your image 14 | 15 | ``` 16 | docker build -t grafana_cloudsql_cos_ts/aiops . -f Dockerfile.stages 17 | 18 | docker build -t grafana_cloudsql_cos_ts/aiops . 19 | 20 | ``` 21 | 22 | Save the image into zip file, and copy to another machine 23 | ``` 24 | 25 | sudo docker save -o 26 | 27 | ``` 28 | Copy to local machine and load into a new image 29 | ``` 30 | scp tmhoangt@cloud_gpu:/home/tmhoangt/webapps/aiops/grafana_webapp.tar . 31 | 32 | docker load -i grafana_webapp.tar 33 | //zcat .tar.gz | docker load 34 | ``` 35 | 36 | Create a new image that can be uploaded to the private registry 37 | ``` 38 | 39 | ``` 40 | 41 | Push your Docker image into JFrog Artifactory 42 | 43 | ``` 44 | //https://docs.docker.com/engine/reference/commandline/login/#provide-a-password-using-stdin 45 | 46 | cat jfrog_api.txt | docker login -u tmhoangt@us.ibm.com res-edge-ai-team-sdk-docker-local.artifactory.swg-devops.com --password-stdin 47 | 48 | ``` 49 | 50 | Now deploy a backend app to IBM Code Engine 51 | 52 | ``` 53 | 54 | ibmcloud code-engine application create --name backend --image ibmcom/backend --cluster-local 55 | ibmcloud ce application create --name maas-app --image res-hcls-mcm-brain-docker-local.artifactory.swg-devops.com/maas/app:latest --concurrency 1 --cpu 1 --max-scale 250 --registry-secret artifactory 56 | 57 | ``` 58 | -------------------------------------------------------------------------------- /Grafana/front-end/cloudsql-cos-ts/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /Grafana/front-end/cloudsql-cos-ts/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | #### 0.4.0 (2021-04-07) 2 | 3 | ##### Release Hightlights front-end 4 | 5 | * support macros: $__timeFilter, $__timeFilter(string), $__timeFilter(col-name, [type]), $__source, $__dest ($__dest(csv), $__dest()), $__source_prev(query-refId) 6 | * returned data, in either `table` and `time-series` form, is now converted to DataFrame 7 | * adding data source location is optional at plugin instance creation time 8 | * adding data destination location is optional at plugin instance creation time 9 | * a query can be configured as an intermediate for the next queries, i.e. no returned data is needed. 10 | * syntax highlighting support in query editor 11 | 12 | ##### Other Changes/Improvements 13 | -------------------------------------------------------------------------------- /Grafana/front-end/cloudsql-cos-ts/LICENSE: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------ 2 | Copyright IBM Corp. 2020 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ------------------------------------------------------------------------------ 16 | -------------------------------------------------------------------------------- /Grafana/front-end/cloudsql-cos-ts/README.md: -------------------------------------------------------------------------------- 1 | This is the front-end part of Grafana CloudSQL-COS-TS plugin 2 | -------------------------------------------------------------------------------- /Grafana/front-end/cloudsql-cos-ts/build/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | #### 0.4.0 (2021-04-07) 2 | 3 | ##### Release Hightlights front-end 4 | 5 | * support macros: $__timeFilter, $__timeFilter(string), $__timeFilter(col-name, [type]), $__source, $__dest ($__dest(csv), $__dest()), $__source_prev(query-refId) 6 | * returned data, in either `table` and `time-series` form, is now converted to DataFrame 7 | * adding data source location is optional at plugin instance creation time 8 | * adding data destination location is optional at plugin instance creation time 9 | * a query can be configured as an intermediate for the next queries, i.e. no returned data is needed. 10 | * syntax highlighting support in query editor 11 | 12 | ##### Other Changes/Improvements 13 | -------------------------------------------------------------------------------- /Grafana/front-end/cloudsql-cos-ts/build/LICENSE: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------ 2 | Copyright IBM Corp. 2020 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ------------------------------------------------------------------------------ 16 | -------------------------------------------------------------------------------- /Grafana/front-end/cloudsql-cos-ts/build/README.md: -------------------------------------------------------------------------------- 1 | This is the front-end part of Grafana CloudSQL-COS-TS plugin 2 | -------------------------------------------------------------------------------- /Grafana/front-end/cloudsql-cos-ts/build/img/cloudsql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM-Cloud/sql-query-clients/1963cca543dccd2734aa66bdb0229c862516cfdb/Grafana/front-end/cloudsql-cos-ts/build/img/cloudsql.png -------------------------------------------------------------------------------- /Grafana/front-end/cloudsql-cos-ts/build/img/cos-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM-Cloud/sql-query-clients/1963cca543dccd2734aa66bdb0229c862516cfdb/Grafana/front-end/cloudsql-cos-ts/build/img/cos-logo.png -------------------------------------------------------------------------------- /Grafana/front-end/cloudsql-cos-ts/build/img/cos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM-Cloud/sql-query-clients/1963cca543dccd2734aa66bdb0229c862516cfdb/Grafana/front-end/cloudsql-cos-ts/build/img/cos.png -------------------------------------------------------------------------------- /Grafana/front-end/cloudsql-cos-ts/build/img/ibm-cloud-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM-Cloud/sql-query-clients/1963cca543dccd2734aa66bdb0229c862516cfdb/Grafana/front-end/cloudsql-cos-ts/build/img/ibm-cloud-logo.png -------------------------------------------------------------------------------- /Grafana/front-end/cloudsql-cos-ts/build/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM-Cloud/sql-query-clients/1963cca543dccd2734aa66bdb0229c862516cfdb/Grafana/front-end/cloudsql-cos-ts/build/img/logo.png -------------------------------------------------------------------------------- /Grafana/front-end/cloudsql-cos-ts/build/img/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Grafana/front-end/cloudsql-cos-ts/build/img/logo.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM-Cloud/sql-query-clients/1963cca543dccd2734aa66bdb0229c862516cfdb/Grafana/front-end/cloudsql-cos-ts/build/img/logo.xcf -------------------------------------------------------------------------------- /Grafana/front-end/cloudsql-cos-ts/build/module.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! ***************************************************************************** 2 | Copyright (c) Microsoft Corporation. 3 | 4 | Permission to use, copy, modify, and/or distribute this software for any 5 | purpose with or without fee is hereby granted. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 9 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 11 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 12 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 13 | PERFORMANCE OF THIS SOFTWARE. 14 | ***************************************************************************** */ 15 | -------------------------------------------------------------------------------- /Grafana/front-end/cloudsql-cos-ts/build/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "datasource", 3 | "name": "cloudsql-cos-ts", 4 | "id": "ibm-cloudsql-cos-ts-datasource", 5 | "metrics": true, 6 | "info": { 7 | "description": "TimeSeries stored in IBM COS via IBM CloudSQL", 8 | "author": { 9 | "name": "Tuan M. Hoang Trong", 10 | "url": "http://www.research.ibm.com/labs/watson/" 11 | }, 12 | "keywords": [ 13 | "timeseries", 14 | "IBM", 15 | "COS", 16 | "CloudSQL" 17 | ], 18 | "logos": { 19 | "small": "img/logo.png", 20 | "large": "img/logo.png" 21 | }, 22 | "links": [ 23 | { 24 | "name": "Website", 25 | "url": "https://github.ibm.com/Common-TimeSeries-Analytics-Library/cloud_sqlquery/tree/master/grafana-plugins/cloudsql-cos-ts" 26 | }, 27 | { 28 | "name": "License", 29 | "url": "https://github.ibm.com/Common-TimeSeries-Analytics-Library/cloud_sqlquery/tree/master/grafana-plugins/cloudsql-cos-ts/LICENSE" 30 | } 31 | ], 32 | "screenshots": [], 33 | "version": "0.0.3", 34 | "updated": "2021-04-07" 35 | }, 36 | "dependencies": { 37 | "grafanaVersion": "7.4.x", 38 | "plugins": [] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Grafana/front-end/cloudsql-cos-ts/jest.config.js: -------------------------------------------------------------------------------- 1 | // This file is needed because it is used by vscode and other tools that 2 | // call `jest` directly. However, unless you are doing anything special 3 | // do not edit this file 4 | 5 | const standard = require('@grafana/toolkit/src/config/jest.plugin.config'); 6 | 7 | // This process will use the same config that `yarn test` is using 8 | module.exports = standard.jestConfig(); 9 | -------------------------------------------------------------------------------- /Grafana/front-end/cloudsql-cos-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ibm-cloudsql-cos-ts", 3 | "version": "0.4.0", 4 | "description": "IBM CloudSQL COS TimeSeries", 5 | "scripts": { 6 | "build": "grafana-toolkit plugin:build", 7 | "test": "grafana-toolkit plugin:test", 8 | "dev": "grafana-toolkit plugin:dev", 9 | "watch": "grafana-toolkit plugin:dev --watch" 10 | }, 11 | "author": "Tuan M. Hoang Trong", 12 | "license": "SEE LICENSE IN copyright.txt", 13 | "devDependencies": { 14 | "@babel/compat-data": "^7.12.7", 15 | "@babel/core": "7.0.0", 16 | "@babel/preset-env": "7.0.0", 17 | "@grafana/data": "next", 18 | "@grafana/toolkit": "next", 19 | "@grafana/ui": "next", 20 | "@popperjs/core": "^2.5.4", 21 | "@types/angular": "1.6.56", 22 | "@types/angular-route": "1.7.0", 23 | "@types/classnames": "^2.2.11", 24 | "@types/lodash": "latest", 25 | "@types/slate": "0.47.1", 26 | "@types/slate-plain-serializer": "0.6.1", 27 | "@types/slate-react": "0.22.5", 28 | "@typescript-eslint/eslint-plugin": "^3.0.0", 29 | "@typescript-eslint/parser": "^3.0.0", 30 | "babel-loader": "8.0.6", 31 | "bufferutil": "^4.0.2", 32 | "canvas": "^2.6.1", 33 | "eslint": "6.8.0", 34 | "eslint-config-prettier": "^6.10.0", 35 | "eslint-loader": "^3.0.3", 36 | "eslint-plugin-jsdoc": "21.0.0", 37 | "eslint-plugin-prettier": "3.1.2", 38 | "eslint-plugin-react": "7.18.3", 39 | "fibers": "^5.0.0", 40 | "immutable": "3.8.1", 41 | "jest": "^26.6.3", 42 | "material-ui": "next", 43 | "prettier": "1.19.1", 44 | "react": "16.12.0", 45 | "react-dom": "16.12.0", 46 | "react-grid-layout": "0.17.1", 47 | "react-highlight-words": "0.16.0", 48 | "react-loadable": "5.5.0", 49 | "react-popper": "^2.2.4", 50 | "react-redux": "7.2.0", 51 | "react-sizeme": "2.6.12", 52 | "react-split-pane": "0.1.89", 53 | "react-transition-group": "4.3.0", 54 | "react-use": "13.27.0", 55 | "react-virtualized-auto-sizer": "1.0.2", 56 | "react-window": "1.8.5", 57 | "redux": "4.0.5", 58 | "redux-logger": "3.0.6", 59 | "redux-thunk": "2.3.0", 60 | "sass": "^1.29.0", 61 | "serialize-javascript": "^5.0.1", 62 | "slate": "0.47.0", 63 | "slate-react": "0.22.0", 64 | "ts-jest": "^26.4.4", 65 | "ts-loader": "^7.0.0", 66 | "ts-node": "^9.0.0", 67 | "typescript": "^3.7.5", 68 | "utf-8-validate": "^5.0.3", 69 | "webpack": "4.41.5", 70 | "webpack-bundle-analyzer": "3.6.0", 71 | "webpack-cleanup-plugin": "0.5.1", 72 | "webpack-cli": "3.3.10", 73 | "webpack-dev-server": "^3.11.0", 74 | "webpack-merge": "4.2.2" 75 | }, 76 | "resolutions": { 77 | "@babel/preset-env": "7.5.5", 78 | "serialize-javascript": "3.1.0" 79 | }, 80 | "engines": { 81 | "node": ">=10 <13" 82 | }, 83 | "dependencies": { 84 | "@grafana/runtime": "^7.3.4", 85 | "@grafana/slate-react": "^0.22.9-grafana", 86 | "@material-ui/core": "^4.11.1", 87 | "@types/prismjs": "^1.16.2", 88 | "classnames": "^2.2.6", 89 | "common-tags": "^1.8.0", 90 | "prismjs": "^1.22.0", 91 | "react-simple-code-editor": "^0.11.0" 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Grafana/front-end/cloudsql-cos-ts/src/CheatSheet.tsx: -------------------------------------------------------------------------------- 1 | //# ------------------------------------------------------------------------------ 2 | //# Copyright IBM Corp. 2020 3 | //# 4 | //# Licensed under the Apache License, Version 2.0 (the "License"); 5 | //# you may not use this file except in compliance with the License. 6 | //# You may obtain a copy of the License at 7 | //# 8 | //# http://www.apache.org/licenses/LICENSE-2.0 9 | //# 10 | //# Unless required by applicable law or agreed to in writing, software 11 | //# distributed under the License is distributed on an "AS IS" BASIS, 12 | //# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | //# See the License for the specific language governing permissions and 14 | //# limitations under the License. 15 | //# ------------------------------------------------------------------------------ 16 | import React from "react"; 17 | import { ExploreStartPageProps, DataQuery } from "@grafana/data"; 18 | import { stripIndent, stripIndents } from "common-tags"; 19 | import { css, cx } from "emotion"; 20 | 21 | interface QueryExampleCat { 22 | category: string; 23 | examples: Array<{ 24 | title: string; 25 | queryText: string; 26 | format?: string; 27 | time_column?: string; 28 | metrics_column?: string; 29 | }>; 30 | } 31 | //interface QueryExample { 32 | // title: string; 33 | // label: string; 34 | // expression?: string; 35 | //} 36 | 37 | const SQLQUERY_EXAMPLES: QueryExampleCat[] = [ 38 | { 39 | category: "Time-Series", 40 | examples: [ 41 | { 42 | title: 43 | "Return single time-series [replace COS_IN_URL, update format, and column names (field_name, time_stamp, observation) to match those in your data]", 44 | format: "time_series", 45 | time_column: "tt", 46 | queryText: stripIndents`WITH container_ts_table AS 47 | (SELECT field_name, 48 | ts 49 | FROM STORED AS PARQUET USING TIME_SERIES_FORMAT(KEY="field_name", timetick="time_stamp", value="observation") IN ts), 50 | tmp_table AS 51 | ( 52 | SELECT field_name AS user_agent, 53 | ts_explode(ts_seg_sum(TS_SEGMENT_BY_TIME(ts, 604800000, 604800000))) AS (tt, 54 | value) 55 | FROM container_ts_table) 56 | SELECT tt, log(value) as value 57 | FROM tmp_table 58 | WHERE user_agent LIKE " STORED AS PARQUET`, 60 | }, 61 | { 62 | title: 63 | "Return multiple time-series [replace COS_IN_URL, update format, and column names (field_name, time_stamp, observation) to match those in your data]", 64 | format: "time_series", 65 | time_column: "tt", 66 | metrics_column: "user_agent", 67 | queryText: stripIndents`WITH container_ts_table AS 68 | (SELECT field_name, 69 | ts 70 | FROM STORED AS PARQUET USING TIME_SERIES_FORMAT(KEY="field_name", timetick="time_stamp", value="observation") IN ts), 71 | tmp_table AS 72 | ( 73 | SELECT field_name AS user_agent, 74 | ts_explode(ts_seg_sum(TS_SEGMENT_BY_TIME(ts, 604800000, 604800000))) AS (tt, 75 | value) 76 | FROM container_ts_table) 77 | SELECT tt, log(value) as value 78 | FROM tmp_table 79 | INTO STORED AS PARQUET`, 80 | }, 81 | ], 82 | }, 83 | { 84 | category: "Table", 85 | examples: [ 86 | { 87 | title: "Return a table-form data", 88 | format: "table", 89 | queryText: stripIndents`WITH container_ts_table AS 90 | (SELECT field_name, ts 91 | FROM STORED AS PARQUET USING TIME_SERIES_FORMAT(KEY="field_name", timetick="time_stamp", value="observation") IN ts) 92 | SELECT field_name AS user_agent, 93 | ts_explode(ts_seg_sum(TS_SEGMENT_BY_TIME(ts, 604800000, 604800000))) AS (tt, value) 94 | FROM container_ts_table INTO STORED AS PARQUET`, 95 | }, 96 | ], 97 | }, 98 | ]; 99 | 100 | function renderHighlightedMarkup(code: string, keyPrefix: string) { 101 | const spans = code; 102 | return
{spans}
; 103 | } 104 | 105 | //https://github.com/grafana/grafana/blob/376a9d35e4da9cd21040d55d6b2280f102b38a4e/public/sass/pages/_explore.scss 106 | const exampleCategory = css` 107 | margin-top: 5px; 108 | `; 109 | 110 | const cheatsheetitem = css` 111 | margin: $space-lg 0; 112 | `; 113 | 114 | const cheatsheetitem__title = css` 115 | font-size: $font-size-h3; 116 | `; 117 | 118 | const cheatsheetitem__example = css` 119 | margin: $space-xs 0; 120 | cursor: pointer; 121 | `; 122 | 123 | export default class CheatSheet extends React.PureComponent< 124 | ExploreStartPageProps, 125 | { userExamples: string[] } 126 | > { 127 | renderExpression( 128 | expr: string, 129 | keyPrefix: string, 130 | format: string, 131 | time_column: string, 132 | metrics_column: string 133 | ) { 134 | const { onClickExample } = this.props; 135 | 136 | return ( 137 |
142 | onClickExample({ 143 | refId: "A", 144 | queryText: expr, 145 | format: format, 146 | time_column: time_column, 147 | metrics_column: metrics_column, 148 | } as DataQuery) 149 | } 150 | > 151 |
{renderHighlightedMarkup(expr, keyPrefix)}
152 |
153 | ); 154 | } 155 | render() { 156 | return ( 157 |
158 |

CloudSQL Cheat Sheet

159 | {SQLQUERY_EXAMPLES.map((cat, j) => ( 160 |
161 |
162 | {cat.category} 163 |
164 | {cat.examples.map((item, i) => ( 165 |
166 |

{item.title}

167 | {this.renderExpression( 168 | item.queryText, 169 | `item-${i}`, 170 | item.format | "", 171 | item.time_column | "", 172 | item.metrics_column | "" 173 | )} 174 |
175 | ))} 176 |
177 | ))} 178 |
179 | ); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /Grafana/front-end/cloudsql-cos-ts/src/ConfigEditor.tsx: -------------------------------------------------------------------------------- 1 | //# ------------------------------------------------------------------------------ 2 | //# Copyright IBM Corp. 2020 3 | //# 4 | //# Licensed under the Apache License, Version 2.0 (the "License"); 5 | //# you may not use this file except in compliance with the License. 6 | //# You may obtain a copy of the License at 7 | //# 8 | //# http://www.apache.org/licenses/LICENSE-2.0 9 | //# 10 | //# Unless required by applicable law or agreed to in writing, software 11 | //# distributed under the License is distributed on an "AS IS" BASIS, 12 | //# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | //# See the License for the specific language governing permissions and 14 | //# limitations under the License. 15 | //# ------------------------------------------------------------------------------ 16 | import React, { PureComponent } from "react"; 17 | import { DataSourceHttpSettings } from "@grafana/ui"; 18 | import { DataSourcePluginOptionsEditorProps } from "@grafana/data"; 19 | import { CloudSQLSettings } from "./configuration/CloudSQLSettings"; 20 | import { COSIBMDataSourceOptions } from "./types"; 21 | //import { COSIBMDataSourceOptions, COSIBMSecureJsonData } from './types'; 22 | 23 | export type Props = DataSourcePluginOptionsEditorProps; 24 | //export type Props = DataSourcePluginOptionsEditorProps; 25 | 26 | export class ConfigEditor extends PureComponent { 27 | render() { 28 | // a props is split into 2 parts: the data part, and the callback part 29 | const { options, onOptionsChange } = this.props; 30 | 31 | return ( 32 | <> 33 | 40 | 43 | onOptionsChange({ ...options, jsonData: newValue }) 44 | } 45 | /> 46 | 47 | ); 48 | } 49 | } 50 | export default ConfigEditor; 51 | -------------------------------------------------------------------------------- /Grafana/front-end/cloudsql-cos-ts/src/QueryEditor.tsx: -------------------------------------------------------------------------------- 1 | //# ------------------------------------------------------------------------------ 2 | //# Copyright IBM Corp. 2020 3 | //# 4 | //# Licensed under the Apache License, Version 2.0 (the "License"); 5 | //# you may not use this file except in compliance with the License. 6 | //# You may obtain a copy of the License at 7 | //# 8 | //# http://www.apache.org/licenses/LICENSE-2.0 9 | //# 10 | //# Unless required by applicable law or agreed to in writing, software 11 | //# distributed under the License is distributed on an "AS IS" BASIS, 12 | //# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | //# See the License for the specific language governing permissions and 14 | //# limitations under the License. 15 | //# ------------------------------------------------------------------------------ 16 | import defaults from "lodash/defaults"; 17 | 18 | import React, { PureComponent, ChangeEvent } from "react"; 19 | import { LegacyForms } from "@grafana/ui"; 20 | //import { Checkbox, LegacyForms } from '@grafana/ui'; 21 | //import { LegacyForms, QueryField } from '@grafana/ui'; 22 | import { QueryEditorProps, SelectableValue, HistoryItem } from "@grafana/data"; 23 | import { COSIBMDataSource } from "./DataSource"; 24 | import { 25 | FORMAT_OPTIONS, 26 | CloudSQLQuery, 27 | COSIBMDataSourceOptions, 28 | } from "./types"; 29 | import { CloudSQLHelp } from "./sql/help"; 30 | 31 | const { Select, FormField, Switch } = LegacyForms; 32 | 33 | type Props = QueryEditorProps< 34 | COSIBMDataSource, 35 | CloudSQLQuery, 36 | COSIBMDataSourceOptions 37 | >; 38 | //NOTE: .datasource: COSIBMDataSource 39 | // . .query: CloudSQLQuery 40 | // . (some others .data: PanelData, .exploreMode: ExploreMode, .exploreId?: any, history?: HistoryItem[]) 41 | import { CloudSQLQueryField } from "./configuration/QueryFields"; 42 | 43 | export interface State { 44 | //default legend formatting 45 | legendFormat: string; 46 | formatOption: SelectableValue; 47 | interval: string; 48 | intervalFactorOption: SelectableValue; 49 | instant: boolean; 50 | showingHelp: boolean; 51 | } 52 | 53 | export class QueryEditor extends PureComponent { 54 | onComponentDidMount() {} 55 | constructor(props: Props) { 56 | super(props); 57 | const { query, onChange } = props; 58 | if (query.get_result === undefined) { 59 | onChange({ ...query, get_result: true, showingHelp: false }); 60 | } 61 | } 62 | 63 | //onQueryTextChange = (event: ChangeEvent) => { 64 | // const { onChange, query } = this.props; 65 | // onChange({ ...query, queryText: event.target.value }); 66 | //}; 67 | onQueryTextChange = (value: string, override?: boolean) => { 68 | const { query, onChange, onRunQuery } = this.props; 69 | 70 | if (onChange) { 71 | // Update the query whenever the query field changes. 72 | onChange({ ...query, queryText: value }); 73 | 74 | // Run the query on Enter. 75 | if (override && onRunQuery) { 76 | onRunQuery(); 77 | } 78 | } 79 | }; 80 | 81 | render() { 82 | const query = defaults(this.props.query, ""); 83 | const ds = this.props.datasource; 84 | query.name = ds.name; 85 | query.id = ds.id; 86 | const props = this.props; 87 | //const { queryText, constant } = query; 88 | //const { constant } = query; 89 | //props.history = []; 90 | const no_history: Array> = []; 91 | 92 | const onTimeColumnChange = (event: ChangeEvent) => { 93 | //const { onChange, query, onRunQuery } = this.props; 94 | const { onChange, query } = this.props; 95 | onChange({ ...query, time_column: event.target.value }); 96 | //onRunQuery(); // executes the query 97 | }; 98 | const onMetricsColumnChange = (event: ChangeEvent) => { 99 | //const { onChange, query, onRunQuery } = props; 100 | const { onChange, query } = this.props; 101 | onChange({ ...query, metrics_column: event.target.value }); 102 | //onRunQuery(); // executes the query 103 | }; 104 | const onFormatChange = (option: SelectableValue) => { 105 | const { query, onChange, onRunQuery } = this.props; 106 | query.format = option.value!; 107 | //this.setState({ formatOption: option }, this.onRunQuery); 108 | if (onChange) { 109 | const nextQuery = { 110 | ...query, 111 | format: option.value!, 112 | }; 113 | onChange(nextQuery); 114 | // // Update the query whenever the query field changes. 115 | // onChange({ ...query, queryText: value }); 116 | // 117 | onRunQuery(); 118 | } 119 | }; 120 | 121 | return ( 122 | <> 123 |
124 |
125 | 135 | 145 |
146 |
147 |
Format
148 | o.value === value.format_type 173 | )} 174 | defaultValue={value.format_type} 175 | onChange={(option) => { 176 | option_changeHandler("format_type", option); 177 | }} 178 | /> 179 |
180 | )} 181 | {usingTable && ( 182 |
183 | 191 |
192 | )} 193 |
194 | 195 | 196 | ); 197 | }; 198 | -------------------------------------------------------------------------------- /Grafana/front-end/cloudsql-cos-ts/src/configuration/Dialog.tsx: -------------------------------------------------------------------------------- 1 | //# ------------------------------------------------------------------------------ 2 | //# Copyright IBM Corp. 2020 3 | //# 4 | //# Licensed under the Apache License, Version 2.0 (the "License"); 5 | //# you may not use this file except in compliance with the License. 6 | //# You may obtain a copy of the License at 7 | //# 8 | //# http://www.apache.org/licenses/LICENSE-2.0 9 | //# 10 | //# Unless required by applicable law or agreed to in writing, software 11 | //# distributed under the License is distributed on an "AS IS" BASIS, 12 | //# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | //# See the License for the specific language governing permissions and 14 | //# limitations under the License. 15 | //# ------------------------------------------------------------------------------ 16 | import React from "react"; 17 | import { 18 | Button, 19 | Dialog, 20 | DialogTitle, 21 | DialogContent, 22 | DialogContentText, 23 | DialogActions, 24 | } from "@material-ui/core"; 25 | import { DataSourceInstanceSettings } from "@grafana/data"; 26 | 27 | import { COSIBMDataSourceOptions } from "../types"; 28 | 29 | export function AlertDialog( 30 | props: DataSourceInstanceSettings 31 | ) { 32 | const [open, setOpen] = React.useState(false); 33 | 34 | const handleClickOpen = () => { 35 | setOpen(true); 36 | }; 37 | 38 | const handleClose = () => { 39 | setOpen(false); 40 | }; 41 | 42 | return ( 43 | <> 44 |
45 | 48 | 54 | 55 | {"Datasource setting info"} 56 | 57 | 58 | 59 | URL {props.url} API Key {props.jsonData.apiKey} 60 | CRN {props.jsonData.instance_crn} 61 | 62 | 63 | 64 | 67 | 70 | 71 | 72 |
73 | 74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /Grafana/front-end/cloudsql-cos-ts/src/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM-Cloud/sql-query-clients/1963cca543dccd2734aa66bdb0229c862516cfdb/Grafana/front-end/cloudsql-cos-ts/src/img/logo.png -------------------------------------------------------------------------------- /Grafana/front-end/cloudsql-cos-ts/src/module.ts: -------------------------------------------------------------------------------- 1 | //# ------------------------------------------------------------------------------ 2 | //# Copyright IBM Corp. 2020 3 | //# 4 | //# Licensed under the Apache License, Version 2.0 (the "License"); 5 | //# you may not use this file except in compliance with the License. 6 | //# You may obtain a copy of the License at 7 | //# 8 | //# http://www.apache.org/licenses/LICENSE-2.0 9 | //# 10 | //# Unless required by applicable law or agreed to in writing, software 11 | //# distributed under the License is distributed on an "AS IS" BASIS, 12 | //# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | //# See the License for the specific language governing permissions and 14 | //# limitations under the License. 15 | //# ------------------------------------------------------------------------------ 16 | import { DataSourcePlugin } from "@grafana/data"; 17 | import { COSIBMDataSource } from "./DataSource"; 18 | import { ConfigEditor } from "./ConfigEditor"; 19 | import { QueryEditor } from "./QueryEditor"; 20 | import CloudSQLCheatSheet from "./CheatSheet"; 21 | import CloudSQLExploreQueryEditor from "./ExploreQueryEditor"; 22 | import { CloudSQLQuery, COSIBMDataSourceOptions } from "./types"; 23 | 24 | export const plugin = new DataSourcePlugin< 25 | COSIBMDataSource, 26 | CloudSQLQuery, 27 | COSIBMDataSourceOptions 28 | >(COSIBMDataSource) 29 | .setConfigEditor(ConfigEditor) 30 | //.setExploreMetricsQueryField(CloudSQLExploreQueryEditor) 31 | .setExploreQueryField(CloudSQLExploreQueryEditor) 32 | //.setExploreMetricsQueryField(CloudSQLExploreQueryEditor) 33 | //.setExploreLogsQueryField(CloudSQLExploreQueryEditor) 34 | .setExploreStartPage(CloudSQLCheatSheet) 35 | .setQueryEditor(QueryEditor); 36 | -------------------------------------------------------------------------------- /Grafana/front-end/cloudsql-cos-ts/src/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "datasource", 3 | "name": "cloudsql-cos-ts", 4 | "id": "ibm-cloudsql-cos-ts-datasource", 5 | "metrics": true, 6 | "info": { 7 | "description": "TimeSeries stored in IBM COS via IBM CloudSQL", 8 | "author": { 9 | "name": "Tuan M. Hoang Trong", 10 | "url": "http://www.research.ibm.com/labs/watson/" 11 | }, 12 | "keywords": [ 13 | "timeseries", 14 | "IBM", 15 | "COS", 16 | "CloudSQL" 17 | ], 18 | "logos": { 19 | "small": "img/logo.png", 20 | "large": "img/logo.png" 21 | }, 22 | "links": [ 23 | { 24 | "name": "Website", 25 | "url": "https://github.ibm.com/Common-TimeSeries-Analytics-Library/cloud_sqlquery/tree/master/grafana-plugins/cloudsql-cos-ts" 26 | }, 27 | { 28 | "name": "License", 29 | "url": "https://github.ibm.com/Common-TimeSeries-Analytics-Library/cloud_sqlquery/tree/master/grafana-plugins/cloudsql-cos-ts/LICENSE" 30 | } 31 | ], 32 | "screenshots": [], 33 | "version": "0.0.3", 34 | "updated": "%TODAY%" 35 | }, 36 | "dependencies": { 37 | "grafanaVersion": "7.4.x", 38 | "plugins": [] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Grafana/front-end/cloudsql-cos-ts/src/sql/add_label_to_query.ts: -------------------------------------------------------------------------------- 1 | //# ------------------------------------------------------------------------------ 2 | //# Copyright IBM Corp. 2020 3 | //# 4 | //# Licensed under the Apache License, Version 2.0 (the "License"); 5 | //# you may not use this file except in compliance with the License. 6 | //# You may obtain a copy of the License at 7 | //# 8 | //# http://www.apache.org/licenses/LICENSE-2.0 9 | //# 10 | //# Unless required by applicable law or agreed to in writing, software 11 | //# distributed under the License is distributed on an "AS IS" BASIS, 12 | //# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | //# See the License for the specific language governing permissions and 14 | //# limitations under the License. 15 | //# ------------------------------------------------------------------------------ 16 | import _ from "lodash"; 17 | 18 | // TODO TUAN 19 | const keywords = 20 | "by|without|on|ignoring|group_left|group_right|bool|or|and|unless"; 21 | 22 | // Duplicate from mode-prometheus.js, which can't be used in tests due to global ace not being loaded. 23 | const builtInWords = [ 24 | keywords, 25 | "count|count_values|min|max|avg|sum|stddev|stdvar|bottomk|topk|quantile", 26 | "true|false|null|__name__|job", 27 | "abs|absent|ceil|changes|clamp_max|clamp_min|count_scalar|day_of_month|day_of_week|days_in_month|delta|deriv", 28 | "drop_common_labels|exp|floor|histogram_quantile|holt_winters|hour|idelta|increase|irate|label_replace|ln|log2", 29 | "log10|minute|month|predict_linear|rate|resets|round|scalar|sort|sort_desc|sqrt|time|vector|year|avg_over_time", 30 | "min_over_time|max_over_time|sum_over_time|count_over_time|quantile_over_time|stddev_over_time|stdvar_over_time", 31 | ] 32 | .join("|") 33 | .split("|"); 34 | 35 | const metricNameRegexp = /([A-Za-z:][\w:]*)\b(?![\(\]{=!",])/g; 36 | const selectorRegexp = /{([^{]*)}/g; 37 | 38 | export function addLabelToQuery( 39 | query: string, 40 | key: string, 41 | value: string, 42 | operator?: string, 43 | hasNoMetrics?: boolean 44 | ): string { 45 | if (!key || !value) { 46 | throw new Error("Need label to add to query."); 47 | } 48 | 49 | // Add empty selectors to bare metric names 50 | let previousWord: string; 51 | query = query.replace(metricNameRegexp, (match, word, offset) => { 52 | const insideSelector = isPositionInsideChars(query, offset, "{", "}"); 53 | // Handle "sum by (key) (metric)" 54 | const previousWordIsKeyWord = 55 | previousWord && keywords.split("|").indexOf(previousWord) > -1; 56 | 57 | // check for colon as as "word boundary" symbol 58 | const isColonBounded = word.endsWith(":"); 59 | 60 | previousWord = word; 61 | 62 | if ( 63 | !hasNoMetrics && 64 | !insideSelector && 65 | !isColonBounded && 66 | !previousWordIsKeyWord && 67 | builtInWords.indexOf(word) === -1 68 | ) { 69 | return `${word}{}`; 70 | } 71 | return word; 72 | }); 73 | 74 | // Adding label to existing selectors 75 | let match = selectorRegexp.exec(query); 76 | const parts: any[] = []; 77 | let lastIndex = 0; 78 | let suffix = ""; 79 | 80 | while (match) { 81 | const prefix = query.slice(lastIndex, match.index); 82 | const selector = match[1]; 83 | const selectorWithLabel = addLabelToSelector( 84 | selector, 85 | key, 86 | value, 87 | operator 88 | ); 89 | lastIndex = match.index + match[1].length + 2; 90 | suffix = query.slice(match.index + match[0].length); 91 | parts.push(prefix, selectorWithLabel); 92 | match = selectorRegexp.exec(query); 93 | } 94 | 95 | parts.push(suffix); 96 | return parts.join(""); 97 | } 98 | 99 | const labelRegexp = /(\w+)\s*(=|!=|=~|!~)\s*("[^"]*")/g; 100 | 101 | export function addLabelToSelector( 102 | selector: string, 103 | labelKey: string, 104 | labelValue: string, 105 | labelOperator?: string 106 | ) { 107 | const parsedLabels: any[] = []; 108 | 109 | // Split selector into labels 110 | if (selector) { 111 | let match = labelRegexp.exec(selector); 112 | while (match) { 113 | parsedLabels.push({ key: match[1], operator: match[2], value: match[3] }); 114 | match = labelRegexp.exec(selector); 115 | } 116 | } 117 | 118 | // Add new label 119 | const operatorForLabelKey = labelOperator || "="; 120 | parsedLabels.push({ 121 | key: labelKey, 122 | operator: operatorForLabelKey, 123 | value: `"${labelValue}"`, 124 | }); 125 | 126 | // Sort labels by key and put them together 127 | const formatted = _.chain(parsedLabels) 128 | .uniqWith(_.isEqual) 129 | .compact() 130 | .sortBy("key") 131 | .map(({ key, operator, value }) => `${key}${operator}${value}`) 132 | .value() 133 | .join(","); 134 | 135 | return `{${formatted}}`; 136 | } 137 | 138 | function isPositionInsideChars( 139 | text: string, 140 | position: number, 141 | openChar: string, 142 | closeChar: string 143 | ) { 144 | const nextSelectorStart = text.slice(position).indexOf(openChar); 145 | const nextSelectorEnd = text.slice(position).indexOf(closeChar); 146 | return ( 147 | nextSelectorEnd > -1 && 148 | (nextSelectorStart === -1 || nextSelectorStart > nextSelectorEnd) 149 | ); 150 | } 151 | 152 | export default addLabelToQuery; 153 | -------------------------------------------------------------------------------- /Grafana/front-end/cloudsql-cos-ts/src/sql/help.tsx: -------------------------------------------------------------------------------- 1 | //# ------------------------------------------------------------------------------ 2 | //# Copyright IBM Corp. 2020 3 | //# 4 | //# Licensed under the Apache License, Version 2.0 (the "License"); 5 | //# you may not use this file except in compliance with the License. 6 | //# You may obtain a copy of the License at 7 | //# 8 | //# http://www.apache.org/licenses/LICENSE-2.0 9 | //# 10 | //# Unless required by applicable law or agreed to in writing, software 11 | //# distributed under the License is distributed on an "AS IS" BASIS, 12 | //# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | //# See the License for the specific language governing permissions and 14 | //# limitations under the License. 15 | //# ------------------------------------------------------------------------------ 16 | /* eslint-disable */ 17 | import React, { PureComponent } from 'react'; 18 | //import { readFileSync } from 'fs'; 19 | 20 | // tslint:disable-block 21 | /* tslint:disable */ 22 | export class CloudSQLHelp extends PureComponent { 23 | render() { 24 | //const data = readFileSync('./help_sql.txt', { encoding: 'utf-8', flag: 'r' }); 25 | //var myTxt = require('./help_sql.txt'); 26 | return ( 27 | <> 28 |
29 |
30 | {/*
{data}
*/} 31 |
32 | Time series
33 | 
34 | 35 | - return column named time (in UTC), as a unix time stamp or any sql native date data type. You can use the macros below. 36 |
37 | - any other columns returned will be the time point values. 38 |
39 | Optional: 40 |
41 | - return column named metric to represent the series name. 42 |
43 | - If multiple value columns are returned the metric column is used as prefix. 44 |
45 | - If no column named metric is found the column name of the value column is used as series name 46 |
47 | 48 | Resultsets of time series queries need to be sorted by time. 49 |
50 |
51 |
52 | Table: 53 |
54 | - return any set of columns 55 |
56 | 57 |
58 | Variables: 59 |
60 | - ${variable_name} - > in the dashboard's setting, you select 'Variables' and create a new one with the name `variable_name` (with values can be user-input or retrieved as a result of a CloudSQL Query), then you can reference to the selected value in the query using the above syntax: dollar sign, open curly brance, name and closing curly brace. 61 |
62 | NOTE: For string-value, put that into the double-quote, i.e. "${variable_name}" 63 |
64 | 65 |
66 | Macros: 67 |
68 | - $__source -> the datasource as provided in DataSource setting 69 |
70 | - $__source_test -> the fake time-series datasource, e.g. $__source_test(TS) or $__source_test(MTS) 71 |
72 | - $__dest[(format [,suffix])] -> the location to store data as provided in DataSource setting, e.g. $__dest, $__dest(), $__dest(csv), $__dest(CSV), $__dest(parquet, a/b/c) 73 |
74 | Example you want to save queried data to the TARGET_COS_URL with suffix 'a/b/c' in the format 'PARQUET: 75 |
76 | SELECT * FROM $__source INTO $__dest(parquet, a/b/c) 77 |
78 |
79 | - $__source_prev -> reference to the output from a previous query in the same dashboard/panel, e.g. $__source_prev(A) 80 |
81 | - $__timeFilter() -> time_column BETWEEN '2017-04-21T05:01:17Z' AND '2017-04-21T05:01:17Z' 82 | - $__timeFilterColumn(column-name, [type]) -> add time filter using the given column name (1st argument), and its type (2nd argument) 83 |
84 | 85 |
86 | Cloud SQL: 87 |
88 | DISTRIBUTE BY, SORT BY and CLUSTER BY only have an effect during your SQL query execution and do not influence the query result written back to Cloud Object Storage. Use these clauses only in execution of subqueries in order to optimize the outer query execution that works on the intermediate result sets produced by the sub queries. 89 |
90 |
91 |
92 |
93 | 94 | ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Grafana/front-end/cloudsql-cos-ts/src/sql/language_utils.ts: -------------------------------------------------------------------------------- 1 | //# ------------------------------------------------------------------------------ 2 | //# Copyright IBM Corp. 2020 3 | //# 4 | //# Licensed under the Apache License, Version 2.0 (the "License"); 5 | //# you may not use this file except in compliance with the License. 6 | //# You may obtain a copy of the License at 7 | //# 8 | //# http://www.apache.org/licenses/LICENSE-2.0 9 | //# 10 | //# Unless required by applicable law or agreed to in writing, software 11 | //# distributed under the License is distributed on an "AS IS" BASIS, 12 | //# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | //# See the License for the specific language governing permissions and 14 | //# limitations under the License. 15 | //# ------------------------------------------------------------------------------ 16 | import { CloudSQLMetricsMetadata } from "../types"; 17 | import { addLabelToQuery } from "./add_label_to_query"; 18 | 19 | export const RATE_RANGES = ["1m", "5m", "10m", "30m", "1h"]; 20 | 21 | export const processHistogramLabels = (labels: string[]) => { 22 | const result: string[] = []; 23 | const regexp = new RegExp("_bucket($|:)"); 24 | for (let index = 0; index < labels.length; index++) { 25 | const label = labels[index]; 26 | const isHistogramValue = regexp.test(label); 27 | if (isHistogramValue) { 28 | if (result.indexOf(label) === -1) { 29 | result.push(label); 30 | } 31 | } 32 | } 33 | 34 | return { values: { __name__: result } }; 35 | }; 36 | 37 | export function processLabels( 38 | labels: Array<{ [key: string]: string }>, 39 | withName = false 40 | ) { 41 | const values: { [key: string]: string[] } = {}; 42 | labels.forEach((l) => { 43 | const { __name__, ...rest } = l; 44 | if (withName) { 45 | values["__name__"] = values["__name__"] || []; 46 | if (!values["__name__"].includes(__name__)) { 47 | values["__name__"].push(__name__); 48 | } 49 | } 50 | 51 | Object.keys(rest).forEach((key) => { 52 | if (!values[key]) { 53 | values[key] = []; 54 | } 55 | if (!values[key].includes(rest[key])) { 56 | values[key].push(rest[key]); 57 | } 58 | }); 59 | }); 60 | return { values, keys: Object.keys(values) }; 61 | } 62 | 63 | // const cleanSelectorRegexp = /\{(\w+="[^"\n]*?")(,\w+="[^"\n]*?")*\}/; 64 | export const selectorRegexp = /\{[^}]*?\}/; 65 | export const labelRegexp = /\b(\w+)(!?=~?)("[^"\n]*?")/g; 66 | export function parseSelector( 67 | query: string, 68 | cursorOffset = 1 69 | ): { labelKeys: any[]; selector: string } { 70 | if (!query.match(selectorRegexp)) { 71 | // Special matcher for metrics 72 | if (query.match(/^[A-Za-z:][\w:]*$/)) { 73 | return { 74 | selector: `{__name__="${query}"}`, 75 | labelKeys: ["__name__"], 76 | }; 77 | } 78 | throw new Error("Query must contain a selector: " + query); 79 | } 80 | 81 | // Check if inside a selector 82 | const prefix = query.slice(0, cursorOffset); 83 | const prefixOpen = prefix.lastIndexOf("{"); 84 | const prefixClose = prefix.lastIndexOf("}"); 85 | if (prefixOpen === -1) { 86 | throw new Error("Not inside selector, missing open brace: " + prefix); 87 | } 88 | if (prefixClose > -1 && prefixClose > prefixOpen) { 89 | throw new Error( 90 | "Not inside selector, previous selector already closed: " + prefix 91 | ); 92 | } 93 | const suffix = query.slice(cursorOffset); 94 | const suffixCloseIndex = suffix.indexOf("}"); 95 | const suffixClose = suffixCloseIndex + cursorOffset; 96 | const suffixOpenIndex = suffix.indexOf("{"); 97 | const suffixOpen = suffixOpenIndex + cursorOffset; 98 | if (suffixClose === -1) { 99 | throw new Error( 100 | "Not inside selector, missing closing brace in suffix: " + suffix 101 | ); 102 | } 103 | if (suffixOpenIndex > -1 && suffixOpen < suffixClose) { 104 | throw new Error( 105 | "Not inside selector, next selector opens before this one closed: " + 106 | suffix 107 | ); 108 | } 109 | 110 | // Extract clean labels to form clean selector, incomplete labels are dropped 111 | const selector = query.slice(prefixOpen, suffixClose); 112 | const labels: { [key: string]: { value: string; operator: string } } = {}; 113 | selector.replace(labelRegexp, (label, key, operator, value) => { 114 | const labelOffset = query.indexOf(label); 115 | const valueStart = labelOffset + key.length + operator.length + 1; 116 | const valueEnd = 117 | labelOffset + key.length + operator.length + value.length - 1; 118 | // Skip label if cursor is in value 119 | if (cursorOffset < valueStart || cursorOffset > valueEnd) { 120 | labels[key] = { value, operator }; 121 | } 122 | return ""; 123 | }); 124 | 125 | // Add metric if there is one before the selector 126 | const metricPrefix = query.slice(0, prefixOpen); 127 | const metricMatch = metricPrefix.match(/[A-Za-z:][\w:]*$/); 128 | if (metricMatch) { 129 | labels["__name__"] = { value: `"${metricMatch[0]}"`, operator: "=" }; 130 | } 131 | 132 | // Build sorted selector 133 | const labelKeys = Object.keys(labels).sort(); 134 | const cleanSelector = labelKeys 135 | .map((key) => `${key}${labels[key].operator}${labels[key].value}`) 136 | .join(","); 137 | 138 | const selectorString = ["{", cleanSelector, "}"].join(""); 139 | 140 | return { labelKeys, selector: selectorString }; 141 | } 142 | 143 | export function expandRecordingRules( 144 | query: string, 145 | mapping: { [name: string]: string } 146 | ): string { 147 | const ruleNames = Object.keys(mapping); 148 | const rulesRegex = new RegExp( 149 | `(\\s|^)(${ruleNames.join("|")})(\\s|$|\\(|\\[|\\{)`, 150 | "ig" 151 | ); 152 | const expandedQuery = query.replace( 153 | rulesRegex, 154 | (match, pre, name, post) => `${pre}${mapping[name]}${post}` 155 | ); 156 | 157 | // Split query into array, so if query uses operators, we can correctly add labels to each individual part. 158 | const queryArray = expandedQuery.split(/(\+|\-|\*|\/|\%|\^)/); 159 | 160 | // Regex that matches occurences of ){ or }{ or ]{ which is a sign of incorrecly added labels. 161 | const invalidLabelsRegex = /(\)\{|\}\{|\]\{)/; 162 | const correctlyExpandedQueryArray = queryArray.map((query) => { 163 | let expression = query; 164 | if (expression.match(invalidLabelsRegex)) { 165 | expression = addLabelsToExpression(expression, invalidLabelsRegex); 166 | } 167 | return expression; 168 | }); 169 | 170 | return correctlyExpandedQueryArray.join(""); 171 | } 172 | 173 | function addLabelsToExpression( 174 | expr: string, 175 | invalidLabelsRegexp: RegExp 176 | ): string { 177 | // Split query into 2 parts - before the invalidLabelsRegex match and after. 178 | const indexOfRegexMatch = expr?.match(invalidLabelsRegexp)?.index; 179 | if (!indexOfRegexMatch) { 180 | return ""; 181 | } 182 | const exprBeforeRegexMatch = expr.substr(0, indexOfRegexMatch + 1); 183 | const exprAfterRegexMatch = expr.substr(indexOfRegexMatch + 1); 184 | 185 | // Create arrayOfLabelObjects with label objects that have key, operator and value. 186 | const arrayOfLabelObjects: Array<{ 187 | key: string; 188 | operator: string; 189 | value: string; 190 | }> = []; 191 | exprAfterRegexMatch.replace(labelRegexp, (label, key, operator, value) => { 192 | arrayOfLabelObjects.push({ key, operator, value }); 193 | return ""; 194 | }); 195 | 196 | // Loop trough all of the label objects and add them to query. 197 | // As a starting point we have valid query without the labels. 198 | let result = exprBeforeRegexMatch; 199 | arrayOfLabelObjects.filter(Boolean).forEach((obj) => { 200 | // Remove extra set of quotes from obj.value 201 | const value = obj.value.substr(1, obj.value.length - 2); 202 | result = addLabelToQuery(result, obj.key, value, obj.operator); 203 | }); 204 | 205 | return result; 206 | } 207 | 208 | /** 209 | * Adds metadata for synthetic metrics for which the API does not provide metadata. 210 | * See https://github.com/grafana/grafana/issues/22337 for details. 211 | * 212 | * @param metadata HELP and TYPE metadata from /api/v1/metadata 213 | */ 214 | export function fixSummariesMetadata( 215 | metadata: CloudSQLMetricsMetadata 216 | ): CloudSQLMetricsMetadata { 217 | if (!metadata) { 218 | return metadata; 219 | } 220 | const summaryMetadata: CloudSQLMetricsMetadata = {}; 221 | for (const metric in metadata) { 222 | const item = metadata[metric][0]; 223 | if (item.type === "summary") { 224 | summaryMetadata[`${metric}_count`] = [ 225 | { 226 | type: "counter", 227 | help: `Count of events that have been observed for the base metric (${item.help})`, 228 | }, 229 | ]; 230 | summaryMetadata[`${metric}_sum`] = [ 231 | { 232 | type: "counter", 233 | help: `Total sum of all observed values for the base metric (${item.help})`, 234 | }, 235 | ]; 236 | } 237 | } 238 | return { ...metadata, ...summaryMetadata }; 239 | } 240 | -------------------------------------------------------------------------------- /Grafana/front-end/cloudsql-cos-ts/src/types.ts: -------------------------------------------------------------------------------- 1 | //# ------------------------------------------------------------------------------ 2 | //# Copyright IBM Corp. 2020 3 | //# 4 | //# Licensed under the Apache License, Version 2.0 (the "License"); 5 | //# you may not use this file except in compliance with the License. 6 | //# You may obtain a copy of the License at 7 | //# 8 | //# http://www.apache.org/licenses/LICENSE-2.0 9 | //# 10 | //# Unless required by applicable law or agreed to in writing, software 11 | //# distributed under the License is distributed on an "AS IS" BASIS, 12 | //# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | //# See the License for the specific language governing permissions and 14 | //# limitations under the License. 15 | //# ------------------------------------------------------------------------------ 16 | import { DataQuery, DataSourceJsonData, SelectableValue } from "@grafana/data"; 17 | 18 | export const FORMAT_OPTIONS: Array> = [ 19 | { label: "Time series", value: "time_series" }, 20 | { label: "Table", value: "table" }, 21 | ]; 22 | 23 | export interface CloudSQLQuery extends DataQuery { 24 | queryText?: string; // sql_stmt?: string; 25 | 26 | id?: number; //track the ID of the associated datasource instance 27 | name?: string; //track the name of the associated datasource instance 28 | format: string; //'timeseries' or something else? 29 | time_column?: string; 30 | metrics_column?: string; //the column from which uniques values represent different metrics 31 | get_result: boolean; // default is checked, i.e. the queried data is also returned 32 | showingHelp: boolean; 33 | } 34 | 35 | export const defaultTSQuery: Partial = { 36 | //return single time-series 37 | queryText: `WITH container_ts_table AS 38 | (SELECT field_name, 39 | ts 40 | FROM STORED AS PARQUET USING TIME_SERIES_FORMAT(KEY="field_name", timetick="time_stamp", value="observation") IN ts), 41 | tmp_table AS 42 | ( 43 | SELECT field_name AS user_agent, 44 | ts_explode(ts_seg_sum(TS_SEGMENT_BY_TIME(ts, 604800000, 604800000))) AS (tt, 45 | value) 46 | FROM container_ts_table) 47 | SELECT tt, log(value) as value 48 | FROM tmp_table 49 | WHERE user_agent LIKE "COS GO" 50 | INTO STORED AS PARQUET`, 51 | format: "time_series", 52 | time_column: "tt", 53 | }; 54 | export const defaultMTSQuery: Partial = { 55 | //return multiple time-series 56 | queryText: `WITH container_ts_table AS 57 | (SELECT field_name, 58 | ts 59 | FROM STORED AS PARQUET USING TIME_SERIES_FORMAT(KEY="field_name", timetick="time_stamp", value="observation") IN ts), 60 | tmp_table AS 61 | ( 62 | SELECT 63 | ts_explode(ts_seg_sum(TS_SEGMENT_BY_TIME(ts, 604800000, 604800000))) AS (tt, 64 | value), field_name AS user_agent 65 | FROM container_ts_table) 66 | SELECT tt, log(value) as value, user_agent 67 | FROM tmp_table 68 | INTO STORED AS PARQUET`, 69 | format: "time_series", 70 | time_column: "tt", 71 | metrics_column: "user_agent", 72 | }; 73 | export const defaultQuery: Partial = { 74 | queryText: `WITH container_ts_table AS 75 | (SELECT field_name, 76 | ts 77 | FROM STORED AS PARQUET USING TIME_SERIES_FORMAT(KEY="field_name", timetick="time_stamp", value="observation") IN ts) 78 | SELECT field_name AS user_agent, 79 | ts_explode(ts_seg_sum(TS_SEGMENT_BY_TIME(ts, 604800000, 604800000))) AS (tt, 80 | value) 81 | FROM container_ts_table INTO STORED AS PARQUET`, 82 | format: "table", 83 | }; 84 | 85 | export interface ClousSQLQueryRequest extends CloudSQLQuery {} 86 | 87 | export interface CloudSQLMetricsMetadataItem { 88 | type: string; 89 | help: string; 90 | unit?: string; 91 | } 92 | // https://github.com/grafana/grafana/blob/0c70308870e1748063b66274941772deba8cb76d/public/app/plugins/datasource/prometheus/types.ts 93 | export interface CloudSQLMetricsMetadata { 94 | [metric: string]: CloudSQLMetricsMetadataItem[]; 95 | } 96 | 97 | export const DataFormatTypeOptions = [ 98 | { value: "JSON", label: "JSON" }, 99 | { value: "CSV", label: "CSV" }, 100 | { value: "PARQUET", label: "PARQUET" }, 101 | { value: "AVRO", label: "AVRO" }, 102 | { value: "ORC", label: "ORC" }, 103 | ] as SelectableValue[]; 104 | /** 105 | * These are options configured for each DataSource instance 106 | */ 107 | export interface COSIBMDataSourceOptions extends DataSourceJsonData { 108 | //path?: string; 109 | apiKey: string; //Cloud API key 110 | instance_crn: string; // SQLQuery instance CRN 111 | source_cos_url?: string; // IBM COS with bucket where the data is stored 112 | table?: string; //HIVE table 113 | using_table: boolean; //decide between 'table' vs. 'cos_in_url' 114 | format_type?: string; // one of JSON, CSV, PARQUET 115 | target_cos_url: string; // IBM COS with bucket where the queried data is stored 116 | instance_rate_limit: number; 117 | sql_ui_link?: string; 118 | 119 | disableMetricsLookup?: boolean; 120 | } 121 | 122 | /** 123 | * Value that is used in the backend, but never sent over HTTP to the frontend 124 | */ 125 | export interface COSIBMSecureJsonData {} 126 | -------------------------------------------------------------------------------- /Grafana/front-end/cloudsql-cos-ts/src/utils/CancelablePromise.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/facebook/react/issues/5465 2 | 3 | export interface CancelablePromise { 4 | promise: Promise; 5 | cancel: () => void; 6 | } 7 | 8 | export const makePromiseCancelable = ( 9 | promise: Promise 10 | ): CancelablePromise => { 11 | let hasCanceled_ = false; 12 | 13 | const wrappedPromise = new Promise((resolve, reject) => { 14 | promise.then((val) => 15 | hasCanceled_ ? reject({ isCanceled: true }) : resolve(val) 16 | ); 17 | promise.catch((error) => 18 | hasCanceled_ ? reject({ isCanceled: true }) : reject(error) 19 | ); 20 | }); 21 | 22 | return { 23 | promise: wrappedPromise, 24 | cancel() { 25 | hasCanceled_ = true; 26 | }, 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /Grafana/front-end/cloudsql-cos-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/@grafana/toolkit/src/config/tsconfig.plugin.json", 3 | "include": ["src", "types"], 4 | "exclude": ["src/*backup*", "src_*" ], 5 | "compilerOptions": { 6 | "rootDir": "./src", 7 | "baseUrl": "./src", 8 | "typeRoots": ["./node_modules/@types"], 9 | "noImplicitAny": false 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /Node/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | package-lock.json -------------------------------------------------------------------------------- /Node/README.md: -------------------------------------------------------------------------------- 1 | # ibmcloudsql - Node.js 2 | 3 | Allows you to run SQL statements in the IBM Cloud on data stored on object storage. 4 | 5 | ## Example Usage 6 | 7 | ``` 8 | var apikey = process.env.IBM_API_KEY; 9 | var crn = process.env.IBM_SQL_CRN; 10 | var targetCosUrl = process.env.IBM_COS_TARGET_BUCKET_URL; 11 | 12 | var sqlQuery = new SqlQuery(apikey, crn, targetCosUrl); 13 | 14 | sqlQuery.runSql("select * from cos://us-south/employees/banklist.csv limit 5").then(data => console.log(data)); 15 | ``` 16 | 17 | ## SQLQuery function list 18 | * `SqlQuery(api_key, instance_crn, target_cos_url)` Constructor 19 | * `logon()` Needs to be called before any other method below. Logon is valid for one hour. 20 | * `submitSql(sql_text)` returns `jobId`as string 21 | * `waitForJob(jobId)` Waits for job to end and returns job completion state (either `completed` or `failed`) 22 | * `getResult(jobId)` returns SQL result data frame 23 | * `deleteResult(jobId)` deletes all result set objects in cloud object storage for the given jobId 24 | * `getJob(jobId)` returns details for the given SQL job as a JSON object 25 | * `getJobs()` returns the list of recent 30 submitted SQL jobs with all details as a data frame 26 | * `runSql(sql_text)` Compound method that calls `submitSql`, `waitForJob` and `getResult` in sequence 27 | * `sqlUILink()` Returns browser link for SQL Query web console for currently configured instance 28 | -------------------------------------------------------------------------------- /Node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ibmcloudsql", 3 | "version": "0.1.0", 4 | "description": "Node module to interact with IBM Cloud SQL Query", 5 | "main": "lib/sqlQuery.js", 6 | "scripts": { 7 | "test": "mocha" 8 | }, 9 | "engines": { 10 | "node": ">=8.0" 11 | }, 12 | "keywords": [ 13 | "IBM sql-query", 14 | "IBM cloud sql-query", 15 | "sql query", 16 | "sql-query" 17 | ], 18 | "author": "Abdullah Alger ", 19 | "contributors": [ 20 | "John O'Connor " 21 | ], 22 | "homepage": "https://console.bluemix.net/docs/services/sql-query/getting-started.html", 23 | "bugs": { 24 | "url": "https://github.com/IBM-Cloud/sql-query-clients/issues" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "github:IBM-Cloud/sql-query-clients" 29 | }, 30 | "license": "Apache-2.0", 31 | "devDependencies": { 32 | "chai": "^4.1.2", 33 | "jshint": "^2.9.5", 34 | "mocha": "^7.2.0" 35 | }, 36 | "dependencies": { 37 | "csvtojson": "^1.1.9", 38 | "ibm-cos-sdk": "^1.2.0", 39 | "node-fetch": "^2.1.2", 40 | "xml2js": "^0.4.19" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Node/test/sqlquery_spec.js: -------------------------------------------------------------------------------- 1 | const expect = require('chai').expect; 2 | const SqlQuery = require('../'); 3 | 4 | describe('sqlQuery', () => { 5 | describe('new', () => { 6 | let sqlQuery = new SqlQuery('mock-api-key', 'mock-crn', 'https://'); 7 | 8 | it('returns object', () => { 9 | expect(sqlQuery).to.be.a('object'); 10 | }); 11 | 12 | it('has function runSql()', () => { 13 | expect(sqlQuery).to.have.property('runSql').to.be.a('function'); 14 | }); 15 | }); 16 | }); -------------------------------------------------------------------------------- /Python/.gitignore: -------------------------------------------------------------------------------- 1 | ### Egg info 2 | *.egg-info 3 | *.egg 4 | *.EGG 5 | *.EGG-INFO 6 | *.DS_Store 7 | 8 | ### Local Python env and test 9 | ibmcloudsql/bin 10 | ibmcloudsql/.Python 11 | ibmcloudsql/lib 12 | ibmcloudsql/include 13 | ibmcloudsql/pip-selfcheck.json 14 | ibmcloudsql/test_credentials.py 15 | cloud_function/my_bind_as_python_function.sh 16 | cloud_function/my_bind.sh 17 | cloud_function/build_and_test.sh 18 | cloud_function/virtualenv 19 | cloud_function/recreate_docker.sh 20 | 21 | ### Build and distribution 22 | build/ 23 | dist/ 24 | 25 | ### Compiled and backup files 26 | __pycache__ 27 | *.pyc 28 | *.pyo 29 | *.tmp* 30 | *.bak 31 | .coverage 32 | .pytest_cache 33 | -------------------------------------------------------------------------------- /Python/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | -------------------------------------------------------------------------------- /Python/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | responses = ">=0.10.14" 8 | pytest = ">=2.8.0" 9 | pytest-cov = "*" 10 | numpy = ">=1.22.2" 11 | 12 | [packages] 13 | "ibmcloudsql" = {path = ".", editable = true} 14 | 15 | [scripts] 16 | ci = "pytest" 17 | coverage = "pytest --cov=ibmcloudsql tests/" 18 | -------------------------------------------------------------------------------- /Python/README.md: -------------------------------------------------------------------------------- 1 | # ibmcloudsql 2 | 3 | Allows you to run SQL statements in the IBM Cloud on data stored on object storage:: 4 | 5 | ## Building and testing the library locally 6 | ### Set up Python environment 7 | Run `source ./setup_env.sh` which creates and activates a clean virtual Python environment. It uses Python 2.7 by default. Adapt line 2 inside the script if you want a different version. 8 | ### Install the local code in your Python environment 9 | Run `./_install.sh`. 10 | ### Test the library locally 11 | 1. Create a file `ibmcloudsql/test_credentials.py` with the following three lines and your according properties: 12 | ``` 13 | apikey='' 14 | instance_crn='' 15 | result_location='' 16 | ... 17 | ``` 18 | see details in the template file 19 | 20 | 2. Run `python ibmcloudsql/test.py`. 21 | ### Packaging and publishing distribution 22 | 1. Make sure to increase `version=...` in `setup.py` before creating a new package. 23 | 2. Run `package.sh`. It will prompt for user and password that must be authorized for package `ibmcloudsql` on pypi.org. 24 | 25 | ## Example usage 26 | ``` 27 | import ibmcloudsql 28 | my_ibmcloud_apikey = '' 29 | my_instance_crn='' 30 | my_target_cos_url='//[]>' 31 | sqlClient = SQLQuery(my_ibmcloud_apikey, my_instance_crn) 32 | sqlClient.run_sql('SELECT * FROM cos://us-geo/sql/orders.parquet STORED AS PARQUET LIMIT 5 INTO {} STORED AS CSV'.format(my_target_cos_url)).head() 33 | ``` 34 | 35 | ## Demo notebook 36 | You can use IBM Watson Studio with the following [demo notebook](https://dataplatform.cloud.ibm.com/analytics/notebooks/v2/440b3665-367f-4fc9-86d8-4fe7eae13b18/view?access_token=3c1471a6970890fe28cadf118215df44e82c2472a83c4051e3ff80fe505448ed) that shows some elaborate examples of using various aspects of ibmcloudsql. 37 | 38 | ## SQLQuery method list 39 | * `SQLQuery(api_key, instance_crn, target_cos_url=None, token=None, client_info='')` Constructor 40 | * `logon(force=False, token=None)` Needs to be called before any other method below. It exchanges the `api_key` set at initialization for a temporary oauth token. The invocation is a No-Op if previous logon is less than 5 minutes ago. You can force logon anyway with optional paramater `force=True`. When you have inititialized the client without an `api_key` but instead specified a custom `token` then you can specify a fresh `token to logon method to update the client with that. 41 | * `submit_sql(sql_text, pagesize=None)` Returns `jobId`as string. Optional pagesize parameter (in rows) for paginated result objects. 42 | * `wait_for_job(jobId)` Waits for job to end and returns job completion state (either `completed` or `failed`) 43 | * `get_result(jobId, pagenumber=None)` returns SQL result data frame for entire result or for specified page of results. 44 | * `list_results(jobId)` Returns a data frame with the list of result objects written 45 | * `delete_result(jobId)` Deletes all result set objects in cloud object storage for the given jobId 46 | * `rename_exact_result(jobId)` Renames single partitioned query result to exact single object name without folder hierarchy. 47 | * `get_job(jobId)` Returns details for the given SQL job as a json object 48 | * `get_jobs()` Returns the list of recent 30 submitted SQL jobs with all details as a data frame 49 | * `run_sql(sql_text)` Compound method that calls `submit_sql`, `wait_for_job` and `wait_for_job` in sequenceA 50 | * `sql_ui_link()` Returns browser link for Data Engine web console for currently configured instance 51 | * `get_cos_summary(cos_url)` Returns summary for stored number of objects and volume for a given cos url as a json 52 | * `list_cos_objects(cos_url)` Returns a data frame with the list of objects found in the given cos url 53 | * `export_job_history(cos_url)` Exports new jobs as parquet file to the given `cos_url`. 54 | * `export_tags_for_cos_objects(cos_url, export_target_cos_file)` Exports all objects as a parquet file to the given `cos_url` that have tags configured along with the value for each tag. 55 | 56 | ## Exceptions 57 | * `RateLimitedException(message)` raised when jobs can't be submitted due to 429 / Plan limit for concurrent queries has been reached 58 | ## Constructor options 59 | * `api_key`: IAM API key. When this parameter is set to `None` then you must specify an own valid IAM otauth token in the parameter `token`. 60 | * `instance_crn`: Data Engine instance CRN identifier 61 | * `target_cos_url`: Optional default target URL. Don't use when you want to provide target URL in SQL statement text. 62 | * `token`: Optional custom IAM oauth token. When you specify this then you must set `api_key` parameter to `None`. 63 | * `client_info`: Optional string to identify your client application in IBM Cloud for PD reasons. 64 | * `max_tries`: Optional integer to specify maximum attempts when dealing with request rate limit. Default value is `1`, which means it will through exception `RateLimitedException` when response status code is `429`. It will enable _exponential backoff_ when specifying any positive number greater than `1`. For instance, given `max_tries=5`, assuming it will get response status code `429` for 4 times until the 5th attempt will get response status code `201`, the wait time will be `2s`, `4s`, `8s` and `16s` for each attempts. 65 | 66 | ## Limitations 67 | Data Engine Python SDK does not support Pyinstaller. 68 | -------------------------------------------------------------------------------- /Python/_install.sh: -------------------------------------------------------------------------------- 1 | pip install --force-reinstall . 2 | 3 | -------------------------------------------------------------------------------- /Python/cloud_function/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ibmfunctions/action-python-v3 2 | 3 | RUN pip install --force-reinstall ibmcloudsql 4 | 5 | ADD sqlfunction.py /action/sqlfunction.py 6 | RUN echo '#!/bin/bash\n\ 7 | python /action/sqlfunction.py "$1"' > /action/exec 8 | RUN chmod +x /action/exec 9 | 10 | CMD ["/bin/bash", "-c", "cd actionProxy && python -u actionproxy.py"] 11 | 12 | -------------------------------------------------------------------------------- /Python/cloud_function/README.md: -------------------------------------------------------------------------------- 1 | # Generic Cloud Function for IBM Cloud Data Engine 2 | 3 | This directory contains a simple IBM Cloud Function in Python that calls a custom SQL query in the IBM Cloud Data Engine service using the ibmcloudsql Python package. It prereqs the bx CLI tool and the cloud function plugin. 4 | 5 | ## Function parameters: 6 | * `sql` The SQL statement text. Refer to IBM Cloud Data Engine documentation for details and use the IBM Cloud Data Engine web console to author and test your SQL statement interactively. 7 | * `api_key` - Your IAM API key for your IBM Cloud user or service ID with access to your Data Engine instance and COS bucket 8 | * `instance_crn` - The instance CRN of your Data Engine instance. Find it in IBM Cloud console dashboard for your instance. Press the `Instance CRN` button there to copy it toyour clipboard 9 | * `target_url` - Cloud Object Storage URL for the SQL result target. Format: `cos:////[]` 10 | * `client_info` (optional) - Tracking information to identify your client application inside IBM cloud 11 | * `async` (optional) - When set to true: only submit the SQL statement and don't wait for it to finish. When not provided the default is false. 12 | 13 | ## 1. Optional: Build and publish SQL cloud function docker image using `build.sh` 14 | This creates a new docker image with all required dependencies and packages and the according client code to invoke the Data Engine API. You only need to do this if you made changes to things in this repository. You will then also need to adapt the `register.sh` script to reference your own pushed docker image. You can use the `invoke.py`script to test your docker image as an action locally before pushing to docker hub and creating a real IBM Cloud Function with it. Here is an [article](https://medium.com/openwhisk/advanced-debugging-of-openwhisk-actions-518414636932) describing the usage of this test script. 15 | 16 | ## 2. Register the SQL cloud function using `register.sh` 17 | This registers a Cloud Function called `sqlcloudfunction`inside your IBM Cloud org and space. 18 | 19 | ## 3. Bind using `bind.sh` to your COS and Data Engine instances 20 | This sets the required parameters for API key, instance CRN and COS URL for SQL results. You need to edit the script with your instance data before running it. 21 | 22 | ## 4. Call the function with a SQL statement using `call.sh` 23 | This calls the function with a simple sample statement passing to the function as a function parameter. You can change the SQL statement text to any other statement that works for you and your data. 24 | 25 | *Note*: you can also decide to provide or override the COS and Data Engine instance information at invocation time by passing the parameters there. You can also decide to register the function with a hard coded SQL statement text by providing the `sql` parameter in the bind script. 26 | 27 | * More background on [Python cloud function](https://console.bluemix.net/docs/openwhisk/openwhisk_actions.html#creating-python-actions) creation and handling. 28 | * [Python environments](https://console.bluemix.net/docs/openwhisk/openwhisk_reference.html#openwhisk_ref_python_environments) for cloud functions 29 | * IBM Cloud CLI [download](https://console.bluemix.net/docs/cli/reference/bluemix_cli/download_cli.html#download_install) 30 | * Cloud functions [CLI plug-in](https://console.bluemix.net/docs/openwhisk/bluemix_cli.html#cloudfunctions_cli) 31 | -------------------------------------------------------------------------------- /Python/cloud_function/__main__.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # Copyright IBM Corp. 2018 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ------------------------------------------------------------------------------ 16 | # 17 | # main() will be run when you invoke this action 18 | # 19 | # @param Cloud Functions actions accept a single parameter, which must be a JSON object. 20 | # 21 | # @return The output of this action, which must be a JSON object. 22 | # 23 | # 24 | import sys 25 | import ibmcloudsql 26 | 27 | 28 | def main(args): 29 | ibmcloud_apikey = args.get("apikey", "") 30 | if ibmcloud_apikey == "": 31 | return {'error': 'No API key specified'} 32 | sql_instance_crn = args.get("sqlquery_instance_crn", "") 33 | if sql_instance_crn == "": 34 | return {'error': 'No Data Engine instance CRN specified'} 35 | target_url = args.get("target_url", "") 36 | client_information = args.get("client_info", "ibmcloudsql cloud function") 37 | sql_statement_text = args.get("sql", "") 38 | if sql_statement_text == "": 39 | return {'error': 'No SQL statement specified'} 40 | sqlClient = ibmcloudsql.SQLQuery(ibmcloud_apikey, sql_instance_crn, target_url, client_info=client_information) 41 | sqlClient.logon() 42 | try: 43 | jobId = sqlClient.submit_sql(sql_statement_text) 44 | except Exception as e: 45 | return {'Error': e} 46 | 47 | jobDetails = sqlClient.get_job(jobId) 48 | 49 | access_code = 'import ibmcloudsql\n' 50 | access_code += 'api_key="" # ADD YOUR API KEY HERE\n' 51 | access_code += 'sqlClient = ibmcloudsql.SQLQuery(api_key, ' + sql_instance_crn + ', ' + target_url + ')\n' 52 | access_code += 'sqlClient.logon()\n' 53 | access_code += 'result_df = sqlClient.get_result(' + jobId + ')\n' 54 | 55 | return {'jobId': jobId, 'result_location': jobDetails['resultset_location'], 'job_status': jobDetails['status'], 56 | 'result_access_pandas': access_code} 57 | 58 | -------------------------------------------------------------------------------- /Python/cloud_function/bind.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # You need to edit this script and replace the placeholders below with 4 | # your instance and API key data. 5 | 6 | ibmcloud fn action update sqlcloudfunction \ 7 | --param apikey \ 8 | --param sqlquery_instance_crn \ 9 | --param target_url //[prefix]) here> 10 | -------------------------------------------------------------------------------- /Python/cloud_function/bind_as_python_function.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # You need to edit this script and replace the placeholders below with 4 | # your instance and API key data. 5 | 6 | ibmcloud fn action update sqlcloudfunction_as_python \ 7 | --param apikey \ 8 | --param sqlquery_instance_crn \ 9 | --param target_url //[prefix]) here> 10 | -------------------------------------------------------------------------------- /Python/cloud_function/build.sh: -------------------------------------------------------------------------------- 1 | docker build --no-cache --tag torsstei/sqlfunction:latest . 2 | docker push torsstei/sqlfunction 3 | docker tag torsstei/sqlfunction:latest ibmfunctions/sqlquery:latest 4 | docker push ibmfunctions/sqlquery:latest 5 | 6 | -------------------------------------------------------------------------------- /Python/cloud_function/call.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Call the cloud function with a SQL statement as parameter 4 | # We use the -br parameter so that it is a blocking call and the function results 5 | # get displayed on the console afterwards: 6 | ibmcloud fn action invoke -br --result sqlcloudfunction \ 7 | --param sql "SELECT * FROM cos://us-geo/sql/employees.parquet STORED AS PARQUET LIMIT 3 INTO cos://us-south/sqltempregional/" \ 8 | | python -m json.tool 9 | 10 | # List the recent cloud function executions (a.k.a. activations): 11 | ibmcloud fn activation list 12 | 13 | # Display the results of a specific previous cloud function run. You need to replace the 14 | # ID with the activation ID of your choice: 15 | #ibmcloud fn activation get b8696d16977f45e8a96d16977f95e804 16 | -------------------------------------------------------------------------------- /Python/cloud_function/call_as_python_function.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Call the cloud function with a SQL statement as parameter 4 | # We use the -br parameter so that it is a blocking call and the function results 5 | # get displayed on the console afterwards: 6 | ibmcloud fn action invoke -br --result sqlcloudfunction_as_python \ 7 | --param sql "SELECT * FROM cos://us-geo/sql/employees.parquet STORED AS PARQUET LIMIT 3" 8 | 9 | # List the recent cloud function executions (a.k.a. activations): 10 | ibmcloud fn activation list 11 | 12 | # Display the results of a specific previous cloud function run. You need to replace the 13 | # ID with the activation ID of your choice: 14 | #ibmcloud fn activation get b8696d16977f45e8a96d16977f95e804 15 | -------------------------------------------------------------------------------- /Python/cloud_function/ibmcloudsql_cloudfunction.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM-Cloud/sql-query-clients/1963cca543dccd2734aa66bdb0229c862516cfdb/Python/cloud_function/ibmcloudsql_cloudfunction.zip -------------------------------------------------------------------------------- /Python/cloud_function/invoke.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Executable Python script for testing the action proxy. 3 | 4 | This script is useful for testing the action proxy (or its derivatives) 5 | by simulating invoker interactions. Use it in combination with 6 | docker run which starts up the action proxy. 7 | Example: 8 | docker run -i -t -p 8080:8080 dockerskeleton # locally built images may be referenced without a tag 9 | ./invoke.py init 10 | ./invoke.py run '{"some":"json object as a string"}' 11 | 12 | For additional help, try ./invoke.py -h 13 | 14 | /* 15 | * Licensed to the Apache Software Foundation (ASF) under one or more 16 | * contributor license agreements. See the NOTICE file distributed with 17 | * this work for additional information regarding copyright ownership. 18 | * The ASF licenses this file to You under the Apache License, Version 2.0 19 | * (the "License"); you may not use this file except in compliance with 20 | * the License. You may obtain a copy of the License at 21 | * 22 | * http://www.apache.org/licenses/LICENSE-2.0 23 | * 24 | * Unless required by applicable law or agreed to in writing, software 25 | * distributed under the License is distributed on an "AS IS" BASIS, 26 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 27 | * See the License for the specific language governing permissions and 28 | * limitations under the License. 29 | */ 30 | """ 31 | 32 | import os 33 | import re 34 | import sys 35 | import json 36 | import base64 37 | import requests 38 | import codecs 39 | import traceback 40 | import argparse 41 | try: 42 | import argcomplete 43 | except ImportError: 44 | argcomplete = False 45 | 46 | def main(): 47 | try: 48 | args = parseArgs() 49 | exitCode = { 50 | 'init' : init, 51 | 'run' : run 52 | }[args.cmd](args) 53 | except Exception as e: 54 | print(e) 55 | exitCode = 1 56 | sys.exit(exitCode) 57 | 58 | def dockerHost(): 59 | dockerHost = 'localhost' 60 | if 'DOCKER_HOST' in os.environ: 61 | try: 62 | dockerHost = re.compile('tcp://(.*):[\d]+').findall(os.environ['DOCKER_HOST'])[0] 63 | except Exception: 64 | print('cannot determine docker host from %s' % os.environ['DOCKER_HOST']) 65 | sys.exit(-1) 66 | return dockerHost 67 | 68 | def containerRoute(args, path): 69 | return 'http://%s:%s/%s' % (args.host, args.port, path) 70 | 71 | def parseArgs(): 72 | parser = argparse.ArgumentParser(description='initialize and run an OpenWhisk action container') 73 | parser.add_argument('-v', '--verbose', help='verbose output', action='store_true') 74 | parser.add_argument('--host', help='action container host', default=dockerHost()) 75 | parser.add_argument('-p', '--port', help='action container port number', default=8080, type=int) 76 | 77 | subparsers = parser.add_subparsers(title='available commands', dest='cmd') 78 | 79 | initmenu = subparsers.add_parser('init', help='initialize container with src or zip/tgz file') 80 | initmenu.add_argument('main', nargs='?', default='main', help='name of the "main" entry method for the action') 81 | initmenu.add_argument('artifact', help='a source file or zip/tgz archive') 82 | 83 | runmenu = subparsers.add_parser('run', help='send arguments to container to run action') 84 | runmenu.add_argument('payload', nargs='?', help='the arguments to send to the action, either a reference to a file or an inline JSON object', default=None) 85 | 86 | if argcomplete: 87 | argcomplete.autocomplete(parser) 88 | return parser.parse_args() 89 | 90 | def init(args): 91 | main = args.main 92 | artifact = args.artifact 93 | 94 | if artifact and (artifact.endswith('.zip') or artifact.endswith('tgz') or artifact.endswith('jar')): 95 | with open(artifact, 'rb') as fp: 96 | contents = fp.read() 97 | contents = base64.b64encode(contents) 98 | binary = True 99 | elif artifact != '': 100 | with(codecs.open(artifact, 'r', 'utf-8')) as fp: 101 | contents = fp.read() 102 | binary = False 103 | else: 104 | contents = None 105 | binary = False 106 | 107 | r = requests.post( 108 | containerRoute(args, 'init'), 109 | json = {"value": {"code": contents, 110 | "binary": binary, 111 | "main": main}}) 112 | print(r.text) 113 | 114 | def run(args): 115 | value = processPayload(args.payload) 116 | if args.verbose: 117 | print('Sending value: %s...' % json.dumps(value)[0:40]) 118 | r = requests.post(containerRoute(args, 'run'), json = {"value": value}) 119 | print(r.text) 120 | 121 | def processPayload(payload): 122 | if payload and os.path.exists(payload): 123 | with open(payload) as fp: 124 | return json.load(fp) 125 | try: 126 | d = json.loads(payload if payload else '{}') 127 | if isinstance(d, dict): 128 | return d 129 | else: 130 | raise 131 | except: 132 | print('payload must be a JSON object.') 133 | sys.exit(-1) 134 | 135 | if __name__ == '__main__': 136 | main() 137 | -------------------------------------------------------------------------------- /Python/cloud_function/register.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Create/Replace the cloud function 4 | # We set the maximum allowed timeout of 5 minutes: 5 | ibmcloud fn action delete sqlcloudfunction 6 | ibmcloud fn action create sqlcloudfunction --timeout 300000 --docker ibmfunctions/sqlquery 7 | -------------------------------------------------------------------------------- /Python/cloud_function/register_as_python_function.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Create a fresh Python virtual environment and activate it: 4 | /usr/local/bin/virtualenv virtualenv 5 | source virtualenv/bin/activate 6 | 7 | # Install ibmcloudsql and afterwards remove dependencies that are already 8 | # available in cloud function python-jessie:3 runtime: 9 | pip install --upgrade --force-reinstall ibmcloudsql 10 | pip uninstall --yes urllib3 simplejson six setuptools python-dateutil pytz pandas numpy jmespath docutils 11 | 12 | # Create zip package with cloud function code in __main__.py and with packages in virtualenv: 13 | # (Note: the virtual environment folder has to be literally "virtualenv" so that cloud function 14 | # can find it.) 15 | rm ibmcloudsql_cloudfunction.zip 16 | zip -r ibmcloudsql_cloudfunction.zip virtualenv __main__.py 17 | 18 | # Create/Replace the cloud function 19 | # We set the maximum allowed timeout of 5 minutes: 20 | ibmcloud fn action delete sqlcloudfunction_as_python 21 | ibmcloud fn action create sqlcloudfunction_as_python --timeout 300000 --kind python-jessie:3 ibmcloudsql_cloudfunction.zip 22 | -------------------------------------------------------------------------------- /Python/cloud_function/sqlfunction.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # Copyright IBM Corp. 2018 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ------------------------------------------------------------------------------ 16 | # 17 | # @param Cloud Functions actions accept a single parameter, which must be a JSON object. 18 | # 19 | # @return The output of this action, which must be a JSON object. 20 | # 21 | # 22 | import sys 23 | import ibmcloudsql 24 | import json 25 | import os 26 | 27 | 28 | args = json.loads(sys.argv[1]) 29 | ibmcloud_apikey = args.get("apikey", "") 30 | sql_instance_crn = args.get("sqlquery_instance_crn", "") 31 | target_url = args.get("target_url", "") 32 | client_information = args.get("client_info", "ibmcloudsql cloud function: " + os.environ['__OW_ACTION_NAME']) 33 | sql_job_id = args.get("jobid", "") 34 | sql_statement_text = args.get("sql", "") 35 | sql_index = args.get("index", "") 36 | sql_max_results = args.get("maxresults", "") 37 | sql_async_execution = args.get("async", False) 38 | 39 | if ibmcloud_apikey == "": 40 | print({'error': 'No API key specified'}) 41 | quit() 42 | if sql_instance_crn == "": 43 | print({'error': 'No Data Engine instance CRN specified'}) 44 | quit() 45 | if sql_statement_text == "": 46 | if sql_job_id == "": 47 | print({'error': 'Neither SQL statement nor job id specified'}) 48 | quit() 49 | if sql_index == "": 50 | print({'info': 'No starting index specified. Will return starting with first row'}) 51 | if sql_max_results == "": 52 | print({'info': 'No max results specified. Will return all results'}) 53 | 54 | sqlClient = ibmcloudsql.SQLQuery(ibmcloud_apikey, sql_instance_crn, target_url, client_info=client_information) 55 | sqlClient.logon() 56 | next_index = "" 57 | if sql_job_id == "": 58 | jobId = sqlClient.submit_sql(sql_statement_text) 59 | if not sql_async_execution: 60 | sqlClient.wait_for_job(jobId) 61 | if sql_max_results == "": 62 | result = sqlClient.get_result(jobId) 63 | else: 64 | result = sqlClient.get_result(jobId).iloc[0:sql_max_results] 65 | if len(sqlClient.get_result(jobId).index) > sql_max_results: next_index = sql_max_results 66 | else: 67 | first_index = sql_index 68 | last_index = first_index+sql_max_results 69 | result = sqlClient.get_result(sql_job_id).iloc[first_index:last_index] 70 | jobId = sql_job_id 71 | if len(sqlClient.get_result(sql_job_id).index) > last_index: next_index = last_index 72 | jobDetails = sqlClient.get_job(jobId) 73 | access_code = 'import ibmcloudsql\n' 74 | access_code += 'api_key="" # ADD YOUR API KEY HERE\n' 75 | access_code += 'sqlClient = ibmcloudsql.SQLQuery(api_key, ' + sql_instance_crn + ', ' + target_url + ')\n' 76 | access_code += 'sqlClient.logon()\n' 77 | access_code += 'result_df = sqlClient.get_result(' + jobId + ')\n' 78 | 79 | result_json={'action_name': os.environ['__OW_ACTION_NAME'], 'jobId': jobId, 'result_location': jobDetails['resultset_location'], 80 | 'async_execution': sql_async_execution, 'job_status': jobDetails['status'], 'result_access_pandas': access_code} 81 | if not sql_async_execution: 82 | result_json['result_set_sample'] = result.to_json(orient='table') 83 | result_json['result_next_index'] = next_index 84 | print(json.dumps(result_json)) 85 | 86 | 87 | -------------------------------------------------------------------------------- /Python/ibmcloudsql/__init__.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # Copyright IBM Corp. 2018 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ------------------------------------------------------------------------------ 16 | 17 | __version__ = "0.5.14" 18 | # flake8: noqa F401 19 | from .SQLQuery import SQLQuery 20 | from .sql_query_ts import SQLClientTimeSeries 21 | from .exceptions import RateLimitedException 22 | from .sql_magic import SQLBuilder 23 | -------------------------------------------------------------------------------- /Python/ibmcloudsql/exceptions.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # Copyright IBM Corp. 2020 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ------------------------------------------------------------------------------ 16 | 17 | 18 | class RateLimitedException(Exception): 19 | """The error when number of requests exceeds the capacity""" 20 | 21 | def __init__(self, msg, original_exception=None): 22 | """create exception object""" 23 | if original_exception is not None: 24 | super().__init__(msg + (": %s" % original_exception)) 25 | else: 26 | super().__init__(msg) 27 | self.original_exception = original_exception 28 | 29 | 30 | class InternalError502Exception(Exception): 31 | """The error when Data Engine returns a 502 internal error""" 32 | 33 | def __init__(self, msg, original_exception=None): 34 | """create exception object""" 35 | if original_exception is not None: 36 | super().__init__(msg + (": %s" % original_exception)) 37 | else: 38 | super().__init__(msg) 39 | self.original_exception = original_exception 40 | 41 | class InternalError524Exception(Exception): 42 | """The error when Data Engine returns a 524 internal error""" 43 | 44 | def __init__(self, msg, original_exception=None): 45 | """create exception object""" 46 | if original_exception is not None: 47 | super().__init__(msg + (": %s" % original_exception)) 48 | else: 49 | super().__init__(msg) 50 | self.original_exception = original_exception 51 | 52 | class CosUrlNotFoundException(Exception): 53 | """The error when the Cloud-Object Storage (COS) URL being used is invalid or not accessible""" 54 | 55 | def __init__(self, msg, original_exception=None): 56 | """create exception object""" 57 | if original_exception is not None: 58 | super().__init__(msg + (": %s" % original_exception)) 59 | else: 60 | super().__init__(msg) 61 | self.original_exception = original_exception 62 | 63 | 64 | class CosUrlInaccessibleException(Exception): 65 | """The error when the Cloud-Object Storage (COS) URL being used is not accessible""" 66 | 67 | def __init__(self, msg, original_exception=None): 68 | """create exception object""" 69 | if original_exception is not None: 70 | super().__init__(msg + (": %s" % original_exception)) 71 | else: 72 | super().__init__(msg) 73 | self.original_exception = original_exception 74 | 75 | 76 | class SqlQueryCrnInvalidFormatException(Exception): 77 | """The error when the Data Engine CRN is not correct""" 78 | 79 | def __init__(self, msg, original_exception=None): 80 | """create exception object""" 81 | if original_exception is not None: 82 | super().__init__(msg + (": %s" % original_exception)) 83 | else: 84 | super().__init__(msg) 85 | self.original_exception = original_exception 86 | 87 | 88 | class SqlQueryInvalidPlanException(Exception): 89 | """The error when the used feature is not supported by the current service plan - 90 | e.g. need to upgrade to Standard Plan or higher""" 91 | 92 | def __init__(self, msg, original_exception=None): 93 | """create exception object""" 94 | if original_exception is not None: 95 | super().__init__(msg + (": %s" % original_exception)) 96 | else: 97 | super().__init__(msg) 98 | self.original_exception = original_exception 99 | 100 | 101 | class SqlQueryFailException(Exception): 102 | """The error raised when a running sql job fails, e.g. timeout""" 103 | 104 | def __init__(self, msg, original_exception=None): 105 | """create exception object""" 106 | if original_exception is not None: 107 | super().__init__(msg + (": %s" % original_exception)) 108 | else: 109 | super().__init__(msg) 110 | self.original_exception = original_exception 111 | 112 | 113 | class SqlQueryCreateTableException(SqlQueryFailException): 114 | """The error raised when a running create-table sql job fails""" 115 | 116 | def __init__(self, msg, original_exception=None): 117 | """create exception object""" 118 | if original_exception is not None: 119 | super().__init__(msg + (": %s" % original_exception)) 120 | else: 121 | super().__init__(msg) 122 | self.original_exception = original_exception 123 | 124 | 125 | class SqlQueryDropTableException(SqlQueryFailException): 126 | """The error raised when a running drop-table sql job fails""" 127 | 128 | def __init__(self, msg, original_exception=None): 129 | """create exception object""" 130 | if original_exception is not None: 131 | super().__init__(msg + (": %s" % original_exception)) 132 | else: 133 | super().__init__(msg) 134 | self.original_exception = original_exception 135 | 136 | 137 | class SqlQueryInvalidFormatException(SqlQueryFailException): 138 | """The error raised when the format of COS URL is not valid""" 139 | 140 | def __init__(self, msg, original_exception=None): 141 | """create exception object""" 142 | if original_exception is not None: 143 | super().__init__(msg + (": %s" % original_exception)) 144 | else: 145 | super().__init__(msg) 146 | self.original_exception = original_exception 147 | 148 | 149 | class UnsupportedStorageFormatException(Exception): 150 | """The error when the SQL uses a format of data that has not been supported yet""" 151 | 152 | def __init__(self, msg, original_exception=None): 153 | """create exception object""" 154 | if original_exception is not None: 155 | super().__init__(msg + (": %s" % original_exception)) 156 | else: 157 | super().__init__(msg) 158 | self.original_exception = original_exception 159 | 160 | 161 | class InternalErrorException(Exception): 162 | """The error when Data Engine crash with an internal error""" 163 | 164 | def __init__(self, msg, original_exception=None): 165 | """create exception object""" 166 | if original_exception is not None: 167 | super().__init__(msg + (": %s" % original_exception)) 168 | else: 169 | super().__init__(msg) 170 | self.original_exception = original_exception 171 | -------------------------------------------------------------------------------- /Python/ibmcloudsql/test_credentials.py.template: -------------------------------------------------------------------------------- 1 | apikey='' 2 | instance_crn='' 3 | instance_rate_limit='' 4 | result_location='' 5 | eu_instance_crn='' 6 | eu_result_location='' -------------------------------------------------------------------------------- /Python/package.sh: -------------------------------------------------------------------------------- 1 | python setup.py register sdist 2 | 3 | VERSION=$(sed -n 's/^Version: *\([^ ]*\)/\1/p' ibmcloudsql.egg-info/PKG-INFO) 4 | if [[ -z "${VERSION}" ]]; then echo 'Error: No package version found.'; exit 1; fi 5 | TAG="pip-${VERSION}" 6 | echo "Tagging current branch as $TAG" 7 | git tag -a ${TAG} -m "Release ${VERSION} of ibmcloudsql pip package" 8 | git push origin ${TAG} 9 | 10 | echo "Publishing ibmcloudsql version ${VERSION}" 11 | twine upload --skip-existing dist/* 12 | -------------------------------------------------------------------------------- /Python/setup.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # Copyright IBM Corp. 2018 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ------------------------------------------------------------------------------ 16 | """Setup file.""" 17 | import codecs 18 | import os.path 19 | 20 | from setuptools import setup 21 | # read the contents of your README file 22 | from pathlib import Path 23 | this_directory = Path(__file__).parent 24 | long_description = (this_directory / "README.md").read_text() 25 | 26 | 27 | def readme(): 28 | """Return README.rst content.""" 29 | with open("README.rst") as f: 30 | return f.read() 31 | 32 | 33 | def _read(rel_path): 34 | here = os.path.abspath(os.path.dirname(__file__)) 35 | with codecs.open(os.path.join(here, rel_path), "r") as fp: 36 | return fp.read() 37 | 38 | 39 | def get_version(rel_path): 40 | """Get package version.""" 41 | for line in _read(rel_path).splitlines(): 42 | if line.startswith("__version__"): 43 | delim = '"' if '"' in line else "'" 44 | return line.split(delim)[1] 45 | else: 46 | raise RuntimeError("Unable to find version string.") 47 | 48 | 49 | setup( 50 | name="ibmcloudsql", 51 | version=get_version("ibmcloudsql/__init__.py"), 52 | python_requires=">=3.8, <4", 53 | install_requires=[ 54 | "pandas>=1.1.0", 55 | "requests>= 2.2.0", 56 | "ibm-cos-sdk-core>=2.10.0", 57 | "ibm-cos-sdk>=2.10.0", 58 | "numpy>=1.20.3", 59 | "pyarrow", 60 | "backoff==1.10.0", 61 | "sqlparse>=0.4.2", 62 | "packaging", 63 | "pre-commit", 64 | "isodate", 65 | "importlib-metadata", 66 | "typing-extensions", 67 | "python-dateutil", 68 | "deprecated", 69 | ], 70 | description="Python client for interacting with IBM Cloud Data Engine service", # noqa 71 | long_description=long_description, 72 | long_description_content_type='text/markdown', 73 | url="https://github.com/IBM-Cloud/sql-query-clients", 74 | author="IBM Corp.", 75 | author_email="torsten@de.ibm.com", 76 | license="Apache 2.0", 77 | classifiers=[ 78 | "Development Status :: 4 - Beta", 79 | "License :: OSI Approved :: Apache Software License", 80 | "Programming Language :: Python :: 3.8", 81 | "Topic :: Database", 82 | ], 83 | keywords="sql cloud object_storage IBM", 84 | packages=["ibmcloudsql"], 85 | ) 86 | -------------------------------------------------------------------------------- /Python/setup_env.sh: -------------------------------------------------------------------------------- 1 | pip3 install virtualenv 2 | virtualenv -p `which python3` ibmcloudsql 3 | # Run this outside of the script in your local shell: 4 | source ibmcloudsql/bin/activate 5 | pip3 install twine 6 | 7 | -------------------------------------------------------------------------------- /Python/tests/test_cos.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | import responses 3 | import pytest 4 | 5 | from ibmcloudsql import cos 6 | 7 | @pytest.fixture 8 | def parsedurl_instance(): 9 | return cos.ParsedUrl() 10 | 11 | @pytest.fixture 12 | def cos_url(): 13 | cos_url = "cos://us-south/cos-access-ts/test/Location=us-south/DC=rgfra02/Year=2019/Month=10/D" 14 | return cos_url 15 | 16 | def test_get_endpoint(parsedurl_instance, cos_url): 17 | x = parsedurl_instance.get_endpoint(cos_url) 18 | assert(x == "s3.us-south.cloud-object-storage.appdomain.cloud") 19 | 20 | def test_get_bucket(parsedurl_instance, cos_url): 21 | x = parsedurl_instance.get_bucket(cos_url) 22 | assert(x == "cos-access-ts") 23 | 24 | def test_get_prefix(parsedurl_instance, cos_url): 25 | x = parsedurl_instance.get_prefix(cos_url) 26 | assert(x == "test/Location=us-south/DC=rgfra02/Year=2019/Month=10/D") 27 | 28 | def test_get_exact_url(parsedurl_instance, cos_url): 29 | x = parsedurl_instance.get_exact_url(cos_url) 30 | assert(x == "cos://s3.us-south.cloud-object-storage.appdomain.cloud/cos-access-ts/test/Location=us-south/DC=rgfra02/Year=2019/Month=10/D") 31 | 32 | def test_analyze_cos_url(parsedurl_instance, cos_url): 33 | x = parsedurl_instance.analyze_cos_url(cos_url) 34 | assert(x.prefix == "test/Location=us-south/DC=rgfra02/Year=2019/Month=10/D") 35 | assert(x.bucket == "cos-access-ts") 36 | assert(x.endpoint == "s3.us-south.cloud-object-storage.appdomain.cloud") 37 | 38 | @pytest.fixture 39 | def cos_instance(): 40 | cos_url = "cos://us-south/cos-access-ts/test/" 41 | cos_instance = cos.COSClient('mock-api-key', cos_url, client_info='ibmcloudsql test') 42 | 43 | # TODO mock method .logon() instead of hacking 44 | # disable authentication step for 300s 45 | cos_instance.logged_on = True 46 | cos_instance.last_logon = datetime.now() 47 | cos_instance.request_headers.update( 48 | {'authorization': 'Bearer {}'.format("mock")}) 49 | return cos_instance 50 | 51 | def test_cos_init(cos_instance): 52 | assert(cos_instance.request_headers == {'Content-Type': 'application/json', 53 | 'Accept': 'application/json', 54 | 'User-Agent': cos_instance.user_agent, 55 | 'authorization': 'Bearer {}'.format("mock") 56 | }) 57 | -------------------------------------------------------------------------------- /Python/tests/test_sqlquery.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | import responses 4 | import pytest 5 | 6 | from ibmcloudsql import SQLQuery, RateLimitedException 7 | 8 | 9 | @pytest.fixture 10 | def sqlquery_client(): 11 | cos_url = "cos://us-south/cos-access-ts/test/" 12 | sql_client = SQLQuery('mock-api-key', 'mock-crn', cos_url, client_info='ibmcloudsql test') 13 | 14 | # TODO mock method .logon() instead of hacking 15 | # disable authentication step for 300s 16 | sql_client.logged_on = True 17 | sql_client.last_logon = datetime.now() 18 | sql_client.request_headers.update( 19 | {'authorization': 'Bearer {}'.format("mock")}) 20 | return sql_client 21 | 22 | def test_init(sqlquery_client): 23 | assert(sqlquery_client.request_headers == {'Content-Type': 'application/json', 24 | 'Accept': 'application/json', 25 | 'User-Agent': sqlquery_client.user_agent, 26 | 'authorization': 'Bearer {}'.format("mock") 27 | }) 28 | 29 | @responses.activate 30 | def test_submit_sql_no_retry(sqlquery_client): 31 | '''expect exception when getting status code 429''' 32 | mock_error_message = 'too many requests' 33 | responses.add(responses.POST, 'https://api.dataengine.cloud.ibm.com/v2/sql_jobs', 34 | json={'errors': [{'message': mock_error_message}]}, status=429) 35 | 36 | with pytest.raises(RateLimitedException, match=mock_error_message) as exc_info: 37 | sqlquery_client.submit_sql('VALUES (1)') 38 | 39 | @responses.activate 40 | def test_submit_sql_w_retry(sqlquery_client): 41 | '''retry when getting status code 429''' 42 | mock_error_message = 'too many requests' 43 | mock_job_id = 'fake-digest' 44 | 45 | sqlquery_client.max_tries = 3 46 | 47 | responses.add(responses.POST, 'https://api.dataengine.cloud.ibm.com/v2/sql_jobs', 48 | json={'errors': [{'message': mock_error_message}]}, status=429) 49 | responses.add(responses.POST, 'https://api.dataengine.cloud.ibm.com/v2/sql_jobs', 50 | json={'errors': [{'message': mock_error_message}]}, status=429) 51 | responses.add(responses.POST, 'https://api.dataengine.cloud.ibm.com/v2/sql_jobs', 52 | json={'job_id': mock_job_id}, status=201) 53 | 54 | assert sqlquery_client.submit_sql('VALUES (1)') == mock_job_id 55 | 56 | def test_get_jobs_with_status(sqlquery_client): 57 | status = "wrong" 58 | job_id_list = [] 59 | with pytest.raises(ValueError): 60 | assert sqlquery_client.get_jobs_with_status(job_id_list, status) 61 | assert str(e.value) == "`status` must be a value in ['running', 'completed', 'failed']" 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sql-query-clients [![Actions Status](https://github.com/IBM-Cloud/sql-query-clients/workflows/Python%20CI/badge.svg)](https://github.com/IBM-Cloud/sql-query-clients/actions) [![Actions Status](https://github.com/IBM-Cloud/sql-query-clients/workflows/Node.js%20CI/badge.svg)](https://github.com/IBM-Cloud/sql-query-clients/actions) 2 | 3 | This repository contains application client samples and blueprint code for the [IBM Cloud Data Engine service](https://cloud.ibm.com/catalog/services/sql-query#about). 4 | 5 | 6 | ## List of clients 7 | * [ibmcloudsql](https://github.com/IBM-Cloud/sql-query-clients/tree/master/Python) Python SDK 8 | * [Cloud Function](https://github.com/IBM-Cloud/sql-query-clients/tree/master/Python/cloud_function) for Data Engine (uses `ibmcloudsql`) 9 | * [sqlQuery](https://github.com/IBM-Cloud/sql-query-clients/tree/master/Node) Node SDK **deprecated** (Use this [Data Engine Node SDK]( https://github.com/IBM/sql-query-node-sdk) instead) 10 | * [Dremio](https://github.com/IBM-Cloud/sql-query-clients/tree/master/Dremio) connector 11 | * [Grafana](https://github.com/IBM-Cloud/sql-query-clients/tree/master/Grafana) connector 12 | 13 | ## Documentation 14 | * Please refer to [Data Engine Getting Started](https://cloud.ibm.com/docs/services/sql-query?topic=sql-query-getting-started) for general information and turorial for this service. 15 | * The documentation for [ibmcloudsql Python API](https://ibm-cloud.github.io/sql-query-clients/) 16 | * How to use `ibmcloudsql` in a [Python Notebook](https://dataplatform.cloud.ibm.com/exchange/public/entry/view/4a9bb1c816fb1e0f31fec5d580e4e14d) 17 | 18 | ## How to generate docs 19 | 20 | Requirement: conda 21 | 22 | * In the docs folder, you run [if you don’t have sphinx and required package yet] 23 | 24 | `make setup` 25 | 26 | This create `sphinx` environment. Make sure you're in this environment each time you run the below commands 27 | 28 | * Get docstring updated 29 | 30 | `make python` 31 | 32 | * generate docs in local file (in /tmp/sql-query-clients-docs/html - for the purpose of local check the output) 33 | 34 | `make html` 35 | 36 | Check the output by opening the browser with the URL: `file:///tmp/sql-query-clients-docs/html/index.html` 37 | 38 | * (check the local content carefully before running this) generate and commit to the server [only those with proper priviledges] 39 | 40 | `make buildandcommithtml` 41 | -------------------------------------------------------------------------------- /Samples/sample-payload-iotmessages.avro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM-Cloud/sql-query-clients/1963cca543dccd2734aa66bdb0229c862516cfdb/Samples/sample-payload-iotmessages.avro -------------------------------------------------------------------------------- /cosaccess/.gitignore: -------------------------------------------------------------------------------- 1 | ### Egg info 2 | *.egg-info 3 | *.egg 4 | *.EGG 5 | *.EGG-INFO 6 | *.DS_Store 7 | 8 | ### Local Python env and test 9 | cosaccess/bin 10 | cosaccess/.Python 11 | cosaccess/lib 12 | cosaccess/include 13 | cosaccess/pip-selfcheck.json 14 | cosaccess/test_credentials.py 15 | 16 | ### Build and distribution 17 | build/ 18 | dist/ 19 | 20 | ### Compiled and backup files 21 | __pycache__ 22 | *.pyc 23 | *.pyo 24 | *.tmp* 25 | *.bak 26 | .coverage 27 | .pytest_cache 28 | .venv 29 | -------------------------------------------------------------------------------- /cosaccess/README.md: -------------------------------------------------------------------------------- 1 | # `cosaccess` Package for Fine-grained Access Management on COS 2 | See also this [introduction video](https://www.youtube.com/watch?v=pfl0qFRpKc0). 3 | 4 | This library is provided as is and is not an official client sdk library. As such support tickets can not be raised for this package. 5 | 6 | The purpose of the `cosaccess` package is to provide an easy to use interface to data engineers and data lake administrators to set up and manage table level access control for IBM Cloud Object Storage (COS) based data lakes. It also minimizes the required information that you need to have at hand. Basically, you just bring your API key and the COS bucket name for your data lake and you can go from there. The following diagram illustrates the multitudes of SDKs and endpoints that you would normally be required to understand and consume. 7 |
8 | 9 | 10 | 11 | ![](cosaccess.png?raw=true) 12 | 13 | Following list of SDKs and APIs are being consumed and abstracted by `cosaccess`: 14 | * [IAM Identity API](https://cloud.ibm.com/apidocs/iam-identity-token-api?code=python#list-service-ids) 15 | * [IAM Token Service API](https://cloud.ibm.com/docs/account?topic=account-iamtoken_from_apikey) 16 | * [IAM Policy API](https://cloud.ibm.com/apidocs/iam-policy-management?code=python#list-policies) 17 | * [IAM Users API](https://cloud.ibm.com/apidocs/user-management?code=python#list-users) 18 | * [IAM Access Group API](https://cloud.ibm.com/apidocs/iam-access-groups?code=python#introduction) 19 | * [COS API (ibm_boto3)](https://cloud.ibm.com/docs/cloud-object-storage?topic=cloud-object-storage-python) 20 | * [COS Resource Configuration API](https://cloud.ibm.com/apidocs/cos/cos-configuration?code=python#getbucketconfig) 21 | 22 | ## Setup 23 | ``` 24 | pip install cosaccess 25 | ``` 26 | 27 | Manage IBM COS access privileges on bucket and object level 28 | 29 | ## Example usage 30 | ``` 31 | from cosaccess import CosAccessManager 32 | cosaccess = CosAccessManager('') 33 | cosaccess.get_policies_for_cos_bucket('') 34 | ``` 35 | 36 | ## Demo 37 | You can find a fully reproducible end-to-end demo in the [COS FGAC Demo.ipynb](COS%20FGAC%20Demo.ipynb) notebook here in this repository. An extended variant of the same demo can be found in the [Data Engine FGAC Demo.ipynb](Data%20Engine%20FGAC%20Demo.ipynb) notebook also here in the repository. 38 | 39 | You can run these notebooks yourself using Jupyter as follows: 40 | 41 | ```bash 42 | # Clone the Repository 43 | git clone https://github.com/IBM-Cloud/sql-query-clients.git 44 | 45 | # Change directory 46 | cd sql-query-clients/cosaccess 47 | 48 | # Set up your virtual environment 49 | source ./setup_env.sh 50 | 51 | # Optionally you can set the following environment variables to avoid entering them in the notebook interactively: 52 | export FGACDEMO_APIKEY= 53 | export FGACDEMO_BUCKET= 54 | export FGACDEMO_DATAENGINE_INSTANCE= 55 | 56 | # Install Jupyter 57 | pip install jupyter 58 | 59 | # Run Jupyter 60 | jupyter notebook 61 | 62 | ``` 63 | 64 | ## CosAccessManager method list 65 | ### Initialization 66 | * `CosAccessManager(apikey, account_id=None)` Constructor. `apikey`: IAM API key. `account_id`: The IBM Cloud account holding the COS instance(s) for which you want to manage access. Default is the account of the provided API Key. 67 | ### Retrieving Access Policies 68 | * `get_policies_for_cos_bucket(cosBucket, prefix = None, roles = None):` Returns a dataframe with all policies defined on the COS bucket. When prefix is provided the results only show polcies that are relevant for access to that prefix path. When a list of roles is provided only policies that assign at least on of these roles are returned. 69 | * `get_policy(policy_id)` Returns a JSON dict with all policy details for the provided policy ID 70 | * `list_policies(roles = None)` Returns an array of JSON dicts with all policies and their details in the account. When a list of roles is provided only policies that assign at least on of these roles are returned. 71 | * `list_policies_for_service(serviceName, roles = None)` Returns an array of JSON dicts with all policies and their details specified for the provided service type (e.g., `cloud-object-storage`). When a list of roles is provided only policies that assign at least on of these roles are returned. 72 | * `list_policies_for_cos_instance(cosServiceInstance, roles = None)` Returns an array of JSON dicts with all policies and their details specified COS service instance ID. When a list of roles is provided only policies that assign at least on of these roles are returned. 73 | * `list_policies_for_cos_bucket(cosBucket, prefix = None, roles = None):` Returns an array of JSON dicts with all policies defined on the COS bucket. When prefix is provided the results only show polcies that are relevant for access to that prefix path. When a list of roles is provided only policies that assign at least on of these roles are returned. 74 | ### CRUD for Access Policies 75 | * `grant_bucket_access(roles, cos_bucket, prefixes = None, access_group = None, iam_id = None)` Create new access policy for the COS bucket and optionally prefix 76 | * `update_bucket_access(policy_id, roles, cos_bucket, prefixes = None, access_group = None, iam_id = None)` Overwrites an existing access policy for the COS bucket and optionally prefix 77 | * `remove_bucket_access(policy_id)` Deletes an existing access policy 78 | ### COS Helper Methods 79 | * `get_cos_instance_id(bucket)` Returns the instance ID of the COS instance holding the provided COS bucket 80 | * `get_cos_instance_crn(bucket)` Returns the instance CRN of the COS instance holding the provided COS bucket 81 | * `list_cos_endpoints()` Returns a JSON dict with all endpoints for COS supported by IBM Cloud 82 | * `get_cos_endpoint(cos_bucket)` Returns the endpoint for a bucket 83 | * `touch_cos_object(cos_bucket, object_path)` Creates an empty object in the specified bucket and with the specified path. Similar to `touch` shell command 84 | * `delete_cos_object(cos_bucket, object_path)` Delete the object with the specified path from the specified bucket 85 | * `get_cos_objects(cos_bucket, prefix)` Returns a dataframe with all objects in the specified bucket and prefix 86 | ### Working with Users 87 | * `get_users()` Returns a dataframe with all users and their details in the account 88 | * `get_user_iam_id(user_id):` Get the IAM ID of a user specified by name (IBM ID email address) 89 | * `get_user_name(iam_id)` Get user name in format ` ` for a given IAM ID 90 | ### Working with Service IDs 91 | * `get_service_ids()` Returns a dataframe with all Service IDs and their details in the account 92 | * `get_service_id_iam_id(service_id):` Get the IAM ID of a Service ID specified by Service ID name 93 | * `get_service_id_name(iam_id)` Get Service ID name for a given IAM ID 94 | * `get_service_id_details(service_id_name, service_id)` Return the details of a service ID identified by either name or ID. 95 | * `create_service_id(service_id_name, with_apikey)` Create a new service ID. When optional parameter with_apikey is set to True there will also be an API krey created and assoctiated with the new service ID 96 | * `delete_service_id(service_id_name, service_id)` Delete a service ID identified by either name or ID. 97 | ### Working with Access Groups 98 | * `get_access_groups()` Returns a dataframe with all acces groups and their details in the account 99 | * `get_access_group_id(access_group)` Get the access group ID for an access group name 100 | * `get_access_group_name(access_group_id)` Get the access group name for an access group ID 101 | * `get_access_group_members(access_group_name, access_group_id)` Return a dataframe with all members of an access group identified by either name or ID 102 | * `add_member_to_access_group(access_group_name, access_group_id, user_name, user_id, service_id_name, service_id)` Add a new member (either a user or a Service ID) to an access group identified by either name or ID 103 | * `delete_member_from_access_groupaccess_group_name, access_group_id, user_name, user_id, service_id_name, service_id)` Remove a member (either a user or a Service ID) from an access group identified by either name or ID 104 | * `create_access_group(access_group_name)` Create a new access group 105 | * `delete_access_group(access_group_name, access_group_id, force)` Delete an access group identified by either name or ID. Set force to True to delete the group also when it still has members. 106 | * `get_access_group_members(access_group_name, access_group_id)` Show all members of the access group identified by either name or ID. 107 | * `add_member_to_access_group(access_group_name, access_group_id, user_name, user_id, service_id_name, service_id)` Add new member (user or service ID, specified by ither ID or name) to the access group identified by either name or ID. 108 | * `delete_member_from_access_group(access_group_name, access_group_id, user_name, user_id, service_id_name, service_id)` Remove new member (user or service ID, specified by ither ID or name) from the access group identified by either name or ID. 109 | 110 | ## Building and testing the library locally 111 | ### Set up Python environment 112 | Run `source ./setup_env.sh` which creates and activates a clean virtual Python environment. 113 | ### Install the local code in your Python environment 114 | Run `./_install.sh`. 115 | ### Test the library locally 116 | 1. Create a file `cosaccess/test_credentials.py` with the IBM Cloud IAM API Key: 117 | ``` 118 | apikey='' 119 | ``` 120 | you can use the template file `cosaccess/test_credentials.py.template` 121 | 122 | 2. Run `python cosaccess/test.py`. 123 | 124 | ### Packaging and publishing distribution 125 | 1. Make sure to increase `version=...` in `setup.py` before creating a new package. 126 | 2. Run `package.sh`. It will prompt for user and password that must be authorized for package `cosaccess` on pypi.org. 127 | -------------------------------------------------------------------------------- /cosaccess/_install.sh: -------------------------------------------------------------------------------- 1 | pip install --no-deps --force-reinstall . 2 | 3 | -------------------------------------------------------------------------------- /cosaccess/cosaccess.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM-Cloud/sql-query-clients/1963cca543dccd2734aa66bdb0229c862516cfdb/cosaccess/cosaccess.png -------------------------------------------------------------------------------- /cosaccess/cosaccess.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM-Cloud/sql-query-clients/1963cca543dccd2734aa66bdb0229c862516cfdb/cosaccess/cosaccess.pptx -------------------------------------------------------------------------------- /cosaccess/cosaccess/__init__.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # Copyright IBM Corp. 2023 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ------------------------------------------------------------------------------ 16 | 17 | __version__ = "0.1.18" 18 | # flake8: noqa F401 19 | from .cosaccess import CosAccessManager 20 | -------------------------------------------------------------------------------- /cosaccess/cosaccess/pyvenv.cfg: -------------------------------------------------------------------------------- 1 | home = /Library/Frameworks/Python.framework/Versions/3.11/bin 2 | implementation = CPython 3 | version_info = 3.11.5.final.0 4 | virtualenv = 20.24.6 5 | include-system-site-packages = false 6 | base-prefix = /Library/Frameworks/Python.framework/Versions/3.11 7 | base-exec-prefix = /Library/Frameworks/Python.framework/Versions/3.11 8 | base-executable = /Library/Frameworks/Python.framework/Versions/3.11/bin/python3 9 | -------------------------------------------------------------------------------- /cosaccess/cosaccess/test.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is the test script 3 | """ 4 | # flake8: noqa W191 5 | import pandas as pd 6 | import cosaccess # noqa 7 | import test_credentials # noqa 8 | from cosaccess import CosAccessManager 9 | 10 | pd.set_option("display.max_colwidth", None) 11 | pd.set_option("display.max_columns", 20) 12 | 13 | print("Initializing CosAccessManager...") 14 | cosaccess = CosAccessManager(apikey=test_credentials.apikey) 15 | 16 | print("get_users()...") 17 | print(cosaccess.get_users().head(5)) 18 | 19 | print("get_service_ids()...") 20 | print(cosaccess.get_service_ids().head(5)) 21 | 22 | print("get_access_groups()...") 23 | print(cosaccess.get_access_groups().head(5)) 24 | 25 | print("get_policies_for_cos_bucket()...") 26 | print(cosaccess.get_policies_for_cos_bucket("sqltempregional").head(5)) 27 | 28 | print("get_policies_for_cos_bucket()...") 29 | print(cosaccess.get_policies_for_cos_bucket("results").head(5)) 30 | 31 | print("get_policies_for_cos_bucket() with prefix...") 32 | print(cosaccess.get_policies_for_cos_bucket("results", "data_mart/table1/foo").head(5)) 33 | 34 | -------------------------------------------------------------------------------- /cosaccess/cosaccess/test_credentials.py.template: -------------------------------------------------------------------------------- 1 | apikey='' -------------------------------------------------------------------------------- /cosaccess/package.sh: -------------------------------------------------------------------------------- 1 | python setup.py register sdist 2 | 3 | VERSION=$(sed -n 's/^Version: *\([^ ]*\)/\1/p' cosaccess.egg-info/PKG-INFO) 4 | if [[ -z "${VERSION}" ]]; then echo 'Error: No package version found.'; exit 1; fi 5 | TAG="pip-${VERSION}" 6 | echo "Tagging current branch as $TAG" 7 | git tag -a ${TAG} -m "Release ${VERSION} of ibmcloudsql pip package" 8 | git push origin ${TAG} 9 | 10 | echo "Publishing ibmcloudsql version ${VERSION}" 11 | twine upload --skip-existing dist/* -------------------------------------------------------------------------------- /cosaccess/setup.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # Copyright IBM Corp. 2018 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ------------------------------------------------------------------------------ 16 | """Setup file.""" 17 | import codecs 18 | import os.path 19 | 20 | from setuptools import setup 21 | # read the contents of your README file 22 | from pathlib import Path 23 | this_directory = Path(__file__).parent 24 | long_description = (this_directory / "README.md").read_text() 25 | 26 | 27 | def readme(): 28 | """Return README.rst content.""" 29 | with open("README.rst") as f: 30 | return f.read() 31 | 32 | 33 | def _read(rel_path): 34 | here = os.path.abspath(os.path.dirname(__file__)) 35 | with codecs.open(os.path.join(here, rel_path), "r") as fp: 36 | return fp.read() 37 | 38 | 39 | def get_version(rel_path): 40 | """Get package version.""" 41 | for line in _read(rel_path).splitlines(): 42 | if line.startswith("__version__"): 43 | delim = '"' if '"' in line else "'" 44 | return line.split(delim)[1] 45 | else: 46 | raise RuntimeError("Unable to find version string.") 47 | 48 | 49 | setup( 50 | name="cosaccess", 51 | version=get_version("cosaccess/__init__.py"), 52 | python_requires=">=2.7, <4", 53 | install_requires=[ 54 | "pandas", 55 | "ibm_cloud_sdk_core", 56 | "ibm_platform_services", 57 | "ibm-cos-sdk-config", 58 | "ibm-cos-sdk", 59 | "requests", 60 | ], 61 | description="Python client for managing IAM policies fine grained access control in IBM Cloud Object Storage", # noqa 62 | long_description=long_description, 63 | long_description_content_type='text/markdown', 64 | url="https://github.com/IBM-Cloud/sql-query-clients/cosaccess", 65 | author="IBM Corp.", 66 | author_email="torsten@de.ibm.com", 67 | license="Apache 2.0", 68 | classifiers=[ 69 | "License :: OSI Approved :: Apache Software License", 70 | "Programming Language :: Python :: 2.7", 71 | "Programming Language :: Python :: 3.8", 72 | "Topic :: Database", 73 | ], 74 | keywords="cloud object_storage IBM", 75 | packages=["cosaccess"], 76 | ) 77 | -------------------------------------------------------------------------------- /cosaccess/setup_env.sh: -------------------------------------------------------------------------------- 1 | pip3 install virtualenv 2 | virtualenv -p `which python3` cosaccess 3 | # Run this outside of the script in your local shell: 4 | source cosaccess/bin/activate 5 | pip3 install twine 6 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | #SPHINXBUILD = sphinx-build 7 | SPHINXBUILD = python3 -msphinx 8 | SPHINXPROJ = SQLClient 9 | SOURCEDIR = source 10 | #BUILDDIR = build 11 | BUILDDIR = /tmp/sql-query-clients-docs 12 | PDFBUILDDIR = /tmp 13 | PDF = ../manual.pdf 14 | 15 | # Put it first so that "make" without argument is like "make help". 16 | help: 17 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 18 | 19 | .PHONY: help Makefile 20 | 21 | # Catch-all target: route all unknown targets to Sphinx using the new 22 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 23 | %: Makefile 24 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 25 | 26 | $(BUILDDIR)/html: 27 | mkdir $(BUILDDIR) 28 | cd $(BUILDDIR); git clone git@github.com:IBM-Cloud/sql-query-clients.git html; cd html; git checkout -b gh-pages remotes/origin/gh-pages 29 | 30 | html: | $(BUILDDIR)/html 31 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 32 | 33 | # Internal variables. 34 | PAPEROPT_a4 = -D latex_paper_size=a4 35 | PAPEROPT_letter = -D latex_paper_size=letter 36 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 37 | 38 | setup_conda: 39 | conda create -n sphinx python=3.6 40 | conda activate sphinx 41 | make setup 42 | echo Make sure you're in 'sphinx' virtual environment each time you build the docs 43 | 44 | setup: 45 | pip install numpydoc m2r recommonmark sphinx==2.4.4 --force-reinstall 46 | pip install python-dateutil backoff numpy pandas ibm-cos-sdk sqlparse deprecated 47 | 48 | latexpdf: 49 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(PDFBUILDDIR)/latex 50 | @echo "Running LaTeX files through pdflatex..." 51 | make -C $(PDFBUILDDIR)/latex all-pdf 52 | cp $(PDFBUILDDIR)/latex/*.pdf $(PDF) 53 | @echo "pdflatex finished; see PDF files in $(PDF)" 54 | python: 55 | rm source/ibmcloudsql.rst 56 | sphinx-apidoc -f -o source ../Python/ibmcloudsql ... ../Python/ibmcloudsql/test* 57 | 58 | buildandcommithtml: html 59 | cd $(BUILDDIR)/html; git add . ; git commit -m "rebuilt docs"; git push origin gh-pages 60 | echo Goto https://ibm-cloud.github.io/sql-query-clients/ to see the new docs. 61 | -------------------------------------------------------------------------------- /docs/source/_static/css/custom.css: -------------------------------------------------------------------------------- 1 | /* 2 | div.sphinxsidebar { 3 | max-height: 100%; 4 | overflow-y: auto; 5 | } 6 | 7 | div.sphinxsidebar #searchbox input[type="text"] { 8 | width: 160px; 9 | } 10 | 11 | div.sphinxsidebar .search > div { 12 | display: table-cell; 13 | } 14 | */ -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/master/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | import os 16 | import sys 17 | sys.path.insert(0, os.path.abspath('../../')) 18 | sys.path.insert(0, os.path.abspath('../../Python/')) 19 | sys.path.insert(0, os.path.abspath('../../Python/ibmcloudsql/')) 20 | sys.setrecursionlimit(1500) 21 | import recommonmark 22 | from recommonmark.parser import CommonMarkParser 23 | import m2r 24 | 25 | # -- Project information ----------------------------------------------------- 26 | 27 | project = 'ibmcloudsql' 28 | copyright = '2020, IBM Research' 29 | author = 'Tuan M. HoangTrong' 30 | 31 | # The short X.Y version 32 | version = '0.0' 33 | # The full version, including alpha/beta/rc tags 34 | release = 'alpha' 35 | 36 | # -- General configuration --------------------------------------------------- 37 | 38 | # If your documentation needs a minimal Sphinx version, state it here. 39 | # 40 | # needs_sphinx = '1.0' 41 | 42 | # Add any Sphinx extension module names here, as strings. They can be 43 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 44 | # ones. 45 | extensions = [ 46 | 'sphinx.ext.autodoc', 47 | 'sphinx.ext.doctest', 48 | 'sphinx.ext.todo', 49 | 'sphinx.ext.coverage', 50 | 'sphinx.ext.mathjax', 51 | 'sphinx.ext.ifconfig', 52 | 'sphinx.ext.viewcode', 53 | 'sphinx.ext.githubpages', 54 | 'numpydoc', 55 | #'sphinx.ext.autosummary', 56 | #'sphinx.ext.inheritance_diagram', 57 | #'recommonmark', 58 | 'm2r', 59 | 'sphinx.ext.napoleon', 60 | 'sphinx.ext.graphviz' 61 | ] 62 | 63 | # Add any paths that contain templates here, relative to this directory. 64 | templates_path = ['_templates'] 65 | 66 | # The suffix(es) of source filenames. 67 | # You can specify multiple suffix as a list of string: 68 | # 69 | source_parsers = { 70 | #'.md': CommonMarkParser, 71 | } 72 | 73 | source_suffix = ['.rst', '.md'] 74 | #source_suffix = '.rst' 75 | # source_suffix = { 76 | # '.rst': 'restructuredtext', 77 | # '.txt': 'markdown', 78 | # '.md': 'markdown', 79 | # } 80 | 81 | # The master toctree document. 82 | master_doc = 'index' 83 | 84 | # The language for content autogenerated by Sphinx. Refer to documentation 85 | # for a list of supported languages. 86 | # 87 | # This is also used if you do content translation via gettext catalogs. 88 | # Usually you set "language" from the command line for these cases. 89 | language = None 90 | 91 | # List of patterns, relative to source directory, that match files and 92 | # directories to ignore when looking for source files. 93 | # This pattern also affects html_static_path and html_extra_path . 94 | exclude_patterns = [] 95 | 96 | # The name of the Pygments (syntax highlighting) style to use. 97 | # pygments_style = 'sphinx' 98 | pygments_style = None 99 | 100 | # -- Options for HTML output ------------------------------------------------- 101 | 102 | # The theme to use for HTML and HTML Help pages. See the documentation for 103 | # a list of builtin themes. 104 | # 105 | html_theme = 'alabaster' 106 | # html_theme = "classic" 107 | 108 | html_theme_path = ['_themes'] 109 | 110 | # Theme options are theme-specific and customize the look and feel of a theme 111 | # further. For a list of options available for each theme, see the 112 | # documentation. 113 | # 114 | if html_theme == "classic": 115 | html_theme_options = {"rightsidebar": "true", "relbarbgcolor": "black"} 116 | elif html_theme == "alabaster": 117 | html_theme_options = { 118 | "fixed_sidebar": True, 119 | } 120 | 121 | # Add any paths that contain custom static files (such as style sheets) here, 122 | # relative to this directory. They are copied after the builtin static files, 123 | # so a file named "default.css" will overwrite the builtin "default.css". 124 | html_static_path = ['_static'] 125 | html_css_files = [ 126 | 'css/custom.css', 127 | ] 128 | html_js_files = [ 129 | 'js/custom.js', 130 | ] 131 | 132 | # Custom sidebar templates, must be a dictionary that maps document names 133 | # to template names. 134 | # 135 | # The default sidebars (for documents that don't match any pattern) are 136 | # defined by theme itself. Builtin themes are using these templates by 137 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 138 | # 'searchbox.html']``. 139 | # 140 | # html_sidebars = {} 141 | if html_theme == "alabaster": 142 | html_sidebars = { 143 | '**': [ 144 | 'about.html', 145 | 'navigation.html', 146 | 'relations.html', 147 | 'searchbox.html', 148 | 'donate.html', 149 | ] 150 | } 151 | # html_sidebars = { 152 | # '**': [ 153 | # 'localtoc.html', 154 | # 'relations.html', 155 | # 'searchbox.html', 156 | # # located at _templates/ 157 | # #'foo.html', 158 | # ] 159 | 160 | # } 161 | 162 | # -- Options for HTMLHelp output --------------------------------------------- 163 | 164 | # Output file base name for HTML help builder. 165 | htmlhelp_basename = 'SQLClientdoc' 166 | 167 | # -- Options for LaTeX output ------------------------------------------------ 168 | 169 | latex_elements = { 170 | # The paper size ('letterpaper' or 'a4paper'). 171 | # 172 | # 'papersize': 'letterpaper', 173 | 174 | # The font size ('10pt', '11pt' or '12pt'). 175 | # 176 | # 'pointsize': '10pt', 177 | 178 | # Additional stuff for the LaTeX preamble. 179 | # 180 | # 'preamble': '', 181 | 182 | # Latex figure (float) alignment 183 | # 184 | # 'figure_align': 'htbp', 185 | } 186 | 187 | # Grouping the document tree into LaTeX files. List of tuples 188 | # (source start file, target name, title, 189 | # author, documentclass [howto, manual, or own class]). 190 | latex_documents = [ 191 | (master_doc, 'SQLClient.tex', 192 | 'SQLClient Documentation', 'Tuan M. HoangTrong', 'manual'), 193 | ] 194 | 195 | # -- Options for manual page output ------------------------------------------ 196 | 197 | # One entry per manual page. List of tuples 198 | # (source start file, name, description, authors, manual section). 199 | man_pages = [(master_doc, 'timeseries-sqlclient', 200 | 'SQLClient Documentation', [author], 1)] 201 | 202 | # -- Options for Texinfo output ---------------------------------------------- 203 | 204 | # Grouping the document tree into Texinfo files. List of tuples 205 | # (source start file, target name, title, author, 206 | # dir menu entry, description, category) 207 | texinfo_documents = [ 208 | (master_doc, 'SQLClient', 'SQLClient Documentation', 209 | author, 'SQLClient', 'One line description of project.', 210 | 'Miscellaneous'), 211 | ] 212 | 213 | # -- Extension configuration ------------------------------------------------- 214 | 215 | # -- Options for todo extension ---------------------------------------------- 216 | 217 | # If true, `todo` and `todoList` produce output, else they produce nothing. 218 | todo_include_todos = True 219 | -------------------------------------------------------------------------------- /docs/source/cos.rst: -------------------------------------------------------------------------------- 1 | .. _cos-label: 2 | 3 | COSClient Class 4 | ================================================ 5 | 6 | :mod:`ibmcloudsql.cos` provides 3 classes 7 | 8 | * :py:class:`ibmcloudsql.cos.ParsedUrl` class that provides APIs to extract information from a given URL, for example from a COS URL. 9 | * :py:class:`ibmcloudsql.cos.COSClient` class that does what ParsedUrl can and provides APIs to interact with COS (an extension to ibm-cos-sdk). 10 | * :py:class:`ibmcloudsql.cos.ProjectLib` class that provides APIs to read/write data to a project's COS. 11 | 12 | Parsed URL 13 | ----------- 14 | 15 | :class:`.ParsedUrl` provides the APIs to parse a COS URL. 16 | 17 | Project Lib 18 | ------------ 19 | 20 | :class:`ProjectLib` class maintains a reference to an object in IBM Watson Studio's ProjectLib 21 | that can read a file stored as an asset and load them into the current notebook. 22 | 23 | This file can be used in a number of scenarios, such as: 24 | 25 | 1. Store/reload progress information 26 | 2. Load external Python packages 27 | 3. Store/reload data files 28 | 29 | *Example*: 30 | When SQLQuery launches many jobs, there is a chance that the progress is stopped, and 31 | you don't want to restart from the beginning. The above setting enables the SQLQuery to skip the 32 | completed sql queries. The ``file_name`` argument is a reference to the name of the file that you can 33 | use for saving and restoring the job progress. 34 | 35 | .. code-block:: python 36 | 37 | from project_lib import Project 38 | project = Project(project_id=your_project_id, 39 | project_access_token=your_project_cos_acess_token) 40 | 41 | # default file extension: json 42 | file_name = "aiops" 43 | sqlClient.connect_project_lib(project, file_name) 44 | 45 | 46 | 47 | COS Client 48 | ----------- 49 | 50 | :class:`.COSClient` is also an :class:`.IBMCloudAccess` and a :class:`.ParsedUrl`. 51 | 52 | COSClient class further provides the following APIs: 53 | 54 | .. 1. interact with COS URL: based on :py:class:`ibmcloudsql.cos.ParsedUrl` class 55 | 56 | * To interact with the COS instance: 57 | 58 | 1. :meth:`.delete_objects` 59 | 2. :meth:`.delete_empty_objects` 60 | 3. :meth:`.list_cos_objects` 61 | 4. :meth:`.update_bucket` 62 | 5. :meth:`.get_bucket_info` 63 | 6. :meth:`.get_cos_summary` 64 | 65 | * To interact with the ProjectLib's data: 66 | 67 | 1. :meth:`.connect_project_lib` 68 | 2. :meth:`.read_project_lib_data` 69 | 3. :meth:`.write_project_lib_data` 70 | -------------------------------------------------------------------------------- /docs/source/ibmcloudsql.rst: -------------------------------------------------------------------------------- 1 | ibmcloudsql package 2 | =================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | SQL Query module 8 | --------------------------- 9 | 10 | .. automodule:: ibmcloudsql.SQLQuery 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | Catalog/Table module 16 | --------------------------------- 17 | 18 | .. automodule:: ibmcloudsql.catalog_table 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | Cloud Object Storage module 24 | ---------------------------- 25 | 26 | .. automodule:: ibmcloudsql.cos 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | Exceptions module 32 | ----------------------------- 33 | 34 | .. automodule:: ibmcloudsql.exceptions 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | Magic module 40 | ----------------------------- 41 | 42 | .. automodule:: ibmcloudsql.sql_magic 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | Time-Series SQL Query module 48 | --------------------------------- 49 | 50 | .. automodule:: ibmcloudsql.sql_query_ts 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | 55 | Utilities module 56 | ---------------------------- 57 | 58 | .. automodule:: ibmcloudsql.utilities 59 | :members: 60 | :undoc-members: 61 | :show-inheritance: 62 | 63 | 64 | Module contents 65 | --------------- 66 | 67 | .. automodule:: ibmcloudsql 68 | :members: 69 | :undoc-members: 70 | :show-inheritance: 71 | -------------------------------------------------------------------------------- /docs/source/includeme.rst: -------------------------------------------------------------------------------- 1 | Github Repository 2 | ================================================ 3 | 4 | Github repository for `ibmcloudsql `_. 5 | 6 | Following is the content of the repository's `README `_: 7 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. TimeSeries-SQLClient documentation master file, created by 2 | sphinx-quickstart on Thu Mar 5 09:15:41 2020. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | ibmcloudsql Python SDK documentation 7 | ================================================ 8 | 9 | .. toctree:: 10 | :maxdepth: 3 11 | :caption: Contents: 12 | 13 | intro 14 | sql_query 15 | cos 16 | sql_magic 17 | utilities 18 | modules 19 | includeme 20 | 21 | 22 | Indices and tables 23 | ================== 24 | 25 | * :ref:`genindex` 26 | * :ref:`modindex` 27 | * :ref:`search` 28 | -------------------------------------------------------------------------------- /docs/source/intro.rst: -------------------------------------------------------------------------------- 1 | Introduction 2 | ================================================ 3 | 4 | ibmcloudsql 5 | ------------------------ 6 | 7 | The **ibmcloudsql** library provides to Python applications the APIs for working with structured data stored on IBM Cloud Object Storage using SQL language, which involves multiple resources/catalogs: 8 | 9 | 1. `IAM `_ that controls access to all IBM cloud catalogs/resources. 10 | 2. `IBM COS `_ where input and outpout data is stored. 11 | 3. `IBM Cloud SQL Query `_ that offers the SQL-based data processing service. 12 | 4. `IBM Watson Studio `_ (optional): that provides notebook environment for running notebook, in that (Python) code and data asset are also stored, implicitly, in the Project's IBM Cloud Object Storage instance. Such project asset can be accessed using the `project-lib library `_. 13 | 14 | .. 15 | 5. Visualization: can be done with Python code via IBM Watson Studio's notebook. 16 | 6. Visualization: can be done via ... 17 | 7. The back-end server may be running on `IBM Cloud Function `_. 18 | 19 | The **ibmcloudsql** library comes with multiple submodules, each contains one or many classes. The classes relate to each other via subclass mechanism. The submodules are the following: 20 | 21 | * :ref:`utilities ` provides access to (1) - the IBM Cloud service connectivity-related functionality. 22 | * :ref:`cos ` provides access to (1, 2, 4) - the COS-related functionality. 23 | * :ref:`sql_magic ` provides the capability to construct a complex SQL statement, including time-series-related functionality. 24 | * :ref:`SQLQuery ` provides access to (3) and to those provided by these submodules (utilities, cos, sql_magic). 25 | 26 | For getting started, use this `starter notebook `_. 27 | which provides notebook-based examples of user-specific code that also utilizes above submodules. 28 | 29 | install 30 | ------------------------ 31 | 32 | To install the published release of **ibmcloudsql** from `PyPi `_, run the following: 33 | 34 | .. code-block:: console 35 | 36 | pip install ibmcloudsql 37 | 38 | For development purpose, you can also checkout the source, and then install in editable mode: 39 | 40 | .. code-block:: console 41 | 42 | git clone git@github.com:IBM-Cloud/sql-query-clients.git 43 | cd sql-query-client/Python 44 | pip install -e . 45 | 46 | To run the test on the package, run one of the following: 47 | 48 | .. code-block:: console 49 | 50 | python -m pytest 51 | 52 | pytest 53 | 54 | -------------------------------------------------------------------------------- /docs/source/modules.rst: -------------------------------------------------------------------------------- 1 | API Reference 2 | ============= 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | ibmcloudsql 8 | -------------------------------------------------------------------------------- /docs/source/sql_magic.rst: -------------------------------------------------------------------------------- 1 | .. _sql-magic-label: 2 | 3 | SQLBuilder Class 4 | ================================================ 5 | 6 | :mod:`ibmcloudsql.sql_magic` provides the following three classes: 7 | 8 | * :py:class:`.TimeSeriesTransformInput` class: provides utilities for mapping user-friendly APIs to library-friendly APIs 9 | * :py:class:`.TimeSeriesSchema` class: provides hints about the schema of the data to support :meth:`.get_ts_datasource` 10 | * :py:class:`.SQLBuilder` class: provides APIs to help constructing a complete SQL query without knowing the details about syntax specific to IBM Cloud SQL 11 | 12 | Time series transform input 13 | --------------------------- 14 | 15 | A :class:`.TimeSeriesTransformInput` class: provides utilities for mapping from user-friendly time series query into library-friendly time series query 16 | 17 | Example: 18 | 19 | .. code-block:: console 20 | 21 | sql_stmt = """ 22 | ts_segment_by_time(ts, week, week) 23 | """ 24 | sql_stmt = """ 25 | ts_segment_by_time(ts, 604800000, 604800000) 26 | """ 27 | 28 | * :meth:`.transform_sql`: the decorator that is applied on the :meth:`.SQLBuilder.print_sql` and :meth:`.SQLBuilder.format_` methods 29 | * :meth:`.ts_segment_by_time` 30 | 31 | Time series schema 32 | ------------------------ 33 | 34 | * :attr:`unixtime_columns`: shows which columns contain time stamp data in Unix time format 35 | 36 | Time series schema SQL Magic 37 | ----------------------------- 38 | 39 | A :class:`.SQLBuilder` class is also a :class:`.TimeSeriesSchema` class. 40 | 41 | * :meth:`.reset_`: resets the internal storage of an SQL statement (use this before constructing a new one) 42 | * :meth:`.print_sql`: prints and checks the current content of an SQL statement 43 | * :meth:`.get_sql`: returns the string representation of the SQL statement 44 | 45 | * :meth:`.with_`: provides table name and the SQL query for that table 46 | * :meth:`.select_`: provides column names 47 | * :meth:`.from_table_`: the table name 48 | * :meth:`.from_cos_`: provides COS URL and format of data via `format_type` option 49 | * :meth:`.from_view_`: provides SQL statement that returns a view 50 | * :meth:`.where_`: where condition 51 | * :meth:`.order_by_`: lists columns 52 | * :meth:`.group_by_`: lists columns 53 | * :meth:`.store_at_`: provides COS URL and format of data 54 | * :meth:`.partition_objects_`: provides number of objects 55 | * :meth:`.partition_rows_`: provides number of rows per object 56 | * :meth:`.partition_by_`: provides the string of tuple of column names for HIVE catalog partitioning 57 | * :meth:`.format_`: applies transformation needed to map user-friendly time series queries into library-friendly time series queries 58 | * :meth:`.join_cos_`: JOIN statement using COS URL 59 | * :meth:`.join_table_`: JOIN statement using table name 60 | 61 | Example: we can generate the SQL string using SQLBuilder APIs 62 | 63 | .. code-block:: python 64 | :linenos: 65 | 66 | sqlmagic = ibmcloudsql.SQLBuilder() 67 | (sqlClient 68 | .with_("humidity_location_table", 69 | (sqlmagic.select_("location") 70 | .from_view_("select count(*) as count, location from dht where humidity > 70.0 group by location") 71 | .where_("count > 1000 and count < 2000") 72 | ).reset_() 73 | ) 74 | .with_("pm_location_table", 75 | (sqlmagic.select_("location") 76 | .from_view_("select count(*) as count, location from sds group by location") 77 | .where_("count > 1000 and count < 2000") 78 | ).reset_() 79 | ) 80 | .select_("humidity_location_table.location") 81 | .from_table_("humidity_location_table") 82 | .join_table_("pm_location_table", type="inner", condition="humidity_location_table.location=pm_location_table.location") 83 | .store_at_(targeturl) 84 | ) 85 | result = sqlClient.run() 86 | 87 | This is the typically way to generate the SQL similar to the one above 88 | 89 | .. code-block:: python 90 | :linenos: 91 | 92 | stmt = """ 93 | WITH 94 | humidity_location_table AS ( 95 | -- 1. Select locations from DHT where humidity is >70% and the length is data is between 1000 and 2000 96 | SELECT location from ( 97 | SELECT 98 | COUNT(*) AS count, 99 | location 100 | FROM DHT 101 | WHERE humidity > 70.0 102 | GROUP BY location 103 | ) 104 | WHERE count > 1000 AND count < 2000 105 | ), 106 | pm_location_table AS ( 107 | -- 2. Select locations from PM where length is data is between 1000 and 2000 108 | SELECT location from ( 109 | SELECT 110 | COUNT(*) AS count, 111 | location 112 | FROM SDS 113 | GROUP BY location 114 | ) 115 | WHERE count > 1000 AND count < 2000 116 | ) 117 | -- 3. Select those locations that are present in both PM and DHT tables 118 | SELECT 119 | humidity_location_table.location 120 | FROM humidity_location_table 121 | INNER JOIN pm_location_table 122 | ON humidity_location_table.location=pm_location_table.location 123 | INTO {} 124 | """.format(targeturl) 125 | result = sqlClient.execute_sql(stmt) 126 | -------------------------------------------------------------------------------- /docs/source/sql_query.rst: -------------------------------------------------------------------------------- 1 | .. _sql_query-label: 2 | 3 | SQLQuery Class 4 | ================================================ 5 | 6 | :mod:`ibmcloudsql.SQLQuery` provides the following single class: 7 | 8 | * :py:class:`.SQLQuery` class 9 | 10 | An :class:`.SQLQuery` class is also a :class:`.COSClient` class, and an :class:`.SQLBuilder` class. 11 | This way the :class:`.SQLQuery` class acts as the central consolidated API interface for the entire ibmcloudsql module. 12 | 13 | 14 | .. code-block:: python 15 | 16 | sqlClient = SQLQuery(....) 17 | 18 | 19 | 1. :py:class:`.SQLQuery` (...): instantiate with initial settings 20 | 2. :meth:`.SQLQuery.configure`: update the setting 21 | 22 | Help 23 | ------------ 24 | .. 25 | 1. :meth:`.help` 26 | 2. :meth:`.sql_info` 27 | 3. :meth:`.get_job_demo` 28 | 4. :meth:`.get_cos_summary_demo` 29 | 5. :meth:`.list_results_demo` 30 | 31 | 1. :meth:`.analyze`: If you are stuck with a query that takes too long, try this one. It may provide you with suggestions on how to improve your query or revise your data source. 32 | 33 | Submit SQL jobs 34 | ------------------- 35 | 1. :meth:`.SQLQuery.submit_sql` 36 | 2. :meth:`.SQLQuery.run_sql`, :meth:`.SQLQuery.execute_sql` 37 | 3. :meth:`.submit_and_track_sql` 38 | 4. :meth:`.get_schema_data` 39 | 5. :meth:`.submit` and :meth:`.run`: The two new methods run an SQL statement generated by the approach provided by :class:`SQLBuilder` class. 40 | 41 | Work with query results 42 | -------------------------- 43 | 44 | 1. :meth:`.get_result` 45 | 2. :meth:`.delete_result` 46 | 3. :meth:`.rename_exact_result`: Modify the created objects on Cloud Object Storage. 47 | 4. :meth:`.rename_exact_result_joblist` 48 | 5. :meth:`.delete_empty_objects` 49 | 6. :meth:`.list_results` 50 | 51 | Manage jobs 52 | -------------- 53 | 54 | 1. :meth:`.my_jobs` 55 | 2. :meth:`.wait_for_job` 56 | 3. :meth:`.process_failed_jobs_until_all_completed` 57 | 4. :meth:`.get_job` 58 | 5. :meth:`.get_jobs` 59 | 6. :meth:`.get_number_running_jobs` 60 | 7. :meth:`.get_jobs_with_status` 61 | 8. :meth:`.get_jobs_count_with_status` 62 | 9. :meth:`.export_job_history` 63 | 64 | Manage Cloud Object Storage URL 65 | ------------------------------- 66 | 67 | See :class:`.COSClient` 68 | 69 | Manage table catalog 70 | ------------------------ 71 | 72 | From :class:`.HiveMetastore` 73 | 74 | 1. :meth:`.show_tables` 75 | 2. :meth:`.drop_all_tables` 76 | 3. :meth:`.drop_tables` 77 | 4. :meth:`.drop_table` 78 | 5. :meth:`.create_table` 79 | 6. :meth:`.create_partitioned_table`: For partitioned tables. 80 | 7. :meth:`.recover_table_partitions` 81 | 8. :meth:`.describe_table` 82 | 83 | Data skipping 84 | ---------------------- 85 | 86 | [Not available yet] 87 | 88 | Limitations 89 | ------------------------------------------------ 90 | 91 | * The SQL statement string size limit is 200KB. 92 | * Maximum five concurrent SQL queries for a standard SQL Query instance. 93 | * Maximum duration of one hour for a query job. However, many jobs can be stopped much earlier due to the current mechanism of AIM token timeout, and this token is shared across all current SQL queries. 94 | 95 | Tips 96 | ----- 97 | 98 | * Combine the SQL query if you can, as there is an overhead (and possibly $ cost) for a REST API request. However, also consider the current limit for a YARN executor of 7.5GB, so design the SQL query accordingly. It is best if the data being accessed is organized with multiple objects of ideal sizes (see below), since this enables more parallelism in the Object Storage. 99 | * Complex data can only be stored using Json or Parquet, it is faster with Parquet. 100 | * Avoid storing the data with a single object's size larger than 200MB. To check, consider using :meth:`.get_cos_summary` or :meth:`.list_results`. To resolve the issue, consider using the following: 101 | 102 | + Partition table into multiple buckets/objects type-1: PARTITION INTO BUCKETS/OBJECTS, with maximum allowed for 'x' is 50. 103 | + Partition table into multiple buckets/objects type-2: PARTITIONED EVERY ROWS. 104 | + Hive-style partitioning: PARTITION BY (col1, col2, ...). 105 | * When partitioning according to a column that has NULL values, Spark will use “__HIVE_DEFAULT_PARTITION__” in the object name, for example, /Location=__HIVE_DEFAULT_PARTITION__/. 106 | 107 | .. code-block:: python 108 | 109 | sqlClient.list_results(job_id) 110 | 111 | .. code-block:: console 112 | 113 | ObjectURL Size Bucket Object 114 | 0 cos://s3.us-south.cloud-object-storage.appdomain.cloud/sql-query-cos-access-ts/jobid=a3475263-469a-4e22-b382-1d0ae8f1d1fa 0 sql-query-cos-access-ts jobid=a3475263-469a-4e22-b382-1d0ae8f1d1fa 115 | 1 cos://s3.us-south.cloud-object-storage.appdomain.cloud/sql-query-cos-access-ts/jobid=a3475263-469a-4e22-b382-1d0ae8f1d1fa/_SUCCESS 0 sql-query-cos-access-ts jobid=a3475263-469a-4e22-b382-1d0ae8f1d1fa/_SUCCESS 116 | 2 cos://s3.us-south.cloud-object-storage.appdomain.cloud/sql-query-cos-access-ts/jobid=a3475263-469a-4e22-b382-1d0ae8f1d1fa/part-00000-e299e734-43e3-4032-b27d-b0d7e93d51c2-c000-attempt_20200318152159_0040_m_000000_0.snappy.parquet 7060033106 sql-query-cos-access-ts jobid=a3475263-469a-4e22-b382-1d0ae8f1d1fa/part-00000-e299e734-43e3-4032-b27d-b0d7e93d51c2-c000-attempt_20200318152159_0040_m_000000_0.snappy.parquet 117 | 118 | 119 | References 120 | -------------- 121 | 122 | * `Sparksql-parser `_: The module contains code with the know how to parse an SQLCloud-specific statement and transform it into a valid SQL statement. 123 | * `Grammar `_ 124 | * `Tips for data layout `_ 125 | * `Data skipping `_ 126 | 127 | SQLClientTimeSeries Class 128 | ================================================ 129 | 130 | :mod:`ibmcloudsql.sql_query_ts` provides the :py:class:`.SQLClientTimeSeries` class which is derived from :py:class:`.SQLQuery` class. The class provides APIs to make it easier to issue SQL statement that contains time-series functions. 131 | 132 | 133 | Prepare data for time series 134 | ------------------------------------- 135 | 136 | * :meth:`.get_ts_datasource` 137 | 138 | Let's assume that you create a HIVE catalog table to store the data for fast access that is used as the data source via `table_name` argument. 139 | 140 | A time series is comprised of the following: 141 | 142 | 1. The `time_stamp` information. 143 | 2. The `observation` information. 144 | 3. Category, for example, the `key`. 145 | 146 | Very often, the raw data is too dense to be digested into a time series. Such data has to be transformed into a coarser timescale, using a proper aggregated function, for example, avg() or max(). The time window during which the summarized data point is to be collected is given by passing a value to `granularity` argument, as in the following example: 147 | 148 | * `raw`: No change, just extract to a new location. 149 | * `per_sec`, or `PT1S`: Per every second. 150 | * `per_2sec`, or `PT2S`: Per every two seconds. 151 | * `per_min`, or `PT1M`: Per every minute. 152 | * `per_5min`, or `PT5M`: Per every five minutes. 153 | 154 | In general, a valid value to `granularity` follows the following conventions: 155 | 156 | * 'per_[x]sec' and 'per_[x]min' with x is divisible by 60 157 | * ISO 8601 duration standard 158 | 159 | The transformed data is then copied and saved into a new location (the time-series data source), which is specified by the following: 160 | 161 | * `cos_out`: Cloud Object Storage URL (stored as PARQUET). 162 | * `num_objects`: Split into multiple objects. 163 | * `num_rows`: Split into multiple objects based on number of rows per object. 164 | 165 | **At the end of the transformation**, the data source to be used for time series creation comprises the following three columns: 166 | 167 | * `field_name`: Representing category. 168 | * `time_stamp`: Representing the point of time at the given granularity. 169 | * `observation`: Representing the recorded information. 170 | 171 | If you use a generic name, you can quickly apply it to any data source. 172 | 173 | User-friendly SQL string 174 | ------------------------------------- 175 | 176 | Several time-series functions being used in CloudSQL accept parameters in the unit of mili-seconds, which is not user-friendly. 177 | The class provides the built-in functionality to map user-friendly name such as `hour`, `day`, `2hour`, `week` into the right unit, before sending the query to CloudSQL service. 178 | -------------------------------------------------------------------------------- /docs/source/utilities.rst: -------------------------------------------------------------------------------- 1 | .. _utilities-label: 2 | 3 | 4 | IBMCloudAccess Class 5 | ================================================ 6 | 7 | The module provides :py:class:`.IBMCloudAccess` class, and the following module-level utilities: 8 | 9 | * :func:`rename_keys`: Renames keys in a dict. 10 | * :func:`static_vars`: Decorator that assigns initial values to 'static' variables in function. To access these variables, use `.` 11 | --------------------------------------------------------------------------------