Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(carto): Allow configuring MAX_GET_LENGTH #9159

Merged
merged 4 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 20 additions & 16 deletions docs/api-reference/carto/data-sources.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import {vectorTableSource} from '@deck.gl/carto';
const data = vectorTableSource({
accessToken: 'XXX',
connectionName: 'carto_dw',
tableName: 'carto-demo-data.demo_tables.chicago_crime_sample',
})
tableName: 'carto-demo-data.demo_tables.chicago_crime_sample'
});
```

### Promise API
Expand Down Expand Up @@ -51,7 +51,7 @@ type SourceOptions = {
apiBaseUrl?: string;
clientId?: string;
headers?: Record<string, string>;
mapsUrl?: string;
maxLengthURL?: number;
};
```

Expand All @@ -64,7 +64,7 @@ type VectorTableSourceOptions = {
columns?: string[];
spatialDataColumn?: string;
tableName: string;
}
};
```

#### vectorQuerySource
Expand All @@ -74,15 +74,15 @@ type VectorQuerySourceOptions = {
spatialDataColumn?: string;
sqlQuery: string;
queryParameters: QueryParameters;
}
};
```

#### vectorTilesetSource

```ts
type VectorTilesetSourceOptions = {
tableName: string;
}
};
```

#### h3TableSource
Expand All @@ -94,7 +94,7 @@ type H3TableSourceOptions = {
columns?: string[];
spatialDataColumn?: string;
tableName: string;
}
};
```

#### h3QuerySource
Expand All @@ -106,15 +106,15 @@ type H3QuerySourceOptions = {
spatialDataColumn?: string;
sqlQuery: string;
queryParameters: QueryParameters;
}
};
```

#### h3TilesetSource

```ts
type H3TilesetSourceOptions = {
tableName: string;
}
};
```

#### quadbinTableSource
Expand All @@ -126,7 +126,7 @@ type QuadbinTableSourceOptions = {
columns?: string[];
spatialDataColumn?: string;
tableName: string;
}
};
```

#### quadbinQuerySource
Expand All @@ -138,23 +138,23 @@ type QuadbinQuerySourceOptions = {
spatialDataColumn?: string;
sqlQuery: string;
queryParameters: QueryParameters;
}
};
```

#### quadbinTilesetSource

```ts
type QuadbinTilesetSourceOptions = {
tableName: string;
}
};
```

#### rasterTilesetSource (Experimental)

```ts
type RasterTilesetSourceOptions = {
tableName: string;
}
};
```

Boundary sources are experimental sources where both the tileset and the properties props need a specific schema to work. [Read more about Boundaries in the CARTO documentation](https://docs.carto.com/carto-for-developers/guides/use-boundaries-in-your-application).
Expand All @@ -166,7 +166,7 @@ type BoundaryTableSourceOptions = {
tilesetTableName: string;
columns?: string[];
propertiesTableName: string;
}
};
```

#### boundaryQuerySource (Experimental)
Expand All @@ -176,14 +176,15 @@ type BoundaryQuerySourceOptions = {
tilesetTableName: string;
propertiesSqlQuery: string;
queryParameters?: QueryParameters;
}
};
```

### QueryParameters

QueryParameters are used to parametrize SQL queries. The format depends on the source's provider, some examples:

[PostgreSQL and Redshift](https://node-postgres.com/features/queries):

```ts
vectorQuerySource({
...,
Expand All @@ -193,6 +194,7 @@ vectorQuerySource({
```

[BigQuery positional](https://cloud.google.com/bigquery/docs/parameterized-queries#node.js):

```ts
vectorQuerySource({
...,
Expand All @@ -201,8 +203,8 @@ vectorQuerySource({
})
```


[BigQuery named parameters](https://cloud.google.com/bigquery/docs/parameterized-queries#node.js):

```ts
vectorQuerySource({
...,
Expand All @@ -212,6 +214,7 @@ vectorQuerySource({
```

[Snowflake positional](https://docs.snowflake.com/en/user-guide/nodejs-driver-use.html#binding-statement-parameters) :

```ts
vectorQuerySource({
...,
Expand All @@ -230,6 +233,7 @@ vectorQuerySource({
```

[Databricks ODBC](https://github.com/markdirish/node-odbc#bindparameters-callback)

```ts
vectorQuerySource({
...
Expand Down
4 changes: 3 additions & 1 deletion modules/carto/src/api/common.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export const DEFAULT_API_BASE_URL = 'https://gcp-us-east1.api.carto.com';
export const DEFAULT_CLIENT = 'deck-gl-carto';
export const V3_MINOR_VERSION = '3.4';
export const MAX_GET_LENGTH = 8192;

// Fastly default limit is 8192; leave some padding.
export const DEFAULT_MAX_LENGTH_URL = 7000;
56 changes: 38 additions & 18 deletions modules/carto/src/api/fetch-map.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable camelcase */
import {CartoAPIError} from './carto-api-error';
import {DEFAULT_API_BASE_URL, DEFAULT_CLIENT} from './common';
import {DEFAULT_API_BASE_URL, DEFAULT_CLIENT, DEFAULT_MAX_LENGTH_URL} from './common';
import {buildPublicMapUrl, buildStatsUrl} from './endpoints';
import {
GeojsonResult,
Expand Down Expand Up @@ -36,13 +36,14 @@ type Dataset = {
};

/* global clearInterval, setInterval, URL */
/* eslint-disable complexity, max-statements */
/* eslint-disable complexity, max-statements, max-params */
async function _fetchMapDataset(
dataset: Dataset,
accessToken: string,
apiBaseUrl: string,
clientId?: string,
headers?: Record<string, string>
headers?: Record<string, string>,
maxLengthURL = DEFAULT_MAX_LENGTH_URL
) {
const {
aggregationExp,
Expand All @@ -64,7 +65,8 @@ async function _fetchMapDataset(
clientId,
connectionName,
format,
headers
headers,
maxLengthURL
};

if (type === 'tileset') {
Expand Down Expand Up @@ -114,7 +116,8 @@ async function _fetchTilestats(
attribute: string,
dataset: Dataset,
accessToken: string,
apiBaseUrl: string
apiBaseUrl: string,
maxLengthURL = DEFAULT_MAX_LENGTH_URL
) {
const {connectionName, data, id, source, type, queryParameters} = dataset;
const errorContext: APIErrorContext = {
Expand Down Expand Up @@ -144,7 +147,8 @@ async function _fetchTilestats(
baseUrl,
headers,
parameters,
errorContext
errorContext,
maxLengthURL
});

// Replace tilestats for attribute with value from API
Expand All @@ -158,17 +162,19 @@ async function fillInMapDatasets(
{datasets, token}: {datasets: Dataset[]; token: string},
clientId: string,
apiBaseUrl: string,
headers?: Record<string, string>
headers?: Record<string, string>,
maxLengthURL = DEFAULT_MAX_LENGTH_URL
) {
const promises = datasets.map(dataset =>
_fetchMapDataset(dataset, token, apiBaseUrl, clientId, headers)
_fetchMapDataset(dataset, token, apiBaseUrl, clientId, headers, maxLengthURL)
);
return await Promise.all(promises);
}

async function fillInTileStats(
{datasets, keplerMapConfig, token}: {datasets: Dataset[]; keplerMapConfig: any; token: string},
apiBaseUrl: string
apiBaseUrl: string,
maxLengthURL = DEFAULT_MAX_LENGTH_URL
) {
const attributes: {attribute: string; dataset: any}[] = [];
const {layers} = keplerMapConfig.config.visState;
Expand Down Expand Up @@ -197,7 +203,7 @@ async function fillInTileStats(
}

const promises = filteredAttributes.map(({attribute, dataset}) =>
_fetchTilestats(attribute, dataset, token, apiBaseUrl)
_fetchTilestats(attribute, dataset, token, apiBaseUrl, maxLengthURL)
);
return await Promise.all(promises);
}
Expand Down Expand Up @@ -237,6 +243,13 @@ export type FetchMapOptions = {
* Callback function that will be invoked whenever data in layers is changed. If provided, `autoRefresh` must also be provided.
*/
onNewData?: (map: any) => void;

/**
* Maximum URL character length. Above this limit, requests use POST.
* Used to avoid browser and CDN limits.
* @default {@link DEFAULT_MAX_LENGTH_URL}
*/
maxLengthURL?: number;
};

export type FetchMapResult = ParseMapResult & {
Expand All @@ -255,7 +268,8 @@ export async function fetchMap({
clientId = DEFAULT_CLIENT,
headers = {},
autoRefresh,
onNewData
onNewData,
maxLengthURL = DEFAULT_MAX_LENGTH_URL
}: FetchMapOptions): Promise<FetchMapResult> {
assert(cartoMapId, 'Must define CARTO map id: fetchMap({cartoMapId: "XXXX-XXXX-XXXX"})');
assert(apiBaseUrl, 'Must define apiBaseUrl');
Expand All @@ -275,18 +289,24 @@ export async function fetchMap({

const baseUrl = buildPublicMapUrl({apiBaseUrl, cartoMapId});
const errorContext: APIErrorContext = {requestType: 'Public map', mapId: cartoMapId};
const map = await requestWithParameters({baseUrl, headers, errorContext});
const map = await requestWithParameters({baseUrl, headers, errorContext, maxLengthURL});

// Periodically check if the data has changed. Note that this
// will not update when a map is published.
let stopAutoRefresh: (() => void) | undefined;
if (autoRefresh) {
// eslint-disable-next-line @typescript-eslint/no-misused-promises
const intervalId = setInterval(async () => {
const changed = await fillInMapDatasets(map, clientId, apiBaseUrl, {
...headers,
'If-Modified-Since': new Date().toUTCString()
});
const changed = await fillInMapDatasets(
map,
clientId,
apiBaseUrl,
{
...headers,
'If-Modified-Since': new Date().toUTCString()
},
maxLengthURL
);
if (onNewData && changed.some(v => v === true)) {
onNewData(parseMap(map));
}
Expand Down Expand Up @@ -315,11 +335,11 @@ export async function fetchMap({
fetchBasemapProps({config: map.keplerMapConfig.config, errorContext}),

// Mutates map.datasets so that dataset.data contains data
fillInMapDatasets(map, clientId, apiBaseUrl, headers)
fillInMapDatasets(map, clientId, apiBaseUrl, headers, maxLengthURL)
]);

// Mutates attributes in visualChannels to contain tile stats
await fillInTileStats(map, apiBaseUrl);
await fillInTileStats(map, apiBaseUrl, maxLengthURL);

const out = {...parseMap(map), basemap, ...{stopAutoRefresh}};

Expand Down
4 changes: 3 additions & 1 deletion modules/carto/src/api/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const query = async function (options: QueryOptions): Promise<QueryResult
const {
apiBaseUrl = SOURCE_DEFAULTS.apiBaseUrl,
clientId = SOURCE_DEFAULTS.clientId,
maxLengthURL = SOURCE_DEFAULTS.maxLengthURL,
connectionName,
sqlQuery,
queryParameters
Expand All @@ -35,6 +36,7 @@ export const query = async function (options: QueryOptions): Promise<QueryResult
baseUrl,
parameters,
headers,
errorContext
errorContext,
maxLengthURL
});
};
8 changes: 5 additions & 3 deletions modules/carto/src/api/request-with-parameters.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {VERSION} from '@deck.gl/core';
import {isPureObject} from '../utils';
import {CartoAPIError} from './carto-api-error';
import {MAX_GET_LENGTH, V3_MINOR_VERSION} from './common';
import {DEFAULT_MAX_LENGTH_URL, V3_MINOR_VERSION} from './common';
import type {APIErrorContext} from './types';

/**
Expand All @@ -25,12 +25,14 @@ export async function requestWithParameters<T = any>({
baseUrl,
parameters = {},
headers: customHeaders = {},
errorContext
errorContext,
maxLengthURL = DEFAULT_MAX_LENGTH_URL
}: {
baseUrl: string;
parameters?: Record<string, unknown>;
headers?: Record<string, string>;
errorContext: APIErrorContext;
maxLengthURL?: number;
}): Promise<T> {
parameters = {...DEFAULT_PARAMETERS, ...parameters};
baseUrl = excludeURLParameters(baseUrl, Object.keys(parameters));
Expand All @@ -44,7 +46,7 @@ export async function requestWithParameters<T = any>({

/* global fetch */
const fetchPromise =
url.length > MAX_GET_LENGTH
url.length > maxLengthURL
? fetch(baseUrl, {method: 'POST', body: JSON.stringify(parameters), headers})
: fetch(url, {headers});

Expand Down
Loading
Loading