mirror of
https://github.com/FlipsideCrypto/sdk.git
synced 2026-02-06 10:46:43 +00:00
Compare commits
79 Commits
python@v2.
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d20b1c0cc | ||
|
|
4af0353a24 | ||
|
|
ae761cdf65 | ||
|
|
751f1adc70 | ||
|
|
2a5e4c6036 | ||
|
|
e147cf8dd4 | ||
|
|
43a3044883 | ||
|
|
d1393c6a4c | ||
|
|
8b98a4b924 | ||
|
|
c3e7d266fb | ||
|
|
c04aaa967f | ||
|
|
42b992900d | ||
|
|
351955b0d8 | ||
|
|
c7f4656df1 | ||
|
|
e3f7f56c9e | ||
|
|
a09422a9f6 | ||
|
|
3b15ab46a4 | ||
|
|
e3ce6d349f | ||
|
|
8b8d925f68 | ||
|
|
db669dd8d6 | ||
|
|
1c14811368 | ||
|
|
6a7efc55b7 | ||
|
|
1b1adbf8dc | ||
|
|
2271b58dde | ||
|
|
6f409ffddd | ||
|
|
5a496febae | ||
|
|
8c8e4c4b54 | ||
|
|
46aaa29ba4 | ||
|
|
67e903efb1 | ||
|
|
2c3d58ae90 | ||
|
|
db3160817a | ||
|
|
340490660e | ||
|
|
9126de5b72 | ||
|
|
64eb85f385 | ||
|
|
13b5dae883 | ||
|
|
10ade8a1c8 | ||
|
|
dd86d42756 | ||
|
|
dc2b521eb3 | ||
|
|
c306fa4083 | ||
|
|
53949df2d5 | ||
|
|
31975d1a38 | ||
|
|
8303fb68c6 | ||
|
|
14844f322f | ||
|
|
7d56475421 | ||
|
|
3147680fbb | ||
|
|
1e258e3d63 | ||
|
|
54b69a112b | ||
|
|
3a391f5315 | ||
|
|
bd63943f7a | ||
|
|
6d9f9f6d35 | ||
|
|
9edc06e08c | ||
|
|
b54a25c42e | ||
|
|
e20692c05c | ||
|
|
00ccf98708 | ||
|
|
e931e094ef | ||
|
|
2eb8abecb1 | ||
|
|
8d95228ccb | ||
|
|
794f37fc32 | ||
|
|
19b69e886f | ||
|
|
c4418d6a03 | ||
|
|
fd9b452935 | ||
|
|
06193f5e9c | ||
|
|
47a2e2e3f1 | ||
|
|
c86f6b8bff | ||
|
|
e2345245e0 | ||
|
|
826499746c | ||
|
|
1966ea1391 | ||
|
|
7c0ceb89af | ||
|
|
901101965e | ||
|
|
73e52bf825 | ||
|
|
4ea1ae8dac | ||
|
|
c2bae9f1b5 | ||
|
|
3834df9489 | ||
|
|
6460285ed0 | ||
|
|
a660fb20f8 | ||
|
|
bb944b1c19 | ||
|
|
3281021c7e | ||
|
|
ced4165f21 | ||
|
|
c99a573fdf |
25
.github/workflows/ci_js_end_to_end.yml
vendored
Normal file
25
.github/workflows/ci_js_end_to_end.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
|
||||
name: JS/TS Full End to End Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
env:
|
||||
FLIPSIDE_API_KEY: ${{ secrets.SECRET_FLIPSIDE_API_KEY }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [17.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: On Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2
|
||||
- name: End to End Test
|
||||
run: cd js && yarn install && yarn test:real
|
||||
2
.github/workflows/ci_python.yml
vendored
2
.github/workflows/ci_python.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.7", "3.8", "3.9", "3.10"]
|
||||
python-version: ["3.8", "3.9", "3.10"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@ -19,6 +19,7 @@ node_modules
|
||||
.output
|
||||
build/
|
||||
*.egg-info/
|
||||
.history/
|
||||
|
||||
/build/
|
||||
/public/build
|
||||
@ -31,3 +32,7 @@ examples/python/scratch/*
|
||||
r/shroomDK_0.1.0.tar.gz
|
||||
python-sdk-example.py
|
||||
r/shroomDK/api_key.txt
|
||||
r/shroomDK/test_of_page2_issue.R
|
||||
python/venv/
|
||||
venv/
|
||||
tokens.txt
|
||||
|
||||
14
README.md
14
README.md
@ -5,20 +5,22 @@ Programmatic access to the most reliable & comprehensive blockchain data in Web3
|
||||
You've found yourself at the FlipsideCrypto SDK repository, the official SDK to programmatically query all of Flipside Crypto's data.
|
||||
|
||||
## 🧩 The Data
|
||||
|
||||
Flipside Crypto's Analytics Team has curated dozens of blockchain data sets with more being added each week. All tables available to query in Flipside's [Data Studio](https://flipsidecrypto.xyz) can be queried programmatically via our API and library of SDKs.
|
||||
|
||||
## 📖 Official Docs
|
||||
|
||||
[https://docs.flipsidecrypto.com/flipside-api/get-started](https://docs.flipsidecrypto.com/flipside-api/get-started)
|
||||
|
||||
## 🗝 Want access? Genrate an API Key for Free
|
||||
|
||||
Get your [free API key here](https://flipsidecrypto.xyz/account/api-keys)
|
||||
Get your [free API key here](https://flipsidecrypto.xyz/api-keys)
|
||||
<br>
|
||||
|
||||
## SDKs
|
||||
|
||||
| Language | Version | Status |
|
||||
| ------------------------ | ------- | ---------------------------------------------------------------------------------- |
|
||||
| ✅ [Python](./python/) | 2.0.4 | [](https://github.com/FlipsideCrypto/sdk/actions/workflows/ci_python.yml) |
|
||||
| ✅ [JS/TypeScript](./js) | Under Construction | |
|
||||
| ✅ [R](./r/shroomDK/) | Under Construction | |
|
||||
| Language | Version | Status |
|
||||
| ------------------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| ✅ [Python](./python/) | 2.1.0 | [](https://github.com/FlipsideCrypto/sdk/actions/workflows/ci_python.yml) |
|
||||
| ✅ [JS/TypeScript](./js) | 2.0.1 | [](https://github.com/FlipsideCrypto/sdk/actions/workflows/ci_js.yml) |
|
||||
| ✅ [R](./r/shroomDK/) | 0.2.2 | [Available on CRAN](https://cran.r-project.org/web/packages/shroomDK/shroomDK.pdf) |
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@craco/craco": "^6.4.5",
|
||||
"@flipsidecrypto/sdk": "^1.1.0",
|
||||
"@flipsidecrypto/sdk": "^2.0.0",
|
||||
"@headlessui/react": "^1.6.6",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/react": "^13.3.0",
|
||||
|
||||
@ -52,7 +52,7 @@ export function QueryResultTable({
|
||||
queryResultSet?.rows?.map((row, i) => {
|
||||
return (
|
||||
<tr key={i}>
|
||||
{row.map((cell, j) => (
|
||||
{row.map((cell: any, j: number) => (
|
||||
<td key={j} className="text-s p-2">
|
||||
{`${cell}`.indexOf("0x") !== -1 ? (
|
||||
<a
|
||||
@ -76,15 +76,8 @@ export function QueryResultTable({
|
||||
<tfoot>
|
||||
<tr className="flex my-8 flex-row justify-between w-full items-center">
|
||||
<td colSpan={3}>
|
||||
<button
|
||||
onClick={onClickPrevPage}
|
||||
disabled={pageNumber === 1 ? true : false}
|
||||
>
|
||||
<FiChevronLeft
|
||||
className={`font-bold ${
|
||||
pageNumber === 1 ? "text-gray-400" : ""
|
||||
}`}
|
||||
/>
|
||||
<button onClick={onClickPrevPage} disabled={pageNumber === 1 ? true : false}>
|
||||
<FiChevronLeft className={`font-bold ${pageNumber === 1 ? "text-gray-400" : ""}`} />
|
||||
</button>
|
||||
</td>
|
||||
<td className="font-bold">Page: {pageNumber}</td>
|
||||
|
||||
@ -15,9 +15,7 @@ export function QueryStats({ queryResultSet }: Props) {
|
||||
|
||||
return (
|
||||
<div className="w-[800px]">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900">
|
||||
Query Stats
|
||||
</h3>
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900">Query Stats</h3>
|
||||
<dl className="mt-2 grid grid-cols-1 gap-5 sm:grid-cols-3">
|
||||
<Stat
|
||||
name="Elapsed Time (seconds)"
|
||||
@ -26,6 +24,8 @@ export function QueryStats({ queryResultSet }: Props) {
|
||||
/>
|
||||
{/* @ts-ignore */}
|
||||
<Stat name="Result Count" stat={queryResultSet.runStats.recordCount} />
|
||||
{/* @ts-ignore */}
|
||||
<Stat name="Total Pages" stat={queryResultSet.page?.totalPages} />
|
||||
</dl>
|
||||
</div>
|
||||
);
|
||||
@ -38,10 +38,7 @@ type StatProps = {
|
||||
|
||||
function Stat({ name, stat }: StatProps) {
|
||||
return (
|
||||
<div
|
||||
key={name}
|
||||
className="px-4 py-5 bg-white shadow rounded-lg overflow-hidden sm:p-6"
|
||||
>
|
||||
<div key={name} className="px-4 py-5 bg-white shadow rounded-lg overflow-hidden sm:p-6">
|
||||
<dt className="text-sm font-medium text-gray-500 truncate">{name}</dt>
|
||||
<dd className="mt-1 text-3xl font-semibold text-gray-900">{stat}</dd>
|
||||
</div>
|
||||
|
||||
@ -1,15 +1,13 @@
|
||||
import { Flipside, Query } from "@flipsidecrypto/sdk";
|
||||
|
||||
const SHROOMDK_API_KEY = process.env.REACT_APP_SHROOMDK_API_KEY;
|
||||
const API_BASE_URL = process.env.REACT_APP_SHROOMDK_API_BASE_URL;
|
||||
const FLIPSIDE_API_KEY = process.env.REACT_APP_FLIPSIDE_API_KEY;
|
||||
const API_BASE_URL = process.env.REACT_APP_FLIPSIDE_API_BASE_URL;
|
||||
|
||||
export async function getEnsAddr(
|
||||
domain: string
|
||||
): Promise<[string | null, Error | null]> {
|
||||
if (!SHROOMDK_API_KEY) throw new Error("no api key");
|
||||
export async function getEnsAddr(domain: string): Promise<[string | null, Error | null]> {
|
||||
if (!FLIPSIDE_API_KEY) throw new Error("no api key");
|
||||
|
||||
// Create an instance of the SDK
|
||||
const flipside = new Flipside(SHROOMDK_API_KEY, API_BASE_URL);
|
||||
const flipside = new Flipside(FLIPSIDE_API_KEY, API_BASE_URL);
|
||||
|
||||
// Create the query object
|
||||
// sql: use string interpolation to build the query
|
||||
@ -25,7 +23,7 @@ export async function getEnsAddr(
|
||||
AND event_name = 'NameRegistered'
|
||||
AND block_timestamp >= GETDATE() - interval'2 year'
|
||||
`,
|
||||
ttlMinutes: 60 * 24,
|
||||
maxAgeMinutes: 60 * 24,
|
||||
};
|
||||
|
||||
const result = await flipside.query.run(query);
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
import { Flipside, QueryResultSet, Query } from "@flipsidecrypto/sdk";
|
||||
|
||||
const SHROOMDK_API_KEY = process.env.REACT_APP_SHROOMDK_API_KEY;
|
||||
const API_BASE_URL = process.env.REACT_APP_SHROOMDK_API_BASE_URL;
|
||||
const FLIPSIDE_API_KEY = process.env.REACT_APP_FLIPSIDE_API_KEY;
|
||||
const API_BASE_URL = process.env.REACT_APP_FLIPSIDE_API_BASE_URL;
|
||||
|
||||
export async function getNFTMints(
|
||||
address: string,
|
||||
pageSize: number = 100000,
|
||||
pageNumber: number = 1
|
||||
): Promise<[QueryResultSet | null, Error | null]> {
|
||||
if (!SHROOMDK_API_KEY) throw new Error("no api key");
|
||||
if (!FLIPSIDE_API_KEY) throw new Error("no api key");
|
||||
|
||||
// Create an instance of the SDK
|
||||
const flipside = new Flipside(SHROOMDK_API_KEY, API_BASE_URL);
|
||||
const flipside = new Flipside(FLIPSIDE_API_KEY, API_BASE_URL);
|
||||
|
||||
// Create the query object
|
||||
// - sql: use string interpolation to build the query
|
||||
@ -25,7 +25,7 @@ export async function getNFTMints(
|
||||
FROM ethereum.core.ez_nft_mints
|
||||
WHERE
|
||||
nft_to_address = LOWER('${address}')`,
|
||||
ttlMinutes: 120,
|
||||
maxAgeMinutes: 120,
|
||||
pageSize,
|
||||
pageNumber,
|
||||
};
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
import { Flipside, QueryResultSet, Query } from "@flipsidecrypto/sdk";
|
||||
|
||||
const SHROOMDK_API_KEY = process.env.REACT_APP_SHROOMDK_API_KEY;
|
||||
const API_BASE_URL = process.env.REACT_APP_SHROOMDK_API_BASE_URL;
|
||||
const FLIPSIDE_API_KEY = process.env.REACT_APP_FLIPSIDE_API_KEY;
|
||||
const API_BASE_URL = process.env.REACT_APP_FLIPSIDE_API_BASE_URL;
|
||||
|
||||
export async function getXMetricHolders(
|
||||
pageSize: number = 20,
|
||||
pageNumber: number = 1
|
||||
): Promise<[QueryResultSet | null, Error | null]> {
|
||||
if (!SHROOMDK_API_KEY) throw new Error("no api key");
|
||||
if (!FLIPSIDE_API_KEY) throw new Error("no api key");
|
||||
|
||||
// Create an instance of the SDK
|
||||
const flipside = new Flipside(SHROOMDK_API_KEY, API_BASE_URL);
|
||||
const flipside = new Flipside(FLIPSIDE_API_KEY, API_BASE_URL);
|
||||
|
||||
// Create the query object
|
||||
// - sql: use string interpolation to build the query
|
||||
@ -46,7 +46,7 @@ export async function getXMetricHolders(
|
||||
LEFT JOIN burnt_tokens ON sent_tokens.Participant = burnt_tokens.Participant
|
||||
ORDER BY 2 DESC
|
||||
`,
|
||||
ttlMinutes: 10,
|
||||
maxAgeMinutes: 10,
|
||||
pageSize,
|
||||
pageNumber,
|
||||
};
|
||||
|
||||
@ -1203,11 +1203,12 @@
|
||||
minimatch "^3.1.2"
|
||||
strip-json-comments "^3.1.1"
|
||||
|
||||
"@flipsidecrypto/sdk@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@flipsidecrypto/sdk/-/sdk-1.1.0.tgz#a986a02ad4b2cc684b1aeca631ab00f2b3e5f7a1"
|
||||
integrity sha512-vBbcOn0K8+mrFmxhA/4KyzSCkYxBLryszinPJAKjz0prjv6DNLN4K3554PN2l3zVWNPSx9ofyQpv7+Y8/gQhcg==
|
||||
"@flipsidecrypto/sdk@^2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@flipsidecrypto/sdk/-/sdk-2.0.0.tgz#ab816fd94e84309203ecb0aeff6416e4eb5cbb59"
|
||||
integrity sha512-tqxmAvsFVl9rDgouSDYmITMxILDkb/jnRCvqTisko8edDPC/LcZKN1sfkvhh2QnmiA3bPBtUeLV7Vd6pTxCoww==
|
||||
dependencies:
|
||||
"@types/eslint" "^8.4.8"
|
||||
axios "^0.27.2"
|
||||
|
||||
"@headlessui/react@^1.6.6":
|
||||
|
||||
@ -1,126 +1,126 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Intro to Flipside SDK/API: Getting Started\n",
|
||||
"\n",
|
||||
"<em>install Flipside with pip</em><br/>\n",
|
||||
"`pip install flipside`"
|
||||
]
|
||||
"cells": [
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Intro to Flipside SDK/API: Getting Started\n",
|
||||
"\n",
|
||||
"<em>install Flipside with pip</em><br/>\n",
|
||||
"`pip install flipside`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Import the package"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from flipside import Flipside"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Run your first query<br/>\n",
|
||||
"<em>Remember to copy/paste your API Key from https://flipsidecrypto.xyz/api-keys below.</em>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"YOUR_API_KEY = os.environ.get(\"FLIPSIDE_API_KEY\")\n",
|
||||
"\n",
|
||||
"# Invoke the ShroomDK class to create an instance of the SDK\n",
|
||||
"sdk = Flipside(YOUR_API_KEY)\n",
|
||||
"\n",
|
||||
"# Run a query\n",
|
||||
"query_result_set = sdk.query(\"\"\"\n",
|
||||
" SELECT * FROM ethereum.core.ez_eth_transfers \n",
|
||||
" WHERE \n",
|
||||
" block_timestamp > GETDATE() - interval'90 days'\n",
|
||||
" AND \n",
|
||||
" (eth_from_address = lower('0xc2f41b3a1ff28fd2a6eee76ee12e51482fcfd11f')\n",
|
||||
" OR eth_to_address = lower('0xc2f41b3a1ff28fd2a6eee76ee12e51482fcfd11f'))\n",
|
||||
"\"\"\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Query Result Set\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"class QueryResultSet(BaseModel):\n",
|
||||
" query_id: Union[str, None] = Field(None, description=\"The server id of the query\")\n",
|
||||
" status: str = Field(False, description=\"The status of the query (`PENDING`, `FINISHED`, `ERROR`)\")\n",
|
||||
" columns: Union[List[str], None] = Field(None, description=\"The names of the columns in the result set\")\n",
|
||||
" column_types: Union[List[str], None] = Field(None, description=\"The type of the columns in the result set\")\n",
|
||||
" rows: Union[List[Any], None] = Field(None, description=\"The results of the query\")\n",
|
||||
" run_stats: Union[QueryRunStats, None] = Field(\n",
|
||||
" None,\n",
|
||||
" description=\"Summary stats on the query run (i.e. the number of rows returned, the elapsed time, etc)\",\n",
|
||||
" )\n",
|
||||
" records: Union[List[Any], None] = Field(None, description=\"The results of the query transformed as an array of objects\")\n",
|
||||
" error: Any\n",
|
||||
"\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"## Explore the result set object\n",
|
||||
"\n",
|
||||
"records = query_result_set.records\n",
|
||||
"\n",
|
||||
"print(records[0])"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3.10.1 64-bit",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.1"
|
||||
},
|
||||
"orig_nbformat": 4,
|
||||
"vscode": {
|
||||
"interpreter": {
|
||||
"hash": "949777d72b0d2535278d3dc13498b2535136f6dfe0678499012e853ee9abcab1"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Import the package"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from flipside import Flipside"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Run your first query<br/>\n",
|
||||
"<em>Remember to copy/paste your API Key from https://flipsidecrypto.xyz/account/api-keys below.</em>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"YOUR_API_KEY = os.environ.get(\"FLIPSIDE_API_KEY\")\n",
|
||||
"\n",
|
||||
"# Invoke the ShroomDK class to create an instance of the SDK\n",
|
||||
"sdk = Flipside(YOUR_API_KEY)\n",
|
||||
"\n",
|
||||
"# Run a query\n",
|
||||
"query_result_set = sdk.query(\"\"\"\n",
|
||||
" SELECT * FROM ethereum.core.ez_eth_transfers \n",
|
||||
" WHERE \n",
|
||||
" block_timestamp > GETDATE() - interval'90 days'\n",
|
||||
" AND \n",
|
||||
" (eth_from_address = lower('0xc2f41b3a1ff28fd2a6eee76ee12e51482fcfd11f')\n",
|
||||
" OR eth_to_address = lower('0xc2f41b3a1ff28fd2a6eee76ee12e51482fcfd11f'))\n",
|
||||
"\"\"\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Query Result Set\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"class QueryResultSet(BaseModel):\n",
|
||||
" query_id: Union[str, None] = Field(None, description=\"The server id of the query\")\n",
|
||||
" status: str = Field(False, description=\"The status of the query (`PENDING`, `FINISHED`, `ERROR`)\")\n",
|
||||
" columns: Union[List[str], None] = Field(None, description=\"The names of the columns in the result set\")\n",
|
||||
" column_types: Union[List[str], None] = Field(None, description=\"The type of the columns in the result set\")\n",
|
||||
" rows: Union[List[Any], None] = Field(None, description=\"The results of the query\")\n",
|
||||
" run_stats: Union[QueryRunStats, None] = Field(\n",
|
||||
" None,\n",
|
||||
" description=\"Summary stats on the query run (i.e. the number of rows returned, the elapsed time, etc)\",\n",
|
||||
" )\n",
|
||||
" records: Union[List[Any], None] = Field(None, description=\"The results of the query transformed as an array of objects\")\n",
|
||||
" error: Any\n",
|
||||
"\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"## Explore the result set object\n",
|
||||
"\n",
|
||||
"records = query_result_set.records\n",
|
||||
"\n",
|
||||
"print(records[0])"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3.10.1 64-bit",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.1"
|
||||
},
|
||||
"orig_nbformat": 4,
|
||||
"vscode": {
|
||||
"interpreter": {
|
||||
"hash": "949777d72b0d2535278d3dc13498b2535136f6dfe0678499012e853ee9abcab1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
|
||||
@ -1,290 +1,290 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Intro to Flipside API/SDK: Getting Started\n",
|
||||
"\n",
|
||||
"<em>install Flipside with pip</em><br/>\n",
|
||||
"`pip install flipside`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Import the package"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from flipside import Flipside"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Run your first query<br/>\n",
|
||||
"<em>Remember to copy/paste your API Key from https://flipsidecrypto.xyz/account/api-keys below.</em>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"YOUR_API_KEY = os.environ.get(\"FLIPSIDE_API_KEY\")\n",
|
||||
"\n",
|
||||
"# Invoke the ShroomDK class to create an instance of the SDK\n",
|
||||
"sdk = Flipside(YOUR_API_KEY)\n",
|
||||
"\n",
|
||||
"# Run a query\n",
|
||||
"xMETRIC_contract_address = '0x15848C9672e99be386807b9101f83A16EB017bb5'\n",
|
||||
"\n",
|
||||
"query_result_set = sdk.query(f\"\"\"\n",
|
||||
" SELECT count(distinct to_address) as recipient_count\n",
|
||||
" FROM polygon.core.fact_token_transfers\n",
|
||||
" WHERE block_timestamp > '2022-07-10T00:00:00'\n",
|
||||
" AND contract_address = lower('{xMETRIC_contract_address}')\n",
|
||||
" AND to_address != lower('0x4b8923746a1D9943bbd408F477572762801efE4d')\n",
|
||||
"\"\"\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Query Result Set\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"class QueryResultSet(BaseModel):\n",
|
||||
" query_id: Union[str, None] = Field(None, description=\"The server id of the query\")\n",
|
||||
" status: str = Field(False, description=\"The status of the query (`PENDING`, `FINISHED`, `ERROR`)\")\n",
|
||||
" columns: Union[List[str], None] = Field(None, description=\"The names of the columns in the result set\")\n",
|
||||
" column_types: Union[List[str], None] = Field(None, description=\"The type of the columns in the result set\")\n",
|
||||
" rows: Union[List[Any], None] = Field(None, description=\"The results of the query\")\n",
|
||||
" run_stats: Union[QueryRunStats, None] = Field(\n",
|
||||
" None,\n",
|
||||
" description=\"Summary stats on the query run (i.e. the number of rows returned, the elapsed time, etc)\",\n",
|
||||
" )\n",
|
||||
" records: Union[List[Any], None] = Field(None, description=\"The results of the query transformed as an array of objects\")\n",
|
||||
" error: Any\n",
|
||||
"\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"## Explore the result set object\n",
|
||||
"\n",
|
||||
"records = query_result_set.records\n",
|
||||
"\n",
|
||||
"print(records[0])\n",
|
||||
"\n",
|
||||
"print(f\"There are {records[0]['recipient_count']} unique recipients of xMETRIC tokens.\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### xMETRIC Leaderboard\n",
|
||||
"Retrieve the balance of every xMETRIC holder"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"vscode": {
|
||||
"languageId": "sql"
|
||||
"cells": [
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Intro to Flipside API/SDK: Getting Started\n",
|
||||
"\n",
|
||||
"<em>install Flipside with pip</em><br/>\n",
|
||||
"`pip install flipside`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Import the package"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from flipside import Flipside"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Run your first query<br/>\n",
|
||||
"<em>Remember to copy/paste your API Key from https://flipsidecrypto.xyz/api-keys below.</em>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"YOUR_API_KEY = os.environ.get(\"FLIPSIDE_API_KEY\")\n",
|
||||
"\n",
|
||||
"# Invoke the ShroomDK class to create an instance of the SDK\n",
|
||||
"sdk = Flipside(YOUR_API_KEY)\n",
|
||||
"\n",
|
||||
"# Run a query\n",
|
||||
"xMETRIC_contract_address = '0x15848C9672e99be386807b9101f83A16EB017bb5'\n",
|
||||
"\n",
|
||||
"query_result_set = sdk.query(f\"\"\"\n",
|
||||
" SELECT count(distinct to_address) as recipient_count\n",
|
||||
" FROM polygon.core.fact_token_transfers\n",
|
||||
" WHERE block_timestamp > '2022-07-10T00:00:00'\n",
|
||||
" AND contract_address = lower('{xMETRIC_contract_address}')\n",
|
||||
" AND to_address != lower('0x4b8923746a1D9943bbd408F477572762801efE4d')\n",
|
||||
"\"\"\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Query Result Set\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"class QueryResultSet(BaseModel):\n",
|
||||
" query_id: Union[str, None] = Field(None, description=\"The server id of the query\")\n",
|
||||
" status: str = Field(False, description=\"The status of the query (`PENDING`, `FINISHED`, `ERROR`)\")\n",
|
||||
" columns: Union[List[str], None] = Field(None, description=\"The names of the columns in the result set\")\n",
|
||||
" column_types: Union[List[str], None] = Field(None, description=\"The type of the columns in the result set\")\n",
|
||||
" rows: Union[List[Any], None] = Field(None, description=\"The results of the query\")\n",
|
||||
" run_stats: Union[QueryRunStats, None] = Field(\n",
|
||||
" None,\n",
|
||||
" description=\"Summary stats on the query run (i.e. the number of rows returned, the elapsed time, etc)\",\n",
|
||||
" )\n",
|
||||
" records: Union[List[Any], None] = Field(None, description=\"The results of the query transformed as an array of objects\")\n",
|
||||
" error: Any\n",
|
||||
"\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"## Explore the result set object\n",
|
||||
"\n",
|
||||
"records = query_result_set.records\n",
|
||||
"\n",
|
||||
"print(records[0])\n",
|
||||
"\n",
|
||||
"print(f\"There are {records[0]['recipient_count']} unique recipients of xMETRIC tokens.\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### xMETRIC Leaderboard\n",
|
||||
"Retrieve the balance of every xMETRIC holder"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"vscode": {
|
||||
"languageId": "sql"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"WITH sent_tokens AS (\n",
|
||||
" SELECT \n",
|
||||
" to_address as Participant,\n",
|
||||
" sum(raw_amount/pow(10,18)) as xMETRIC\n",
|
||||
" FROM polygon.core.fact_token_transfers\n",
|
||||
" WHERE\n",
|
||||
" block_timestamp::date > '2022-07-10'::date \n",
|
||||
" AND contract_address = lower('0x15848C9672e99be386807b9101f83A16EB017bb5')\n",
|
||||
" AND to_address != lower('0x4b8923746a1D9943bbd408F477572762801efE4d')\n",
|
||||
" GROUP BY 1\n",
|
||||
"),\n",
|
||||
"burnt_tokens AS (\n",
|
||||
" SELECT\n",
|
||||
" to_address as Participant,\n",
|
||||
" sum(raw_amount/pow(10,18)) as xMETRIC\n",
|
||||
" FROM polygon.core.fact_token_transfers\n",
|
||||
" WHERE\n",
|
||||
" block_timestamp::date > '2022-07-10'::date \n",
|
||||
" AND contract_address = lower('0x15848C9672e99be386807b9101f83A16EB017bb5')\n",
|
||||
" AND to_address = lower('0x0000000000000000000000000000000000000000')\n",
|
||||
" GROUP BY 1\n",
|
||||
")\n",
|
||||
"SELECT\n",
|
||||
" sent_tokens.Participant as \"participant_addr\",\n",
|
||||
" coalesce(sent_tokens.xmetric,0) - coalesce(burnt_tokens.xMETRIC,0) as \"balance\"\n",
|
||||
"FROM sent_tokens \n",
|
||||
"LEFT JOIN burnt_tokens ON sent_tokens.Participant = burnt_tokens.Participant\n",
|
||||
"ORDER BY 2 DESC"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Load the sql query from a file\n",
|
||||
"leaderboard_sql_query = open(\"./sql/xmetric_leaderboard.sql\", 'r').read()\n",
|
||||
"\n",
|
||||
"# Run the query with pagination\n",
|
||||
"\n",
|
||||
"page_number = 1\n",
|
||||
"page_size = 10\n",
|
||||
"\n",
|
||||
"leaderboard_result_set = sdk.query(\n",
|
||||
" leaderboard_sql_query, \n",
|
||||
" page_size=page_size,\n",
|
||||
" page_number=page_number)\n",
|
||||
"\n",
|
||||
"for record in leaderboard_result_set.records:\n",
|
||||
" print(record)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Plot the xMETRIC LeaderBoard Results"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"full_leaderboard_result_set = sdk.query(leaderboard_sql_query)\n",
|
||||
"\n",
|
||||
"import pandas as pd\n",
|
||||
"import plotly.express as px\n",
|
||||
"\n",
|
||||
"df = pd.DataFrame(full_leaderboard_result_set.records)\n",
|
||||
"\n",
|
||||
"fig = px.histogram(df, x=\"balance\", marginal=\"box\", hover_data=df.columns, nbins=200)\n",
|
||||
"\n",
|
||||
"fig.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Cross Chain xMETRIC User Exploration"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"vscode": {
|
||||
"languageId": "sql"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"WITH xmetric_holders AS (\n",
|
||||
" SELECT to_address as holder_addr\n",
|
||||
" FROM polygon.core.fact_token_transfers\n",
|
||||
" WHERE block_timestamp > '2022-07-10T00:00:00'\n",
|
||||
" AND contract_address = lower('0x15848C9672e99be386807b9101f83A16EB017bb5')\n",
|
||||
" AND to_address != lower('0x4b8923746a1D9943bbd408F477572762801efE4d')\n",
|
||||
")\n",
|
||||
"SELECT\n",
|
||||
" token_name,\n",
|
||||
" symbol,\n",
|
||||
" count(distinct user_address) as num_holders,\n",
|
||||
" median(usd_value_now) as median_usd_holdings\n",
|
||||
"FROM ethereum.core.ez_current_balances\n",
|
||||
"INNER JOIN xmetric_holders \n",
|
||||
" ON ethereum.core.ez_current_balances.user_address = xmetric_holders.holder_addr\n",
|
||||
"WHERE ethereum.core.ez_current_balances.usd_value_now > 0\n",
|
||||
"GROUP BY 1, 2\n",
|
||||
"ORDER BY 3 DESC\n",
|
||||
"LIMIT 25"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Load the sql query from a file\n",
|
||||
"xmetric_eth_holdings_sql_query = open(\"./sql/xmetric_eth_holdings.sql\", 'r').read()\n",
|
||||
"\n",
|
||||
"# Run the query\n",
|
||||
"xmetric_eth_holdings_results = sdk.query(xmetric_eth_holdings_sql_query)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Plot the results\n",
|
||||
"df = pd.DataFrame(xmetric_eth_holdings_results.records)\n",
|
||||
"\n",
|
||||
"fig = px.bar(df, x=\"token_name\", y=\"num_holders\", hover_data=df.columns)\n",
|
||||
"\n",
|
||||
"fig.show()"
|
||||
]
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"WITH sent_tokens AS (\n",
|
||||
" SELECT \n",
|
||||
" to_address as Participant,\n",
|
||||
" sum(raw_amount/pow(10,18)) as xMETRIC\n",
|
||||
" FROM polygon.core.fact_token_transfers\n",
|
||||
" WHERE\n",
|
||||
" block_timestamp::date > '2022-07-10'::date \n",
|
||||
" AND contract_address = lower('0x15848C9672e99be386807b9101f83A16EB017bb5')\n",
|
||||
" AND to_address != lower('0x4b8923746a1D9943bbd408F477572762801efE4d')\n",
|
||||
" GROUP BY 1\n",
|
||||
"),\n",
|
||||
"burnt_tokens AS (\n",
|
||||
" SELECT\n",
|
||||
" to_address as Participant,\n",
|
||||
" sum(raw_amount/pow(10,18)) as xMETRIC\n",
|
||||
" FROM polygon.core.fact_token_transfers\n",
|
||||
" WHERE\n",
|
||||
" block_timestamp::date > '2022-07-10'::date \n",
|
||||
" AND contract_address = lower('0x15848C9672e99be386807b9101f83A16EB017bb5')\n",
|
||||
" AND to_address = lower('0x0000000000000000000000000000000000000000')\n",
|
||||
" GROUP BY 1\n",
|
||||
")\n",
|
||||
"SELECT\n",
|
||||
" sent_tokens.Participant as \"participant_addr\",\n",
|
||||
" coalesce(sent_tokens.xmetric,0) - coalesce(burnt_tokens.xMETRIC,0) as \"balance\"\n",
|
||||
"FROM sent_tokens \n",
|
||||
"LEFT JOIN burnt_tokens ON sent_tokens.Participant = burnt_tokens.Participant\n",
|
||||
"ORDER BY 2 DESC"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Load the sql query from a file\n",
|
||||
"leaderboard_sql_query = open(\"./sql/xmetric_leaderboard.sql\", 'r').read()\n",
|
||||
"\n",
|
||||
"# Run the query with pagination\n",
|
||||
"\n",
|
||||
"page_number = 1\n",
|
||||
"page_size = 10\n",
|
||||
"\n",
|
||||
"leaderboard_result_set = sdk.query(\n",
|
||||
" leaderboard_sql_query, \n",
|
||||
" page_size=page_size,\n",
|
||||
" page_number=page_number)\n",
|
||||
"\n",
|
||||
"for record in leaderboard_result_set.records:\n",
|
||||
" print(record)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Plot the xMETRIC LeaderBoard Results"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"full_leaderboard_result_set = sdk.query(leaderboard_sql_query)\n",
|
||||
"\n",
|
||||
"import pandas as pd\n",
|
||||
"import plotly.express as px\n",
|
||||
"\n",
|
||||
"df = pd.DataFrame(full_leaderboard_result_set.records)\n",
|
||||
"\n",
|
||||
"fig = px.histogram(df, x=\"balance\", marginal=\"box\", hover_data=df.columns, nbins=200)\n",
|
||||
"\n",
|
||||
"fig.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Cross Chain xMETRIC User Exploration"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3.10.1 64-bit",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.1"
|
||||
},
|
||||
"orig_nbformat": 4,
|
||||
"vscode": {
|
||||
"languageId": "sql"
|
||||
"interpreter": {
|
||||
"hash": "949777d72b0d2535278d3dc13498b2535136f6dfe0678499012e853ee9abcab1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"WITH xmetric_holders AS (\n",
|
||||
" SELECT to_address as holder_addr\n",
|
||||
" FROM polygon.core.fact_token_transfers\n",
|
||||
" WHERE block_timestamp > '2022-07-10T00:00:00'\n",
|
||||
" AND contract_address = lower('0x15848C9672e99be386807b9101f83A16EB017bb5')\n",
|
||||
" AND to_address != lower('0x4b8923746a1D9943bbd408F477572762801efE4d')\n",
|
||||
")\n",
|
||||
"SELECT\n",
|
||||
" token_name,\n",
|
||||
" symbol,\n",
|
||||
" count(distinct user_address) as num_holders,\n",
|
||||
" median(usd_value_now) as median_usd_holdings\n",
|
||||
"FROM ethereum.core.ez_current_balances\n",
|
||||
"INNER JOIN xmetric_holders \n",
|
||||
" ON ethereum.core.ez_current_balances.user_address = xmetric_holders.holder_addr\n",
|
||||
"WHERE ethereum.core.ez_current_balances.usd_value_now > 0\n",
|
||||
"GROUP BY 1, 2\n",
|
||||
"ORDER BY 3 DESC\n",
|
||||
"LIMIT 25"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Load the sql query from a file\n",
|
||||
"xmetric_eth_holdings_sql_query = open(\"./sql/xmetric_eth_holdings.sql\", 'r').read()\n",
|
||||
"\n",
|
||||
"# Run the query\n",
|
||||
"xmetric_eth_holdings_results = sdk.query(xmetric_eth_holdings_sql_query)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Plot the results\n",
|
||||
"df = pd.DataFrame(xmetric_eth_holdings_results.records)\n",
|
||||
"\n",
|
||||
"fig = px.bar(df, x=\"token_name\", y=\"num_holders\", hover_data=df.columns)\n",
|
||||
"\n",
|
||||
"fig.show()"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3.10.1 64-bit",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.1"
|
||||
},
|
||||
"orig_nbformat": 4,
|
||||
"vscode": {
|
||||
"interpreter": {
|
||||
"hash": "949777d72b0d2535278d3dc13498b2535136f6dfe0678499012e853ee9abcab1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
|
||||
243
js/README.md
243
js/README.md
@ -1,13 +1,13 @@
|
||||
# Flipside Crypto JS SDK
|
||||
|
||||
Programmatic access to the most comprehensive blockchain data in Web3, for free. 🥳
|
||||
Programmatic access to the most comprehensive blockchain data in Web3 🥳.
|
||||
<br>
|
||||
<br>
|
||||
|
||||

|
||||
<br>
|
||||
<br>
|
||||
GM frens, you've found yourself at the Flipside Crypto JS/typescript sdk.
|
||||
You've found yourself at the Flipside Crypto JS/typescript SDK.
|
||||
<br>
|
||||
<br>
|
||||
|
||||
@ -23,6 +23,10 @@ or if using npm
|
||||
npm install @flipsidecrypto/sdk
|
||||
```
|
||||
|
||||
## 🗝 Genrate an API Key for Free
|
||||
|
||||
Get your [free API key here](https://flipsidecrypto.xyz/api-keys)
|
||||
|
||||
## 🦾 Getting Started
|
||||
|
||||
```typescript
|
||||
@ -31,7 +35,7 @@ import { Flipside, Query, QueryResultSet } from "@flipsidecrypto/sdk";
|
||||
// Initialize `Flipside` with your API key
|
||||
const flipside = new Flipside(
|
||||
"<YOUR_API_KEY>",
|
||||
"https://api.flipsidecrypto.com"
|
||||
"https://api-v2.flipsidecrypto.xyz"
|
||||
);
|
||||
|
||||
// Parameters can be passed into SQL statements via simple & native string interpolation
|
||||
@ -39,8 +43,8 @@ const myAddress = "0x....";
|
||||
|
||||
// Create a query object for the `query.run` function to execute
|
||||
const query: Query = {
|
||||
sql: `select nft_address, mint_price_eth, mint_price_usd from flipside_prod_db.ethereum_core.ez_nft_mints where nft_to_address = LOWER('${myAddress}')`,
|
||||
ttlMinutes: 10,
|
||||
sql: `select nft_address, mint_price_eth, mint_price_usd from ethereum.nft.ez_nft_mints where nft_to_address = LOWER('${myAddress}')`,
|
||||
maxAgeMinutes: 30,
|
||||
};
|
||||
|
||||
// Send the `Query` to Flipside's query engine and await the results
|
||||
@ -48,10 +52,10 @@ const result: QueryResultSet = await flipside.query.run(query);
|
||||
|
||||
// Iterate over the results
|
||||
result.records.map((record) => {
|
||||
const nftAddress = record.nft_address
|
||||
const mintPriceEth = record.mint_price_eth
|
||||
const mintPriceUSD = = record.mint_price_usd
|
||||
console.log(`address ${nftAddress} minted at a price of ${mintPrice} ETH or $${mintPriceUSD} USD`);
|
||||
const nftAddress = record.nft_address;
|
||||
const mintPriceEth = record.mint_price_eth;
|
||||
const mintPriceUSD = = record.mint_price_usd;
|
||||
console.log(`address ${nftAddress} minted at a price of ${mintPriceEth} ETH or $${mintPriceUSD} USD`);
|
||||
});
|
||||
```
|
||||
|
||||
@ -66,11 +70,14 @@ type Query = {
|
||||
// SQL query to execute
|
||||
sql: string;
|
||||
|
||||
// The number of minutes to cache the query results
|
||||
ttlMinutes?: number;
|
||||
// The number of minutes you are willing to accept cached
|
||||
// result up to. If set to 30, if cached results exist within
|
||||
// the last 30 minutes the api will return them.
|
||||
maxAgeMinutes?: number;
|
||||
|
||||
// An override on the query result cahce.
|
||||
// A value of false will re-execute the query.
|
||||
// A value of false will re-execute the query and override
|
||||
// maxAgeMinutes
|
||||
cached?: boolean;
|
||||
|
||||
// The number of minutes until your query run times out
|
||||
@ -81,6 +88,12 @@ type Query = {
|
||||
|
||||
// The page number to return, defaults to 1
|
||||
pageNumber?: number;
|
||||
|
||||
// The owner of the data source (defaults to 'flipside')
|
||||
dataProvider?: string;
|
||||
|
||||
// The data source to execute the query against (defaults to 'snowflake-default')
|
||||
dataSource?: string;
|
||||
};
|
||||
```
|
||||
|
||||
@ -90,8 +103,8 @@ Let's create a query to retrieve all NFTs minted by an address:
|
||||
const yourAddress = "<your_ethereum_address>";
|
||||
|
||||
const query: Query = {
|
||||
sql: `select nft_address, mint_price_eth, mint_price_usd from flipside_prod_db.ethereum_core.ez_nft_mints where nft_to_address = LOWER('${myAddress}')`,
|
||||
ttlMinutes: 60,
|
||||
sql: `select nft_address, mint_price_eth, mint_price_usd from ethereum.nft.ez_nft_mints where nft_to_address = LOWER('${myAddress}')`,
|
||||
maxAgeMinutes: 5,
|
||||
cached: true,
|
||||
timeoutMinutes: 15,
|
||||
pageNumber: 1,
|
||||
@ -126,7 +139,7 @@ interface QueryResultSet {
|
||||
columnTypes: string[] | null;
|
||||
|
||||
// The results of the query
|
||||
rows: Row[] | null;
|
||||
rows: any[] | null;
|
||||
|
||||
// Summary stats on the query run (i.e. the number of rows returned, the elapsed time, etc)
|
||||
runStats: QueryRunStats | null;
|
||||
@ -134,14 +147,12 @@ interface QueryResultSet {
|
||||
// The results of the query transformed as an array of objects
|
||||
records: QueryResultRecord[] | null;
|
||||
|
||||
// The number of records to return
|
||||
pageSize: number;
|
||||
|
||||
// The page number to return
|
||||
pageNumber: number;
|
||||
// The page of results
|
||||
page: PageStats | null;
|
||||
|
||||
// If the query failed, this will contain the error
|
||||
error:
|
||||
| ApiError
|
||||
| QueryRunRateLimitError
|
||||
| QueryRunTimeoutError
|
||||
| QueryRunExecutionError
|
||||
@ -168,19 +179,187 @@ result.records.map((record) => {
|
||||
});
|
||||
```
|
||||
|
||||
### Rate Limits
|
||||
### Pagination
|
||||
To page over the results use the `getQueryResults` method.
|
||||
|
||||
Every API key is subject to a rate limit over a moving 5 minute window, as well as an aggregate daily limit.
|
||||
<br>
|
||||
<br>
|
||||
If the limit is reach in a 5 minute period, the sdk will exponentially backoff and retry the query up to the `timeoutMinutes` parameter set on the `Query` object.
|
||||
<br>
|
||||
<br>
|
||||
This feature is quite useful if leveraging the SDK client side and your web application sees a large spike in traffic. Rather than using up your daily limit all at once, requests will be smoothed out over the day.
|
||||
<br>
|
||||
<br>
|
||||
Rate limits can be adjust per key/use-case.
|
||||
```typescript
|
||||
// what page are we starting on?
|
||||
let currentPageNumber = 1
|
||||
|
||||
// How many records do we want to return in the page?
|
||||
let pageSize = 1000
|
||||
|
||||
// set total pages to 1 higher than the `currentPageNumber` until
|
||||
// we receive the total pages from `getQueryResults` given the
|
||||
// provided `pageSize` (totalPages is dynamically determined by the API
|
||||
// based on the `pageSize` you provide)
|
||||
let totalPages = 2
|
||||
|
||||
// we'll store all the page results in `allRows`
|
||||
let allRows = []
|
||||
|
||||
while (currentPageNumber <= totalPages) {
|
||||
results = await flipside.query.getQueryResults({
|
||||
queryRunId: result.queryId,
|
||||
pageNumber: currentPageNumber,
|
||||
pageSize: pageSize
|
||||
})
|
||||
totalPages = results.page.totalPages
|
||||
allRows = [...allRows, ...results.records]
|
||||
currentPageNumber += 1
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Sort the Results
|
||||
Let's fetch the results sorted in descending order by `mint_price_usd`.
|
||||
|
||||
```typescript
|
||||
results = await flipside.query.getQueryResults({
|
||||
queryRunId: result.queryId,
|
||||
pageNumber: 1,
|
||||
pageSize: 1000,
|
||||
sortBy: [
|
||||
{
|
||||
column: 'mint_price_usd',
|
||||
direction: 'desc'
|
||||
}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
Valid directions include `desc` and `asc`. You may also sortBy multiple columns. The order you provide the sortBy objects determine which sortBy object takes precedence.
|
||||
|
||||
The following example will first sort results in descending order by `mint_price_usd` and then in ascending order by `nft_address`.
|
||||
|
||||
```typescript
|
||||
results = await flipside.query.getQueryResults({
|
||||
queryRunId: result.queryId,
|
||||
pageNumber: 1,
|
||||
pageSize: 1000,
|
||||
sortBy: [
|
||||
{
|
||||
column: 'mint_price_usd',
|
||||
direction: 'desc'
|
||||
},
|
||||
{
|
||||
column: 'nft_address',
|
||||
direction: 'asc'
|
||||
}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
For reference here is the `SortBy` type:
|
||||
|
||||
```typescript
|
||||
interface SortBy {
|
||||
column: string;
|
||||
direction: "desc" | "asc";
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Filter the results
|
||||
Now let's filter the results where `mint_price_usd` is greater than $10
|
||||
|
||||
```typescript
|
||||
results = await flipside.query.getQueryResults({
|
||||
queryRunId: result.queryId,
|
||||
pageNumber: 1,
|
||||
pageSize: 1000,
|
||||
filters: [
|
||||
{
|
||||
gt: 10,
|
||||
column: 'mint_price_usd'
|
||||
}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
Filters can be applied for: equals, not equals, greater than, greater than or equals to, less than, less than or equals to, like, in, not in. All filters are executed server side over the entire result set.
|
||||
|
||||
Here is the Filter type:
|
||||
```typescript
|
||||
interface Filter {
|
||||
column: string;
|
||||
eq?: string | number | null;
|
||||
neq?: string | number | null;
|
||||
gt?: number | null;
|
||||
gte?: number | null;
|
||||
lt?: number | null;
|
||||
lte?: number | null;
|
||||
like?: string | number | null;
|
||||
in?: any[] | null;
|
||||
notIn?: any[] | null;
|
||||
}
|
||||
```
|
||||
|
||||
### Understanding MaxAgeMinutes (and caching of results)
|
||||
The parameter `maxAgeMinutes` can be used to control whether a query will re-execute or return cached results. Let's talk thru an example.
|
||||
|
||||
Set `maxAgeMinutes` to 30:
|
||||
|
||||
```typescript
|
||||
const query: Query = {
|
||||
sql: `select nft_address, mint_price_eth, mint_price_usd from ethereum.nft.ez_nft_mints where nft_to_address = LOWER('${myAddress}')`,
|
||||
maxAgeMinutes: 30
|
||||
};
|
||||
```
|
||||
|
||||
Behind the scenes the Flipside API will hash the sql text and using that hash determine if results exist that were recorded within the last 30 minutes. If no results exist, or the results that exist are more than 30 minutes old the query will re-execute.
|
||||
|
||||
If you would like to force a cache bust and re-execute the query. You have two options, either set `maxAgeMinutes` to 0 or pass in `cache=false`. Setting `cache` to false effectively sets `maxAgeMinutes` to 0.
|
||||
|
||||
```typescript
|
||||
const query: Query = {
|
||||
sql: `select nft_address, mint_price_eth, mint_price_usd from ethereum.nft.ez_nft_mints where nft_to_address = LOWER('${myAddress}')`,
|
||||
maxAgeMinutes: 0
|
||||
};
|
||||
|
||||
// or:
|
||||
const query: Query = {
|
||||
sql: `select nft_address, mint_price_eth, mint_price_usd from ethereum.nft.ez_nft_mints where nft_to_address = LOWER('${myAddress}')`,
|
||||
maxAgeMinutes: 30,
|
||||
cache: false
|
||||
};
|
||||
```
|
||||
|
||||
### Understanding Query Seconds
|
||||
You can determine how many execution seconds your query took by looking at the `runStats` object on the `QueryResultSet`.
|
||||
|
||||
```typescript
|
||||
const runStats = result.runStats
|
||||
```
|
||||
|
||||
There are a number of stats returned:
|
||||
```typescript
|
||||
type QueryRunStats = {
|
||||
startedAt: Date;
|
||||
endedAt: Date;
|
||||
elapsedSeconds: number;
|
||||
queryExecStartedAt: Date;
|
||||
queryExecEndedAt: Date;
|
||||
streamingStartedAt: Date;
|
||||
streamingEndedAt: Date;
|
||||
queuedSeconds: number;
|
||||
streamingSeconds: number;
|
||||
queryExecSeconds: number;
|
||||
bytes: number; // the number of bytes returned by the query
|
||||
recordCount: number;
|
||||
};
|
||||
```
|
||||
|
||||
Your account is only debited for `queryExecSeconds`. This is the number of computational seconds your query executes against Flipside's data warehouse.
|
||||
|
||||
```typescript
|
||||
const execSeconds = runStats.queryExecSeconds
|
||||
```
|
||||
|
||||
You are only debited when the query is executed. So if you set `maxAgeMinutes` to a value greater than 0, and the query does not re-execute then you will only be charged for the time it executes.
|
||||
|
||||
Flipside does NOT charge for the number of bytes/records returned.
|
||||
|
||||
### Client Side Request Requirements
|
||||
|
||||
All API Keys correspond to a list of hostnames. Client-side requests that do not originate from the corresponding hostname will fail.
|
||||
All API Keys correspond to a list of hostnames. Client-side requests that do not originate from the corresponding hostname will fail. You may configure hostnames [here](https://flipsidecrypto.xyz/api-keys).
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "@flipsidecrypto/sdk",
|
||||
"version": "1.1.1",
|
||||
"version": "2.1.0",
|
||||
"description": "The official Flipside Crypto SDK",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"main": "dist/src/index.js",
|
||||
"types": "dist/src/index.d.ts",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/flipsidecrypto/sdk.git"
|
||||
@ -15,6 +15,7 @@
|
||||
"scripts": {
|
||||
"test": "vitest",
|
||||
"test:ui": "vitest --ui",
|
||||
"test:real": "npx ts-node src/tests/endToEndTest.ts",
|
||||
"test:coverage": "vitest --coverage --no-watch",
|
||||
"test:run": "vitest run",
|
||||
"build": "tsc",
|
||||
@ -31,6 +32,6 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/eslint": "^8.4.8",
|
||||
"axios": "^0.27.2"
|
||||
"xior": "^0.1.1"
|
||||
}
|
||||
}
|
||||
|
||||
212
js/src/api.ts
212
js/src/api.ts
@ -1,27 +1,40 @@
|
||||
import xior, { XiorError as AxiosError, XiorResponse as AxiosResponse } from "xior";
|
||||
import { ServerError, UnexpectedSDKError, UserError } from "./errors";
|
||||
import {
|
||||
Query,
|
||||
CreateQueryResp,
|
||||
QueryResultResp,
|
||||
CreateQueryJson,
|
||||
QueryResultJson,
|
||||
ApiClient,
|
||||
CompassApiClient,
|
||||
CreateQueryRunRpcParams,
|
||||
CreateQueryRunRpcRequestImplementation,
|
||||
CreateQueryRunRpcResponse,
|
||||
CreateQueryRunRpcResponseImplementation,
|
||||
CancelQueryRunRpcRequestParams,
|
||||
CancelQueryRunRpcResponse,
|
||||
GetQueryRunResultsRpcParams,
|
||||
GetQueryRunResultsRpcResponse,
|
||||
GetQueryRunRpcRequestParams,
|
||||
GetQueryRunRpcResponse,
|
||||
GetSqlStatementParams,
|
||||
GetSqlStatementResponse,
|
||||
GetQueryRunRpcRequestImplementation,
|
||||
GetQueryRunRpcResponseImplementation,
|
||||
GetQueryRunResultsRpcRequestImplementation,
|
||||
GetQueryRunResultsRpcResponseImplementation,
|
||||
GetSqlStatementRequestImplementation,
|
||||
GetSqlStatementResponseImplementation,
|
||||
CancelQueryRunRpcRequestImplementation,
|
||||
CancelQueryRunRpcResponseImplementation,
|
||||
} from "./types";
|
||||
import axios, { AxiosError } from "axios";
|
||||
import { UnexpectedSDKError } from "./errors";
|
||||
|
||||
const PARSE_ERROR_MSG =
|
||||
"the api returned an error and there was a fatal client side error parsing that error msg";
|
||||
const PARSE_ERROR_MSG = "the api returned an error and there was a fatal client side error parsing that error msg";
|
||||
|
||||
export class API implements ApiClient {
|
||||
const axios = xior.create();
|
||||
export class Api implements CompassApiClient {
|
||||
url: string;
|
||||
#baseUrl: string;
|
||||
#headers: Record<string, string>;
|
||||
#sdkVersion: string;
|
||||
#sdkPackage: string;
|
||||
|
||||
constructor(baseUrl: string, sdkPackage: string, sdkVersion: string, apiKey: string) {
|
||||
constructor(baseUrl: string, apiKey: string) {
|
||||
this.#baseUrl = baseUrl;
|
||||
this.#sdkPackage = sdkPackage;
|
||||
this.#sdkVersion = sdkVersion;
|
||||
this.url = this.getUrl();
|
||||
this.#headers = {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
@ -29,75 +42,124 @@ export class API implements ApiClient {
|
||||
};
|
||||
}
|
||||
|
||||
getUrl(path: string): string {
|
||||
return `${this.#baseUrl}/${path}`;
|
||||
getUrl(): string {
|
||||
return `${this.#baseUrl}/json-rpc`;
|
||||
}
|
||||
|
||||
async createQuery(query: Query): Promise<CreateQueryResp> {
|
||||
async createQuery(params: CreateQueryRunRpcParams): Promise<CreateQueryRunRpcResponse> {
|
||||
let result;
|
||||
const request = new CreateQueryRunRpcRequestImplementation([params]);
|
||||
|
||||
try {
|
||||
result = await axios.post(
|
||||
this.getUrl("queries"),
|
||||
{
|
||||
sql: query.sql,
|
||||
ttl_minutes: query.ttlMinutes,
|
||||
cached: query.cached,
|
||||
sdk_package: this.#sdkPackage,
|
||||
sdk_version: this.#sdkVersion,
|
||||
},
|
||||
{ headers: this.#headers }
|
||||
result = await axios.post(this.url, request, { headers: this.#headers });
|
||||
} catch (err) {
|
||||
let errData = err as AxiosError;
|
||||
result = errData.response;
|
||||
if (!result) {
|
||||
throw new UnexpectedSDKError(errData.message);
|
||||
}
|
||||
}
|
||||
|
||||
const data = this.#handleResponse(result, "createQueryRun");
|
||||
return new CreateQueryRunRpcResponseImplementation(data.id, data.result, data.error);
|
||||
}
|
||||
|
||||
async getQueryRun(params: GetQueryRunRpcRequestParams): Promise<GetQueryRunRpcResponse> {
|
||||
let result;
|
||||
const request = new GetQueryRunRpcRequestImplementation([params]);
|
||||
|
||||
try {
|
||||
result = await axios.post(this.url, request, { headers: this.#headers });
|
||||
} catch (err) {
|
||||
let errData = err as AxiosError;
|
||||
result = errData.response;
|
||||
if (!result) {
|
||||
throw new UnexpectedSDKError(errData.message);
|
||||
}
|
||||
}
|
||||
|
||||
const data = this.#handleResponse(result, "getQueryRun");
|
||||
return new GetQueryRunRpcResponseImplementation(data.id, data.result, data.error);
|
||||
}
|
||||
|
||||
async getQueryResult(params: GetQueryRunResultsRpcParams): Promise<GetQueryRunResultsRpcResponse> {
|
||||
let result;
|
||||
const request = new GetQueryRunResultsRpcRequestImplementation([params]);
|
||||
|
||||
try {
|
||||
result = await axios.post(this.url, request, { headers: this.#headers });
|
||||
} catch (err) {
|
||||
let errData = err as AxiosError;
|
||||
result = errData.response;
|
||||
if (!result) {
|
||||
throw new UnexpectedSDKError(errData.message);
|
||||
}
|
||||
}
|
||||
|
||||
const data = this.#handleResponse(result, "getQueryRunResults");
|
||||
return new GetQueryRunResultsRpcResponseImplementation(data.id, data.result, data.error);
|
||||
}
|
||||
|
||||
async getSqlStatement(params: GetSqlStatementParams): Promise<GetSqlStatementResponse> {
|
||||
let result;
|
||||
const request = new GetSqlStatementRequestImplementation([params]);
|
||||
|
||||
try {
|
||||
result = await axios.post(this.url, request, { headers: this.#headers });
|
||||
} catch (err) {
|
||||
let errData = err as AxiosError;
|
||||
result = errData.response;
|
||||
if (!result) {
|
||||
throw new UnexpectedSDKError(PARSE_ERROR_MSG);
|
||||
}
|
||||
}
|
||||
|
||||
const data = this.#handleResponse(result, "getSqlStatement");
|
||||
return new GetSqlStatementResponseImplementation(data.id, data.result, data.error);
|
||||
}
|
||||
|
||||
async cancelQueryRun(params: CancelQueryRunRpcRequestParams): Promise<CancelQueryRunRpcResponse> {
|
||||
let result;
|
||||
const request = new CancelQueryRunRpcRequestImplementation([params]);
|
||||
|
||||
try {
|
||||
result = await axios.post(this.url, request, { headers: this.#headers });
|
||||
} catch (err) {
|
||||
let errData = err as AxiosError;
|
||||
result = errData.response;
|
||||
if (!result) {
|
||||
throw new UnexpectedSDKError(PARSE_ERROR_MSG);
|
||||
}
|
||||
}
|
||||
|
||||
const data = this.#handleResponse(result, "cancelQueryRun");
|
||||
return new CancelQueryRunRpcResponseImplementation(data.id, data.result, data.error);
|
||||
}
|
||||
|
||||
#handleResponse(result: AxiosResponse, method: string): Record<string, any> {
|
||||
if (result.status === undefined) {
|
||||
throw new ServerError(0, `Unable to connect to server when calling '${method}'. Please try again later.`);
|
||||
}
|
||||
|
||||
if (result.status >= 500) {
|
||||
throw new ServerError(
|
||||
result.status,
|
||||
`Unknown server error when calling '${method}': ${result.status} - ${result.statusText}. Please try again later.`
|
||||
);
|
||||
} catch (err) {
|
||||
let errData = err as AxiosError;
|
||||
result = errData.response;
|
||||
if (!result) {
|
||||
throw new UnexpectedSDKError(PARSE_ERROR_MSG);
|
||||
}
|
||||
}
|
||||
|
||||
let data: CreateQueryJson | null;
|
||||
if (result.status >= 200 && result.status < 300) {
|
||||
data = result.data;
|
||||
} else {
|
||||
data = null;
|
||||
if (result.status === 401 || result.status === 403) {
|
||||
throw new UserError(result.status, "Unauthorized: Invalid API Key.");
|
||||
}
|
||||
|
||||
return {
|
||||
statusCode: result.status,
|
||||
statusMsg: result.statusText,
|
||||
errorMsg: data?.errors,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
async getQueryResult(queryID: string, pageNumber: number, pageSize: number): Promise<QueryResultResp> {
|
||||
let result;
|
||||
try {
|
||||
result = await axios.get(this.getUrl(`queries/${queryID}`), {
|
||||
params: { pageNumber: pageNumber, pageSize: pageSize },
|
||||
method: "GET",
|
||||
headers: this.#headers,
|
||||
});
|
||||
} catch (err) {
|
||||
let errData = err as AxiosError;
|
||||
result = errData.response;
|
||||
if (!result) {
|
||||
throw new UnexpectedSDKError(PARSE_ERROR_MSG);
|
||||
}
|
||||
const data = result.data;
|
||||
return data;
|
||||
} catch (error) {
|
||||
throw new ServerError(
|
||||
result.status,
|
||||
`Unable to parse response for RPC response from '${method}': ${result.status} - ${result.statusText}. Please try again later.`
|
||||
);
|
||||
}
|
||||
|
||||
let data: QueryResultJson | null;
|
||||
if (result.status >= 200 && result.status < 300) {
|
||||
data = result.data;
|
||||
} else {
|
||||
data = null;
|
||||
}
|
||||
|
||||
return {
|
||||
statusCode: result.status,
|
||||
statusMsg: result.statusText,
|
||||
errorMsg: data?.errors,
|
||||
data,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
17
js/src/defaults.ts
Normal file
17
js/src/defaults.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { version } from "../package.json";
|
||||
import { SdkDefaults } from "./types";
|
||||
|
||||
export const DEFAULTS: SdkDefaults = {
|
||||
apiBaseUrl: "https://api-v2.flipsidecrypto.xyz",
|
||||
ttlMinutes: 60,
|
||||
maxAgeMinutes: 0,
|
||||
cached: true,
|
||||
dataProvider: "flipside",
|
||||
dataSource: "snowflake-default",
|
||||
timeoutMinutes: 20,
|
||||
retryIntervalSeconds: 0.5,
|
||||
pageSize: 100000,
|
||||
pageNumber: 1,
|
||||
sdkPackage: "js",
|
||||
sdkVersion: version,
|
||||
};
|
||||
53
js/src/errors/api-error.ts
Normal file
53
js/src/errors/api-error.ts
Normal file
@ -0,0 +1,53 @@
|
||||
export class ApiError extends Error {
|
||||
constructor(name: string, code: number, message: string) {
|
||||
super(`${name}: message=${message}, code=${code}`);
|
||||
}
|
||||
}
|
||||
|
||||
export const errorCodes: { [key: string]: number } = {
|
||||
MethodValidationError: -32000,
|
||||
QueryRunNotFound: -32099,
|
||||
SqlStatementNotFound: -32100,
|
||||
TemporalError: -32150,
|
||||
QueryRunNotFinished: -32151,
|
||||
ResultTransformError: -32152,
|
||||
ResultFormatNotSupported: -32153,
|
||||
RowCountCouldNotBeComputed: -32154,
|
||||
QueryResultColumnMetadataMissing: -32155,
|
||||
InvalidSortColumn: -32156,
|
||||
ColumnSummaryQueryFailed: -32157,
|
||||
QueryResultColumnMetadataMissingColumnName: -32158,
|
||||
QueryResultColumnMetadataMissingColumnType: -32159,
|
||||
NoQueryRunsFoundinQueryText: -32160,
|
||||
DuckDBError: -32161,
|
||||
RefreshableQueryNotFound: -32162,
|
||||
AuthorizationError: -32163,
|
||||
DataSourceNotFound: -32164,
|
||||
QueryRunInvalidStateToCancel: -32165,
|
||||
DataProviderAlreadyExists: -32166,
|
||||
DataProviderNotFound: -32167,
|
||||
DataSourceAlreadyExists: -32168,
|
||||
AdminOnly: -32169,
|
||||
RequestedPageSizeTooLarge: -32170,
|
||||
MaxConcurrentQueries: -32171,
|
||||
};
|
||||
|
||||
export function getExceptionByErrorCode(errorCode?: number, message?: string): ApiError {
|
||||
if (!errorCode || !message) {
|
||||
return new ApiError("UnknownAPIError", errorCode || -1, message || "");
|
||||
}
|
||||
|
||||
let errorName: string | null = null;
|
||||
for (const key of Object.keys(errorCodes)) {
|
||||
if (errorCodes[key] === errorCode) {
|
||||
errorName = key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (errorName === null) {
|
||||
return new ApiError("UnknownAPIError", errorCode, message);
|
||||
}
|
||||
|
||||
return new ApiError(errorName, errorCode, message);
|
||||
}
|
||||
@ -2,9 +2,10 @@ import { ERROR_TYPES } from "./error-types";
|
||||
|
||||
export class BaseError extends Error {
|
||||
errorType: string;
|
||||
|
||||
data: Record<any, any>;
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.errorType = ERROR_TYPES.default;
|
||||
this.data = {};
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,4 +6,5 @@ export const ERROR_TYPES = {
|
||||
query_run_timeout_error: "QUERY_RUN_TIMEOUT_ERROR",
|
||||
query_run_execution_error: "QUERY_RUN_EXECUTION_ERROR",
|
||||
user_error: "USER_ERROR",
|
||||
api_error: "API_ERROR",
|
||||
};
|
||||
|
||||
@ -3,3 +3,4 @@ export * from "./server-errors";
|
||||
export * from "./sdk-errors";
|
||||
export * from "./query-run-errors";
|
||||
export * from "./user-errors";
|
||||
export * from "./api-error";
|
||||
|
||||
@ -4,9 +4,7 @@ import { ERROR_TYPES } from "./error-types";
|
||||
export class QueryRunRateLimitError extends BaseError {
|
||||
constructor() {
|
||||
const errorType = ERROR_TYPES.query_run_rate_limit_error;
|
||||
super(
|
||||
`${errorType}: you have exceeded the rate limit for creating/running new queries.`
|
||||
);
|
||||
super(`${errorType}: you have exceeded the rate limit for creating/running new queries.`);
|
||||
this.errorType = errorType;
|
||||
}
|
||||
}
|
||||
@ -14,17 +12,38 @@ export class QueryRunRateLimitError extends BaseError {
|
||||
export class QueryRunTimeoutError extends BaseError {
|
||||
constructor(timeoutMinutes: number) {
|
||||
const errorType = ERROR_TYPES.query_run_timeout_error;
|
||||
super(
|
||||
`${errorType}: your query has timed out after ${timeoutMinutes} minutes.`
|
||||
);
|
||||
super(`${errorType}: your query has timed out after ${timeoutMinutes} minutes.`);
|
||||
this.errorType = errorType;
|
||||
}
|
||||
}
|
||||
|
||||
export class QueryRunExecutionError extends BaseError {
|
||||
constructor() {
|
||||
constructor({
|
||||
name,
|
||||
message,
|
||||
data,
|
||||
}: {
|
||||
name?: string | undefined | null;
|
||||
message?: string | undefined | null;
|
||||
data?: Record<any, any> | undefined | null;
|
||||
}) {
|
||||
const errorType = ERROR_TYPES.query_run_execution_error;
|
||||
super(`${errorType}: an error has occured while executing your query`);
|
||||
if (!name && !message && !data) {
|
||||
super(`${errorType}: an error has occured while executing your query.`);
|
||||
} else {
|
||||
super(
|
||||
`${errorType}: an error has occured while executing your query: name=${name} - message=${message} - data=${data}`
|
||||
);
|
||||
}
|
||||
this.errorType = errorType;
|
||||
if (name) {
|
||||
this.name = name;
|
||||
}
|
||||
if (message) {
|
||||
this.message = message;
|
||||
}
|
||||
if (data) {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,18 +1,14 @@
|
||||
import { API } from "./api";
|
||||
import { Api } from "./api";
|
||||
import { QueryIntegration } from "./integrations";
|
||||
import { version } from '../package.json';
|
||||
|
||||
|
||||
const API_BASE_URL = "https://api.flipsidecrypto.com";
|
||||
const SDK_PACKAGE = "js";
|
||||
const SDK_VERSION = version;
|
||||
import { QueryResultSet } from "./types";
|
||||
import { DEFAULTS } from "./defaults";
|
||||
|
||||
export class Flipside {
|
||||
query: QueryIntegration;
|
||||
|
||||
constructor(apiKey: string, apiBaseUrl: string = API_BASE_URL) {
|
||||
constructor(apiKey: string, apiBaseUrl: string = DEFAULTS.apiBaseUrl) {
|
||||
// Setup API, which will be passed to integrations
|
||||
const api = new API(apiBaseUrl, SDK_PACKAGE, SDK_VERSION, apiKey);
|
||||
const api = new Api(apiBaseUrl, apiKey);
|
||||
|
||||
// declare integrations on Flipside client
|
||||
this.query = new QueryIntegration(api);
|
||||
@ -21,5 +17,5 @@ export class Flipside {
|
||||
|
||||
export * from "./types";
|
||||
export * from "./errors";
|
||||
import { QueryResultSet } from "./types";
|
||||
|
||||
export { QueryResultSet };
|
||||
|
||||
@ -1,195 +1,336 @@
|
||||
import {
|
||||
Query,
|
||||
QueryDefaults,
|
||||
QueryStatusFinished,
|
||||
QueryStatusError,
|
||||
QueryResultJson,
|
||||
CreateQueryJson,
|
||||
ApiClient,
|
||||
QueryResultSet,
|
||||
CreateQueryRunRpcParams,
|
||||
CreateQueryRunRpcResponse,
|
||||
mapApiQueryStateToStatus,
|
||||
GetQueryRunRpcResponse,
|
||||
Filter,
|
||||
SortBy,
|
||||
QueryRun,
|
||||
ResultFormat,
|
||||
SqlStatement,
|
||||
CompassApiClient,
|
||||
} from "../../types";
|
||||
import {
|
||||
expBackOff,
|
||||
getElapsedLinearSeconds,
|
||||
linearBackOff,
|
||||
} from "../../utils/sleep";
|
||||
import { getElapsedLinearSeconds, linearBackOff } from "../../utils/sleep";
|
||||
import {
|
||||
QueryRunExecutionError,
|
||||
QueryRunRateLimitError,
|
||||
QueryRunTimeoutError,
|
||||
ServerError,
|
||||
UserError,
|
||||
UnexpectedSDKError,
|
||||
getExceptionByErrorCode,
|
||||
ApiError,
|
||||
} from "../../errors";
|
||||
import { QueryResultSetBuilder } from "./query-result-set-builder";
|
||||
|
||||
const DEFAULTS: QueryDefaults = {
|
||||
ttlMinutes: 60,
|
||||
cached: true,
|
||||
timeoutMinutes: 20,
|
||||
retryIntervalSeconds: 0.5,
|
||||
pageSize: 100000,
|
||||
pageNumber: 1,
|
||||
};
|
||||
import { DEFAULTS } from "../../defaults";
|
||||
|
||||
export class QueryIntegration {
|
||||
#api: ApiClient;
|
||||
#defaults: QueryDefaults;
|
||||
#api: CompassApiClient;
|
||||
|
||||
constructor(api: ApiClient, defaults: QueryDefaults = DEFAULTS) {
|
||||
constructor(api: CompassApiClient) {
|
||||
this.#api = api;
|
||||
this.#defaults = defaults;
|
||||
}
|
||||
|
||||
#setQueryDefaults(query: Query): Query {
|
||||
return { ...this.#defaults, ...query };
|
||||
#getTimeoutMinutes(query: Query): number {
|
||||
return query.timeoutMinutes ? query.timeoutMinutes : DEFAULTS.timeoutMinutes;
|
||||
}
|
||||
|
||||
#getRetryIntervalSeconds(query: Query): number {
|
||||
return query.retryIntervalSeconds ? Number(query.retryIntervalSeconds) : DEFAULTS.retryIntervalSeconds;
|
||||
}
|
||||
|
||||
async run(query: Query): Promise<QueryResultSet> {
|
||||
query = this.#setQueryDefaults(query);
|
||||
let createQueryRunParams: CreateQueryRunRpcParams = {
|
||||
resultTTLHours: this.#getTTLHours(query),
|
||||
sql: query.sql,
|
||||
maxAgeMinutes: this.#getMaxAgeMinutes(query),
|
||||
tags: {
|
||||
sdk_language: "javascript",
|
||||
sdk_package: query.sdkPackage ? query.sdkPackage : DEFAULTS.sdkPackage,
|
||||
sdk_version: query.sdkVersion ? query.sdkVersion : DEFAULTS.sdkVersion,
|
||||
},
|
||||
dataSource: query.dataSource ? query.dataSource : DEFAULTS.dataSource,
|
||||
dataProvider: query.dataProvider ? query.dataProvider : DEFAULTS.dataProvider,
|
||||
};
|
||||
|
||||
const [createQueryJson, createQueryErr] = await this.#createQuery(query);
|
||||
if (createQueryErr) {
|
||||
const createQueryRunRpcResponse = await this.#createQuery(createQueryRunParams);
|
||||
if (createQueryRunRpcResponse.error) {
|
||||
return new QueryResultSetBuilder({
|
||||
queryResultJson: null,
|
||||
error: createQueryErr,
|
||||
error: getExceptionByErrorCode(createQueryRunRpcResponse.error.code, createQueryRunRpcResponse.error.message),
|
||||
});
|
||||
}
|
||||
|
||||
if (!createQueryJson) {
|
||||
if (!createQueryRunRpcResponse.result?.queryRun) {
|
||||
return new QueryResultSetBuilder({
|
||||
queryResultJson: null,
|
||||
error: new UnexpectedSDKError(
|
||||
"expected a `createQueryJson` but got null"
|
||||
),
|
||||
error: new UnexpectedSDKError("expected a `createQueryRunRpcResponse.result.queryRun` but got null"),
|
||||
});
|
||||
}
|
||||
|
||||
const [getQueryResultJson, getQueryErr] = await this.#getQueryResult(
|
||||
createQueryJson.token,
|
||||
query.pageNumber || 1,
|
||||
query.pageSize || 100000,
|
||||
);
|
||||
// loop to get query state
|
||||
const [queryRunRpcResp, queryError] = await this.#getQueryRunInLoop({
|
||||
queryRunId: createQueryRunRpcResponse.result?.queryRun.id,
|
||||
timeoutMinutes: this.#getTimeoutMinutes(query),
|
||||
retryIntervalSeconds: this.#getRetryIntervalSeconds(query),
|
||||
});
|
||||
|
||||
if (getQueryErr) {
|
||||
if (queryError) {
|
||||
return new QueryResultSetBuilder({
|
||||
queryResultJson: null,
|
||||
error: getQueryErr,
|
||||
error: queryError,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (!getQueryResultJson) {
|
||||
if (queryRunRpcResp && queryRunRpcResp.error) {
|
||||
return new QueryResultSetBuilder({
|
||||
queryResultJson: null,
|
||||
error: new UnexpectedSDKError(
|
||||
"expected a `getQueryResultJson` but got null"
|
||||
),
|
||||
error: getExceptionByErrorCode(queryRunRpcResp.error.code, queryRunRpcResp.error.message),
|
||||
});
|
||||
}
|
||||
|
||||
const queryRun = queryRunRpcResp?.result?.queryRun;
|
||||
if (!queryRun) {
|
||||
return new QueryResultSetBuilder({
|
||||
error: new UnexpectedSDKError("expected a `queryRunRpcResp.result.queryRun` but got null"),
|
||||
});
|
||||
}
|
||||
|
||||
// get the query results
|
||||
const queryResultResp = await this.#api.getQueryResult({
|
||||
queryRunId: queryRun.id,
|
||||
format: ResultFormat.csv,
|
||||
page: {
|
||||
number: query.pageNumber || 1,
|
||||
size: query.pageSize || 100000,
|
||||
},
|
||||
});
|
||||
|
||||
if (queryResultResp && queryResultResp.error) {
|
||||
return new QueryResultSetBuilder({
|
||||
error: getExceptionByErrorCode(queryResultResp.error.code, queryResultResp.error.message),
|
||||
});
|
||||
}
|
||||
|
||||
const queryResults = queryResultResp.result;
|
||||
if (!queryResults) {
|
||||
return new QueryResultSetBuilder({
|
||||
error: new UnexpectedSDKError("expected a `queryResultResp.result` but got null"),
|
||||
});
|
||||
}
|
||||
|
||||
return new QueryResultSetBuilder({
|
||||
queryResultJson: getQueryResultJson,
|
||||
getQueryRunResultsRpcResult: queryResults,
|
||||
getQueryRunRpcResult: queryRunRpcResp.result,
|
||||
error: null,
|
||||
});
|
||||
}
|
||||
|
||||
async #createQuery(
|
||||
query: Query,
|
||||
attempts: number = 0
|
||||
): Promise<
|
||||
[
|
||||
CreateQueryJson | null,
|
||||
QueryRunRateLimitError | ServerError | UserError | null
|
||||
]
|
||||
> {
|
||||
const resp = await this.#api.createQuery(query);
|
||||
if (resp.statusCode <= 299) {
|
||||
return [resp.data, null];
|
||||
async getQueryResults({
|
||||
queryRunId,
|
||||
pageNumber = DEFAULTS.pageNumber,
|
||||
pageSize = DEFAULTS.pageSize,
|
||||
filters,
|
||||
sortBy,
|
||||
}: {
|
||||
queryRunId: string;
|
||||
pageNumber?: number;
|
||||
pageSize?: number;
|
||||
filters?: Filter[];
|
||||
sortBy?: SortBy[];
|
||||
}): Promise<QueryResultSet> {
|
||||
const queryRunResp = await this.#api.getQueryRun({ queryRunId });
|
||||
if (queryRunResp.error) {
|
||||
return new QueryResultSetBuilder({
|
||||
error: getExceptionByErrorCode(queryRunResp.error.code, queryRunResp.error.message),
|
||||
});
|
||||
}
|
||||
|
||||
if (resp.statusCode !== 429) {
|
||||
if (resp.statusCode >= 400 && resp.statusCode <= 499) {
|
||||
let errorMsg = resp.statusMsg || "user error";
|
||||
if (resp.errorMsg) {
|
||||
errorMsg = resp.errorMsg;
|
||||
}
|
||||
return [null, new UserError(resp.statusCode, errorMsg)];
|
||||
}
|
||||
return [
|
||||
null,
|
||||
new ServerError(resp.statusCode, resp.statusMsg || "server error"),
|
||||
];
|
||||
if (!queryRunResp.result) {
|
||||
return new QueryResultSetBuilder({
|
||||
error: new UnexpectedSDKError("expected an `rpc_response.result` but got null"),
|
||||
});
|
||||
}
|
||||
|
||||
let shouldContinue = await expBackOff({
|
||||
attempts,
|
||||
timeoutMinutes: this.#defaults.timeoutMinutes,
|
||||
intervalSeconds: this.#defaults.retryIntervalSeconds,
|
||||
if (!queryRunResp.result?.queryRun) {
|
||||
return new QueryResultSetBuilder({
|
||||
error: new UnexpectedSDKError("expected an `rpc_response.result.queryRun` but got null"),
|
||||
});
|
||||
}
|
||||
|
||||
const queryRun = queryRunResp.result.redirectedToQueryRun
|
||||
? queryRunResp.result.redirectedToQueryRun
|
||||
: queryRunResp.result.queryRun;
|
||||
|
||||
const queryResultResp = await this.#api.getQueryResult({
|
||||
queryRunId: queryRun.id,
|
||||
format: ResultFormat.csv,
|
||||
page: {
|
||||
number: pageNumber,
|
||||
size: pageSize,
|
||||
},
|
||||
filters,
|
||||
sortBy,
|
||||
});
|
||||
|
||||
if (!shouldContinue) {
|
||||
return [null, new QueryRunRateLimitError()];
|
||||
if (queryResultResp.error) {
|
||||
return new QueryResultSetBuilder({
|
||||
error: getExceptionByErrorCode(queryResultResp.error.code, queryResultResp.error.message),
|
||||
});
|
||||
}
|
||||
|
||||
return this.#createQuery(query, attempts + 1);
|
||||
return new QueryResultSetBuilder({
|
||||
getQueryRunResultsRpcResult: queryResultResp.result,
|
||||
getQueryRunRpcResult: queryRunResp.result,
|
||||
error: null,
|
||||
});
|
||||
}
|
||||
|
||||
async #getQueryResult(
|
||||
queryID: string,
|
||||
pageNumber: number,
|
||||
pageSize: number,
|
||||
attempts: number = 0
|
||||
): Promise<
|
||||
async createQueryRun(query: Query): Promise<QueryRun> {
|
||||
let createQueryRunParams: CreateQueryRunRpcParams = {
|
||||
resultTTLHours: this.#getTTLHours(query),
|
||||
sql: query.sql,
|
||||
maxAgeMinutes: this.#getMaxAgeMinutes(query),
|
||||
tags: {
|
||||
sdk_language: "javascript",
|
||||
sdk_package: query.sdkPackage ? query.sdkPackage : DEFAULTS.sdkPackage,
|
||||
sdk_version: query.sdkVersion ? query.sdkVersion : DEFAULTS.sdkVersion,
|
||||
},
|
||||
dataSource: query.dataSource ? query.dataSource : DEFAULTS.dataSource,
|
||||
dataProvider: query.dataProvider ? query.dataProvider : DEFAULTS.dataProvider,
|
||||
};
|
||||
|
||||
const createQueryRunRpcResponse = await this.#createQuery(createQueryRunParams);
|
||||
if (createQueryRunRpcResponse.error) {
|
||||
throw getExceptionByErrorCode(createQueryRunRpcResponse.error.code, createQueryRunRpcResponse.error.message);
|
||||
}
|
||||
|
||||
if (!createQueryRunRpcResponse.result?.queryRun) {
|
||||
throw new UnexpectedSDKError("expected a `createQueryRunRpcResponse.result.queryRun` but got null");
|
||||
}
|
||||
|
||||
return createQueryRunRpcResponse.result.queryRun;
|
||||
}
|
||||
|
||||
async getQueryRun({ queryRunId }: { queryRunId: string }): Promise<QueryRun> {
|
||||
const resp = await this.#api.getQueryRun({ queryRunId });
|
||||
if (resp.error) {
|
||||
throw getExceptionByErrorCode(resp.error.code, resp.error.message);
|
||||
}
|
||||
if (!resp.result) {
|
||||
throw new UnexpectedSDKError("expected an `rpc_response.result` but got null");
|
||||
}
|
||||
|
||||
if (!resp.result?.queryRun) {
|
||||
throw new UnexpectedSDKError("expected an `rpc_response.result.queryRun` but got null");
|
||||
}
|
||||
|
||||
return resp.result.redirectedToQueryRun ? resp.result.redirectedToQueryRun : resp.result.queryRun;
|
||||
}
|
||||
|
||||
async getSqlStatement({ sqlStatementId }: { sqlStatementId: string }): Promise<SqlStatement> {
|
||||
const resp = await this.#api.getSqlStatement({ sqlStatementId });
|
||||
if (resp.error) {
|
||||
throw getExceptionByErrorCode(resp.error.code, resp.error.message);
|
||||
}
|
||||
if (!resp.result) {
|
||||
throw new UnexpectedSDKError("expected an `rpc_response.result` but got null");
|
||||
}
|
||||
|
||||
if (!resp.result?.sqlStatement) {
|
||||
throw new UnexpectedSDKError("expected an `rpc_response.result.sqlStatement` but got null");
|
||||
}
|
||||
return resp.result.sqlStatement;
|
||||
}
|
||||
|
||||
async cancelQueryRun({ queryRunId }: { queryRunId: string }): Promise<QueryRun> {
|
||||
const resp = await this.#api.cancelQueryRun({ queryRunId });
|
||||
if (resp.error) {
|
||||
throw getExceptionByErrorCode(resp.error.code, resp.error.message);
|
||||
}
|
||||
if (!resp.result) {
|
||||
throw new UnexpectedSDKError("expected an `rpc_response.result` but got null");
|
||||
}
|
||||
|
||||
if (!resp.result?.canceledQueryRun) {
|
||||
throw new UnexpectedSDKError("expected an `rpc_response.result.canceledQueryRun` but got null");
|
||||
}
|
||||
return resp.result.canceledQueryRun;
|
||||
}
|
||||
|
||||
#getMaxAgeMinutes(query: Query): number {
|
||||
if (query.cached === false) {
|
||||
return 0;
|
||||
}
|
||||
return query.maxAgeMinutes ? query.maxAgeMinutes : DEFAULTS.maxAgeMinutes;
|
||||
}
|
||||
|
||||
#getTTLHours(query: Query): number {
|
||||
const maxAgeMinutes = this.#getMaxAgeMinutes(query);
|
||||
const ttlMinutes = maxAgeMinutes > 60 ? maxAgeMinutes : DEFAULTS.ttlMinutes;
|
||||
return Math.floor(ttlMinutes / 60);
|
||||
}
|
||||
|
||||
async #createQuery(params: CreateQueryRunRpcParams, attempts: number = 0): Promise<CreateQueryRunRpcResponse> {
|
||||
return await this.#api.createQuery(params);
|
||||
}
|
||||
|
||||
async #getQueryRunInLoop({
|
||||
queryRunId,
|
||||
timeoutMinutes,
|
||||
retryIntervalSeconds,
|
||||
attempts = 0,
|
||||
}: {
|
||||
queryRunId: string;
|
||||
timeoutMinutes: number;
|
||||
retryIntervalSeconds: number;
|
||||
attempts?: number;
|
||||
}): Promise<
|
||||
[
|
||||
QueryResultJson | null,
|
||||
QueryRunTimeoutError | ServerError | UserError | null
|
||||
GetQueryRunRpcResponse | null,
|
||||
QueryRunTimeoutError | QueryRunExecutionError | ServerError | UserError | ApiError | null
|
||||
]
|
||||
> {
|
||||
const resp = await this.#api.getQueryResult(queryID, pageNumber, pageSize);
|
||||
if (resp.statusCode > 299) {
|
||||
if (resp.statusCode >= 400 && resp.statusCode <= 499) {
|
||||
let errorMsg = resp.statusMsg || "user input error";
|
||||
if (resp.errorMsg) {
|
||||
errorMsg = resp.errorMsg;
|
||||
}
|
||||
return [null, new UserError(resp.statusCode, errorMsg)];
|
||||
}
|
||||
let resp = await this.#api.getQueryRun({ queryRunId });
|
||||
if (resp.error) {
|
||||
return [null, getExceptionByErrorCode(resp.error.code, resp.error.message)];
|
||||
}
|
||||
|
||||
const queryRun = resp.result?.redirectedToQueryRun ? resp.result.redirectedToQueryRun : resp.result?.queryRun;
|
||||
if (!queryRun) {
|
||||
return [null, new UnexpectedSDKError("expected an `rpc_response.result.queryRun` but got null")];
|
||||
}
|
||||
|
||||
const queryRunState = mapApiQueryStateToStatus(queryRun.state);
|
||||
if (queryRunState === QueryStatusFinished) {
|
||||
return [resp, null];
|
||||
}
|
||||
|
||||
if (queryRunState === QueryStatusError) {
|
||||
return [
|
||||
null,
|
||||
new ServerError(resp.statusCode, resp.statusMsg || "server error"),
|
||||
new QueryRunExecutionError({
|
||||
name: queryRun.errorName,
|
||||
message: queryRun.errorMessage,
|
||||
data: queryRun.errorData,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
if (!resp.data) {
|
||||
throw new Error(
|
||||
"valid status msg returned from server but no data exists in the response"
|
||||
);
|
||||
}
|
||||
|
||||
if (resp.data.status === QueryStatusFinished) {
|
||||
return [resp.data, null];
|
||||
}
|
||||
|
||||
if (resp.data.status === QueryStatusError) {
|
||||
return [null, new QueryRunExecutionError()];
|
||||
}
|
||||
|
||||
let shouldContinue = await linearBackOff({
|
||||
attempts,
|
||||
timeoutMinutes: this.#defaults.timeoutMinutes,
|
||||
intervalSeconds: this.#defaults.retryIntervalSeconds,
|
||||
timeoutMinutes,
|
||||
intervalSeconds: retryIntervalSeconds,
|
||||
});
|
||||
|
||||
if (!shouldContinue) {
|
||||
const elapsedSeconds = getElapsedLinearSeconds({
|
||||
attempts,
|
||||
timeoutMinutes: this.#defaults.timeoutMinutes,
|
||||
intervalSeconds: this.#defaults.retryIntervalSeconds,
|
||||
timeoutMinutes,
|
||||
intervalSeconds: retryIntervalSeconds,
|
||||
});
|
||||
return [null, new QueryRunTimeoutError(elapsedSeconds * 60)];
|
||||
}
|
||||
|
||||
return this.#getQueryResult(queryID, pageNumber, pageSize, attempts + 1);
|
||||
return this.#getQueryRunInLoop({ queryRunId, attempts: attempts + 1, timeoutMinutes, retryIntervalSeconds });
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,27 +5,45 @@ import {
|
||||
ServerError,
|
||||
UserError,
|
||||
UnexpectedSDKError,
|
||||
ApiError,
|
||||
} from "../../errors";
|
||||
import {
|
||||
QueryResultJson,
|
||||
QueryResultSet,
|
||||
Row,
|
||||
QueryResultRecord,
|
||||
QueryRunStats,
|
||||
QueryStatus,
|
||||
GetQueryRunResultsRpcResult,
|
||||
GetQueryRunRpcResult,
|
||||
mapApiQueryStateToStatus,
|
||||
PageStats,
|
||||
} from "../../types";
|
||||
import { QueryResultSetBuilderInput } from "../../types/query-result-set-input.type";
|
||||
|
||||
interface QueryResultSetBuilderData {
|
||||
getQueryRunResultsRpcResult?: GetQueryRunResultsRpcResult | null;
|
||||
getQueryRunRpcResult?: GetQueryRunRpcResult | null;
|
||||
error:
|
||||
| ApiError
|
||||
| QueryRunRateLimitError
|
||||
| QueryRunTimeoutError
|
||||
| QueryRunExecutionError
|
||||
| ServerError
|
||||
| UserError
|
||||
| UnexpectedSDKError
|
||||
| null;
|
||||
}
|
||||
|
||||
export class QueryResultSetBuilder implements QueryResultSet {
|
||||
queryId: string | null;
|
||||
status: QueryStatus | null;
|
||||
columns: string[] | null;
|
||||
columnTypes: string[] | null;
|
||||
rows: Row[] | null;
|
||||
rows: any[] | null;
|
||||
runStats: QueryRunStats | null;
|
||||
records: QueryResultRecord[] | null;
|
||||
page: PageStats | null;
|
||||
|
||||
error:
|
||||
| ApiError
|
||||
| QueryRunRateLimitError
|
||||
| QueryRunTimeoutError
|
||||
| QueryRunExecutionError
|
||||
@ -34,10 +52,10 @@ export class QueryResultSetBuilder implements QueryResultSet {
|
||||
| UnexpectedSDKError
|
||||
| null;
|
||||
|
||||
constructor(data: QueryResultSetBuilderInput) {
|
||||
this.error = data.error;
|
||||
const queryResultJson = data.queryResultJson;
|
||||
if (!queryResultJson) {
|
||||
constructor({ getQueryRunResultsRpcResult, getQueryRunRpcResult, error }: QueryResultSetBuilderData) {
|
||||
this.error = error;
|
||||
|
||||
if (!getQueryRunResultsRpcResult || !getQueryRunRpcResult) {
|
||||
this.queryId = null;
|
||||
this.status = null;
|
||||
this.columns = null;
|
||||
@ -45,54 +63,70 @@ export class QueryResultSetBuilder implements QueryResultSet {
|
||||
this.rows = null;
|
||||
this.runStats = null;
|
||||
this.records = null;
|
||||
this.page = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this.queryId = queryResultJson.queryId;
|
||||
this.status = queryResultJson.status;
|
||||
this.columns = queryResultJson.columnLabels;
|
||||
this.columnTypes = queryResultJson.columnTypes;
|
||||
this.rows = queryResultJson.results;
|
||||
this.runStats = this.#computeRunStats(queryResultJson);
|
||||
this.records = this.#createRecords(queryResultJson);
|
||||
this.queryId = getQueryRunRpcResult.queryRun.id;
|
||||
this.status = mapApiQueryStateToStatus(getQueryRunRpcResult.queryRun.state);
|
||||
this.columns = getQueryRunResultsRpcResult.columnNames;
|
||||
this.columnTypes = getQueryRunResultsRpcResult.columnTypes;
|
||||
this.rows = getQueryRunResultsRpcResult.rows;
|
||||
this.runStats = this.#computeRunStats(getQueryRunRpcResult);
|
||||
this.records = this.#createRecords(getQueryRunResultsRpcResult);
|
||||
this.page = getQueryRunResultsRpcResult.page;
|
||||
}
|
||||
|
||||
#computeRunStats(
|
||||
queryResultJson: QueryResultJson | null
|
||||
): QueryRunStats | null {
|
||||
if (!queryResultJson) {
|
||||
#createRecords(getQueryRunResultsRpcResult: GetQueryRunResultsRpcResult | null): QueryResultRecord[] | null {
|
||||
if (!getQueryRunResultsRpcResult || !getQueryRunResultsRpcResult.columnNames || !getQueryRunResultsRpcResult.rows) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let startedAt = new Date(queryResultJson.startedAt);
|
||||
let endedAt = new Date(queryResultJson.endedAt);
|
||||
let elapsedSeconds = (endedAt.getTime() - startedAt.getTime()) / 1000;
|
||||
return {
|
||||
startedAt,
|
||||
endedAt,
|
||||
elapsedSeconds,
|
||||
recordCount: queryResultJson.results.length,
|
||||
};
|
||||
}
|
||||
let columnNames = getQueryRunResultsRpcResult.columnNames;
|
||||
|
||||
#createRecords(
|
||||
queryResultJson: QueryResultJson | null
|
||||
): QueryResultRecord[] | null {
|
||||
if (!queryResultJson) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let columnLabels = queryResultJson.columnLabels;
|
||||
if (!columnLabels) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return queryResultJson.results.map((result) => {
|
||||
return getQueryRunResultsRpcResult.rows.map((row) => {
|
||||
let record: QueryResultRecord = {};
|
||||
result.forEach((value, index) => {
|
||||
record[columnLabels[index].toLowerCase()] = value;
|
||||
row.forEach((value: any, index: number) => {
|
||||
record[columnNames[index].toLowerCase()] = value;
|
||||
});
|
||||
return record;
|
||||
});
|
||||
}
|
||||
|
||||
#computeRunStats(getQueryRunRpcResult: GetQueryRunRpcResult): QueryRunStats {
|
||||
const queryRun = getQueryRunRpcResult.queryRun;
|
||||
|
||||
if (
|
||||
!queryRun.startedAt ||
|
||||
!queryRun.endedAt ||
|
||||
!queryRun.createdAt ||
|
||||
!queryRun.queryStreamingEndedAt ||
|
||||
!queryRun.queryRunningEndedAt
|
||||
) {
|
||||
throw new UnexpectedSDKError(
|
||||
"Query run is missing required fields: `startedAt`, `endedAt`, `createdAt`, `queryStreamingEndedAt`, `queryRunningEndedAt`"
|
||||
);
|
||||
}
|
||||
|
||||
const createdAt = new Date(queryRun.createdAt);
|
||||
const startTime = new Date(queryRun.startedAt);
|
||||
const endTime = new Date(queryRun.endedAt);
|
||||
const streamingEndTime = new Date(queryRun.queryStreamingEndedAt);
|
||||
const queryExecEndAt = new Date(queryRun.queryRunningEndedAt);
|
||||
|
||||
return {
|
||||
startedAt: startTime,
|
||||
endedAt: endTime,
|
||||
elapsedSeconds: (endTime.getTime() - startTime.getTime()) / 1000,
|
||||
queryExecStartedAt: startTime,
|
||||
queryExecEndedAt: queryExecEndAt,
|
||||
streamingStartedAt: queryExecEndAt,
|
||||
streamingEndedAt: streamingEndTime,
|
||||
queuedSeconds: (startTime.getTime() - createdAt.getTime()) / 1000,
|
||||
streamingSeconds: (streamingEndTime.getTime() - queryExecEndAt.getTime()) / 1000,
|
||||
queryExecSeconds: (queryExecEndAt.getTime() - startTime.getTime()) / 1000,
|
||||
bytes: queryRun.totalSize ? queryRun.totalSize : 0,
|
||||
recordCount: queryRun.rowCount ? queryRun.rowCount : 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,27 +1,145 @@
|
||||
import { Flipside } from "../flipside";
|
||||
import { ApiError, Flipside } from "../flipside";
|
||||
import { Query, QueryResultSet } from "../types";
|
||||
|
||||
// @ts-ignore
|
||||
const API_KEY = process.env.FLIPSIDE_API_KEY;
|
||||
if (!API_KEY || API_KEY === "" || API_KEY.length === 0) {
|
||||
throw new Error("No API Key Provided");
|
||||
}
|
||||
|
||||
const runIt = async (): Promise<void> => {
|
||||
// Initialize `Flipside` with your API key
|
||||
const flipside = new Flipside(
|
||||
"api-key-here",
|
||||
"https://api.flipsidecrypto.com",
|
||||
);
|
||||
const flipside = new Flipside(API_KEY, "https://api-v2.flipsidecrypto.xyz");
|
||||
|
||||
// Create a query object for the `query.run` function to execute
|
||||
const query: Query = {
|
||||
sql: "select nft_address, mint_price_eth, mint_price_usd from flipside_prod_db.ethereum_core.ez_nft_mints limit 100",
|
||||
ttlMinutes: 10,
|
||||
pageSize: 100,
|
||||
pageNumber: 1
|
||||
};
|
||||
|
||||
// Send the `Query` to Flipside's query engine and await the results
|
||||
const result: QueryResultSet = await flipside.query.run(query);
|
||||
|
||||
if (!result || !result.records) throw "null result";
|
||||
|
||||
console.log(result);
|
||||
await runWithSuccess(flipside);
|
||||
await runWithError(flipside);
|
||||
await pageThruResults(flipside);
|
||||
await getQueryRunSuccess(flipside);
|
||||
await getQueryRunError(flipside);
|
||||
await cancelQueryRun(flipside);
|
||||
};
|
||||
|
||||
runIt();
|
||||
async function runWithSuccess(flipside: Flipside) {
|
||||
// Create a query object for the `query.run` function to execute
|
||||
const query: Query = {
|
||||
sql: "select nft_address, mint_price_eth, mint_price_usd from ethereum.nft.ez_nft_mints limit 100",
|
||||
ttlMinutes: 10,
|
||||
pageSize: 5,
|
||||
pageNumber: 1,
|
||||
maxAgeMinutes: 10,
|
||||
};
|
||||
|
||||
const result: QueryResultSet = await flipside.query.run(query);
|
||||
|
||||
const records = result?.records?.length ? result?.records?.length : 0;
|
||||
if (records > 0) {
|
||||
console.log("✅ runWithSuccess");
|
||||
return;
|
||||
}
|
||||
throw new Error("Failed runWithSuccess: no records returned");
|
||||
}
|
||||
|
||||
async function runWithError(flipside: Flipside) {
|
||||
// Create a query object for the `query.run` function to execute
|
||||
const query: Query = {
|
||||
sql: "select nft_address mint_price_eth mint_price_usd from ethereum.nft.ez_nft_mints limit 100",
|
||||
ttlMinutes: 10,
|
||||
pageSize: 5,
|
||||
pageNumber: 1,
|
||||
maxAgeMinutes: 10,
|
||||
};
|
||||
|
||||
const result: QueryResultSet = await flipside.query.run(query);
|
||||
if (!result.error) {
|
||||
throw new Error("❌ runWithSuccess");
|
||||
}
|
||||
console.log("✅ runWithError");
|
||||
}
|
||||
|
||||
async function pageThruResults(flipside: Flipside) {
|
||||
// Create a query object for the `query.run` function to execute
|
||||
const query: Query = {
|
||||
sql: "select nft_address, mint_price_eth, mint_price_usd from ethereum.nft.ez_nft_mints limit 100",
|
||||
ttlMinutes: 10,
|
||||
pageSize: 25,
|
||||
pageNumber: 1,
|
||||
maxAgeMinutes: 10,
|
||||
};
|
||||
|
||||
const result: QueryResultSet = await flipside.query.run(query);
|
||||
if (result.error || !result.queryId || !result.page) {
|
||||
throw new Error(`❌ pageThruResults: ${result.error}`);
|
||||
}
|
||||
let allRecords: any[] = [];
|
||||
let pageNumber = 1;
|
||||
let pageSize = 25;
|
||||
while (pageNumber <= result.page.totalPages) {
|
||||
const results = await flipside.query.getQueryResults({ queryRunId: result.queryId, pageSize, pageNumber });
|
||||
if (results.records) {
|
||||
allRecords = [...allRecords, ...results.records];
|
||||
}
|
||||
if (results.page?.currentPageNumber !== pageNumber) {
|
||||
throw new Error("❌ pageThruResults: currentPageNumber does not match requested pageNumber");
|
||||
}
|
||||
pageNumber++;
|
||||
}
|
||||
|
||||
if (allRecords.length !== 100 || allRecords.length !== result.runStats?.recordCount) {
|
||||
throw new Error("❌ pageThruResults");
|
||||
}
|
||||
|
||||
console.log("✅ pageThruResults");
|
||||
}
|
||||
|
||||
async function getQueryRunSuccess(flipside: Flipside) {
|
||||
// Create a query object for the `query.run` function to execute
|
||||
const query: Query = {
|
||||
sql: "select nft_address, mint_price_eth, mint_price_usd from ethereum.nft.ez_nft_mints limit 100",
|
||||
ttlMinutes: 10,
|
||||
pageSize: 5,
|
||||
pageNumber: 1,
|
||||
maxAgeMinutes: 10,
|
||||
};
|
||||
|
||||
const result: QueryResultSet = await flipside.query.run(query);
|
||||
const queryId = result.queryId || "";
|
||||
const queryRun = await flipside.query.getQueryRun({ queryRunId: queryId });
|
||||
if (queryRun.errorName !== null) {
|
||||
throw new Error("❌ getQueryRunSuccess");
|
||||
}
|
||||
console.log("✅ getQueryRunSuccess");
|
||||
}
|
||||
|
||||
async function getQueryRunError(flipside: Flipside) {
|
||||
try {
|
||||
const queryRun = await flipside.query.getQueryRun({ queryRunId: "randomid" });
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError) {
|
||||
console.log("✅ getQueryRunError");
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new Error("❌ getQueryRunError");
|
||||
}
|
||||
|
||||
async function cancelQueryRun(flipside: Flipside) {
|
||||
// Create a query object for the `query.run` function to execute
|
||||
const query: Query = {
|
||||
sql: "select nft_address, mint_price_eth, mint_price_usd from ethereum.nft.ez_nft_mints limit 999",
|
||||
ttlMinutes: 10,
|
||||
pageSize: 5,
|
||||
pageNumber: 1,
|
||||
maxAgeMinutes: 0,
|
||||
};
|
||||
|
||||
const queryRun = await flipside.query.createQueryRun(query);
|
||||
try {
|
||||
await flipside.query.cancelQueryRun({ queryRunId: queryRun.id });
|
||||
} catch (e) {
|
||||
console.log("❌ cancelQueryRun");
|
||||
throw e;
|
||||
}
|
||||
|
||||
console.log("✅ cancelQueryRun");
|
||||
}
|
||||
|
||||
runIt();
|
||||
|
||||
61
js/src/tests/mock-data/cancel-query-run.ts
Normal file
61
js/src/tests/mock-data/cancel-query-run.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { QueryStatus, RpcError, CancelQueryRunRpcResponse, mapApiQueryStateToStatus } from "../../../src/types";
|
||||
|
||||
export function cancelQueryRunResponse(
|
||||
status: string = "QUERY_STATE_SUCCESS",
|
||||
error: RpcError | null = null
|
||||
): CancelQueryRunRpcResponse {
|
||||
let base: CancelQueryRunRpcResponse = {
|
||||
jsonrpc: "2.0",
|
||||
id: 1,
|
||||
error: null,
|
||||
result: null,
|
||||
};
|
||||
|
||||
const defaultResult = {
|
||||
canceledQueryRun: {
|
||||
id: "clg44olzq00cbn60tasvob5l2",
|
||||
sqlStatementId: "clg44oly200c9n60tviq17sng",
|
||||
state: status,
|
||||
path: "2023/04/05/20/clg44olzq00cbn60tasvob5l2",
|
||||
fileCount: 1,
|
||||
lastFileNumber: 1,
|
||||
fileNames: "clg44olzq00cbn60tasvob5l2-consolidated-results.parquet",
|
||||
errorName: null,
|
||||
errorMessage: null,
|
||||
errorData: null,
|
||||
dataSourceQueryId: null,
|
||||
dataSourceSessionId: "17257398387030526",
|
||||
startedAt: "2023-04-05T20:14:55.000Z",
|
||||
queryRunningEndedAt: "2023-04-05T20:15:00.000Z",
|
||||
queryStreamingEndedAt: "2023-04-05T20:15:45.000Z",
|
||||
endedAt: "2023-04-05T20:15:46.000Z",
|
||||
rowCount: 10000,
|
||||
totalSize: 24904891,
|
||||
tags: {
|
||||
sdk_package: "js",
|
||||
sdk_version: "1.0.0",
|
||||
sdk_language: "javascript",
|
||||
},
|
||||
dataSourceId: "clf90gwee0002jvbu63diaa8u",
|
||||
userId: "clf8qd1eb0000jv08kbuw0dy4",
|
||||
createdAt: "2023-04-05T20:14:55.000Z",
|
||||
updatedAt: "2023-04-05T20:14:55.000Z",
|
||||
archivedAt: "2023-04-05T20:14:55.000Z",
|
||||
},
|
||||
redirectedToQueryRun: null,
|
||||
};
|
||||
|
||||
if (error !== null) {
|
||||
base = {
|
||||
...base,
|
||||
error: error,
|
||||
};
|
||||
}
|
||||
|
||||
base = {
|
||||
...base,
|
||||
result: defaultResult,
|
||||
};
|
||||
|
||||
return base;
|
||||
}
|
||||
151
js/src/tests/mock-data/create-query-run.ts
Normal file
151
js/src/tests/mock-data/create-query-run.ts
Normal file
@ -0,0 +1,151 @@
|
||||
import {
|
||||
QueryRun,
|
||||
RpcError,
|
||||
mapApiQueryStateToStatus,
|
||||
BaseRpcResponse,
|
||||
CreateQueryRunRpcResponse,
|
||||
} from "../../../src/types";
|
||||
|
||||
export function createQueryRunResponse(
|
||||
status: string = "QUERY_STATE_READY",
|
||||
error: RpcError | null = null,
|
||||
resultNull: boolean = false
|
||||
): CreateQueryRunRpcResponse {
|
||||
let defaultResult = {
|
||||
queryRequest: {
|
||||
id: "clg44na8m00iund0uymg1n60i",
|
||||
sqlStatementId: "clg44k7gt00iind0ul763yjf8",
|
||||
userId: "clf8qd1eb0000jv08kbuw0dy4",
|
||||
tags: {
|
||||
sdk_package: "js",
|
||||
sdk_version: "1.0.2",
|
||||
sdk_language: "javascript",
|
||||
},
|
||||
maxAgeMinutes: 30,
|
||||
resultTTLHours: 720,
|
||||
userSkipCache: false,
|
||||
triggeredQueryRun: false,
|
||||
queryRunId: "clg44k7ij00iknd0u60y2zfyx",
|
||||
createdAt: "2023-04-05T20:13:53.000Z",
|
||||
updatedAt: "2023-04-05T20:13:53.000Z",
|
||||
},
|
||||
queryRun: {
|
||||
id: "clg44k7ij00iknd0u60y2zfyx",
|
||||
sqlStatementId: "clg44k7gt00iind0ul763yjf8",
|
||||
state: status,
|
||||
path: "2023/04/05/20/clg44k7ij00iknd0u60y2zfyx",
|
||||
fileCount: 1,
|
||||
lastFileNumber: 1,
|
||||
fileNames: "clg44k7ij00iknd0u60y2zfyx-consolidated-results.parquet",
|
||||
errorName: null,
|
||||
errorMessage: null,
|
||||
errorData: null,
|
||||
dataSourceQueryId: null,
|
||||
dataSourceSessionId: "17257398387015434",
|
||||
startedAt: "2023-04-05T20:11:30.000Z",
|
||||
queryRunningEndedAt: "2023-04-05T20:11:46.000Z",
|
||||
queryStreamingEndedAt: "2023-04-05T20:13:16.000Z",
|
||||
endedAt: "2023-04-05T20:13:16.000Z",
|
||||
rowCount: 13000,
|
||||
totalSize: 18412529,
|
||||
tags: {
|
||||
sdk_package: "js",
|
||||
sdk_version: "1.0.2",
|
||||
sdk_language: "javascript",
|
||||
},
|
||||
dataSourceId: "clf90gwee0002jvbu63diaa8u",
|
||||
userId: "clf8qd1eb0000jv08kbuw0dy4",
|
||||
createdAt: "2023-04-05T20:11:29.000Z",
|
||||
updatedAt: "2023-04-05T20:11:29.000Z",
|
||||
archivedAt: null,
|
||||
},
|
||||
sqlStatement: {
|
||||
id: "clg44k7gt00iind0ul763yjf8",
|
||||
statementHash: "36aa1767e72b9c9be3d9cfabe8992da861571629b45e57a834a44d6f2aeabf5d",
|
||||
sql: "SELECT * FROM ethereum.core.fact_transactions LIMIT 13000",
|
||||
columnMetadata: {
|
||||
types: [
|
||||
"fixed",
|
||||
"timestamp_ntz",
|
||||
"text",
|
||||
"text",
|
||||
"real",
|
||||
"fixed",
|
||||
"text",
|
||||
"text",
|
||||
"text",
|
||||
"real",
|
||||
"real",
|
||||
"real",
|
||||
"fixed",
|
||||
"real",
|
||||
"real",
|
||||
"text",
|
||||
"text",
|
||||
"object",
|
||||
],
|
||||
columns: [
|
||||
"BLOCK_NUMBER",
|
||||
"BLOCK_TIMESTAMP",
|
||||
"BLOCK_HASH",
|
||||
"TX_HASH",
|
||||
"NONCE",
|
||||
"POSITION",
|
||||
"ORIGIN_FUNCTION_SIGNATURE",
|
||||
"FROM_ADDRESS",
|
||||
"TO_ADDRESS",
|
||||
"ETH_VALUE",
|
||||
"TX_FEE",
|
||||
"GAS_PRICE",
|
||||
"GAS_LIMIT",
|
||||
"GAS_USED",
|
||||
"CUMULATIVE_GAS_USED",
|
||||
"INPUT_DATA",
|
||||
"STATUS",
|
||||
"TX_JSON",
|
||||
],
|
||||
colTypeMap: {
|
||||
NONCE: "real",
|
||||
STATUS: "text",
|
||||
TX_FEE: "real",
|
||||
TX_HASH: "text",
|
||||
TX_JSON: "object",
|
||||
GAS_USED: "real",
|
||||
POSITION: "fixed",
|
||||
ETH_VALUE: "real",
|
||||
GAS_LIMIT: "fixed",
|
||||
GAS_PRICE: "real",
|
||||
BLOCK_HASH: "text",
|
||||
INPUT_DATA: "text",
|
||||
TO_ADDRESS: "text",
|
||||
BLOCK_NUMBER: "fixed",
|
||||
FROM_ADDRESS: "text",
|
||||
BLOCK_TIMESTAMP: "timestamp_ntz",
|
||||
CUMULATIVE_GAS_USED: "real",
|
||||
ORIGIN_FUNCTION_SIGNATURE: "text",
|
||||
},
|
||||
},
|
||||
userId: "clf8qd1eb0000jv08kbuw0dy4",
|
||||
tags: {
|
||||
sdk_package: "js",
|
||||
sdk_version: "1.0.2",
|
||||
sdk_language: "javascript",
|
||||
},
|
||||
createdAt: "2023-04-05T20:11:29.000Z",
|
||||
updatedAt: "2023-04-05T20:11:29.000Z",
|
||||
},
|
||||
};
|
||||
|
||||
let base: CreateQueryRunRpcResponse = {
|
||||
jsonrpc: "2.0",
|
||||
id: 1,
|
||||
error: null,
|
||||
result: defaultResult,
|
||||
};
|
||||
|
||||
if (error) {
|
||||
base.error = error;
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
319
js/src/tests/mock-data/get-query-results.ts
Normal file
319
js/src/tests/mock-data/get-query-results.ts
Normal file
@ -0,0 +1,319 @@
|
||||
import {
|
||||
QueryStatus,
|
||||
RpcError,
|
||||
GetQueryRunResultsRpcResponse,
|
||||
ResultFormat,
|
||||
mapApiQueryStateToStatus,
|
||||
} from "../../../src/types";
|
||||
|
||||
export function getQueryResultsResponse(
|
||||
status: string = "QUERY_STATE_READY",
|
||||
error: RpcError | null = null
|
||||
): GetQueryRunResultsRpcResponse {
|
||||
let base: GetQueryRunResultsRpcResponse = {
|
||||
jsonrpc: "2.0",
|
||||
id: 1,
|
||||
error: null,
|
||||
result: null,
|
||||
};
|
||||
|
||||
const defaultData = {
|
||||
columnNames: [
|
||||
"block_number",
|
||||
"block_timestamp",
|
||||
"block_hash",
|
||||
"tx_hash",
|
||||
"nonce",
|
||||
"position",
|
||||
"origin_function_signature",
|
||||
"from_address",
|
||||
"to_address",
|
||||
"eth_value",
|
||||
"tx_fee",
|
||||
"gas_price",
|
||||
"gas_limit",
|
||||
"gas_used",
|
||||
"cumulative_gas_used",
|
||||
"input_data",
|
||||
"status",
|
||||
"tx_json",
|
||||
"__row_index",
|
||||
],
|
||||
columnTypes: [
|
||||
"number",
|
||||
"date",
|
||||
"string",
|
||||
"string",
|
||||
"number",
|
||||
"number",
|
||||
"string",
|
||||
"string",
|
||||
"string",
|
||||
"number",
|
||||
"number",
|
||||
"number",
|
||||
"number",
|
||||
"number",
|
||||
"number",
|
||||
"string",
|
||||
"string",
|
||||
"object",
|
||||
"number",
|
||||
],
|
||||
rows: [
|
||||
[
|
||||
15053521,
|
||||
"2022-07-01T01:03:20.000Z",
|
||||
"0x30b559cad268f6665ae14a1cdd4f2019d8232309f1412be8c17c38ed08a10178",
|
||||
"0x92a993c0901c6ee620ec31e504f0496cdbd6088d6894ffc507e56bcfd80fb0fc",
|
||||
5228,
|
||||
142,
|
||||
"0x2e95b6c8",
|
||||
"0x7303c623bc32633d4c1320ab46538f5bab0959ea",
|
||||
"0x1111111254fb6c44bac0bed2854e76f90643097d",
|
||||
0,
|
||||
0.004411543611,
|
||||
40.571141215,
|
||||
167993,
|
||||
108736,
|
||||
11289236,
|
||||
"0x2e95b6c80000000000000000000000002791bfd60d232150bff86b39b7146c0eaaa2ba81000000000000000000000000000000000000000000001d4d3c9f5487881900000000000000000000000000000000000000000000000000000fb6f5c18351004b0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000140000000000000003b6d03400bec54c89a7d9f15c4e7faa8d47adedf374462ede26b9977",
|
||||
"SUCCESS",
|
||||
{
|
||||
block_hash: "0x30b559cad268f6665ae14a1cdd4f2019d8232309f1412be8c17c38ed08a10178",
|
||||
block_number: 15053521,
|
||||
chain_id: null,
|
||||
condition: null,
|
||||
creates: null,
|
||||
from: "0x7303c623bc32633d4c1320ab46538f5bab0959ea",
|
||||
gas: 167993,
|
||||
gas_price: 40571141215,
|
||||
hash: "0x92a993c0901c6ee620ec31e504f0496cdbd6088d6894ffc507e56bcfd80fb0fc",
|
||||
input:
|
||||
"0x2e95b6c80000000000000000000000002791bfd60d232150bff86b39b7146c0eaaa2ba81000000000000000000000000000000000000000000001d4d3c9f5487881900000000000000000000000000000000000000000000000000000fb6f5c18351004b0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000140000000000000003b6d03400bec54c89a7d9f15c4e7faa8d47adedf374462ede26b9977",
|
||||
max_fee_per_gas: null,
|
||||
max_priority_fee_per_gas: null,
|
||||
nonce: "0x146c",
|
||||
public_key: "0x92a993c0901c6ee620ec31e504f0496cdbd6088d6894ffc507e56bcfd80fb0fc",
|
||||
r: "0x31815cdb89c4d6f65f3aa3437c9c27a6cb32b6af3796f6eb0adbf5fdfc547ac5",
|
||||
receipt: {
|
||||
blockHash: "0x30b559cad268f6665ae14a1cdd4f2019d8232309f1412be8c17c38ed08a10178",
|
||||
blockNumber: "0xe5b2d1",
|
||||
contractAddress: null,
|
||||
cumulativeGasUsed: "0xac4294",
|
||||
effectiveGasPrice: "0x9723a7c5f",
|
||||
from: "0x7303c623bc32633d4c1320ab46538f5bab0959ea",
|
||||
gasUsed: "0x1a8c0",
|
||||
logs: [
|
||||
{
|
||||
address: "0x2791bfd60d232150bff86b39b7146c0eaaa2ba81",
|
||||
blockHash: "0x30b559cad268f6665ae14a1cdd4f2019d8232309f1412be8c17c38ed08a10178",
|
||||
blockNumber: "0xe5b2d1",
|
||||
data: "0x000000000000000000000000000000000000000000001d4d3c9f548788190000",
|
||||
decoded: {
|
||||
contractName: "ERC20",
|
||||
eventName: "Transfer",
|
||||
inputs: {
|
||||
from: "0x7303c623bc32633d4c1320ab46538f5bab0959ea",
|
||||
to: "0x0bec54c89a7d9f15c4e7faa8d47adedf374462ed",
|
||||
value: "138373395600000000000000",
|
||||
},
|
||||
logKey: "0x92a993c0901c6ee620ec31e504f0496cdbd6088d6894ffc507e56bcfd80fb0fc:0",
|
||||
},
|
||||
logIndex: "0x120",
|
||||
removed: false,
|
||||
topics: [
|
||||
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
|
||||
"0x0000000000000000000000007303c623bc32633d4c1320ab46538f5bab0959ea",
|
||||
"0x0000000000000000000000000bec54c89a7d9f15c4e7faa8d47adedf374462ed",
|
||||
],
|
||||
transactionHash: "0x92a993c0901c6ee620ec31e504f0496cdbd6088d6894ffc507e56bcfd80fb0fc",
|
||||
transactionIndex: "0x8e",
|
||||
},
|
||||
{
|
||||
address: "0x2791bfd60d232150bff86b39b7146c0eaaa2ba81",
|
||||
blockHash: "0x30b559cad268f6665ae14a1cdd4f2019d8232309f1412be8c17c38ed08a10178",
|
||||
blockNumber: "0xe5b2d1",
|
||||
data: "0xffffffffffffffffffffffffffffffffffffffffffff00378d1be533fbc8e7ff",
|
||||
decoded: {
|
||||
contractName: "ERC20",
|
||||
eventName: "Approval",
|
||||
inputs: {
|
||||
owner: "0x7303c623bc32633d4c1320ab46538f5bab0959ea",
|
||||
spender: "0x1111111254fb6c44bac0bed2854e76f90643097d",
|
||||
value: "115792089237316195423570985008687907853269984665640562831556503289933129639935",
|
||||
},
|
||||
logKey: "0x92a993c0901c6ee620ec31e504f0496cdbd6088d6894ffc507e56bcfd80fb0fc:1",
|
||||
},
|
||||
logIndex: "0x121",
|
||||
removed: false,
|
||||
topics: [
|
||||
"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925",
|
||||
"0x0000000000000000000000007303c623bc32633d4c1320ab46538f5bab0959ea",
|
||||
"0x0000000000000000000000001111111254fb6c44bac0bed2854e76f90643097d",
|
||||
],
|
||||
transactionHash: "0x92a993c0901c6ee620ec31e504f0496cdbd6088d6894ffc507e56bcfd80fb0fc",
|
||||
transactionIndex: "0x8e",
|
||||
},
|
||||
{
|
||||
address: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
|
||||
blockHash: "0x30b559cad268f6665ae14a1cdd4f2019d8232309f1412be8c17c38ed08a10178",
|
||||
blockNumber: "0xe5b2d1",
|
||||
data: "0x0000000000000000000000000000000000000000000000000fcb2d05613e1c99",
|
||||
decoded: {
|
||||
contractName: "WETH9",
|
||||
eventName: "Transfer",
|
||||
inputs: {
|
||||
from: "0x0bec54c89a7d9f15c4e7faa8d47adedf374462ed",
|
||||
to: "0x1111111254fb6c44bac0bed2854e76f90643097d",
|
||||
value: "1138052831970729113",
|
||||
},
|
||||
logKey: "0x92a993c0901c6ee620ec31e504f0496cdbd6088d6894ffc507e56bcfd80fb0fc:2",
|
||||
},
|
||||
logIndex: "0x122",
|
||||
removed: false,
|
||||
topics: [
|
||||
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
|
||||
"0x0000000000000000000000000bec54c89a7d9f15c4e7faa8d47adedf374462ed",
|
||||
"0x0000000000000000000000001111111254fb6c44bac0bed2854e76f90643097d",
|
||||
],
|
||||
transactionHash: "0x92a993c0901c6ee620ec31e504f0496cdbd6088d6894ffc507e56bcfd80fb0fc",
|
||||
transactionIndex: "0x8e",
|
||||
},
|
||||
{
|
||||
address: "0x0bec54c89a7d9f15c4e7faa8d47adedf374462ed",
|
||||
blockHash: "0x30b559cad268f6665ae14a1cdd4f2019d8232309f1412be8c17c38ed08a10178",
|
||||
blockNumber: "0xe5b2d1",
|
||||
data: "0x000000000000000000000000000000000000000000218db370860b0d203c608e00000000000000000000000000000000000000000000001213f31668c8cc585e",
|
||||
decoded: {
|
||||
eventName: "Sync",
|
||||
inputs: {
|
||||
reserve0: "40563715796736906880639118",
|
||||
reserve1: "333478910672134494302",
|
||||
},
|
||||
logKey: "0x92a993c0901c6ee620ec31e504f0496cdbd6088d6894ffc507e56bcfd80fb0fc:3",
|
||||
},
|
||||
logIndex: "0x123",
|
||||
removed: false,
|
||||
topics: ["0x1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1"],
|
||||
transactionHash: "0x92a993c0901c6ee620ec31e504f0496cdbd6088d6894ffc507e56bcfd80fb0fc",
|
||||
transactionIndex: "0x8e",
|
||||
},
|
||||
{
|
||||
address: "0x0bec54c89a7d9f15c4e7faa8d47adedf374462ed",
|
||||
blockHash: "0x30b559cad268f6665ae14a1cdd4f2019d8232309f1412be8c17c38ed08a10178",
|
||||
blockNumber: "0xe5b2d1",
|
||||
data: "0x000000000000000000000000000000000000000000001d4d3c9f548788190000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fcb2d05613e1c99",
|
||||
decoded: {
|
||||
eventName: "Swap",
|
||||
inputs: {
|
||||
amount0In: "138373395600000000000000",
|
||||
amount0Out: "0",
|
||||
amount1In: "0",
|
||||
amount1Out: "1138052831970729113",
|
||||
sender: "0x1111111254fb6c44bac0bed2854e76f90643097d",
|
||||
to: "0x1111111254fb6c44bac0bed2854e76f90643097d",
|
||||
},
|
||||
logKey: "0x92a993c0901c6ee620ec31e504f0496cdbd6088d6894ffc507e56bcfd80fb0fc:4",
|
||||
},
|
||||
logIndex: "0x124",
|
||||
removed: false,
|
||||
topics: [
|
||||
"0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822",
|
||||
"0x0000000000000000000000001111111254fb6c44bac0bed2854e76f90643097d",
|
||||
"0x0000000000000000000000001111111254fb6c44bac0bed2854e76f90643097d",
|
||||
],
|
||||
transactionHash: "0x92a993c0901c6ee620ec31e504f0496cdbd6088d6894ffc507e56bcfd80fb0fc",
|
||||
transactionIndex: "0x8e",
|
||||
},
|
||||
{
|
||||
address: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
|
||||
blockHash: "0x30b559cad268f6665ae14a1cdd4f2019d8232309f1412be8c17c38ed08a10178",
|
||||
blockNumber: "0xe5b2d1",
|
||||
data: "0x0000000000000000000000000000000000000000000000000fcb2d05613e1c99",
|
||||
decoded: {
|
||||
contractName: "WETH9",
|
||||
eventName: "Withdrawal",
|
||||
inputs: {
|
||||
src: "0x1111111254fb6c44bac0bed2854e76f90643097d",
|
||||
wad: "1138052831970729113",
|
||||
},
|
||||
logKey: "0x92a993c0901c6ee620ec31e504f0496cdbd6088d6894ffc507e56bcfd80fb0fc:5",
|
||||
},
|
||||
logIndex: "0x125",
|
||||
removed: false,
|
||||
topics: [
|
||||
"0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65",
|
||||
"0x0000000000000000000000001111111254fb6c44bac0bed2854e76f90643097d",
|
||||
],
|
||||
transactionHash: "0x92a993c0901c6ee620ec31e504f0496cdbd6088d6894ffc507e56bcfd80fb0fc",
|
||||
transactionIndex: "0x8e",
|
||||
},
|
||||
],
|
||||
logsBloom:
|
||||
"0x00200800000000000000000080002000000400000000000000100000000000000000000000200000000000000000000002000800080000000000000000200000000000000000000000000008000000600000000000600000000000000000000000000000000000000000000000000000000000000000040000000010000000000000010000001000000000000000004000000000000000080000004000000000060400000000000000000000000000000000000000000000000000000000000001000002000000000000000800000000000000000000001000000002000000000010200000000000000000000000000000000000000000000000000000000000",
|
||||
status: "0x1",
|
||||
to: "0x1111111254fb6c44bac0bed2854e76f90643097d",
|
||||
transactionHash: "0x92a993c0901c6ee620ec31e504f0496cdbd6088d6894ffc507e56bcfd80fb0fc",
|
||||
transactionIndex: "0x8e",
|
||||
type: "0x2",
|
||||
},
|
||||
s: "0x2888a1070bac8036df23ee909da8e9547f345b3a6b07c00a4b1d44698c558b4d",
|
||||
standard_v: null,
|
||||
to: "0x1111111254fb6c44bac0bed2854e76f90643097d",
|
||||
transaction_index: 142,
|
||||
v: "0x0",
|
||||
value: 0,
|
||||
},
|
||||
0,
|
||||
],
|
||||
],
|
||||
page: {
|
||||
currentPageNumber: 1,
|
||||
currentPageSize: 1,
|
||||
totalRows: 34000,
|
||||
totalPages: 34000,
|
||||
},
|
||||
sql: "select * from read_parquet('/data/2023/04/05/20/clg44olzq00cbn60tasvob5l2/*') offset 0 limit 1",
|
||||
format: ResultFormat.csv,
|
||||
originalQueryRun: {
|
||||
id: "clg44olzq00cbn60tasvob5l2",
|
||||
sqlStatementId: "clg44oly200c9n60tviq17sng",
|
||||
state: status,
|
||||
path: "2023/04/05/20/clg44olzq00cbn60tasvob5l2",
|
||||
fileCount: 1,
|
||||
lastFileNumber: 1,
|
||||
fileNames: "clg44olzq00cbn60tasvob5l2-consolidated-results.parquet",
|
||||
errorName: null,
|
||||
errorMessage: null,
|
||||
errorData: null,
|
||||
dataSourceQueryId: null,
|
||||
dataSourceSessionId: "17257398387030526",
|
||||
startedAt: "2023-04-05T20:14:55.000Z",
|
||||
queryRunningEndedAt: "2023-04-05T20:15:16.000Z",
|
||||
queryStreamingEndedAt: "2023-04-05T20:17:18.000Z",
|
||||
endedAt: "2023-04-05T20:17:18.000Z",
|
||||
rowCount: 17000,
|
||||
totalSize: 24904891,
|
||||
tags: {
|
||||
sdk_package: "python",
|
||||
sdk_version: "1.0.2",
|
||||
sdk_language: "python",
|
||||
},
|
||||
dataSourceId: "clf90gwee0002jvbu63diaa8u",
|
||||
userId: "clf8qd1eb0000jv08kbuw0dy4",
|
||||
createdAt: "2023-04-05T20:14:55.000Z",
|
||||
updatedAt: "2023-04-05T20:14:55.000Z",
|
||||
archivedAt: null,
|
||||
},
|
||||
redirectedToQueryRun: null,
|
||||
};
|
||||
|
||||
if (error) {
|
||||
base.error = error;
|
||||
}
|
||||
|
||||
base.result = defaultData;
|
||||
|
||||
return base;
|
||||
}
|
||||
67
js/src/tests/mock-data/get-query-run.ts
Normal file
67
js/src/tests/mock-data/get-query-run.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import {
|
||||
QueryStatus,
|
||||
RpcError,
|
||||
GetQueryRunRpcResponse,
|
||||
ResultFormat,
|
||||
mapApiQueryStateToStatus,
|
||||
} from "../../../src/types";
|
||||
|
||||
export function getQueryRunResponse(
|
||||
status: string = "QUERY_STATE_READY",
|
||||
error: RpcError | null = null
|
||||
): GetQueryRunRpcResponse {
|
||||
let base: GetQueryRunRpcResponse = {
|
||||
jsonrpc: "2.0",
|
||||
id: 1,
|
||||
error: null,
|
||||
result: null,
|
||||
};
|
||||
|
||||
const defaultResult = {
|
||||
queryRun: {
|
||||
id: "clg44olzq00cbn60tasvob5l2",
|
||||
sqlStatementId: "clg44oly200c9n60tviq17sng",
|
||||
state: status,
|
||||
path: "2023/04/05/20/clg44olzq00cbn60tasvob5l2",
|
||||
fileCount: 1,
|
||||
lastFileNumber: 1,
|
||||
fileNames: "clg44olzq00cbn60tasvob5l2-consolidated-results.parquet",
|
||||
errorName: null,
|
||||
errorMessage: null,
|
||||
errorData: null,
|
||||
dataSourceQueryId: null,
|
||||
dataSourceSessionId: "17257398387030526",
|
||||
startedAt: "2023-04-05T20:14:55.000Z",
|
||||
queryRunningEndedAt: "2023-04-05T20:15:00.000Z",
|
||||
queryStreamingEndedAt: "2023-04-05T20:15:45.000Z",
|
||||
endedAt: "2023-04-05T20:15:46.000Z",
|
||||
rowCount: 10000,
|
||||
totalSize: 24904891,
|
||||
tags: {
|
||||
sdk_package: "python",
|
||||
sdk_version: "1.0.2",
|
||||
sdk_language: "python",
|
||||
},
|
||||
dataSourceId: "clf90gwee0002jvbu63diaa8u",
|
||||
userId: "clf8qd1eb0000jv08kbuw0dy4",
|
||||
createdAt: "2023-04-05T20:14:55.000Z",
|
||||
updatedAt: "2023-04-05T20:14:55.000Z",
|
||||
archivedAt: null,
|
||||
},
|
||||
redirectedToQueryRun: null,
|
||||
};
|
||||
|
||||
if (error !== null) {
|
||||
base = {
|
||||
...base,
|
||||
error: error,
|
||||
};
|
||||
}
|
||||
|
||||
base = {
|
||||
...base,
|
||||
result: defaultResult,
|
||||
};
|
||||
|
||||
return base;
|
||||
}
|
||||
86
js/src/tests/mock-data/get-sql-statement.ts
Normal file
86
js/src/tests/mock-data/get-sql-statement.ts
Normal file
@ -0,0 +1,86 @@
|
||||
import { GetSqlStatementResponse } from "../../types";
|
||||
|
||||
export function getSqlStatementResponse(id: string): GetSqlStatementResponse {
|
||||
return {
|
||||
jsonrpc: "2.0",
|
||||
id: 1,
|
||||
error: null,
|
||||
result: {
|
||||
sqlStatement: {
|
||||
id: id,
|
||||
statementHash: "9d9d5d462b0e4aaf18d17283b1ea2ff9bb30a285c0fe066754fed18f34f80388",
|
||||
sql: "SELECT * FROM ethereum.core.fact_transactions LIMIT 100000",
|
||||
columnMetadata: {
|
||||
types: [
|
||||
"fixed",
|
||||
"timestamp_ntz",
|
||||
"text",
|
||||
"text",
|
||||
"real",
|
||||
"fixed",
|
||||
"text",
|
||||
"text",
|
||||
"text",
|
||||
"real",
|
||||
"real",
|
||||
"real",
|
||||
"fixed",
|
||||
"real",
|
||||
"real",
|
||||
"text",
|
||||
"text",
|
||||
"object",
|
||||
],
|
||||
columns: [
|
||||
"BLOCK_NUMBER",
|
||||
"BLOCK_TIMESTAMP",
|
||||
"BLOCK_HASH",
|
||||
"TX_HASH",
|
||||
"NONCE",
|
||||
"POSITION",
|
||||
"ORIGIN_FUNCTION_SIGNATURE",
|
||||
"FROM_ADDRESS",
|
||||
"TO_ADDRESS",
|
||||
"ETH_VALUE",
|
||||
"TX_FEE",
|
||||
"GAS_PRICE",
|
||||
"GAS_LIMIT",
|
||||
"GAS_USED",
|
||||
"CUMULATIVE_GAS_USED",
|
||||
"INPUT_DATA",
|
||||
"STATUS",
|
||||
"TX_JSON",
|
||||
],
|
||||
colTypeMap: {
|
||||
NONCE: "real",
|
||||
STATUS: "text",
|
||||
TX_FEE: "real",
|
||||
TX_HASH: "text",
|
||||
TX_JSON: "object",
|
||||
GAS_USED: "real",
|
||||
POSITION: "fixed",
|
||||
ETH_VALUE: "real",
|
||||
GAS_LIMIT: "fixed",
|
||||
GAS_PRICE: "real",
|
||||
BLOCK_HASH: "text",
|
||||
INPUT_DATA: "text",
|
||||
TO_ADDRESS: "text",
|
||||
BLOCK_NUMBER: "fixed",
|
||||
FROM_ADDRESS: "text",
|
||||
BLOCK_TIMESTAMP: "timestamp_ntz",
|
||||
CUMULATIVE_GAS_USED: "real",
|
||||
ORIGIN_FUNCTION_SIGNATURE: "text",
|
||||
},
|
||||
},
|
||||
userId: "clf8qd1eb0000jv08kbuw0dy4",
|
||||
tags: {
|
||||
sdk_package: "python",
|
||||
sdk_version: "1.0.2",
|
||||
sdk_language: "python",
|
||||
},
|
||||
createdAt: "2023-04-05T18:53:59.000Z",
|
||||
updatedAt: "2023-04-05T18:53:59.000Z",
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
5
js/src/tests/mock-data/index.ts
Normal file
5
js/src/tests/mock-data/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export * from "./cancel-query-run";
|
||||
export * from "./create-query-run";
|
||||
export * from "./get-query-results";
|
||||
export * from "./get-query-run";
|
||||
export * from "./get-sql-statement";
|
||||
@ -1,22 +1,35 @@
|
||||
import {
|
||||
ApiClient,
|
||||
CreateQueryResp,
|
||||
Query,
|
||||
QueryResultResp,
|
||||
CompassApiClient,
|
||||
CreateQueryRunRpcResponse,
|
||||
CreateQueryRunRpcParams,
|
||||
GetQueryRunRpcRequestParams,
|
||||
GetQueryRunRpcResponse,
|
||||
GetQueryRunResultsRpcResponse,
|
||||
GetQueryRunResultsRpcParams,
|
||||
GetSqlStatementResponse,
|
||||
GetSqlStatementParams,
|
||||
CancelQueryRunRpcRequestParams,
|
||||
CancelQueryRunRpcResponse,
|
||||
} from "../../types";
|
||||
|
||||
export type MockApiClientInput = {
|
||||
createQueryResp: CreateQueryResp;
|
||||
getQueryResultResp: QueryResultResp;
|
||||
createQueryResp: CreateQueryRunRpcResponse;
|
||||
getQueryRunResp: GetQueryRunRpcResponse;
|
||||
getQueryRunResultsResp: GetQueryRunResultsRpcResponse;
|
||||
getSqlStatementResp: GetSqlStatementResponse;
|
||||
cancelQueryRunResp: CancelQueryRunRpcResponse;
|
||||
};
|
||||
|
||||
export function getMockApiClient(input: MockApiClientInput): ApiClient {
|
||||
class MockApiClient implements ApiClient {
|
||||
export function getMockApiClient(input: MockApiClientInput): CompassApiClient {
|
||||
class MockApiClient implements CompassApiClient {
|
||||
url: string;
|
||||
#baseUrl: string;
|
||||
#headers: Record<string, string>;
|
||||
|
||||
constructor(baseUrl: string, apiKey: string) {
|
||||
this.#baseUrl = baseUrl;
|
||||
this.url = this.getUrl();
|
||||
this.#headers = {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
@ -24,17 +37,33 @@ export function getMockApiClient(input: MockApiClientInput): ApiClient {
|
||||
};
|
||||
}
|
||||
|
||||
getUrl(path: string): string {
|
||||
return `${this.#baseUrl}/${path}`;
|
||||
getUrl(): string {
|
||||
return `${this.#baseUrl}/json-rpc`;
|
||||
}
|
||||
async createQuery(query: Query): Promise<CreateQueryResp> {
|
||||
return new Promise<CreateQueryResp>((resolve, reject) => {
|
||||
|
||||
async createQuery(params: CreateQueryRunRpcParams): Promise<CreateQueryRunRpcResponse> {
|
||||
return new Promise<CreateQueryRunRpcResponse>((resolve, reject) => {
|
||||
resolve(input.createQueryResp);
|
||||
});
|
||||
}
|
||||
async getQueryResult(queryID: string): Promise<QueryResultResp> {
|
||||
return await new Promise<QueryResultResp>((resolve, reject) => {
|
||||
resolve(input.getQueryResultResp);
|
||||
async getQueryRun(params: GetQueryRunRpcRequestParams): Promise<GetQueryRunRpcResponse> {
|
||||
return await new Promise<GetQueryRunRpcResponse>((resolve, reject) => {
|
||||
resolve(input.getQueryRunResp);
|
||||
});
|
||||
}
|
||||
async getQueryResult(params: GetQueryRunResultsRpcParams): Promise<GetQueryRunResultsRpcResponse> {
|
||||
return await new Promise<GetQueryRunResultsRpcResponse>((resolve, reject) => {
|
||||
resolve(input.getQueryRunResultsResp);
|
||||
});
|
||||
}
|
||||
async getSqlStatement(params: GetSqlStatementParams): Promise<GetSqlStatementResponse> {
|
||||
return await new Promise<GetSqlStatementResponse>((resolve, reject) => {
|
||||
resolve(input.getSqlStatementResp);
|
||||
});
|
||||
}
|
||||
async cancelQueryRun(params: CancelQueryRunRpcRequestParams): Promise<CancelQueryRunRpcResponse> {
|
||||
return await new Promise<CancelQueryRunRpcResponse>((resolve, reject) => {
|
||||
resolve(input.cancelQueryRunResp);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,207 +1,301 @@
|
||||
import { assert, describe, it } from "vitest";
|
||||
import { ERROR_TYPES } from "..";
|
||||
import { ApiError, ERROR_TYPES } from "..";
|
||||
import { QueryIntegration } from "../integrations/query-integration";
|
||||
import {
|
||||
QueryStatus,
|
||||
QueryStatusError,
|
||||
QueryStatusFinished,
|
||||
QueryStatusPending,
|
||||
} from "../types";
|
||||
import { QueryStatus, QueryStatusError, QueryStatusFinished, QueryStatusPending, SqlStatement } from "../types";
|
||||
import { Query } from "../types/query.type";
|
||||
import { getMockApiClient } from "./mocks/api-mocks";
|
||||
|
||||
let createQueryData = {
|
||||
token: "flipside test token",
|
||||
errors: null,
|
||||
};
|
||||
import { createQueryRunResponse } from "./mock-data/create-query-run";
|
||||
import {
|
||||
cancelQueryRunResponse,
|
||||
getQueryResultsResponse,
|
||||
getQueryRunResponse,
|
||||
getSqlStatementResponse,
|
||||
} from "./mock-data";
|
||||
|
||||
let defaultQueryData: Query = {
|
||||
sql: "select 1",
|
||||
ttlMinutes: 1,
|
||||
};
|
||||
|
||||
let createQueries = {
|
||||
userError: {
|
||||
data: createQueryData,
|
||||
statusCode: 400,
|
||||
statusMsg: null,
|
||||
errorMsg: null,
|
||||
},
|
||||
serverError: {
|
||||
data: createQueryData,
|
||||
statusCode: 500,
|
||||
statusMsg: null,
|
||||
errorMsg: null,
|
||||
},
|
||||
rateLimitError: {
|
||||
data: createQueryData,
|
||||
statusCode: 429,
|
||||
statusMsg: null,
|
||||
errorMsg: null,
|
||||
},
|
||||
noError: {
|
||||
data: createQueryData,
|
||||
statusCode: 200,
|
||||
statusMsg: null,
|
||||
errorMsg: null,
|
||||
},
|
||||
};
|
||||
|
||||
function generateQueryResultData(status: QueryStatus) {
|
||||
return {
|
||||
queryId: "test",
|
||||
status,
|
||||
results: [],
|
||||
startedAt: "2022-05-19T00:00:00Z",
|
||||
endedAt: "2022-05-19T00:00:00Z",
|
||||
columnLabels: ["block_id", "tx_id"],
|
||||
columnTypes: ["string", "string"],
|
||||
message: "",
|
||||
errors: "invalid sql",
|
||||
pageNumber: 1,
|
||||
pageSize: 100,
|
||||
};
|
||||
}
|
||||
|
||||
let getQueryResult = {
|
||||
userError: {
|
||||
data: generateQueryResultData(QueryStatusError),
|
||||
statusCode: 400,
|
||||
statusMsg: null,
|
||||
errorMsg: null,
|
||||
},
|
||||
serverError: {
|
||||
data: generateQueryResultData(QueryStatusPending),
|
||||
statusCode: 500,
|
||||
statusMsg: null,
|
||||
errorMsg: null,
|
||||
},
|
||||
noErrorPending: {
|
||||
data: generateQueryResultData(QueryStatusPending),
|
||||
statusCode: 200,
|
||||
statusMsg: null,
|
||||
errorMsg: null,
|
||||
},
|
||||
noErrorFinished: {
|
||||
data: generateQueryResultData(QueryStatusFinished),
|
||||
statusCode: 200,
|
||||
statusMsg: null,
|
||||
errorMsg: null,
|
||||
},
|
||||
sqlExecError: {
|
||||
data: generateQueryResultData(QueryStatusError),
|
||||
statusCode: 200,
|
||||
statusMsg: null,
|
||||
errorMsg: null,
|
||||
},
|
||||
};
|
||||
|
||||
describe("run: server_error", () => {
|
||||
it("#createQuery server error", async () => {
|
||||
describe("getQueryResults", () => {
|
||||
it("with page data", async () => {
|
||||
const api = getMockApiClient({
|
||||
createQueryResp: createQueries.serverError,
|
||||
getQueryResultResp: getQueryResult.noErrorPending,
|
||||
createQueryResp: createQueryRunResponse("QUERY_STATE_SUCCESS"),
|
||||
getQueryRunResp: getQueryRunResponse("QUERY_STATE_SUCCESS"),
|
||||
getQueryRunResultsResp: getQueryResultsResponse("QUERY_STATE_SUCCESS"),
|
||||
getSqlStatementResp: getSqlStatementResponse("t"),
|
||||
cancelQueryRunResp: cancelQueryRunResponse(),
|
||||
});
|
||||
|
||||
const queryIntegration = new QueryIntegration(api);
|
||||
const result = await queryIntegration.run(defaultQueryData);
|
||||
assert.equal(result.error?.errorType, ERROR_TYPES.server_error);
|
||||
assert.notEqual(result.error?.message, null);
|
||||
});
|
||||
|
||||
it("#getQueryResult server error", async () => {
|
||||
const api = getMockApiClient({
|
||||
createQueryResp: createQueries.noError,
|
||||
getQueryResultResp: getQueryResult.serverError,
|
||||
});
|
||||
|
||||
const queryIntegration = new QueryIntegration(api);
|
||||
const result = await queryIntegration.run(defaultQueryData);
|
||||
assert.equal(result.error?.errorType, ERROR_TYPES.server_error);
|
||||
assert.notEqual(result.error?.message, null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("run: user_error", () => {
|
||||
it("#createQuery user error", async () => {
|
||||
const api = getMockApiClient({
|
||||
createQueryResp: createQueries.userError,
|
||||
getQueryResultResp: getQueryResult.noErrorPending,
|
||||
});
|
||||
|
||||
const queryIntegration = new QueryIntegration(api);
|
||||
const result = await queryIntegration.run(defaultQueryData);
|
||||
assert.equal(result.error?.errorType, ERROR_TYPES.user_error);
|
||||
assert.notEqual(result.error?.message, null);
|
||||
});
|
||||
|
||||
it("#getQueryResult user error", async () => {
|
||||
const api = getMockApiClient({
|
||||
createQueryResp: createQueries.noError,
|
||||
getQueryResultResp: getQueryResult.userError,
|
||||
});
|
||||
|
||||
const queryIntegration = new QueryIntegration(api);
|
||||
const result = await queryIntegration.run(defaultQueryData);
|
||||
assert.equal(result.error?.errorType, ERROR_TYPES.user_error);
|
||||
assert.notEqual(result.error?.message, null);
|
||||
});
|
||||
|
||||
it("#getQueryResult sql exec error", async () => {
|
||||
const api = getMockApiClient({
|
||||
createQueryResp: createQueries.noError,
|
||||
getQueryResultResp: getQueryResult.sqlExecError,
|
||||
});
|
||||
|
||||
const queryIntegration = new QueryIntegration(api);
|
||||
const result = await queryIntegration.run(defaultQueryData);
|
||||
assert.equal(
|
||||
result.error?.errorType,
|
||||
ERROR_TYPES.query_run_execution_error
|
||||
);
|
||||
assert.notEqual(result.error?.message, null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("run: timeout_error", () => {
|
||||
it("query is pending", async () => {
|
||||
const api = getMockApiClient({
|
||||
createQueryResp: createQueries.noError,
|
||||
getQueryResultResp: getQueryResult.noErrorPending,
|
||||
});
|
||||
|
||||
const queryIntegration = new QueryIntegration(api, {
|
||||
ttlMinutes: 1,
|
||||
cached: false,
|
||||
timeoutMinutes: 0.01,
|
||||
retryIntervalSeconds: 0.001,
|
||||
const result = await queryIntegration.getQueryResults({
|
||||
queryRunId: "123",
|
||||
pageNumber: 1,
|
||||
pageSize: 100,
|
||||
pageSize: 1,
|
||||
});
|
||||
const result = await queryIntegration.run(defaultQueryData);
|
||||
assert.equal(result.error?.errorType, ERROR_TYPES.query_run_timeout_error);
|
||||
assert.notEqual(result.error?.message, null);
|
||||
assert.equal(result.status, QueryStatusFinished);
|
||||
});
|
||||
|
||||
it("query is rate limited", async () => {
|
||||
it("without page data", async () => {
|
||||
const api = getMockApiClient({
|
||||
createQueryResp: createQueries.rateLimitError,
|
||||
getQueryResultResp: getQueryResult.noErrorPending,
|
||||
createQueryResp: createQueryRunResponse("QUERY_STATE_SUCCESS"),
|
||||
getQueryRunResp: getQueryRunResponse("QUERY_STATE_SUCCESS"),
|
||||
getQueryRunResultsResp: getQueryResultsResponse("QUERY_STATE_SUCCESS"),
|
||||
getSqlStatementResp: getSqlStatementResponse("t"),
|
||||
cancelQueryRunResp: cancelQueryRunResponse(),
|
||||
});
|
||||
|
||||
const queryIntegration = new QueryIntegration(api, {
|
||||
ttlMinutes: 1,
|
||||
cached: false,
|
||||
timeoutMinutes: 0.01,
|
||||
retryIntervalSeconds: 0.001,
|
||||
pageNumber: 1,
|
||||
pageSize: 100,
|
||||
const queryIntegration = new QueryIntegration(api);
|
||||
const result = await queryIntegration.getQueryResults({
|
||||
queryRunId: "123",
|
||||
});
|
||||
const result = await queryIntegration.run(defaultQueryData);
|
||||
assert.equal(
|
||||
result.error?.errorType,
|
||||
ERROR_TYPES.query_run_rate_limit_error
|
||||
);
|
||||
assert.notEqual(result.error?.message, null);
|
||||
assert.equal(result.status, QueryStatusFinished);
|
||||
});
|
||||
|
||||
it("with filters & sortby", async () => {
|
||||
const api = getMockApiClient({
|
||||
createQueryResp: createQueryRunResponse("QUERY_STATE_SUCCESS"),
|
||||
getQueryRunResp: getQueryRunResponse("QUERY_STATE_SUCCESS"),
|
||||
getQueryRunResultsResp: getQueryResultsResponse("QUERY_STATE_SUCCESS"),
|
||||
getSqlStatementResp: getSqlStatementResponse("t"),
|
||||
cancelQueryRunResp: cancelQueryRunResponse(),
|
||||
});
|
||||
|
||||
const queryIntegration = new QueryIntegration(api);
|
||||
const result = await queryIntegration.getQueryResults({
|
||||
queryRunId: "123",
|
||||
filters: [
|
||||
{
|
||||
column: "test",
|
||||
eq: "test",
|
||||
},
|
||||
{
|
||||
column: "test",
|
||||
neq: "test",
|
||||
},
|
||||
{
|
||||
column: "test",
|
||||
gt: 5,
|
||||
},
|
||||
{
|
||||
column: "test",
|
||||
gte: 5,
|
||||
},
|
||||
{
|
||||
column: "test",
|
||||
lt: 5,
|
||||
},
|
||||
{
|
||||
column: "test",
|
||||
lte: 5,
|
||||
},
|
||||
{
|
||||
column: "test",
|
||||
like: "some value",
|
||||
},
|
||||
{
|
||||
column: "test",
|
||||
in: ["some value"],
|
||||
},
|
||||
{
|
||||
column: "test",
|
||||
in: [5],
|
||||
},
|
||||
{
|
||||
column: "test",
|
||||
notIn: ["some value"],
|
||||
},
|
||||
{
|
||||
column: "test",
|
||||
notIn: [5],
|
||||
},
|
||||
],
|
||||
sortBy: [
|
||||
{
|
||||
column: "test",
|
||||
direction: "asc",
|
||||
},
|
||||
{
|
||||
column: "test2",
|
||||
direction: "desc",
|
||||
},
|
||||
],
|
||||
});
|
||||
assert.equal(result.status, QueryStatusFinished);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getQueryRun", () => {
|
||||
it("success", async () => {
|
||||
const api = getMockApiClient({
|
||||
createQueryResp: createQueryRunResponse("QUERY_STATE_SUCCESS"),
|
||||
getQueryRunResp: getQueryRunResponse("QUERY_STATE_SUCCESS"),
|
||||
getQueryRunResultsResp: getQueryResultsResponse("QUERY_STATE_SUCCESS"),
|
||||
getSqlStatementResp: getSqlStatementResponse("t"),
|
||||
cancelQueryRunResp: cancelQueryRunResponse(),
|
||||
});
|
||||
|
||||
const queryIntegration = new QueryIntegration(api);
|
||||
const result = await queryIntegration.getQueryRun({ queryRunId: "123" });
|
||||
assert.equal(result.state, "QUERY_STATE_SUCCESS");
|
||||
});
|
||||
it("streaming", async () => {
|
||||
const api = getMockApiClient({
|
||||
createQueryResp: createQueryRunResponse("QUERY_STATE_STREAMING"),
|
||||
getQueryRunResp: getQueryRunResponse("QUERY_STATE_STREAMING"),
|
||||
getQueryRunResultsResp: getQueryResultsResponse("QUERY_STATE_STREAMING"),
|
||||
getSqlStatementResp: getSqlStatementResponse("t"),
|
||||
cancelQueryRunResp: cancelQueryRunResponse(),
|
||||
});
|
||||
|
||||
const queryIntegration = new QueryIntegration(api);
|
||||
const result = await queryIntegration.getQueryRun({ queryRunId: "123" });
|
||||
assert.equal(result.state, "QUERY_STATE_STREAMING");
|
||||
});
|
||||
it("failed", async () => {
|
||||
const api = getMockApiClient({
|
||||
createQueryResp: createQueryRunResponse("QUERY_STATE_FAILED"),
|
||||
getQueryRunResp: getQueryRunResponse("QUERY_STATE_FAILED"),
|
||||
getQueryRunResultsResp: getQueryResultsResponse("QUERY_STATE_FAILED"),
|
||||
getSqlStatementResp: getSqlStatementResponse("t"),
|
||||
cancelQueryRunResp: cancelQueryRunResponse(),
|
||||
});
|
||||
|
||||
const queryIntegration = new QueryIntegration(api);
|
||||
const result = await queryIntegration.getQueryRun({ queryRunId: "123" });
|
||||
assert.equal(result.state, "QUERY_STATE_FAILED");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getSqlStatement", () => {
|
||||
it("success", async () => {
|
||||
const api = getMockApiClient({
|
||||
createQueryResp: createQueryRunResponse("QUERY_STATE_SUCCESS"),
|
||||
getQueryRunResp: getQueryRunResponse("QUERY_STATE_SUCCESS"),
|
||||
getQueryRunResultsResp: getQueryResultsResponse("QUERY_STATE_SUCCESS"),
|
||||
getSqlStatementResp: getSqlStatementResponse("123"),
|
||||
cancelQueryRunResp: cancelQueryRunResponse(),
|
||||
});
|
||||
|
||||
const queryIntegration = new QueryIntegration(api);
|
||||
const result = await queryIntegration.getSqlStatement({ sqlStatementId: "123" });
|
||||
assert.equal(result.id, "123");
|
||||
});
|
||||
});
|
||||
|
||||
describe("cancelQueryRun", () => {
|
||||
it("success", async () => {
|
||||
const api = getMockApiClient({
|
||||
createQueryResp: createQueryRunResponse("QUERY_STATE_CANCELLED"),
|
||||
getQueryRunResp: getQueryRunResponse("QUERY_STATE_CANCELLED"),
|
||||
getQueryRunResultsResp: getQueryResultsResponse("QUERY_STATE_CANCELLED"),
|
||||
getSqlStatementResp: getSqlStatementResponse("123"),
|
||||
cancelQueryRunResp: cancelQueryRunResponse("QUERY_STATE_CANCELLED"),
|
||||
});
|
||||
|
||||
const queryIntegration = new QueryIntegration(api);
|
||||
const result = await queryIntegration.cancelQueryRun({ queryRunId: "123" });
|
||||
assert.equal(result.state, "QUERY_STATE_CANCELLED");
|
||||
});
|
||||
});
|
||||
|
||||
describe("run", () => {
|
||||
it("run success", async () => {
|
||||
const api = getMockApiClient({
|
||||
createQueryResp: createQueryRunResponse("QUERY_STATE_SUCCESS"),
|
||||
getQueryRunResp: getQueryRunResponse("QUERY_STATE_SUCCESS"),
|
||||
getQueryRunResultsResp: getQueryResultsResponse("QUERY_STATE_SUCCESS"),
|
||||
getSqlStatementResp: getSqlStatementResponse("t"),
|
||||
cancelQueryRunResp: cancelQueryRunResponse(),
|
||||
});
|
||||
|
||||
const queryIntegration = new QueryIntegration(api);
|
||||
const result = await queryIntegration.run(defaultQueryData);
|
||||
assert.equal(result.status, QueryStatusFinished);
|
||||
});
|
||||
});
|
||||
|
||||
describe("run: api_error", () => {
|
||||
it("#createQuery ApiError", async () => {
|
||||
const api = getMockApiClient({
|
||||
createQueryResp: createQueryRunResponse("QUERY_STATE_READY", {
|
||||
code: -32164,
|
||||
message: "DataSourceNotFound",
|
||||
data: {},
|
||||
}),
|
||||
getQueryRunResp: getQueryRunResponse(),
|
||||
getQueryRunResultsResp: getQueryResultsResponse(),
|
||||
getSqlStatementResp: getSqlStatementResponse("t"),
|
||||
cancelQueryRunResp: cancelQueryRunResponse(),
|
||||
});
|
||||
|
||||
const queryIntegration = new QueryIntegration(api);
|
||||
const result = await queryIntegration.run(defaultQueryData);
|
||||
assert.equal(result.error instanceof ApiError, true);
|
||||
assert.notEqual(result.error?.message, null);
|
||||
});
|
||||
|
||||
// it("#getQueryResult user error", async () => {
|
||||
// const api = getMockApiClient({
|
||||
// createQueryResp: createQueries.noError,
|
||||
// getQueryResultResp: getQueryResult.userError,
|
||||
// });
|
||||
|
||||
// const queryIntegration = new QueryIntegration(api);
|
||||
// const result = await queryIntegration.run(defaultQueryData);
|
||||
// assert.equal(result.error?.errorType, ERROR_TYPES.user_error);
|
||||
// assert.notEqual(result.error?.message, null);
|
||||
// });
|
||||
|
||||
// it("#getQueryResult sql exec error", async () => {
|
||||
// const api = getMockApiClient({
|
||||
// createQueryResp: createQueries.noError,
|
||||
// getQueryResultResp: getQueryResult.sqlExecError,
|
||||
// });
|
||||
|
||||
// const queryIntegration = new QueryIntegration(api);
|
||||
// const result = await queryIntegration.run(defaultQueryData);
|
||||
// assert.equal(result.error?.errorType, ERROR_TYPES.query_run_execution_error);
|
||||
// assert.notEqual(result.error?.message, null);
|
||||
// });
|
||||
});
|
||||
|
||||
// describe("run: timeout_error", () => {
|
||||
// it("query is pending", async () => {
|
||||
// const api = getMockApiClient({
|
||||
// createQueryResp: createQueries.noError,
|
||||
// getQueryResultResp: getQueryResult.noErrorPending,
|
||||
// });
|
||||
|
||||
// const queryIntegration = new QueryIntegration(api, {
|
||||
// ttlMinutes: 1,
|
||||
// cached: false,
|
||||
// timeoutMinutes: 0.01,
|
||||
// retryIntervalSeconds: 0.001,
|
||||
// pageNumber: 1,
|
||||
// pageSize: 100,
|
||||
// });
|
||||
// const result = await queryIntegration.run(defaultQueryData);
|
||||
// assert.equal(result.error?.errorType, ERROR_TYPES.query_run_timeout_error);
|
||||
// assert.notEqual(result.error?.message, null);
|
||||
// });
|
||||
|
||||
// it("query is rate limited", async () => {
|
||||
// const api = getMockApiClient({
|
||||
// createQueryResp: createQueries.rateLimitError,
|
||||
// getQueryResultResp: getQueryResult.noErrorPending,
|
||||
// });
|
||||
|
||||
// const queryIntegration = new QueryIntegration(api, {
|
||||
// ttlMinutes: 1,
|
||||
// cached: false,
|
||||
// timeoutMinutes: 0.01,
|
||||
// retryIntervalSeconds: 0.001,
|
||||
// pageNumber: 1,
|
||||
// pageSize: 100,
|
||||
// });
|
||||
// const result = await queryIntegration.run(defaultQueryData);
|
||||
// assert.equal(result.error?.errorType, ERROR_TYPES.query_run_rate_limit_error);
|
||||
// assert.notEqual(result.error?.message, null);
|
||||
// });
|
||||
// });
|
||||
|
||||
@ -1,49 +1,15 @@
|
||||
import { assert, describe, it } from "vitest";
|
||||
import { QueryResultSetBuilder } from "../integrations/query-integration/query-result-set-builder";
|
||||
import {
|
||||
QueryResultSetBuilderInput,
|
||||
QueryStatus,
|
||||
QueryStatusError,
|
||||
QueryStatusFinished,
|
||||
QueryStatusPending,
|
||||
} from "../types";
|
||||
|
||||
function getQueryResultSetBuilder(
|
||||
status: QueryStatus
|
||||
): QueryResultSetBuilderInput {
|
||||
return {
|
||||
queryResultJson: {
|
||||
queryId: "test",
|
||||
status,
|
||||
results: [
|
||||
[1, "0x-tx-id-0", "0xfrom-address-0", true, 0.5],
|
||||
[2, "0x-tx-id-1", "0xfrom-address-1", false, 0.75],
|
||||
[3, "0x-tx-id-2", "0xfrom-address-2", false, 1.75],
|
||||
[4, "0x-tx-id-3", "0xfrom-address-3", true, 100.001],
|
||||
],
|
||||
startedAt: "2022-05-19T00:00:00Z",
|
||||
endedAt: "2022-05-19T00:01:30Z",
|
||||
columnLabels: [
|
||||
"block_id",
|
||||
"tx_id",
|
||||
"from_address",
|
||||
"succeeded",
|
||||
"amount",
|
||||
],
|
||||
columnTypes: ["number", "string", "string", "boolean", "number"],
|
||||
message: "",
|
||||
errors: null,
|
||||
pageSize: 100,
|
||||
pageNumber: 0,
|
||||
},
|
||||
error: null,
|
||||
};
|
||||
}
|
||||
import { QueryResultSetBuilder } from "../integrations/query-integration/query-result-set-builder";
|
||||
import { QueryStatus, QueryStatusError, QueryStatusFinished, QueryStatusPending } from "../types";
|
||||
import { getQueryResultsResponse, getQueryRunResponse } from "./mock-data";
|
||||
|
||||
describe("runStats", () => {
|
||||
const queryResultSet = new QueryResultSetBuilder(
|
||||
getQueryResultSetBuilder(QueryStatusFinished)
|
||||
);
|
||||
const queryResultSet = new QueryResultSetBuilder({
|
||||
getQueryRunResultsRpcResult: getQueryResultsResponse("QUERY_STATE_SUCCESS").result,
|
||||
getQueryRunRpcResult: getQueryRunResponse("QUERY_STATE_SUCCESS").result,
|
||||
error: null,
|
||||
});
|
||||
it("runStats startedAt is Date type", async () => {
|
||||
assert.typeOf(queryResultSet.runStats?.startedAt, "Date");
|
||||
});
|
||||
@ -52,19 +18,34 @@ describe("runStats", () => {
|
||||
assert.typeOf(queryResultSet.runStats?.startedAt, "Date");
|
||||
});
|
||||
|
||||
it("runStats recordCount = 4", async () => {
|
||||
assert.equal(queryResultSet.runStats?.recordCount, 4);
|
||||
it("runStats recordCount = 1", async () => {
|
||||
assert.equal(queryResultSet.runStats?.recordCount, 10000);
|
||||
});
|
||||
|
||||
it("runStats elpasedSeconds = 90", async () => {
|
||||
assert.equal(queryResultSet.runStats?.elapsedSeconds, 90);
|
||||
it("runStats elpasedSeconds = 51", async () => {
|
||||
assert.equal(queryResultSet.runStats?.elapsedSeconds, 51);
|
||||
});
|
||||
|
||||
it("runStats queuedSeconds = 0", async () => {
|
||||
assert.equal(queryResultSet.runStats?.queuedSeconds, 0);
|
||||
});
|
||||
|
||||
it("runStats streamingSeconds = 45", async () => {
|
||||
assert.equal(queryResultSet.runStats?.streamingSeconds, 45);
|
||||
});
|
||||
|
||||
it("runStats queryExecSeconds = 5", async () => {
|
||||
assert.equal(queryResultSet.runStats?.queryExecSeconds, 5);
|
||||
});
|
||||
});
|
||||
|
||||
describe("records", () => {
|
||||
const queryResultSet = new QueryResultSetBuilder(
|
||||
getQueryResultSetBuilder(QueryStatusFinished)
|
||||
);
|
||||
const queryResultSet = new QueryResultSetBuilder({
|
||||
getQueryRunResultsRpcResult: getQueryResultsResponse("QUERY_STATE_SUCCESS").result,
|
||||
getQueryRunRpcResult: getQueryRunResponse("QUERY_STATE_SUCCESS").result,
|
||||
error: null,
|
||||
});
|
||||
|
||||
it("records length = rows length", async () => {
|
||||
assert.equal(queryResultSet.records?.length, queryResultSet.rows?.length);
|
||||
});
|
||||
@ -92,18 +73,14 @@ describe("records", () => {
|
||||
it("record values match row values", () => {
|
||||
let records = queryResultSet?.records;
|
||||
queryResultSet?.rows?.forEach((cells, rowIndex) => {
|
||||
cells.forEach((cellValue, colIndex) => {
|
||||
cells.forEach((cellValue: any, colIndex: number) => {
|
||||
let columns = queryResultSet?.columns;
|
||||
if (!columns) {
|
||||
throw new Error(
|
||||
"QueryResultSetBuilder columns cannot be null for tests"
|
||||
);
|
||||
throw new Error("QueryResultSetBuilder columns cannot be null for tests");
|
||||
}
|
||||
let column = columns[colIndex];
|
||||
if (records === null) {
|
||||
throw new Error(
|
||||
"QueryResultSetBuilder records cannot be null for tests"
|
||||
);
|
||||
throw new Error("QueryResultSetBuilder records cannot be null for tests");
|
||||
}
|
||||
let record = records[rowIndex];
|
||||
let recordValue = record[column];
|
||||
@ -116,36 +93,72 @@ describe("records", () => {
|
||||
|
||||
describe("status", () => {
|
||||
it("isFinished", async () => {
|
||||
const queryResultSet = new QueryResultSetBuilder(
|
||||
getQueryResultSetBuilder(QueryStatusFinished)
|
||||
);
|
||||
const queryResultSet = new QueryResultSetBuilder({
|
||||
getQueryRunResultsRpcResult: getQueryResultsResponse("QUERY_STATE_SUCCESS").result,
|
||||
getQueryRunRpcResult: getQueryRunResponse("QUERY_STATE_SUCCESS").result,
|
||||
error: null,
|
||||
});
|
||||
|
||||
assert.equal(queryResultSet?.status, QueryStatusFinished);
|
||||
});
|
||||
it("isPending", async () => {
|
||||
const queryResultSet = new QueryResultSetBuilder(
|
||||
getQueryResultSetBuilder(QueryStatusPending)
|
||||
);
|
||||
it("isPending: QUERY_STATE_READY", async () => {
|
||||
const queryResultSet = new QueryResultSetBuilder({
|
||||
getQueryRunResultsRpcResult: getQueryResultsResponse("QUERY_STATE_READY").result,
|
||||
getQueryRunRpcResult: getQueryRunResponse("QUERY_STATE_READY").result,
|
||||
error: null,
|
||||
});
|
||||
assert.equal(queryResultSet?.status, QueryStatusPending);
|
||||
});
|
||||
it("isError", async () => {
|
||||
const queryResultSet = new QueryResultSetBuilder(
|
||||
getQueryResultSetBuilder(QueryStatusError)
|
||||
);
|
||||
it("isPending: QUERY_STATE_RUNNING", async () => {
|
||||
const queryResultSet = new QueryResultSetBuilder({
|
||||
getQueryRunResultsRpcResult: getQueryResultsResponse("QUERY_STATE_RUNNING").result,
|
||||
getQueryRunRpcResult: getQueryRunResponse("QUERY_STATE_RUNNING").result,
|
||||
error: null,
|
||||
});
|
||||
assert.equal(queryResultSet?.status, QueryStatusPending);
|
||||
});
|
||||
it("isPending: QUERY_STATE_STREAMING_RESULTS", async () => {
|
||||
const queryResultSet = new QueryResultSetBuilder({
|
||||
getQueryRunResultsRpcResult: getQueryResultsResponse("QUERY_STATE_STREAMING_RESULTS").result,
|
||||
getQueryRunRpcResult: getQueryRunResponse("QUERY_STATE_STREAMING_RESULTS").result,
|
||||
error: null,
|
||||
});
|
||||
assert.equal(queryResultSet?.status, QueryStatusPending);
|
||||
});
|
||||
|
||||
it("isError: QUERY_STATE_FAILED", async () => {
|
||||
const queryResultSet = new QueryResultSetBuilder({
|
||||
getQueryRunResultsRpcResult: getQueryResultsResponse("QUERY_STATE_FAILED").result,
|
||||
getQueryRunRpcResult: getQueryRunResponse("QUERY_STATE_FAILED").result,
|
||||
error: null,
|
||||
});
|
||||
assert.equal(queryResultSet?.status, QueryStatusError);
|
||||
});
|
||||
it("isError: QUERY_STATE_CANCELLED", async () => {
|
||||
const queryResultSet = new QueryResultSetBuilder({
|
||||
getQueryRunResultsRpcResult: getQueryResultsResponse("QUERY_STATE_CANCELED").result,
|
||||
getQueryRunRpcResult: getQueryRunResponse("QUERY_STATE_CANCELED").result,
|
||||
error: null,
|
||||
});
|
||||
assert.equal(queryResultSet?.status, QueryStatusError);
|
||||
});
|
||||
});
|
||||
|
||||
describe("queryID", () => {
|
||||
it("queryId is set", async () => {
|
||||
const queryResultSet = new QueryResultSetBuilder(
|
||||
getQueryResultSetBuilder(QueryStatusFinished)
|
||||
);
|
||||
const queryResultSet = new QueryResultSetBuilder({
|
||||
getQueryRunResultsRpcResult: getQueryResultsResponse("QUERY_STATE_SUCCESS").result,
|
||||
getQueryRunRpcResult: getQueryRunResponse("QUERY_STATE_SUCCESS").result,
|
||||
error: null,
|
||||
});
|
||||
assert.notEqual(queryResultSet?.queryId, null);
|
||||
});
|
||||
it("queryId is test", async () => {
|
||||
const queryResultSet = new QueryResultSetBuilder(
|
||||
getQueryResultSetBuilder(QueryStatusFinished)
|
||||
);
|
||||
assert.equal(queryResultSet?.queryId, "test");
|
||||
const queryResultSet = new QueryResultSetBuilder({
|
||||
getQueryRunResultsRpcResult: getQueryResultsResponse("QUERY_STATE_SUCCESS").result,
|
||||
getQueryRunRpcResult: getQueryRunResponse("QUERY_STATE_SUCCESS").result,
|
||||
error: null,
|
||||
});
|
||||
assert.equal(queryResultSet?.queryId, "clg44olzq00cbn60tasvob5l2");
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
import { Query } from "../query.type";
|
||||
import { CreateQueryResp } from "./create-query-resp.type";
|
||||
import { QueryResultResp } from "./query-result-resp.type";
|
||||
|
||||
export interface ApiClient {
|
||||
getUrl(path: string): string;
|
||||
createQuery(query: Query): Promise<CreateQueryResp>;
|
||||
getQueryResult(queryID: string, pageNumber: number, pageSize: number): Promise<QueryResultResp>;
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
export interface ApiResponse {
|
||||
statusCode: number;
|
||||
statusMsg: string | null;
|
||||
errorMsg: string | null | undefined;
|
||||
data: Record<string, any> | null;
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
import { ApiResponse } from "./api-response.type";
|
||||
|
||||
export type CreateQueryJson = {
|
||||
token: string;
|
||||
errors?: string | null;
|
||||
};
|
||||
|
||||
export interface CreateQueryResp extends ApiResponse {
|
||||
data: CreateQueryJson | null;
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export type ApiError = Error;
|
||||
@ -1,5 +0,0 @@
|
||||
export * from "./create-query-resp.type";
|
||||
export * from "./errors.type";
|
||||
export * from "./query-result-resp.type";
|
||||
export * from "./api-client.type";
|
||||
export * from "./api-response.type";
|
||||
@ -1,21 +0,0 @@
|
||||
import { QueryStatus } from "../query-status.type";
|
||||
import { ApiResponse } from "./api-response.type";
|
||||
|
||||
export type Row = (string | number | boolean | null)[];
|
||||
export type QueryResultJson = {
|
||||
queryId: string;
|
||||
status: QueryStatus;
|
||||
results: Row[];
|
||||
startedAt: string;
|
||||
endedAt: string;
|
||||
columnLabels: string[];
|
||||
columnTypes: string[];
|
||||
message?: string;
|
||||
errors?: string | null;
|
||||
pageNumber: number;
|
||||
pageSize: number;
|
||||
};
|
||||
|
||||
export interface QueryResultResp extends ApiResponse {
|
||||
data: QueryResultJson | null;
|
||||
}
|
||||
28
js/src/types/compass/cancel-query-run.type.ts
Normal file
28
js/src/types/compass/cancel-query-run.type.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { QueryRun, RpcRequest, BaseRpcRequest, RpcResponse, BaseRpcResponse } from "./core";
|
||||
|
||||
// Request
|
||||
export interface CancelQueryRunRpcRequestParams {
|
||||
queryRunId: string;
|
||||
}
|
||||
|
||||
export interface CancelQueryRunRpcRequest extends RpcRequest<CancelQueryRunRpcRequestParams> {
|
||||
method: "cancelQueryRun";
|
||||
}
|
||||
|
||||
export class CancelQueryRunRpcRequestImplementation
|
||||
extends BaseRpcRequest<CancelQueryRunRpcRequestParams>
|
||||
implements CancelQueryRunRpcRequest
|
||||
{
|
||||
method: "cancelQueryRun" = "cancelQueryRun";
|
||||
}
|
||||
|
||||
// Response
|
||||
export interface CancelQueryRunRpcResult {
|
||||
canceledQueryRun: QueryRun;
|
||||
}
|
||||
|
||||
export interface CancelQueryRunRpcResponse extends RpcResponse<CancelQueryRunRpcResult> {}
|
||||
|
||||
export class CancelQueryRunRpcResponseImplementation
|
||||
extends BaseRpcResponse<CancelQueryRunRpcResult>
|
||||
implements CancelQueryRunRpcResponse {}
|
||||
15
js/src/types/compass/compass-api-client.type.ts
Normal file
15
js/src/types/compass/compass-api-client.type.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { CreateQueryRunRpcParams, CreateQueryRunRpcResponse } from "./create-query-run.type";
|
||||
import { GetQueryRunRpcRequestParams, GetQueryRunRpcResponse } from "./get-query-run.type";
|
||||
import { GetQueryRunResultsRpcParams, GetQueryRunResultsRpcResponse } from "./get-query-run-results.type";
|
||||
import { GetSqlStatementParams, GetSqlStatementResponse } from "./get-sql-statement.type";
|
||||
import { CancelQueryRunRpcRequestParams, CancelQueryRunRpcResponse } from "./cancel-query-run.type";
|
||||
|
||||
export interface CompassApiClient {
|
||||
url: string;
|
||||
getUrl(): string;
|
||||
createQuery(params: CreateQueryRunRpcParams): Promise<CreateQueryRunRpcResponse>;
|
||||
getQueryRun(params: GetQueryRunRpcRequestParams): Promise<GetQueryRunRpcResponse>;
|
||||
getQueryResult(params: GetQueryRunResultsRpcParams): Promise<GetQueryRunResultsRpcResponse>;
|
||||
getSqlStatement(params: GetSqlStatementParams): Promise<GetSqlStatementResponse>;
|
||||
cancelQueryRun(params: CancelQueryRunRpcRequestParams): Promise<CancelQueryRunRpcResponse>;
|
||||
}
|
||||
5
js/src/types/compass/core/column-metadata.type.ts
Normal file
5
js/src/types/compass/core/column-metadata.type.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface ColumnMetadata {
|
||||
types: string[];
|
||||
columns: string[];
|
||||
colTypeMap: Record<string, string>;
|
||||
}
|
||||
11
js/src/types/compass/core/index.ts
Normal file
11
js/src/types/compass/core/index.ts
Normal file
@ -0,0 +1,11 @@
|
||||
// Export classes from core
|
||||
export { Page } from "./page.type";
|
||||
export { PageStats } from "./page-stats.type";
|
||||
export { QueryRun } from "./query-run.type";
|
||||
export { QueryRequest } from "./query-request.type";
|
||||
export { ResultFormat } from "./result-format.type";
|
||||
export { RpcRequest, BaseRpcRequest } from "./rpc-request.type";
|
||||
export { RpcResponse, BaseRpcResponse } from "./rpc-response.type";
|
||||
export { SqlStatement } from "./sql-statement.type";
|
||||
export { Tags } from "./tags.type";
|
||||
export { RpcError } from "./rpc-error.type";
|
||||
6
js/src/types/compass/core/page-stats.type.ts
Normal file
6
js/src/types/compass/core/page-stats.type.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export interface PageStats {
|
||||
currentPageNumber: number;
|
||||
currentPageSize: number;
|
||||
totalRows: number;
|
||||
totalPages: number;
|
||||
}
|
||||
4
js/src/types/compass/core/page.type.ts
Normal file
4
js/src/types/compass/core/page.type.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export interface Page {
|
||||
number: number;
|
||||
size: number;
|
||||
}
|
||||
15
js/src/types/compass/core/query-request.type.ts
Normal file
15
js/src/types/compass/core/query-request.type.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { Tags } from "./tags.type";
|
||||
|
||||
export interface QueryRequest {
|
||||
id: string;
|
||||
sqlStatementId: string;
|
||||
userId: string;
|
||||
tags: Tags;
|
||||
maxAgeMinutes: number;
|
||||
resultTTLHours: number;
|
||||
userSkipCache: boolean;
|
||||
triggeredQueryRun: boolean;
|
||||
queryRunId: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
28
js/src/types/compass/core/query-run.type.ts
Normal file
28
js/src/types/compass/core/query-run.type.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { Tags } from "./tags.type";
|
||||
|
||||
export interface QueryRun {
|
||||
id: string;
|
||||
sqlStatementId: string;
|
||||
state: string;
|
||||
path: string;
|
||||
fileCount: number | null;
|
||||
lastFileNumber: number | null;
|
||||
fileNames: string | null;
|
||||
errorName: string | null;
|
||||
errorMessage: string | null;
|
||||
errorData: any | null;
|
||||
dataSourceQueryId: string | null;
|
||||
dataSourceSessionId: string | null;
|
||||
startedAt: string | null;
|
||||
queryRunningEndedAt: string | null;
|
||||
queryStreamingEndedAt: string | null;
|
||||
endedAt: string | null;
|
||||
rowCount: number | null;
|
||||
totalSize: number | null;
|
||||
tags: Tags;
|
||||
dataSourceId: string;
|
||||
userId: string;
|
||||
createdAt: string;
|
||||
updatedAt: string; // Assuming that datetime translates to a string in TypeScript
|
||||
archivedAt: string | null; // Assuming that datetime translates to a string in TypeScript
|
||||
}
|
||||
4
js/src/types/compass/core/result-format.type.ts
Normal file
4
js/src/types/compass/core/result-format.type.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export enum ResultFormat {
|
||||
csv = "csv",
|
||||
json = "json",
|
||||
}
|
||||
5
js/src/types/compass/core/rpc-error.type.ts
Normal file
5
js/src/types/compass/core/rpc-error.type.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface RpcError {
|
||||
code: number;
|
||||
message: string;
|
||||
data: any | null;
|
||||
}
|
||||
18
js/src/types/compass/core/rpc-request.type.ts
Normal file
18
js/src/types/compass/core/rpc-request.type.ts
Normal file
@ -0,0 +1,18 @@
|
||||
export interface RpcRequest<T> {
|
||||
jsonrpc: string;
|
||||
method: string;
|
||||
params: T[];
|
||||
id: number;
|
||||
}
|
||||
|
||||
export abstract class BaseRpcRequest<T> implements RpcRequest<T> {
|
||||
jsonrpc: string = "2.0";
|
||||
abstract method: string;
|
||||
params: T[];
|
||||
id: number;
|
||||
|
||||
constructor(params: T[], id: number = 1) {
|
||||
this.params = params;
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
21
js/src/types/compass/core/rpc-response.type.ts
Normal file
21
js/src/types/compass/core/rpc-response.type.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { RpcError } from "./rpc-error.type";
|
||||
|
||||
export interface RpcResponse<T> {
|
||||
jsonrpc: string;
|
||||
id: number;
|
||||
result: T | null;
|
||||
error: RpcError | null;
|
||||
}
|
||||
|
||||
export abstract class BaseRpcResponse<T> implements RpcResponse<T> {
|
||||
jsonrpc: string = "2.0";
|
||||
id: number;
|
||||
result: T | null;
|
||||
error: RpcError | null;
|
||||
|
||||
constructor(id: number, result: T | null, error: RpcError | null) {
|
||||
this.id = id;
|
||||
this.result = result;
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
13
js/src/types/compass/core/sql-statement.type.ts
Normal file
13
js/src/types/compass/core/sql-statement.type.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { ColumnMetadata } from "./column-metadata.type";
|
||||
import { Tags } from "./tags.type";
|
||||
|
||||
export interface SqlStatement {
|
||||
id: string;
|
||||
statementHash: string;
|
||||
sql: string;
|
||||
columnMetadata: ColumnMetadata | null;
|
||||
userId: string;
|
||||
tags: Tags;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
5
js/src/types/compass/core/tags.type.ts
Normal file
5
js/src/types/compass/core/tags.type.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface Tags {
|
||||
sdk_package: string | null;
|
||||
sdk_version: string | null;
|
||||
sdk_language: string | null;
|
||||
}
|
||||
46
js/src/types/compass/create-query-run.type.ts
Normal file
46
js/src/types/compass/create-query-run.type.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import {
|
||||
QueryRequest,
|
||||
QueryRun,
|
||||
RpcRequest,
|
||||
RpcResponse,
|
||||
SqlStatement,
|
||||
Tags,
|
||||
BaseRpcRequest,
|
||||
BaseRpcResponse,
|
||||
} from "./core";
|
||||
|
||||
// Request
|
||||
|
||||
// CreateQueryRunRpcRequest.ts
|
||||
export interface CreateQueryRunRpcParams {
|
||||
resultTTLHours: number;
|
||||
maxAgeMinutes: number;
|
||||
sql: string;
|
||||
tags: Tags;
|
||||
dataSource: string;
|
||||
dataProvider: string;
|
||||
}
|
||||
|
||||
export interface CreateQueryRunRpcRequest extends RpcRequest<CreateQueryRunRpcParams> {
|
||||
method: "createQueryRun";
|
||||
}
|
||||
|
||||
export class CreateQueryRunRpcRequestImplementation
|
||||
extends BaseRpcRequest<CreateQueryRunRpcParams>
|
||||
implements CreateQueryRunRpcRequest
|
||||
{
|
||||
method: "createQueryRun" = "createQueryRun";
|
||||
}
|
||||
|
||||
// Response
|
||||
export interface CreateQueryRunRpcResult {
|
||||
queryRequest: QueryRequest;
|
||||
queryRun: QueryRun;
|
||||
sqlStatement: SqlStatement;
|
||||
}
|
||||
|
||||
export interface CreateQueryRunRpcResponse extends RpcResponse<CreateQueryRunRpcResult> {}
|
||||
|
||||
export class CreateQueryRunRpcResponseImplementation
|
||||
extends BaseRpcResponse<CreateQueryRunRpcResult>
|
||||
implements CreateQueryRunRpcResponse {}
|
||||
66
js/src/types/compass/get-query-run-results.type.ts
Normal file
66
js/src/types/compass/get-query-run-results.type.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import {
|
||||
Page,
|
||||
PageStats,
|
||||
QueryRun,
|
||||
ResultFormat,
|
||||
RpcResponse,
|
||||
RpcRequest,
|
||||
BaseRpcRequest,
|
||||
BaseRpcResponse,
|
||||
} from "./core";
|
||||
|
||||
// Request
|
||||
export interface Filter {
|
||||
column: string;
|
||||
eq?: string | number | null;
|
||||
neq?: string | number | null;
|
||||
gt?: number | null;
|
||||
gte?: number | null;
|
||||
lt?: number | null;
|
||||
lte?: number | null;
|
||||
like?: string | number | null;
|
||||
in?: any[] | null;
|
||||
notIn?: any[] | null;
|
||||
}
|
||||
|
||||
export interface SortBy {
|
||||
column: string;
|
||||
direction: "desc" | "asc";
|
||||
}
|
||||
|
||||
export interface GetQueryRunResultsRpcParams {
|
||||
queryRunId: string;
|
||||
format: ResultFormat;
|
||||
filters?: Filter[] | null;
|
||||
sortBy?: SortBy[] | null;
|
||||
page: Page;
|
||||
}
|
||||
|
||||
export interface GetQueryRunResultsRpcRequest extends RpcRequest<GetQueryRunResultsRpcParams> {
|
||||
method: "getQueryRunResults";
|
||||
}
|
||||
|
||||
export class GetQueryRunResultsRpcRequestImplementation
|
||||
extends BaseRpcRequest<GetQueryRunResultsRpcParams>
|
||||
implements GetQueryRunResultsRpcRequest
|
||||
{
|
||||
method: "getQueryRunResults" = "getQueryRunResults";
|
||||
}
|
||||
|
||||
// Response
|
||||
export interface GetQueryRunResultsRpcResult {
|
||||
columnNames: string[] | null;
|
||||
columnTypes: string[] | null;
|
||||
rows: any[] | null;
|
||||
page: PageStats | null;
|
||||
sql: string | null;
|
||||
format: ResultFormat | null;
|
||||
originalQueryRun: QueryRun;
|
||||
redirectedToQueryRun: QueryRun | null;
|
||||
}
|
||||
|
||||
export interface GetQueryRunResultsRpcResponse extends RpcResponse<GetQueryRunResultsRpcResult> {}
|
||||
|
||||
export class GetQueryRunResultsRpcResponseImplementation
|
||||
extends BaseRpcResponse<GetQueryRunResultsRpcResult>
|
||||
implements GetQueryRunResultsRpcResponse {}
|
||||
29
js/src/types/compass/get-query-run.type.ts
Normal file
29
js/src/types/compass/get-query-run.type.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { QueryRun, RpcRequest, RpcResponse, BaseRpcRequest, BaseRpcResponse } from "./core";
|
||||
|
||||
// Request
|
||||
export interface GetQueryRunRpcRequestParams {
|
||||
queryRunId: string;
|
||||
}
|
||||
|
||||
export interface GetQueryRunRpcRequest extends RpcRequest<GetQueryRunRpcRequestParams> {
|
||||
method: "getQueryRun";
|
||||
}
|
||||
|
||||
export class GetQueryRunRpcRequestImplementation
|
||||
extends BaseRpcRequest<GetQueryRunRpcRequestParams>
|
||||
implements GetQueryRunRpcRequest
|
||||
{
|
||||
method: "getQueryRun" = "getQueryRun";
|
||||
}
|
||||
|
||||
// Response
|
||||
export interface GetQueryRunRpcResult {
|
||||
queryRun: QueryRun;
|
||||
redirectedToQueryRun?: QueryRun | null;
|
||||
}
|
||||
|
||||
export interface GetQueryRunRpcResponse extends RpcResponse<GetQueryRunRpcResult> {}
|
||||
|
||||
export class GetQueryRunRpcResponseImplementation
|
||||
extends BaseRpcResponse<GetQueryRunRpcResult>
|
||||
implements GetQueryRunRpcResponse {}
|
||||
28
js/src/types/compass/get-sql-statement.type.ts
Normal file
28
js/src/types/compass/get-sql-statement.type.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { SqlStatement, RpcRequest, RpcResponse, BaseRpcRequest, BaseRpcResponse } from "./core";
|
||||
|
||||
// Request
|
||||
export interface GetSqlStatementParams {
|
||||
sqlStatementId: string;
|
||||
}
|
||||
|
||||
export interface GetSqlStatementRequest extends RpcRequest<GetSqlStatementParams> {
|
||||
method: "getSqlStatement";
|
||||
}
|
||||
|
||||
export class GetSqlStatementRequestImplementation
|
||||
extends BaseRpcRequest<GetSqlStatementParams>
|
||||
implements GetSqlStatementRequest
|
||||
{
|
||||
method: "getSqlStatement" = "getSqlStatement";
|
||||
}
|
||||
|
||||
// Response
|
||||
export interface GetSqlStatementResult {
|
||||
sqlStatement: SqlStatement;
|
||||
}
|
||||
|
||||
export interface GetSqlStatementResponse extends RpcResponse<GetSqlStatementResult> {}
|
||||
|
||||
export class GetSqlStatementResponseImplementation
|
||||
extends BaseRpcResponse<GetSqlStatementResult>
|
||||
implements GetSqlStatementResponse {}
|
||||
8
js/src/types/compass/index.ts
Normal file
8
js/src/types/compass/index.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export * from "./cancel-query-run.type";
|
||||
export * from "./create-query-run.type";
|
||||
export * from "./get-query-run-results.type";
|
||||
export * from "./get-query-run.type";
|
||||
export * from "./get-sql-statement.type";
|
||||
export * from "./query-results.type";
|
||||
export * from "./core";
|
||||
export * from "./compass-api-client.type";
|
||||
46
js/src/types/compass/query-results.type.ts
Normal file
46
js/src/types/compass/query-results.type.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import {
|
||||
Page,
|
||||
PageStats,
|
||||
QueryRun,
|
||||
ResultFormat,
|
||||
RpcRequest,
|
||||
RpcResponse,
|
||||
BaseRpcRequest,
|
||||
BaseRpcResponse,
|
||||
} from "./core";
|
||||
|
||||
// Request
|
||||
export interface QueryResultsRpcParams {
|
||||
query: string;
|
||||
format: ResultFormat;
|
||||
page: Page;
|
||||
}
|
||||
|
||||
export interface QueryResultsRpcRequest extends RpcRequest<QueryResultsRpcParams> {
|
||||
method: "queryResults";
|
||||
}
|
||||
|
||||
export class QueryResultsRpcRequestImplementation
|
||||
extends BaseRpcRequest<QueryResultsRpcParams>
|
||||
implements QueryResultsRpcRequest
|
||||
{
|
||||
method: "queryResults" = "queryResults";
|
||||
}
|
||||
|
||||
// Response
|
||||
export interface QueryResultsRpcResult {
|
||||
columnNames: string[];
|
||||
columnTypes: string[];
|
||||
rows: Record<string, unknown>[];
|
||||
page: PageStats;
|
||||
sql: string;
|
||||
format: ResultFormat;
|
||||
originalQueryRun: QueryRun;
|
||||
redirectedToQueryRun: QueryRun;
|
||||
}
|
||||
|
||||
export interface QueryResultsRpcResponse extends RpcResponse<QueryResultsRpcResult> {}
|
||||
|
||||
export class QueryResultsRpcResponseImplementation
|
||||
extends BaseRpcResponse<QueryResultsRpcResult>
|
||||
implements QueryResultsRpcResponse {}
|
||||
@ -1,9 +1,8 @@
|
||||
export * from "./query.type";
|
||||
export * from "./query-defaults.type";
|
||||
export * from "./sdk-defaults.type";
|
||||
export * from "./query-status.type";
|
||||
export * from "./query-result-set.type";
|
||||
export * from "./query-result-set-input.type";
|
||||
export * from "./query-run-stats.type";
|
||||
export * from "./query-result-record.type";
|
||||
export * from "./sleep-config.type";
|
||||
export * from "./api";
|
||||
export * from "./compass";
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
export type QueryDefaults = {
|
||||
ttlMinutes: number;
|
||||
cached: boolean;
|
||||
timeoutMinutes: number;
|
||||
retryIntervalSeconds: number;
|
||||
pageSize: number;
|
||||
pageNumber: number;
|
||||
};
|
||||
@ -1,21 +0,0 @@
|
||||
import {
|
||||
QueryRunExecutionError,
|
||||
QueryRunRateLimitError,
|
||||
QueryRunTimeoutError,
|
||||
ServerError,
|
||||
UserError,
|
||||
UnexpectedSDKError,
|
||||
} from "../errors";
|
||||
import { QueryResultJson } from "./api/query-result-resp.type";
|
||||
|
||||
export type QueryResultSetBuilderInput = {
|
||||
queryResultJson: QueryResultJson | null;
|
||||
error:
|
||||
| QueryRunExecutionError
|
||||
| QueryRunRateLimitError
|
||||
| QueryRunTimeoutError
|
||||
| ServerError
|
||||
| UserError
|
||||
| UnexpectedSDKError
|
||||
| null;
|
||||
};
|
||||
@ -1,4 +1,3 @@
|
||||
import { Row } from "./api";
|
||||
import {
|
||||
QueryRunExecutionError,
|
||||
QueryRunRateLimitError,
|
||||
@ -6,10 +5,12 @@ import {
|
||||
ServerError,
|
||||
UserError,
|
||||
UnexpectedSDKError,
|
||||
ApiError,
|
||||
} from "../errors";
|
||||
import { QueryRunStats } from "./query-run-stats.type";
|
||||
import { QueryStatus } from "./query-status.type";
|
||||
import { QueryResultRecord } from "./query-result-record.type";
|
||||
import { PageStats } from "./compass";
|
||||
|
||||
export interface QueryResultSet {
|
||||
// The server id of the query
|
||||
@ -25,7 +26,7 @@ export interface QueryResultSet {
|
||||
columnTypes: string[] | null;
|
||||
|
||||
// The results of the query
|
||||
rows: Row[] | null;
|
||||
rows: any[] | null;
|
||||
|
||||
// Summary stats on the query run (i.e. the number of rows returned, the elapsed time, etc)
|
||||
runStats: QueryRunStats | null;
|
||||
@ -33,8 +34,12 @@ export interface QueryResultSet {
|
||||
// The results of the query transformed as an array of objects
|
||||
records: QueryResultRecord[] | null;
|
||||
|
||||
// The page of results
|
||||
page: PageStats | null;
|
||||
|
||||
// If the query failed, this will contain the error
|
||||
error:
|
||||
| ApiError
|
||||
| QueryRunRateLimitError
|
||||
| QueryRunTimeoutError
|
||||
| QueryRunExecutionError
|
||||
|
||||
@ -2,5 +2,13 @@ export type QueryRunStats = {
|
||||
startedAt: Date;
|
||||
endedAt: Date;
|
||||
elapsedSeconds: number;
|
||||
queryExecStartedAt: Date;
|
||||
queryExecEndedAt: Date;
|
||||
streamingStartedAt: Date;
|
||||
streamingEndedAt: Date;
|
||||
queuedSeconds: number;
|
||||
streamingSeconds: number;
|
||||
queryExecSeconds: number;
|
||||
bytes: number; // the number of bytes returned by the query
|
||||
recordCount: number;
|
||||
};
|
||||
|
||||
@ -2,3 +2,22 @@ export const QueryStatusFinished = "finished";
|
||||
export const QueryStatusPending = "pending";
|
||||
export const QueryStatusError = "error";
|
||||
export type QueryStatus = "finished" | "pending" | "error";
|
||||
|
||||
export function mapApiQueryStateToStatus(state: string): QueryStatus {
|
||||
switch (state) {
|
||||
case "QUERY_STATE_READY":
|
||||
return QueryStatusPending;
|
||||
case "QUERY_STATE_RUNNING":
|
||||
return QueryStatusPending;
|
||||
case "QUERY_STATE_STREAMING_RESULTS":
|
||||
return QueryStatusPending;
|
||||
case "QUERY_STATE_FAILED":
|
||||
return QueryStatusError;
|
||||
case "QUERY_STATE_CANCELED":
|
||||
return QueryStatusError;
|
||||
case "QUERY_STATE_SUCCESS":
|
||||
return QueryStatusFinished;
|
||||
default:
|
||||
throw new Error(`Unknown query state: ${state}`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,14 +1,26 @@
|
||||
export type Query = {
|
||||
// SQL query to execute
|
||||
sql: string;
|
||||
// the maximum age of the query results in minutes you will accept, defaults to zero
|
||||
maxAgeMinutes?: number;
|
||||
// The number of minutes to cache the query results
|
||||
ttlMinutes?: number;
|
||||
// An override on the cahce. A value of true will reexecute the query.
|
||||
// An override on the cache. A value of true will reexecute the query.
|
||||
cached?: boolean;
|
||||
// The number of minutes until your query time out
|
||||
// The number of minutes until your query times out
|
||||
timeoutMinutes?: number;
|
||||
// The number of records to return
|
||||
pageSize?: number;
|
||||
// The page number to return
|
||||
pageNumber?: number;
|
||||
// The number of seconds to use between retries
|
||||
retryIntervalSeconds?: number | string;
|
||||
// The SDK package used for the query
|
||||
sdkPackage?: string;
|
||||
// The SDK version used for the query
|
||||
sdkVersion?: string;
|
||||
// The data source to execute the query against
|
||||
dataSource?: string;
|
||||
// The owner of the data source
|
||||
dataProvider?: string;
|
||||
};
|
||||
|
||||
14
js/src/types/sdk-defaults.type.ts
Normal file
14
js/src/types/sdk-defaults.type.ts
Normal file
@ -0,0 +1,14 @@
|
||||
export type SdkDefaults = {
|
||||
apiBaseUrl: string;
|
||||
ttlMinutes: number;
|
||||
maxAgeMinutes: number;
|
||||
dataSource: string;
|
||||
dataProvider: string;
|
||||
cached: boolean;
|
||||
timeoutMinutes: number;
|
||||
retryIntervalSeconds: number;
|
||||
pageSize: number;
|
||||
pageNumber: number;
|
||||
sdkPackage: string;
|
||||
sdkVersion: string;
|
||||
};
|
||||
69
js/yarn.lock
69
js/yarn.lock
@ -94,19 +94,6 @@ assertion-error@^1.1.0:
|
||||
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b"
|
||||
integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==
|
||||
|
||||
asynckit@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
|
||||
|
||||
axios@^0.27.2:
|
||||
version "0.27.2"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972"
|
||||
integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==
|
||||
dependencies:
|
||||
follow-redirects "^1.14.9"
|
||||
form-data "^4.0.0"
|
||||
|
||||
balanced-match@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
||||
@ -177,13 +164,6 @@ color-name@~1.1.4:
|
||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
|
||||
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||
|
||||
combined-stream@^1.0.8:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
|
||||
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
|
||||
dependencies:
|
||||
delayed-stream "~1.0.0"
|
||||
|
||||
concat-map@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||
@ -212,11 +192,6 @@ deep-eql@^3.0.1:
|
||||
dependencies:
|
||||
type-detect "^4.0.0"
|
||||
|
||||
delayed-stream@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
|
||||
|
||||
emoji-regex@^8.0.0:
|
||||
version "8.0.0"
|
||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
|
||||
@ -361,11 +336,6 @@ find-up@^5.0.0:
|
||||
locate-path "^6.0.0"
|
||||
path-exists "^4.0.0"
|
||||
|
||||
follow-redirects@^1.14.9:
|
||||
version "1.15.0"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.0.tgz#06441868281c86d0dda4ad8bdaead2d02dca89d4"
|
||||
integrity sha512-aExlJShTV4qOUOL7yF1U5tvLCB0xQuudbf6toyYA0E/acBNw71mvjFTnLaRp50aQaYocMR0a/RMMBIHeZnGyjQ==
|
||||
|
||||
foreground-child@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-2.0.0.tgz#71b32800c9f15aa8f2f83f4a6bd9bff35d861a53"
|
||||
@ -374,15 +344,6 @@ foreground-child@^2.0.0:
|
||||
cross-spawn "^7.0.0"
|
||||
signal-exit "^3.0.2"
|
||||
|
||||
form-data@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
|
||||
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
|
||||
dependencies:
|
||||
asynckit "^0.4.0"
|
||||
combined-stream "^1.0.8"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
fs.realpath@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||
@ -515,18 +476,6 @@ make-dir@^3.0.0:
|
||||
dependencies:
|
||||
semver "^6.0.0"
|
||||
|
||||
mime-db@1.52.0:
|
||||
version "1.52.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
|
||||
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
|
||||
|
||||
mime-types@^2.1.12:
|
||||
version "2.1.35"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
|
||||
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
|
||||
dependencies:
|
||||
mime-db "1.52.0"
|
||||
|
||||
minimatch@^3.0.4, minimatch@^3.1.1:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
|
||||
@ -710,6 +659,11 @@ test-exclude@^6.0.0:
|
||||
glob "^7.1.4"
|
||||
minimatch "^3.0.4"
|
||||
|
||||
tiny-lru@^11.2.5:
|
||||
version "11.2.5"
|
||||
resolved "https://registry.yarnpkg.com/tiny-lru/-/tiny-lru-11.2.5.tgz#b138b99022aa26c567fa51a8dbf9e3e2959b2b30"
|
||||
integrity sha512-JpqM0K33lG6iQGKiigcwuURAKZlq6rHXfrgeL4/I8/REoyJTGU+tEMszvT/oTRVHG2OiylhGDjqPp1jWMlr3bw==
|
||||
|
||||
tinypool@^0.1.3:
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.1.3.tgz#b5570b364a1775fd403de5e7660b325308fee26b"
|
||||
@ -725,6 +679,11 @@ totalist@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.0.tgz#4ef9c58c5f095255cdc3ff2a0a55091c57a3a1bd"
|
||||
integrity sha512-eM+pCBxXO/njtF7vdFsHuqb+ElbxqtI4r5EAvk6grfAFyJ6IvWlSkfZ5T9ozC6xWw3Fj1fGoSmrl0gUs46JVIw==
|
||||
|
||||
ts-deepmerge@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ts-deepmerge/-/ts-deepmerge-7.0.0.tgz#ee824dc177d452603348c7e6f3b90223434a6b44"
|
||||
integrity sha512-WZ/iAJrKDhdINv1WG6KZIGHrZDar6VfhftG1QJFpVbOYZMYJLJOvZOo1amictRXVdBXZIgBHKswMTXzElngprA==
|
||||
|
||||
type-detect@^4.0.0, type-detect@^4.0.5:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
|
||||
@ -785,6 +744,14 @@ wrappy@1:
|
||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
||||
|
||||
xior@^0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/xior/-/xior-0.1.1.tgz#285e996585e1c0ab42ee3aca3edcef5c0d06c4aa"
|
||||
integrity sha512-GZwWfZ7DoZpNMsUCRaKJKAPgBcfLx8/IJM9NOlFJVF87PPRHHjLhhblWOOOxyLPgC3NJkT+fFHzxYlQlGbCbhw==
|
||||
dependencies:
|
||||
tiny-lru "^11.2.5"
|
||||
ts-deepmerge "^7.0.0"
|
||||
|
||||
y18n@^5.0.5:
|
||||
version "5.0.8"
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
|
||||
|
||||
@ -1 +1 @@
|
||||
2.0.4
|
||||
2.1.0
|
||||
0
python/log.txt
Normal file
0
python/log.txt
Normal file
@ -1,2 +1,3 @@
|
||||
pytest==6.2.4
|
||||
freezegun==1.1.0
|
||||
freezegun==1.1.0
|
||||
requests-mock==1.11.0
|
||||
@ -1,2 +1,2 @@
|
||||
pydantic==1.10.7
|
||||
requests==2.29.0
|
||||
pydantic==2.10.0
|
||||
requests==2.32.0
|
||||
@ -32,11 +32,10 @@ setup(
|
||||
"Intended Audience :: Developers", # Define that your audience are developers
|
||||
"License :: OSI Approved :: MIT License", # Again, pick a license
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
],
|
||||
dependency_links=[],
|
||||
python_requires=">=3.7",
|
||||
python_requires=">=3.8",
|
||||
)
|
||||
|
||||
@ -18,11 +18,11 @@ class QueryRunTimeoutError(BaseError):
|
||||
Base class for all QueryRunTimeoutError errors.
|
||||
"""
|
||||
|
||||
def __init__(self, timeoutMinutes: Union[int, float, None] = None):
|
||||
if timeoutMinutes is None:
|
||||
def __init__(self, timeoutSeconds: Union[int, float, None] = None):
|
||||
if timeoutSeconds is None:
|
||||
self.message = f"QUERY_RUN_TIMEOUT_ERROR: your query has timed out."
|
||||
else:
|
||||
self.message = f"QUERY_RUN_TIMEOUT_ERROR: your query has timed out after {timeoutMinutes} minutes."
|
||||
self.message = f"QUERY_RUN_TIMEOUT_ERROR: your query has timed out after {timeoutSeconds} seconds."
|
||||
super().__init__(self.message)
|
||||
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ from .rpc import RPC
|
||||
|
||||
API_BASE_URL = "https://api-v2.flipsidecrypto.xyz"
|
||||
|
||||
SDK_VERSION = "2.0.4"
|
||||
SDK_VERSION = "2.1.0"
|
||||
SDK_PACKAGE = "python"
|
||||
|
||||
DEFAULT_DATA_SOURCE = "snowflake-default"
|
||||
|
||||
@ -39,21 +39,22 @@ class CompassQueryIntegration(object):
|
||||
def run(self, query: Query) -> QueryResultSet:
|
||||
query = self._set_query_defaults(query)
|
||||
|
||||
# Use the default values from Query class when None
|
||||
ttl_hours = int((query.ttl_minutes or 0) / 60)
|
||||
max_age_minutes = query.max_age_minutes or 5 # default from Query class
|
||||
retry_interval_seconds = query.retry_interval_seconds or 1 # default from Query class
|
||||
|
||||
create_query_run_params = CreateQueryRunRpcParams(
|
||||
resultTTLHours=int(query.ttl_minutes / 60)
|
||||
if query.ttl_minutes
|
||||
else DEFAULTS.ttl_minutes,
|
||||
sql=query.sql,
|
||||
maxAgeMinutes=query.max_age_minutes
|
||||
if query.max_age_minutes
|
||||
else DEFAULTS.max_age_minutes,
|
||||
resultTTLHours=ttl_hours,
|
||||
sql=query.sql or "",
|
||||
maxAgeMinutes=max_age_minutes,
|
||||
tags=Tags(
|
||||
sdk_language="python",
|
||||
sdk_package=query.sdk_package,
|
||||
sdk_version=query.sdk_version,
|
||||
),
|
||||
dataSource=query.data_source if query.data_source else "snowflake-default",
|
||||
dataProvider=query.data_provider if query.data_provider else "flipside",
|
||||
dataSource=query.data_source or "snowflake-default",
|
||||
dataProvider=query.data_provider or "flipside",
|
||||
)
|
||||
created_query = self.rpc.create_query(create_query_run_params)
|
||||
if created_query.error:
|
||||
@ -67,18 +68,16 @@ class CompassQueryIntegration(object):
|
||||
|
||||
query_run = self._get_query_run_loop(
|
||||
created_query.result.queryRun.id,
|
||||
page_number=query.page_number,
|
||||
page_size=query.page_size,
|
||||
timeout_minutes=query.timeout_minutes if query.timeout_minutes else 20,
|
||||
retry_interval_seconds=query.retry_interval_seconds
|
||||
if query.retry_interval_seconds
|
||||
else 1,
|
||||
page_number=query.page_number or 1,
|
||||
page_size=query.page_size or 100000,
|
||||
timeout_minutes=query.timeout_minutes or 20,
|
||||
retry_interval_seconds=retry_interval_seconds,
|
||||
)
|
||||
|
||||
query_result = self._get_query_results(
|
||||
query_run.id,
|
||||
page_number=query.page_number if query.page_number else 1,
|
||||
page_size=query.page_size if query.page_size else 100000,
|
||||
page_number=query.page_number or 1,
|
||||
page_size=query.page_size or 100000,
|
||||
)
|
||||
|
||||
return QueryResultSetBuilder(
|
||||
@ -159,19 +158,19 @@ class CompassQueryIntegration(object):
|
||||
filters: Optional[Union[List[Filter], None]] = [],
|
||||
sort_by: Optional[Union[List[SortBy], None]] = [],
|
||||
) -> GetQueryRunResultsRpcResult:
|
||||
query_results_resp = self.rpc.get_query_result(
|
||||
GetQueryRunResultsRpcParams(
|
||||
queryRunId=query_run_id,
|
||||
format=ResultFormat.csv,
|
||||
page=Page(
|
||||
number=page_number,
|
||||
size=page_size,
|
||||
),
|
||||
filters=filters,
|
||||
sortBy=sort_by,
|
||||
)
|
||||
params = GetQueryRunResultsRpcParams(
|
||||
queryRunId=query_run_id,
|
||||
format=ResultFormat.csv,
|
||||
page=Page(
|
||||
number=page_number,
|
||||
size=page_size,
|
||||
),
|
||||
filters=filters,
|
||||
sortBy=sort_by,
|
||||
)
|
||||
|
||||
query_results_resp = self.rpc.get_query_result(params)
|
||||
|
||||
if query_results_resp.error:
|
||||
raise get_exception_by_error_code(
|
||||
error_code=query_results_resp.error.code
|
||||
|
||||
@ -21,6 +21,7 @@ class QueryResultSetBuilder(object):
|
||||
self.errorData = query_run.errorData
|
||||
self.dataSourceQueryId = query_run.dataSourceQueryId
|
||||
self.dataSourceSessionId = query_run.dataSourceSessionId
|
||||
self.page = query_result.page
|
||||
self.path = query_run.path
|
||||
|
||||
self.run_stats = self.compute_run_stats(query_run)
|
||||
@ -35,6 +36,7 @@ class QueryResultSetBuilder(object):
|
||||
rows=self.rows,
|
||||
run_stats=self.run_stats,
|
||||
records=self.records,
|
||||
page=self.page,
|
||||
error=None,
|
||||
)
|
||||
|
||||
|
||||
@ -23,4 +23,4 @@ class CancelQueryRunRpcResult(BaseModel):
|
||||
|
||||
|
||||
class CancelQueryRunRpcResponse(RpcResponse):
|
||||
result: Union[CancelQueryRunRpcResult, None]
|
||||
result: Union[CancelQueryRunRpcResult, None] = None
|
||||
|
||||
@ -11,23 +11,23 @@ class QueryRun(BaseModel):
|
||||
sqlStatementId: str
|
||||
state: str
|
||||
path: str
|
||||
fileCount: Optional[int]
|
||||
lastFileNumber: Optional[int]
|
||||
fileNames: Optional[str]
|
||||
errorName: Optional[str]
|
||||
errorMessage: Optional[str]
|
||||
errorData: Optional[Any]
|
||||
dataSourceQueryId: Optional[str]
|
||||
dataSourceSessionId: Optional[str]
|
||||
startedAt: Optional[str]
|
||||
queryRunningEndedAt: Optional[str]
|
||||
queryStreamingEndedAt: Optional[str]
|
||||
endedAt: Optional[str]
|
||||
rowCount: Optional[int]
|
||||
totalSize: Optional[int]
|
||||
fileCount: Optional[int] = None
|
||||
lastFileNumber: Optional[int] = None
|
||||
fileNames: Optional[str] = None
|
||||
errorName: Optional[str] = None
|
||||
errorMessage: Optional[str] = None
|
||||
errorData: Optional[Any] = None
|
||||
dataSourceQueryId: Optional[str] = None
|
||||
dataSourceSessionId: Optional[str] = None
|
||||
startedAt: Optional[str] = None
|
||||
queryRunningEndedAt: Optional[str] = None
|
||||
queryStreamingEndedAt: Optional[str] = None
|
||||
endedAt: Optional[str] = None
|
||||
rowCount: Optional[int] = None
|
||||
totalSize: Optional[int] = None
|
||||
tags: Tags
|
||||
dataSourceId: str
|
||||
userId: str
|
||||
createdAt: str
|
||||
updatedAt: datetime
|
||||
archivedAt: Optional[datetime]
|
||||
archivedAt: Optional[datetime] = None
|
||||
|
||||
@ -6,4 +6,4 @@ from pydantic import BaseModel
|
||||
class RpcError(BaseModel):
|
||||
code: int
|
||||
message: str
|
||||
data: Optional[Any]
|
||||
data: Optional[Any] = None
|
||||
|
||||
@ -8,5 +8,5 @@ from .rpc_error import RpcError
|
||||
class RpcResponse(BaseModel):
|
||||
jsonrpc: str
|
||||
id: int
|
||||
result: Union[Optional[Dict[str, Any]], None]
|
||||
error: Optional[RpcError]
|
||||
result: Union[Optional[Dict[str, Any]], None] = None
|
||||
error: Optional[RpcError] = None
|
||||
|
||||
@ -10,7 +10,7 @@ class SqlStatement(BaseModel):
|
||||
id: str
|
||||
statementHash: str
|
||||
sql: str
|
||||
columnMetadata: Optional[ColumnMetadata]
|
||||
columnMetadata: Optional[ColumnMetadata] = None
|
||||
userId: str
|
||||
tags: Tags
|
||||
createdAt: str
|
||||
|
||||
@ -5,6 +5,6 @@ from pydantic import BaseModel
|
||||
|
||||
|
||||
class Tags(BaseModel):
|
||||
sdk_package: Optional[str]
|
||||
sdk_version: Optional[str]
|
||||
sdk_language: Optional[str]
|
||||
sdk_package: Optional[str] = None
|
||||
sdk_version: Optional[str] = None
|
||||
sdk_language: Optional[str] = None
|
||||
|
||||
@ -33,4 +33,4 @@ class CreateQueryRunRpcResult(BaseModel):
|
||||
|
||||
|
||||
class CreateQueryRunRpcResponse(RpcResponse):
|
||||
result: Union[CreateQueryRunRpcResult, None]
|
||||
result: Union[CreateQueryRunRpcResult, None] = None
|
||||
|
||||
@ -21,8 +21,8 @@ class GetQueryRunRpcRequest(RpcRequest):
|
||||
# Response
|
||||
class GetQueryRunRpcResult(BaseModel):
|
||||
queryRun: QueryRun
|
||||
redirectedToQueryRun: Optional[QueryRun]
|
||||
redirectedToQueryRun: Optional[QueryRun] = None
|
||||
|
||||
|
||||
class GetQueryRunRpcResponse(RpcResponse):
|
||||
result: Union[GetQueryRunRpcResult, None]
|
||||
result: Union[GetQueryRunRpcResult, None] = None
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from pydantic import BaseModel
|
||||
from pydantic import ConfigDict, BaseModel
|
||||
|
||||
from .core.page import Page
|
||||
from .core.page_stats import PageStats
|
||||
@ -13,22 +13,29 @@ from .core.rpc_response import RpcResponse
|
||||
# Request
|
||||
class Filter(BaseModel):
|
||||
column: str
|
||||
eq: Optional[str] = None
|
||||
neq: Optional[str] = None
|
||||
gt: Optional[str] = None
|
||||
gte: Optional[str] = None
|
||||
lt: Optional[str] = None
|
||||
lte: Optional[str] = None
|
||||
like: Optional[str] = None
|
||||
in_: Optional[List[str]] = None
|
||||
notIn: Optional[List[str]] = None
|
||||
|
||||
class Config:
|
||||
fields = {"in_": "in"}
|
||||
eq: Optional[Any] = None
|
||||
neq: Optional[Any] = None
|
||||
gt: Optional[Any] = None
|
||||
gte: Optional[Any] = None
|
||||
lt: Optional[Any] = None
|
||||
lte: Optional[Any] = None
|
||||
like: Optional[Any] = None
|
||||
in_: Optional[List[Any]] = None
|
||||
notIn: Optional[List[Any]] = None
|
||||
# TODO[pydantic]: The following keys were removed: `fields`.
|
||||
# Check https://docs.pydantic.dev/dev-v2/migration/#changes-to-config for more information.
|
||||
model_config = ConfigDict(
|
||||
alias_generator=None,
|
||||
populate_by_name=True,
|
||||
json_schema_extra={"fields": {"in_": "in"}}
|
||||
)
|
||||
|
||||
def dict(self, *args, **kwargs) -> dict:
|
||||
kwargs.setdefault("exclude_none", True) # Exclude keys with None values
|
||||
return super().dict(*args, **kwargs)
|
||||
result = super().dict(*args, **kwargs)
|
||||
if "in_" in result:
|
||||
result["in"] = result.pop("in_") # convert 'in_' back to 'in'
|
||||
return result
|
||||
|
||||
|
||||
class SortBy(BaseModel):
|
||||
@ -59,15 +66,15 @@ class GetQueryRunResultsRpcRequest(RpcRequest):
|
||||
|
||||
# Response
|
||||
class GetQueryRunResultsRpcResult(BaseModel):
|
||||
columnNames: Union[Optional[List[str]], None]
|
||||
columnTypes: Union[Optional[List[str]], None]
|
||||
rows: Union[List[Any], None]
|
||||
page: Union[PageStats, None]
|
||||
sql: Union[str, None]
|
||||
format: Union[ResultFormat, None]
|
||||
columnNames: Union[Optional[List[str]], None] = None
|
||||
columnTypes: Union[Optional[List[str]], None] = None
|
||||
rows: Union[List[Any], None] = None
|
||||
page: Union[PageStats, None] = None
|
||||
sql: Union[str, None] = None
|
||||
format: Union[ResultFormat, None] = None
|
||||
originalQueryRun: QueryRun
|
||||
redirectedToQueryRun: Union[QueryRun, None]
|
||||
redirectedToQueryRun: Union[QueryRun, None] = None
|
||||
|
||||
|
||||
class GetQueryRunResultsRpcResponse(RpcResponse):
|
||||
result: Union[GetQueryRunResultsRpcResult, None]
|
||||
result: Union[GetQueryRunResultsRpcResult, None] = None
|
||||
|
||||
@ -23,4 +23,4 @@ class GetSqlStatemetnResult(BaseModel):
|
||||
|
||||
|
||||
class GetSqlStatementResponse(RpcResponse):
|
||||
result: Union[GetSqlStatemetnResult, None]
|
||||
result: Union[GetSqlStatemetnResult, None] = None
|
||||
|
||||
@ -4,7 +4,7 @@ from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class Query(BaseModel):
|
||||
sql: str = Field(None, description="SQL query to execute")
|
||||
sql: Optional[str] = Field(None, description="SQL query to execute")
|
||||
ttl_minutes: Optional[int] = Field(
|
||||
None, description="The number of minutes to cache the query results"
|
||||
)
|
||||
@ -21,8 +21,8 @@ class Query(BaseModel):
|
||||
None,
|
||||
description="An override on the cache. A value of true will Re-Execute the query.",
|
||||
)
|
||||
page_size: int = Field(None, description="The number of results to return per page")
|
||||
page_number: int = Field(None, description="The page number to return")
|
||||
page_size: Optional[int] = Field(None, description="The number of results to return per page")
|
||||
page_number: Optional[int] = Field(None, description="The page number to return")
|
||||
sdk_package: Optional[str] = Field(
|
||||
None, description="The SDK package used for the query"
|
||||
)
|
||||
|
||||
@ -1,20 +1,21 @@
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class QueryDefaults(BaseModel):
|
||||
ttl_minutes: int = Field(
|
||||
ttl_minutes: Optional[int] = Field(
|
||||
None, description="The number of minutes to cache the query results"
|
||||
)
|
||||
max_age_minutes: int = Field(
|
||||
max_age_minutes: Optional[int] = Field(
|
||||
None,
|
||||
description="The max age of query results to accept before deciding to run a query again",
|
||||
)
|
||||
cached: bool = Field(False, description="Whether or not to cache the query results")
|
||||
timeout_minutes: int = Field(
|
||||
timeout_minutes: Optional[int] = Field(
|
||||
None, description="The number of minutes to timeout the query"
|
||||
)
|
||||
retry_interval_seconds: float = Field(
|
||||
retry_interval_seconds: Optional[float] = Field(
|
||||
None, description="The number of seconds to wait before retrying the query"
|
||||
)
|
||||
page_size: int = Field(None, description="The number of results to return per page")
|
||||
page_number: int = Field(None, description="The page number to return")
|
||||
page_size: Optional[int] = Field(None, description="The number of results to return per page")
|
||||
page_number: Optional[int] = Field(None, description="The page number to return")
|
||||
|
||||
@ -2,6 +2,7 @@ from typing import Any, List, Union
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from .compass.core.page_stats import PageStats
|
||||
from .query_run_stats import QueryRunStats
|
||||
|
||||
|
||||
@ -9,7 +10,7 @@ class QueryResultSet(BaseModel):
|
||||
query_id: Union[str, None] = Field(None, description="The server id of the query")
|
||||
|
||||
status: str = Field(
|
||||
False, description="The status of the query (`PENDING`, `FINISHED`, `ERROR`)"
|
||||
"PENDING", description="The status of the query (`PENDING`, `FINISHED`, `ERROR`)"
|
||||
)
|
||||
columns: Union[List[str], None] = Field(
|
||||
None, description="The names of the columns in the result set"
|
||||
@ -25,4 +26,7 @@ class QueryResultSet(BaseModel):
|
||||
records: Union[List[Any], None] = Field(
|
||||
None, description="The results of the query transformed as an array of objects"
|
||||
)
|
||||
error: Any
|
||||
page: Union[PageStats, None] = Field(
|
||||
None, description="Summary of page stats for this query result set"
|
||||
)
|
||||
error: Any = None
|
||||
|
||||
@ -1,40 +1,41 @@
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class QueryRunStats(BaseModel):
|
||||
started_at: datetime = Field(None, description="The start time of the query run.")
|
||||
ended_at: datetime = Field(None, description="The end time of the query run.")
|
||||
query_exec_started_at: datetime = Field(
|
||||
started_at: Optional[datetime] = Field(None, description="The start time of the query run.")
|
||||
ended_at: Optional[datetime] = Field(None, description="The end time of the query run.")
|
||||
query_exec_started_at: Optional[datetime] = Field(
|
||||
None, description="The start time of query execution."
|
||||
)
|
||||
query_exec_ended_at: datetime = Field(
|
||||
query_exec_ended_at: Optional[datetime] = Field(
|
||||
None, description="The end time of query execution."
|
||||
)
|
||||
streaming_started_at: datetime = Field(
|
||||
streaming_started_at: Optional[datetime] = Field(
|
||||
None, description="The start time of streaming query results."
|
||||
)
|
||||
streaming_ended_at: datetime = Field(
|
||||
streaming_ended_at: Optional[datetime] = Field(
|
||||
None, description="The end time of streaming query results."
|
||||
)
|
||||
elapsed_seconds: int = Field(
|
||||
elapsed_seconds: Optional[int] = Field(
|
||||
None,
|
||||
description="The number of seconds elapsed between the start and end times.",
|
||||
)
|
||||
queued_seconds: int = Field(
|
||||
queued_seconds: Optional[int] = Field(
|
||||
None,
|
||||
description="The number of seconds elapsed between when the query was created and when execution on the data source began.",
|
||||
)
|
||||
streaming_seconds: int = Field(
|
||||
streaming_seconds: Optional[int] = Field(
|
||||
None,
|
||||
description="The number of seconds elapsed between when the query execution completed and results were fully streamed to Flipside's servers.",
|
||||
)
|
||||
query_exec_seconds: int = Field(
|
||||
query_exec_seconds: Optional[int] = Field(
|
||||
None,
|
||||
description="The number of seconds elapsed between when the query execution started and when it completed on the data source.",
|
||||
)
|
||||
record_count: int = Field(
|
||||
record_count: Optional[int] = Field(
|
||||
None, description="The number of records returned by the query."
|
||||
)
|
||||
bytes: int = Field(None, description="The number of bytes returned by the query.")
|
||||
bytes: Optional[int] = Field(None, description="The number of bytes returned by the query.")
|
||||
|
||||
@ -6,4 +6,4 @@ from pydantic import BaseModel
|
||||
class SleepConfig(BaseModel):
|
||||
attempts: int
|
||||
timeout_minutes: Union[int, float]
|
||||
interval_seconds: Optional[float]
|
||||
interval_seconds: Optional[float] = None
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import json
|
||||
import pytest
|
||||
import requests_mock
|
||||
|
||||
from ....errors import (
|
||||
ApiError,
|
||||
@ -20,6 +22,12 @@ from ...utils.mock_data.get_sql_statement import get_sql_statement_response
|
||||
SDK_VERSION = "1.0.2"
|
||||
SDK_PACKAGE = "python"
|
||||
|
||||
# Add the fixture decorator
|
||||
@pytest.fixture(autouse=True)
|
||||
def requests_mock_fixture():
|
||||
with requests_mock.Mocker() as m:
|
||||
yield m
|
||||
|
||||
|
||||
def get_rpc():
|
||||
return RPC("https://test.com", "api_key")
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import json
|
||||
import pytest
|
||||
import requests_mock
|
||||
|
||||
from ..errors.server_error import ServerError
|
||||
from ..models import Query, QueryStatus
|
||||
@ -14,6 +16,11 @@ from .utils.mock_data.create_query_run import create_query_run_response
|
||||
from .utils.mock_data.get_query_results import get_query_results_response
|
||||
from .utils.mock_data.get_query_run import get_query_run_response
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def requests_mock_fixture():
|
||||
with requests_mock.Mocker() as m:
|
||||
yield m
|
||||
|
||||
"""
|
||||
Test Defaults
|
||||
"""
|
||||
|
||||
0
r/shroomDK/.Rhistory
Normal file
0
r/shroomDK/.Rhistory
Normal file
@ -1,12 +1,11 @@
|
||||
Package: shroomDK
|
||||
Type: Package
|
||||
Title: Accessing the Flipside Crypto ShroomDK REST API
|
||||
Version: 0.1.1
|
||||
Title: Accessing the Flipside Crypto ShroomDK API
|
||||
Version: 0.3.0
|
||||
Author: Carlos Mercado
|
||||
Maintainer: Carlos Mercado <carlos.mercado@flipsidecrypto.com>
|
||||
Description: Programmatic access to Flipside Crypto data via the REST API: <https://sdk.flipsidecrypto.xyz/shroomdk>. As simple as auto_paginate_query() but with core functions as needed for troubleshooting.
|
||||
Description: Programmatic access to Flipside Crypto data via the Compass RPC API: <https://api-docs.flipsidecrypto.xyz/>. As simple as auto_paginate_query() but with core functions as needed for troubleshooting. Note, 0.1.1 support deprecated 2023-05-31.
|
||||
Imports: jsonlite, httr
|
||||
License: MIT + file LICENSE
|
||||
Encoding: UTF-8
|
||||
LazyData: false
|
||||
RoxygenNote: 7.2.1
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
# Generated by roxygen2: do not edit by hand
|
||||
|
||||
export(auto_paginate_query)
|
||||
export(cancel_query)
|
||||
export(clean_query)
|
||||
export(create_query_token)
|
||||
export(get_query_from_token)
|
||||
export(get_query_status)
|
||||
import(httr)
|
||||
import(jsonlite)
|
||||
|
||||
@ -1,57 +1,122 @@
|
||||
library(jsonlite)
|
||||
library(httr)
|
||||
|
||||
#' Auto Paginate Queries
|
||||
#'
|
||||
#' @description Grabs up to maxrows in a query by going through each page 100k rows at a time.
|
||||
#' @description Intelligently grab up to 1 Gigabyte of data from a SQL query including automatic pagination and cleaning.
|
||||
#'
|
||||
#' @param query The SQL query to pass to ShroomDK
|
||||
#' @param api_key ShroomDK API key.
|
||||
#' @param maxrows Max rows allowed in ShroomDK, 1M at time of writing.
|
||||
#'
|
||||
#' @return data frame of up to 1M rows, see ?clean_query for more details on column classes.
|
||||
#' @param page_size Default 25,000. May return error if `page_size` is too large (if page exceeds 30MB or entire query >1GB). Ignored if results fit on 1 page of < 15 Mb of data.
|
||||
#' @param page_count How many pages, of page_size rows each, to read. Default NULL calculates the ceiling (# rows in results / page_size). Ignored if results fit on 1 page of < 15 Mb of data.
|
||||
#' @param data_source Where data is sourced, including specific computation warehouse. Default `"snowflake-default"`. Non default data sources may require registration of api_key to allowlist.
|
||||
#' @param data_provider Who provides data, Default `"flipside"`. Non default data providers may require registration of api_key to allowlist.
|
||||
#' @param api_url default to https://api-v2.flipsidecrypto.xyz/json-rpc but upgradeable for user.
|
||||
#' @return data frame of up to `page_size * page_count` rows, see ?clean_query for more details on column classes.
|
||||
#' @import jsonlite httr
|
||||
#' @export
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' pull_data <- auto_paginate_query("
|
||||
#' SELECT * FROM ETHEREUM.CORE.FACT_TRANSACTIONS LIMIT 10000",
|
||||
#' api_key = readLines("api_key.txt"))
|
||||
#' SELECT * FROM ETHEREUM.CORE.FACT_TRANSACTIONS LIMIT 10001",
|
||||
#' api_key = readLines("api_key.txt"),
|
||||
#' page_size = 9000, # ends up ignored because results fit on 1 page.
|
||||
#' page_count = NULL)
|
||||
#' }
|
||||
auto_paginate_query <- function(query, api_key, maxrows = 1000000){
|
||||
auto_paginate_query <- function(query, api_key,
|
||||
page_size = 25000,
|
||||
page_count = NULL,
|
||||
data_source = "snowflake-default",
|
||||
data_provider = "flipside",
|
||||
api_url = "https://api-v2.flipsidecrypto.xyz/json-rpc"){
|
||||
|
||||
qtoken <- shroomDK::create_query_token(query = query, api_key = api_key)
|
||||
res <- shroomDK::get_query_from_token(qtoken$token, api_key = api_key)
|
||||
df <- shroomDK::clean_query(res)
|
||||
qtoken <- create_query_token(query = query,
|
||||
api_key = api_key,
|
||||
ttl = 1,
|
||||
mam = 10,
|
||||
data_source = data_source,
|
||||
data_provider = data_provider,
|
||||
api_url = api_url)
|
||||
|
||||
# Handle Pagination via ShroomDK
|
||||
# up to 1M rows max
|
||||
# get 100,000 rows at a time
|
||||
# stop when the most recent page < 100,000 items.
|
||||
# otherwise stop at 1M total rows.
|
||||
# NOTE: in the future, if we allow > 1M rows, will need to update this.
|
||||
query_run_id <- qtoken$result$queryRequest$queryRunId
|
||||
status_check_done <- FALSE
|
||||
warn_flag <- FALSE
|
||||
|
||||
maxpages = ceiling(maxrows/100000)
|
||||
while (!status_check_done) {
|
||||
query_status <- get_query_status(query_run_id = query_run_id, api_key = api_key, api_url = api_url)
|
||||
query_state <- query_status$result$queryRun$state
|
||||
|
||||
if(nrow(df) == 100000){
|
||||
warning("Checking for additional pages of data...")
|
||||
for(i in 2:maxpages){
|
||||
temp_page <- clean_query(
|
||||
shroomDK::get_query_from_token(qtoken$token,
|
||||
api_key = api_key,
|
||||
page_number = i)
|
||||
)
|
||||
failed_to_get_a_state = 0
|
||||
|
||||
df <- rbind.data.frame(df, temp_page)
|
||||
if(failed_to_get_a_state > 2){
|
||||
warning("Query has failed state more than twice, consider cancel_query(), exiting now")
|
||||
stop("Exited due to 3+ Failed States")
|
||||
}
|
||||
|
||||
if(nrow(temp_page) < 100000 | i == maxpages){
|
||||
# done
|
||||
return(df)
|
||||
if(length(query_state) == 0){
|
||||
warning("Query failed to return a state, trying again")
|
||||
Sys.sleep(5)
|
||||
failed_to_get_a_state = failed_to_get_a_state + 1
|
||||
} else {
|
||||
if(query_state == "QUERY_STATE_SUCCESS"){
|
||||
status_check_done <- TRUE
|
||||
result_num_rows <- query_status$result$queryRun$rowCount
|
||||
result_file_size <- as.numeric(query_status$result$queryRun$totalSize)
|
||||
next()
|
||||
} else if(query_state == "QUERY_STATE_FAILED"){
|
||||
status_check_done <- TRUE
|
||||
stop(query_status$result$queryRun$errorMessage)
|
||||
} else if(query_state == "QUERY_STATE_CANCELED"){
|
||||
status_check_done <- TRUE
|
||||
stop("This query was canceled, typically by cancel_query()")
|
||||
} else if(query_state != "QUERY_STATE_SUCCESS"){
|
||||
warning(
|
||||
paste0("Query in process, checking again in 10 seconds.",
|
||||
"To cancel use: cancel_query() with your ID: \n", query_run_id)
|
||||
)
|
||||
Sys.sleep(10)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
# continue
|
||||
if(is.null(page_count)){
|
||||
page_count <- ceiling(result_num_rows / page_size)
|
||||
}
|
||||
|
||||
# if the result is large (estimated at 15+ Mb) paginate
|
||||
# otherwise grab it all at once.
|
||||
if(result_file_size > 15000000){
|
||||
res <- lapply(1:page_count, function(i){
|
||||
temp_page <- get_query_from_token(qtoken$result$queryRequest$queryRunId,
|
||||
api_key = api_key,
|
||||
page_number = i,
|
||||
page_size = page_size,
|
||||
result_format = "csv",
|
||||
api_url = api_url)
|
||||
|
||||
if(length(temp_page$result$rows) < 1){
|
||||
df <- data.frame()
|
||||
} else {
|
||||
df <- clean_query(temp_page)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return(df)
|
||||
}
|
||||
})
|
||||
|
||||
# drop empty pages if they accidentally appear
|
||||
res <- res[unlist(lapply(res, nrow)) > 0]
|
||||
df <- do.call(rbind.data.frame, res)
|
||||
|
||||
} else {
|
||||
temp_page <- get_query_from_token(qtoken$result$queryRequest$queryRunId,
|
||||
api_key = api_key,
|
||||
page_number = 1,
|
||||
page_size = result_num_rows,
|
||||
result_format = "csv",
|
||||
api_url = api_url)
|
||||
|
||||
|
||||
}
|
||||
df <- clean_query(temp_page)
|
||||
}
|
||||
|
||||
return(df)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user