mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-23 05:46:01 +00:00
Merge branch 'develop'
This commit is contained in:
@@ -297,4 +297,33 @@ describe('Deploy database', () => {
|
|||||||
expect(res.rows[0].val.toString()).toEqual('5');
|
expect(res.rows[0].val.toString()).toEqual('5');
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test.each(engines.enginesPostgre.map(engine => [engine.label, engine]))(
|
||||||
|
'Current timestamp default value - %s',
|
||||||
|
testWrapper(async (conn, driver, engine) => {
|
||||||
|
await testDatabaseDeploy(conn, driver, [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 't1.table.yaml',
|
||||||
|
json: {
|
||||||
|
name: 't1',
|
||||||
|
columns: [
|
||||||
|
{ name: 'id', type: 'int' },
|
||||||
|
{
|
||||||
|
name: 'val',
|
||||||
|
type: 'timestamp',
|
||||||
|
default: 'current_timestamp',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
primaryKey: ['id'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
await driver.query(conn, `insert into t1 (id) values (1)`);
|
||||||
|
const res = await driver.query(conn, ` select val from t1 where id = 1`);
|
||||||
|
expect(res.rows[0].val.toString().substring(0, 2)).toEqual('20');
|
||||||
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -141,6 +141,10 @@ const filterLocal = [
|
|||||||
'-CockroachDB',
|
'-CockroachDB',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const enginesPostgre = engines.filter(x => x.label == 'PostgreSQL');
|
||||||
|
|
||||||
module.exports = process.env.CITEST
|
module.exports = process.env.CITEST
|
||||||
? engines.filter(x => !x.skipOnCI)
|
? engines.filter(x => !x.skipOnCI)
|
||||||
: engines.filter(x => filterLocal.find(y => x.label == y));
|
: engines.filter(x => filterLocal.find(y => x.label == y));
|
||||||
|
|
||||||
|
module.exports.enginesPostgre = enginesPostgre;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "5.0.3",
|
"version": "5.0.4-beta.1",
|
||||||
"name": "dbgate-all",
|
"name": "dbgate-all",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*",
|
"packages/*",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const socket = require('../utility/socket');
|
|||||||
const scheduler = require('./scheduler');
|
const scheduler = require('./scheduler');
|
||||||
const getDiagramExport = require('../utility/getDiagramExport');
|
const getDiagramExport = require('../utility/getDiagramExport');
|
||||||
const apps = require('./apps');
|
const apps = require('./apps');
|
||||||
|
const getMapExport = require('../utility/getMapExport');
|
||||||
|
|
||||||
function serialize(format, data) {
|
function serialize(format, data) {
|
||||||
if (format == 'text') return data;
|
if (format == 'text') return data;
|
||||||
@@ -187,6 +188,12 @@ module.exports = {
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
exportMap_meta: true,
|
||||||
|
async exportMap({ filePath, geoJson }) {
|
||||||
|
await fs.writeFile(filePath, getMapExport(geoJson));
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
exportDiagram_meta: true,
|
exportDiagram_meta: true,
|
||||||
async exportDiagram({ filePath, html, css, themeType, themeClassName }) {
|
async exportDiagram({ filePath, html, css, themeType, themeClassName }) {
|
||||||
await fs.writeFile(filePath, getDiagramExport(html, css, themeType, themeClassName));
|
await fs.writeFile(filePath, getDiagramExport(html, css, themeType, themeClassName));
|
||||||
|
|||||||
77
packages/api/src/utility/getMapExport.js
Normal file
77
packages/api/src/utility/getMapExport.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
const getMapExport = (geoJson) => {
|
||||||
|
return `<html>
|
||||||
|
<meta charset='utf-8'>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.8.0/dist/leaflet.css"
|
||||||
|
integrity="sha512-hoalWLoI8r4UszCkZ5kL8vayOGVae1oxXe/2A4AO6J9+580uKHDO3JdHb7NzwwzK5xr/Fs0W40kiNHxM9vyTtQ=="
|
||||||
|
crossorigin=""/>
|
||||||
|
|
||||||
|
<script src="https://unpkg.com/leaflet@1.8.0/dist/leaflet.js"
|
||||||
|
integrity="sha512-BB3hKbKWOc9Ez/TAwyWxNXeoV9c1v6FIeYiBieIWkpLjauysF18NzgR1MBNBXf8/KABdlkX68nAhlwcDFLGPCQ=="
|
||||||
|
crossorigin=""></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function createMap() {
|
||||||
|
map = leaflet.map('map').setView([50, 15], 13);
|
||||||
|
|
||||||
|
leaflet
|
||||||
|
.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
|
maxZoom: 19,
|
||||||
|
attribution: '<a href="https://dbgate.org" title="Exported from DbGate">DbGate</a> | © OpenStreetMap',
|
||||||
|
})
|
||||||
|
.addTo(map);
|
||||||
|
|
||||||
|
const geoJsonObj = leaflet
|
||||||
|
.geoJSON(${JSON.stringify(geoJson)}, {
|
||||||
|
style: function () {
|
||||||
|
return {
|
||||||
|
weight: 2,
|
||||||
|
fillColor: '#ff7800',
|
||||||
|
color: '#ff7800',
|
||||||
|
opacity: 0.8,
|
||||||
|
fillOpacity: 0.4,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
pointToLayer: (feature, latlng) => {
|
||||||
|
return leaflet.circleMarker(latlng, {
|
||||||
|
radius: 7,
|
||||||
|
weight: 2,
|
||||||
|
fillColor: '#ff0000',
|
||||||
|
color: '#ff0000',
|
||||||
|
opacity: 0.9,
|
||||||
|
fillOpacity: 0.9,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onEachFeature: (feature, layer) => {
|
||||||
|
// does this feature have a property named popupContent?
|
||||||
|
if (feature.properties && feature.properties.popupContent) {
|
||||||
|
layer.bindPopup(feature.properties.popupContent);
|
||||||
|
layer.bindTooltip(feature.properties.popupContent);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.addTo(map);
|
||||||
|
map.fitBounds(geoJsonObj.getBounds());
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#map {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body onload='createMap()'>
|
||||||
|
<div id='map'></div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>`;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = getMapExport;
|
||||||
@@ -483,6 +483,22 @@ export abstract class GridDisplay {
|
|||||||
|
|
||||||
processReferences(select: Select, displayedColumnInfo: DisplayedColumnInfo, options) {}
|
processReferences(select: Select, displayedColumnInfo: DisplayedColumnInfo, options) {}
|
||||||
|
|
||||||
|
createColumnExpression(col, source, alias?) {
|
||||||
|
let expr = null;
|
||||||
|
if (this.dialect.createColumnViewExpression) {
|
||||||
|
expr = this.dialect.createColumnViewExpression(col.columnName, col.dataType, source, alias);
|
||||||
|
if (expr) {
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
exprType: 'column',
|
||||||
|
alias: alias || col.columnName,
|
||||||
|
source,
|
||||||
|
...col,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
createSelectBase(name: NamedObjectInfo, columns: ColumnInfo[], options) {
|
createSelectBase(name: NamedObjectInfo, columns: ColumnInfo[], options) {
|
||||||
if (!columns) return null;
|
if (!columns) return null;
|
||||||
const orderColumnName = columns[0].columnName;
|
const orderColumnName = columns[0].columnName;
|
||||||
@@ -492,12 +508,7 @@ export abstract class GridDisplay {
|
|||||||
name: _.pick(name, ['schemaName', 'pureName']),
|
name: _.pick(name, ['schemaName', 'pureName']),
|
||||||
alias: 'basetbl',
|
alias: 'basetbl',
|
||||||
},
|
},
|
||||||
columns: columns.map(col => ({
|
columns: columns.map(col => this.createColumnExpression(col, { alias: 'basetbl' })),
|
||||||
exprType: 'column',
|
|
||||||
alias: col.columnName,
|
|
||||||
source: { alias: 'basetbl' },
|
|
||||||
...col,
|
|
||||||
})),
|
|
||||||
orderBy: [
|
orderBy: [
|
||||||
{
|
{
|
||||||
exprType: 'column',
|
exprType: 'column',
|
||||||
|
|||||||
@@ -267,12 +267,9 @@ export class TableGridDisplay extends GridDisplay {
|
|||||||
) {
|
) {
|
||||||
for (const column of columns) {
|
for (const column of columns) {
|
||||||
if (this.addAllExpandedColumnsToSelected || this.config.addedColumns.includes(column.uniqueName)) {
|
if (this.addAllExpandedColumnsToSelected || this.config.addedColumns.includes(column.uniqueName)) {
|
||||||
select.columns.push({
|
select.columns.push(
|
||||||
exprType: 'column',
|
this.createColumnExpression(column, { name: column, alias: parentAlias }, column.uniqueName)
|
||||||
columnName: column.columnName,
|
);
|
||||||
alias: column.uniqueName,
|
|
||||||
source: { name: column, alias: parentAlias },
|
|
||||||
});
|
|
||||||
displayedColumnInfo[column.uniqueName] = {
|
displayedColumnInfo[column.uniqueName] = {
|
||||||
...column,
|
...column,
|
||||||
sourceAlias: parentAlias,
|
sourceAlias: parentAlias,
|
||||||
|
|||||||
@@ -35,17 +35,24 @@ export function dumpSqlExpression(dmp: SqlDumper, expr: Expression) {
|
|||||||
dmp.put(')');
|
dmp.put(')');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'methodCall':
|
||||||
|
dumpSqlExpression(dmp, expr.thisObject)
|
||||||
|
dmp.put('.%s(', expr.method);
|
||||||
|
dmp.putCollection(',', expr.args, x => dumpSqlExpression(dmp, x));
|
||||||
|
dmp.put(')');
|
||||||
|
break;
|
||||||
|
|
||||||
case 'transform':
|
case 'transform':
|
||||||
dmp.transform(expr.transform, () => dumpSqlExpression(dmp, expr.expr));
|
dmp.transform(expr.transform, () => dumpSqlExpression(dmp, expr.expr));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'rowNumber':
|
case 'rowNumber':
|
||||||
dmp.put(" ^row_number() ^over (^order ^by ");
|
dmp.put(' ^row_number() ^over (^order ^by ');
|
||||||
dmp.putCollection(', ', expr.orderBy, x => {
|
dmp.putCollection(', ', expr.orderBy, x => {
|
||||||
dumpSqlExpression(dmp, x);
|
dumpSqlExpression(dmp, x);
|
||||||
dmp.put(' %k', x.direction);
|
dmp.put(' %k', x.direction);
|
||||||
});
|
});
|
||||||
dmp.put(")");
|
dmp.put(')');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ export function evaluateExpression(expr: Expression, values) {
|
|||||||
case 'call':
|
case 'call':
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
case 'methodCall':
|
||||||
|
return null;
|
||||||
|
|
||||||
case 'transform':
|
case 'transform':
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -155,6 +155,13 @@ export interface CallExpression {
|
|||||||
argsPrefix?: string; // DISTINCT in case of COUNT DISTINCT
|
argsPrefix?: string; // DISTINCT in case of COUNT DISTINCT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MethodCallExpression {
|
||||||
|
exprType: 'methodCall';
|
||||||
|
method: string;
|
||||||
|
args: Expression[];
|
||||||
|
thisObject: Expression;
|
||||||
|
}
|
||||||
|
|
||||||
export interface TranformExpression {
|
export interface TranformExpression {
|
||||||
exprType: 'transform';
|
exprType: 'transform';
|
||||||
expr: Expression;
|
expr: Expression;
|
||||||
@@ -172,6 +179,7 @@ export type Expression =
|
|||||||
| PlaceholderExpression
|
| PlaceholderExpression
|
||||||
| RawExpression
|
| RawExpression
|
||||||
| CallExpression
|
| CallExpression
|
||||||
|
| MethodCallExpression
|
||||||
| TranformExpression
|
| TranformExpression
|
||||||
| RowNumberExpression;
|
| RowNumberExpression;
|
||||||
export type OrderByExpression = Expression & { direction: 'ASC' | 'DESC' };
|
export type OrderByExpression = Expression & { direction: 'ASC' | 'DESC' };
|
||||||
|
|||||||
@@ -84,3 +84,12 @@ export function getIconForRedisType(type) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isWktGeometry(s) {
|
||||||
|
if (!_isString(s)) return false;
|
||||||
|
|
||||||
|
// return !!s.match(/^POINT\s*\(|/)
|
||||||
|
return !!s.match(
|
||||||
|
/^POINT\s*\(|^LINESTRING\s*\(|^POLYGON\s*\(|^MULTIPOINT\s*\(|^MULTILINESTRING\s*\(|^MULTIPOLYGON\s*\(|^GEOMCOLLECTION\s*\(|^GEOMETRYCOLLECTION\s*\(/
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ColumnInfo, TableInfo, ForeignKeyInfo, DatabaseInfo } from 'dbgate-types';
|
import { ColumnInfo, TableInfo, ForeignKeyInfo, DatabaseInfo } from 'dbgate-types';
|
||||||
|
import { StringNullableChain } from 'lodash';
|
||||||
import _cloneDeep from 'lodash/cloneDeep';
|
import _cloneDeep from 'lodash/cloneDeep';
|
||||||
import _compact from 'lodash/compact';
|
import _compact from 'lodash/compact';
|
||||||
import { DatabaseAnalyser } from './DatabaseAnalyser';
|
import { DatabaseAnalyser } from './DatabaseAnalyser';
|
||||||
@@ -11,6 +12,7 @@ export interface ColumnInfoYaml {
|
|||||||
autoIncrement?: boolean;
|
autoIncrement?: boolean;
|
||||||
references?: string;
|
references?: string;
|
||||||
primaryKey?: boolean;
|
primaryKey?: boolean;
|
||||||
|
default?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DatabaseModelFile {
|
export interface DatabaseModelFile {
|
||||||
@@ -39,6 +41,7 @@ function columnInfoToYaml(column: ColumnInfo, table: TableInfo): ColumnInfoYaml
|
|||||||
const res: ColumnInfoYaml = {
|
const res: ColumnInfoYaml = {
|
||||||
name: column.columnName,
|
name: column.columnName,
|
||||||
type: column.dataType,
|
type: column.dataType,
|
||||||
|
default: column.defaultValue,
|
||||||
};
|
};
|
||||||
if (column.autoIncrement) res.autoIncrement = true;
|
if (column.autoIncrement) res.autoIncrement = true;
|
||||||
if (column.notNull) res.notNull = true;
|
if (column.notNull) res.notNull = true;
|
||||||
@@ -71,6 +74,7 @@ function columnInfoFromYaml(column: ColumnInfoYaml, table: TableInfoYaml): Colum
|
|||||||
dataType: column.length ? `${column.type}(${column.length})` : column.type,
|
dataType: column.length ? `${column.type}(${column.length})` : column.type,
|
||||||
autoIncrement: column.autoIncrement,
|
autoIncrement: column.autoIncrement,
|
||||||
notNull: column.notNull || (table.primaryKey && table.primaryKey.includes(column.name)),
|
notNull: column.notNull || (table.primaryKey && table.primaryKey.includes(column.name)),
|
||||||
|
defaultValue: column.default,
|
||||||
};
|
};
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|||||||
3
packages/types/dialect.d.ts
vendored
3
packages/types/dialect.d.ts
vendored
@@ -34,4 +34,7 @@ export interface SqlDialect {
|
|||||||
disableExplicitTransaction?: boolean;
|
disableExplicitTransaction?: boolean;
|
||||||
|
|
||||||
predefinedDataTypes: string[];
|
predefinedDataTypes: string[];
|
||||||
|
|
||||||
|
// create sql-tree expression
|
||||||
|
createColumnViewExpression(columnName: string, dataType: string, source: { alias: string }, alias?: string): any;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chartjs-plugin-zoom": "^1.2.0",
|
"chartjs-plugin-zoom": "^1.2.0",
|
||||||
"date-fns": "^2.28.0",
|
"date-fns": "^2.28.0",
|
||||||
"interval-operations": "^1.0.7"
|
"interval-operations": "^1.0.7",
|
||||||
|
"leaflet": "^1.8.0",
|
||||||
|
"wellknown": "^0.5.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
7
packages/web/src/celldata/MapCellView.svelte
Normal file
7
packages/web/src/celldata/MapCellView.svelte
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import MapView from '../elements/MapView.svelte';
|
||||||
|
|
||||||
|
export let selection;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<MapView {selection} />
|
||||||
@@ -112,6 +112,14 @@
|
|||||||
onClick: () => getCurrentDataGrid().editJsonDocument(),
|
onClick: () => getCurrentDataGrid().editJsonDocument(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
registerCommand({
|
||||||
|
id: 'dataGrid.openSelectionInMap',
|
||||||
|
category: 'Data grid',
|
||||||
|
name: 'Open selection in map',
|
||||||
|
testEnabled: () => getCurrentDataGrid() != null, // ?.openSelectionInMapEnabled(),
|
||||||
|
onClick: () => getCurrentDataGrid().openSelectionInMap(),
|
||||||
|
});
|
||||||
|
|
||||||
registerCommand({
|
registerCommand({
|
||||||
id: 'dataGrid.viewJsonDocument',
|
id: 'dataGrid.viewJsonDocument',
|
||||||
category: 'Data grid',
|
category: 'Data grid',
|
||||||
@@ -306,6 +314,8 @@
|
|||||||
import { apiCall } from '../utility/api';
|
import { apiCall } from '../utility/api';
|
||||||
import getElectron from '../utility/getElectron';
|
import getElectron from '../utility/getElectron';
|
||||||
import { isCtrlOrCommandKey, isMac } from '../utility/common';
|
import { isCtrlOrCommandKey, isMac } from '../utility/common';
|
||||||
|
import { selectionCouldBeShownOnMap } from '../elements/MapView.svelte';
|
||||||
|
import ErrorMessageModal from '../modals/ErrorMessageModal.svelte';
|
||||||
|
|
||||||
export let onLoadNextData = undefined;
|
export let onLoadNextData = undefined;
|
||||||
export let grider = undefined;
|
export let grider = undefined;
|
||||||
@@ -530,6 +540,23 @@
|
|||||||
openJsonDocument(json);
|
openJsonDocument(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function openSelectionInMap() {
|
||||||
|
const selection = getCellsPublished(selectedCells);
|
||||||
|
if (!selectionCouldBeShownOnMap(selection)) {
|
||||||
|
showModal(ErrorMessageModal, { message: 'There is nothing to be shown on map' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
openNewTab({
|
||||||
|
title: 'Map',
|
||||||
|
icon: 'img map',
|
||||||
|
tabComponent: 'MapTab',
|
||||||
|
props: {
|
||||||
|
selection,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
function getSelectedExportableCell() {
|
function getSelectedExportableCell() {
|
||||||
const electron = getElectron();
|
const electron = getElectron();
|
||||||
if (electron && selectedCells.length == 1) {
|
if (electron && selectedCells.length == 1) {
|
||||||
@@ -1426,6 +1453,7 @@
|
|||||||
{ command: 'dataGrid.generateSqlFromData' },
|
{ command: 'dataGrid.generateSqlFromData' },
|
||||||
{ command: 'dataGrid.openFreeTable' },
|
{ command: 'dataGrid.openFreeTable' },
|
||||||
{ command: 'dataGrid.openChartFromSelection' },
|
{ command: 'dataGrid.openChartFromSelection' },
|
||||||
|
{ command: 'dataGrid.openSelectionInMap', hideDisabled: true },
|
||||||
{ placeTag: 'chart' }
|
{ placeTag: 'chart' }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
201
packages/web/src/elements/MapView.svelte
Normal file
201
packages/web/src/elements/MapView.svelte
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
<script lang="ts" context="module">
|
||||||
|
export function selectionCouldBeShownOnMap(selection) {
|
||||||
|
console.log('selection', selection);
|
||||||
|
if (selection.length > 0 && _.find(selection, x => isWktGeometry(x.value))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
selection.find(x => x.column.toLowerCase().includes('lat')) &&
|
||||||
|
(selection.find(x => x.column.toLowerCase().includes('lon')) ||
|
||||||
|
selection.find(x => x.column.toLowerCase().includes('lng')))
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { onMount, tick } from 'svelte';
|
||||||
|
import 'leaflet/dist/leaflet.css';
|
||||||
|
import leaflet from 'leaflet';
|
||||||
|
import wellknown from 'wellknown';
|
||||||
|
import { isWktGeometry, ScriptWriter, ScriptWriterJson } from 'dbgate-tools';
|
||||||
|
import resizeObserver from '../utility/resizeObserver';
|
||||||
|
import openNewTab from '../utility/openNewTab';
|
||||||
|
import contextMenu from '../utility/contextMenu';
|
||||||
|
import { saveExportedFile, saveFileToDisk } from '../utility/exportFileTools';
|
||||||
|
import { getCurrentConfig } from '../stores';
|
||||||
|
import { apiCall } from '../utility/api';
|
||||||
|
|
||||||
|
export let selection;
|
||||||
|
|
||||||
|
let refContainer;
|
||||||
|
let map;
|
||||||
|
|
||||||
|
let selectionLayers = [];
|
||||||
|
let geoJson;
|
||||||
|
|
||||||
|
function createColumnsTable(cells) {
|
||||||
|
return `<table>${cells.map(cell => `<tr><td>${cell.column}</td><td>${cell.value}</td></tr>`).join('\n')}</table>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addSelectionToMap() {
|
||||||
|
if (!map) return;
|
||||||
|
if (!selection) return;
|
||||||
|
|
||||||
|
for (const selectionLayer of selectionLayers) {
|
||||||
|
selectionLayer.remove();
|
||||||
|
}
|
||||||
|
selectionLayers = [];
|
||||||
|
|
||||||
|
const selectedRows = _.groupBy(selection || [], 'row');
|
||||||
|
|
||||||
|
const features = [];
|
||||||
|
|
||||||
|
for (const rowKey of _.keys(selectedRows)) {
|
||||||
|
const cells = selectedRows[rowKey];
|
||||||
|
const lat = cells.find(x => x.column.toLowerCase().includes('lat'));
|
||||||
|
const lon = cells.find(x => x.column.toLowerCase().includes('lon') || x.column.toLowerCase().includes('lng'));
|
||||||
|
|
||||||
|
const geoValues = cells.map(x => x.value).filter(isWktGeometry);
|
||||||
|
|
||||||
|
if (lat && lon) {
|
||||||
|
features.push({
|
||||||
|
type: 'Feature',
|
||||||
|
properties: {
|
||||||
|
popupContent: createColumnsTable(cells),
|
||||||
|
},
|
||||||
|
geometry: {
|
||||||
|
type: 'Point',
|
||||||
|
coordinates: [lon.value, lat.value],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (geoValues.length > 0) {
|
||||||
|
// parse WKT to geoJSON array
|
||||||
|
features.push(
|
||||||
|
...geoValues.map(wellknown).map(geometry => ({
|
||||||
|
type: 'Feature',
|
||||||
|
properties: {
|
||||||
|
popupContent: createColumnsTable(cells.filter(x => !isWktGeometry(x.value))),
|
||||||
|
},
|
||||||
|
geometry,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (features.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
geoJson = {
|
||||||
|
type: 'FeatureCollection',
|
||||||
|
features,
|
||||||
|
};
|
||||||
|
|
||||||
|
const geoJsonObj = leaflet
|
||||||
|
.geoJSON(geoJson, {
|
||||||
|
style: function () {
|
||||||
|
return {
|
||||||
|
weight: 2,
|
||||||
|
fillColor: '#ff7800',
|
||||||
|
color: '#ff7800',
|
||||||
|
opacity: 0.8,
|
||||||
|
fillOpacity: 0.4,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
pointToLayer: (feature, latlng) => {
|
||||||
|
return leaflet.circleMarker(latlng, {
|
||||||
|
radius: 7,
|
||||||
|
weight: 2,
|
||||||
|
fillColor: '#ff0000',
|
||||||
|
color: '#ff0000',
|
||||||
|
opacity: 0.9,
|
||||||
|
fillOpacity: 0.9,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onEachFeature: (feature, layer) => {
|
||||||
|
// does this feature have a property named popupContent?
|
||||||
|
if (feature.properties && feature.properties.popupContent) {
|
||||||
|
layer.bindPopup(feature.properties.popupContent);
|
||||||
|
layer.bindTooltip(feature.properties.popupContent);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.addTo(map);
|
||||||
|
// geoJsonObj.bindPopup('This is the Transamerica Pyramid'); //.openPopup();
|
||||||
|
map.fitBounds(geoJsonObj.getBounds());
|
||||||
|
selectionLayers.push(geoJsonObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
map = leaflet.map(refContainer).setView([50, 15], 13);
|
||||||
|
|
||||||
|
leaflet
|
||||||
|
.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
|
maxZoom: 19,
|
||||||
|
attribution: '© OpenStreetMap',
|
||||||
|
})
|
||||||
|
.addTo(map);
|
||||||
|
|
||||||
|
addSelectionToMap();
|
||||||
|
// map.fitBounds([
|
||||||
|
// [50, 15],
|
||||||
|
// [50.1, 15],
|
||||||
|
// [50, 15.1],
|
||||||
|
// ]);
|
||||||
|
|
||||||
|
// const marker = leaflet.marker([50, 15]).addTo(map);
|
||||||
|
// <div bind:this={refContainer} class="flex1 map-container" />
|
||||||
|
});
|
||||||
|
|
||||||
|
$: {
|
||||||
|
selection;
|
||||||
|
addSelectionToMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMenu() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
text: 'Open on new tab',
|
||||||
|
onClick: () => {
|
||||||
|
openNewTab({
|
||||||
|
title: 'Map',
|
||||||
|
icon: 'img map',
|
||||||
|
tabComponent: 'MapTab',
|
||||||
|
props: {
|
||||||
|
selection,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Export to HTML file',
|
||||||
|
onClick: () => {
|
||||||
|
saveFileToDisk(async filePath => {
|
||||||
|
await apiCall('files/export-map', {
|
||||||
|
geoJson,
|
||||||
|
filePath,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={refContainer}
|
||||||
|
use:contextMenu={createMenu}
|
||||||
|
class="flex1"
|
||||||
|
use:resizeObserver={true}
|
||||||
|
on:resize={async e => {
|
||||||
|
await tick();
|
||||||
|
map.invalidateSize();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
@@ -165,6 +165,7 @@
|
|||||||
|
|
||||||
'img sort-asc': 'mdi mdi-sort-alphabetical-ascending color-icon-green',
|
'img sort-asc': 'mdi mdi-sort-alphabetical-ascending color-icon-green',
|
||||||
'img sort-desc': 'mdi mdi-sort-alphabetical-descending color-icon-green',
|
'img sort-desc': 'mdi mdi-sort-alphabetical-descending color-icon-green',
|
||||||
|
'img map': 'mdi mdi-map color-icon-blue',
|
||||||
|
|
||||||
'img reference': 'mdi mdi-link-box',
|
'img reference': 'mdi mdi-link-box',
|
||||||
'img link': 'mdi mdi-link',
|
'img link': 'mdi mdi-link',
|
||||||
|
|||||||
8
packages/web/src/tabs/MapTab.svelte
Normal file
8
packages/web/src/tabs/MapTab.svelte
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import MapView from '../elements/MapView.svelte';
|
||||||
|
|
||||||
|
export let selection;
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<MapView {selection} />
|
||||||
@@ -24,6 +24,7 @@ import * as DiagramTab from './DiagramTab.svelte';
|
|||||||
import * as DbKeyDetailTab from './DbKeyDetailTab.svelte';
|
import * as DbKeyDetailTab from './DbKeyDetailTab.svelte';
|
||||||
import * as QueryDataTab from './QueryDataTab.svelte';
|
import * as QueryDataTab from './QueryDataTab.svelte';
|
||||||
import * as ConnectionTab from './ConnectionTab.svelte';
|
import * as ConnectionTab from './ConnectionTab.svelte';
|
||||||
|
import * as MapTab from './MapTab.svelte';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
TableDataTab,
|
TableDataTab,
|
||||||
@@ -52,4 +53,5 @@ export default {
|
|||||||
DbKeyDetailTab,
|
DbKeyDetailTab,
|
||||||
QueryDataTab,
|
QueryDataTab,
|
||||||
ConnectionTab,
|
ConnectionTab,
|
||||||
|
MapTab,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<script lang="ts" context="module">
|
<script lang="ts" context="module">
|
||||||
|
import { isWktGeometry } from 'dbgate-tools';
|
||||||
|
|
||||||
const formats = [
|
const formats = [
|
||||||
{
|
{
|
||||||
type: 'textWrap',
|
type: 'textWrap',
|
||||||
@@ -36,12 +38,23 @@
|
|||||||
component: HtmlCellView,
|
component: HtmlCellView,
|
||||||
single: false,
|
single: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'map',
|
||||||
|
title: 'Map',
|
||||||
|
component: MapCellView,
|
||||||
|
single: false,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function autodetect(selection) {
|
function autodetect(selection) {
|
||||||
if (selection[0]?.engine?.databaseEngineTypes?.includes('document')) {
|
if (selection[0]?.engine?.databaseEngineTypes?.includes('document')) {
|
||||||
return 'jsonRow';
|
return 'jsonRow';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (selectionCouldBeShownOnMap(selection)) {
|
||||||
|
return 'map';
|
||||||
|
}
|
||||||
|
|
||||||
const value = selection.length == 1 ? selection[0].value : null;
|
const value = selection.length == 1 ? selection[0].value : null;
|
||||||
if (_.isString(value)) {
|
if (_.isString(value)) {
|
||||||
if (value.startsWith('[') || value.startsWith('{')) return 'json';
|
if (value.startsWith('[') || value.startsWith('{')) return 'json';
|
||||||
@@ -62,10 +75,12 @@
|
|||||||
import HtmlCellView from '../celldata/HtmlCellView.svelte';
|
import HtmlCellView from '../celldata/HtmlCellView.svelte';
|
||||||
import JsonCellView from '../celldata/JsonCellView.svelte';
|
import JsonCellView from '../celldata/JsonCellView.svelte';
|
||||||
import JsonRowView from '../celldata/JsonRowView.svelte';
|
import JsonRowView from '../celldata/JsonRowView.svelte';
|
||||||
|
import MapCellView from '../celldata/MapCellView.svelte';
|
||||||
import PictureCellView from '../celldata/PictureCellView.svelte';
|
import PictureCellView from '../celldata/PictureCellView.svelte';
|
||||||
import TextCellViewNoWrap from '../celldata/TextCellViewNoWrap.svelte';
|
import TextCellViewNoWrap from '../celldata/TextCellViewNoWrap.svelte';
|
||||||
import TextCellViewWrap from '../celldata/TextCellViewWrap.svelte';
|
import TextCellViewWrap from '../celldata/TextCellViewWrap.svelte';
|
||||||
import ErrorInfo from '../elements/ErrorInfo.svelte';
|
import ErrorInfo from '../elements/ErrorInfo.svelte';
|
||||||
|
import { selectionCouldBeShownOnMap } from '../elements/MapView.svelte';
|
||||||
import SelectField from '../forms/SelectField.svelte';
|
import SelectField from '../forms/SelectField.svelte';
|
||||||
import { selectedCellsCallback } from '../stores';
|
import { selectedCellsCallback } from '../stores';
|
||||||
import WidgetTitle from './WidgetTitle.svelte';
|
import WidgetTitle from './WidgetTitle.svelte';
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ const { driverBase } = global.DBGATE_TOOLS;
|
|||||||
const MsSqlDumper = require('./MsSqlDumper');
|
const MsSqlDumper = require('./MsSqlDumper');
|
||||||
const { mssqlSplitterOptions } = require('dbgate-query-splitter/lib/options');
|
const { mssqlSplitterOptions } = require('dbgate-query-splitter/lib/options');
|
||||||
|
|
||||||
|
const spatialTypes = ['GEOGRAPHY'];
|
||||||
|
|
||||||
/** @type {import('dbgate-types').SqlDialect} */
|
/** @type {import('dbgate-types').SqlDialect} */
|
||||||
const dialect = {
|
const dialect = {
|
||||||
limitSelect: true,
|
limitSelect: true,
|
||||||
@@ -70,6 +72,21 @@ const dialect = {
|
|||||||
'image',
|
'image',
|
||||||
'xml',
|
'xml',
|
||||||
],
|
],
|
||||||
|
|
||||||
|
createColumnViewExpression(columnName, dataType, source, alias) {
|
||||||
|
if (dataType && spatialTypes.includes(dataType.toUpperCase())) {
|
||||||
|
return {
|
||||||
|
exprType: 'methodCall',
|
||||||
|
method: 'STAsText',
|
||||||
|
alias: alias || columnName,
|
||||||
|
thisObject: {
|
||||||
|
exprType: 'column',
|
||||||
|
columnName,
|
||||||
|
source,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @type {import('dbgate-types').EngineDriver} */
|
/** @type {import('dbgate-types').EngineDriver} */
|
||||||
|
|||||||
@@ -2,6 +2,18 @@ const { driverBase } = global.DBGATE_TOOLS;
|
|||||||
const { mysqlSplitterOptions } = require('dbgate-query-splitter/lib/options');
|
const { mysqlSplitterOptions } = require('dbgate-query-splitter/lib/options');
|
||||||
const Dumper = require('./Dumper');
|
const Dumper = require('./Dumper');
|
||||||
|
|
||||||
|
const spatialTypes = [
|
||||||
|
'POINT',
|
||||||
|
'LINESTRING',
|
||||||
|
'POLYGON',
|
||||||
|
'GEOMETRY',
|
||||||
|
'MULTIPOINT',
|
||||||
|
'MULTILINESTRING',
|
||||||
|
'MULTIPOLYGON',
|
||||||
|
'GEOMCOLLECTION',
|
||||||
|
'GEOMETRYCOLLECTION',
|
||||||
|
];
|
||||||
|
|
||||||
/** @type {import('dbgate-types').SqlDialect} */
|
/** @type {import('dbgate-types').SqlDialect} */
|
||||||
const dialect = {
|
const dialect = {
|
||||||
rangeSelect: true,
|
rangeSelect: true,
|
||||||
@@ -68,6 +80,23 @@ const dialect = {
|
|||||||
'time',
|
'time',
|
||||||
'year',
|
'year',
|
||||||
],
|
],
|
||||||
|
|
||||||
|
createColumnViewExpression(columnName, dataType, source, alias) {
|
||||||
|
if (dataType && spatialTypes.includes(dataType.toUpperCase())) {
|
||||||
|
return {
|
||||||
|
exprType: 'call',
|
||||||
|
func: 'ST_AsText',
|
||||||
|
alias: alias || columnName,
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
exprType: 'column',
|
||||||
|
columnName,
|
||||||
|
source,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const mysqlDriverBase = {
|
const mysqlDriverBase = {
|
||||||
|
|||||||
@@ -11,21 +11,36 @@ function normalizeTypeName(dataType) {
|
|||||||
return dataType;
|
return dataType;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getColumnInfo({
|
function getColumnInfo(
|
||||||
is_nullable,
|
{ is_nullable, column_name, data_type, char_max_length, numeric_precision, numeric_ccale, default_value },
|
||||||
column_name,
|
table = undefined,
|
||||||
data_type,
|
geometryColumns = undefined,
|
||||||
char_max_length,
|
geographyColumns = undefined
|
||||||
numeric_precision,
|
) {
|
||||||
numeric_ccale,
|
|
||||||
default_value,
|
|
||||||
}) {
|
|
||||||
const normDataType = normalizeTypeName(data_type);
|
const normDataType = normalizeTypeName(data_type);
|
||||||
let fullDataType = normDataType;
|
let fullDataType = normDataType;
|
||||||
if (char_max_length && isTypeString(normDataType)) fullDataType = `${normDataType}(${char_max_length})`;
|
if (char_max_length && isTypeString(normDataType)) fullDataType = `${normDataType}(${char_max_length})`;
|
||||||
if (numeric_precision && numeric_ccale && isTypeNumeric(normDataType))
|
if (numeric_precision && numeric_ccale && isTypeNumeric(normDataType))
|
||||||
fullDataType = `${normDataType}(${numeric_precision},${numeric_ccale})`;
|
fullDataType = `${normDataType}(${numeric_precision},${numeric_ccale})`;
|
||||||
const autoIncrement = !!(default_value && default_value.startsWith('nextval('));
|
const autoIncrement = !!(default_value && default_value.startsWith('nextval('));
|
||||||
|
if (
|
||||||
|
table &&
|
||||||
|
geometryColumns &&
|
||||||
|
geometryColumns.rows.find(
|
||||||
|
x => x.schema_name == table.schemaName && x.pure_name == table.pureName && x.column_name == column_name
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
fullDataType = 'geometry';
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
table &&
|
||||||
|
geographyColumns &&
|
||||||
|
geographyColumns.rows.find(
|
||||||
|
x => x.schema_name == table.schemaName && x.pure_name == table.pureName && x.column_name == column_name
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
fullDataType = 'geography';
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
columnName: column_name,
|
columnName: column_name,
|
||||||
dataType: fullDataType,
|
dataType: fullDataType,
|
||||||
@@ -145,6 +160,18 @@ class Analyser extends DatabaseAnalyser {
|
|||||||
: await this.driver.query(this.pool, this.createQuery('indexcols', ['tables']));
|
: await this.driver.query(this.pool, this.createQuery('indexcols', ['tables']));
|
||||||
this.feedback({ analysingMessage: 'Loading unique names' });
|
this.feedback({ analysingMessage: 'Loading unique names' });
|
||||||
const uniqueNames = await this.driver.query(this.pool, this.createQuery('uniqueNames', ['tables']));
|
const uniqueNames = await this.driver.query(this.pool, this.createQuery('uniqueNames', ['tables']));
|
||||||
|
|
||||||
|
let geometryColumns = { rows: [] };
|
||||||
|
if (views.rows.find(x => x.pure_name == 'geometry_columns' && x.schema_name == 'public')) {
|
||||||
|
this.feedback({ analysingMessage: 'Loading geometry columns' });
|
||||||
|
geometryColumns = await this.safeQuery(this.createQuery('geometryColumns', ['tables']));
|
||||||
|
}
|
||||||
|
let geographyColumns = { rows: [] };
|
||||||
|
if (views.rows.find(x => x.pure_name == 'geography_columns' && x.schema_name == 'public')) {
|
||||||
|
this.feedback({ analysingMessage: 'Loading geography columns' });
|
||||||
|
geographyColumns = await this.safeQuery(this.createQuery('geographyColumns', ['tables']));
|
||||||
|
}
|
||||||
|
|
||||||
this.feedback({ analysingMessage: 'Finalizing DB structure' });
|
this.feedback({ analysingMessage: 'Finalizing DB structure' });
|
||||||
|
|
||||||
const columnColumnsMapped = fkColumns.rows.map(x => ({
|
const columnColumnsMapped = fkColumns.rows.map(x => ({
|
||||||
@@ -179,7 +206,7 @@ class Analyser extends DatabaseAnalyser {
|
|||||||
...newTable,
|
...newTable,
|
||||||
columns: columns.rows
|
columns: columns.rows
|
||||||
.filter(col => col.pure_name == table.pure_name && col.schema_name == table.schema_name)
|
.filter(col => col.pure_name == table.pure_name && col.schema_name == table.schema_name)
|
||||||
.map(getColumnInfo),
|
.map(col => getColumnInfo(col, newTable, geometryColumns, geographyColumns)),
|
||||||
primaryKey: DatabaseAnalyser.extractPrimaryKeys(newTable, pkColumnsMapped),
|
primaryKey: DatabaseAnalyser.extractPrimaryKeys(newTable, pkColumnsMapped),
|
||||||
foreignKeys: DatabaseAnalyser.extractForeignKeys(newTable, columnColumnsMapped),
|
foreignKeys: DatabaseAnalyser.extractForeignKeys(newTable, columnColumnsMapped),
|
||||||
indexes: indexes.rows
|
indexes: indexes.rows
|
||||||
@@ -231,7 +258,7 @@ class Analyser extends DatabaseAnalyser {
|
|||||||
createSql: `CREATE VIEW "${view.schema_name}"."${view.pure_name}"\nAS\n${view.create_sql}`,
|
createSql: `CREATE VIEW "${view.schema_name}"."${view.pure_name}"\nAS\n${view.create_sql}`,
|
||||||
columns: columns.rows
|
columns: columns.rows
|
||||||
.filter(col => col.pure_name == view.pure_name && col.schema_name == view.schema_name)
|
.filter(col => col.pure_name == view.pure_name && col.schema_name == view.schema_name)
|
||||||
.map(getColumnInfo),
|
.map(col => getColumnInfo(col)),
|
||||||
})),
|
})),
|
||||||
matviews: matviews
|
matviews: matviews
|
||||||
? matviews.rows.map(matview => ({
|
? matviews.rows.map(matview => ({
|
||||||
@@ -242,7 +269,7 @@ class Analyser extends DatabaseAnalyser {
|
|||||||
createSql: `CREATE MATERIALIZED VIEW "${matview.schema_name}"."${matview.pure_name}"\nAS\n${matview.definition}`,
|
createSql: `CREATE MATERIALIZED VIEW "${matview.schema_name}"."${matview.pure_name}"\nAS\n${matview.definition}`,
|
||||||
columns: matviewColumns.rows
|
columns: matviewColumns.rows
|
||||||
.filter(col => col.pure_name == matview.pure_name && col.schema_name == matview.schema_name)
|
.filter(col => col.pure_name == matview.pure_name && col.schema_name == matview.schema_name)
|
||||||
.map(getColumnInfo),
|
.map(col => getColumnInfo(col)),
|
||||||
}))
|
}))
|
||||||
: undefined,
|
: undefined,
|
||||||
procedures: routines.rows
|
procedures: routines.rows
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
module.exports = `
|
||||||
|
select
|
||||||
|
f_table_schema as "schema_name",
|
||||||
|
f_table_name as "pure_name",
|
||||||
|
f_geography_column as "column_name"
|
||||||
|
from public.geography_columns
|
||||||
|
where ('tables:' || f_table_schema || '.' || f_table_name) =OBJECT_ID_CONDITION
|
||||||
|
`;
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
module.exports = `
|
||||||
|
select
|
||||||
|
f_table_schema as "schema_name",
|
||||||
|
f_table_name as "pure_name",
|
||||||
|
f_geometry_column as "column_name"
|
||||||
|
from public.geometry_columns
|
||||||
|
where ('tables:' || f_table_schema || '.' || f_table_name) =OBJECT_ID_CONDITION
|
||||||
|
`;
|
||||||
@@ -13,6 +13,8 @@ const matviewColumns = require('./matviewColumns');
|
|||||||
const indexes = require('./indexes');
|
const indexes = require('./indexes');
|
||||||
const indexcols = require('./indexcols');
|
const indexcols = require('./indexcols');
|
||||||
const uniqueNames = require('./uniqueNames');
|
const uniqueNames = require('./uniqueNames');
|
||||||
|
const geometryColumns = require('./geometryColumns');
|
||||||
|
const geographyColumns = require('./geographyColumns');
|
||||||
|
|
||||||
const fk_keyColumnUsage = require('./fk_key_column_usage');
|
const fk_keyColumnUsage = require('./fk_key_column_usage');
|
||||||
const fk_referentialConstraints = require('./fk_referential_constraints');
|
const fk_referentialConstraints = require('./fk_referential_constraints');
|
||||||
@@ -37,4 +39,6 @@ module.exports = {
|
|||||||
indexes,
|
indexes,
|
||||||
indexcols,
|
indexcols,
|
||||||
uniqueNames,
|
uniqueNames,
|
||||||
|
geometryColumns,
|
||||||
|
geographyColumns,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ const { driverBase } = global.DBGATE_TOOLS;
|
|||||||
const Dumper = require('./Dumper');
|
const Dumper = require('./Dumper');
|
||||||
const { postgreSplitterOptions } = require('dbgate-query-splitter/lib/options');
|
const { postgreSplitterOptions } = require('dbgate-query-splitter/lib/options');
|
||||||
|
|
||||||
|
const spatialTypes = ['GEOGRAPHY'];
|
||||||
|
|
||||||
/** @type {import('dbgate-types').SqlDialect} */
|
/** @type {import('dbgate-types').SqlDialect} */
|
||||||
const dialect = {
|
const dialect = {
|
||||||
rangeSelect: true,
|
rangeSelect: true,
|
||||||
@@ -78,6 +80,23 @@ const dialect = {
|
|||||||
'uuid',
|
'uuid',
|
||||||
'xml',
|
'xml',
|
||||||
],
|
],
|
||||||
|
|
||||||
|
createColumnViewExpression(columnName, dataType, source, alias) {
|
||||||
|
if (dataType && spatialTypes.includes(dataType.toUpperCase())) {
|
||||||
|
return {
|
||||||
|
exprType: 'call',
|
||||||
|
func: 'ST_AsText',
|
||||||
|
alias: alias || columnName,
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
exprType: 'column',
|
||||||
|
columnName,
|
||||||
|
source,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const postgresDriverBase = {
|
const postgresDriverBase = {
|
||||||
|
|||||||
46
yarn.lock
46
yarn.lock
@@ -2956,6 +2956,15 @@ concat-stream@^1.5.0:
|
|||||||
readable-stream "^2.2.2"
|
readable-stream "^2.2.2"
|
||||||
typedarray "^0.0.6"
|
typedarray "^0.0.6"
|
||||||
|
|
||||||
|
concat-stream@~1.5.0:
|
||||||
|
version "1.5.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.5.2.tgz#708978624d856af41a5a741defdd261da752c266"
|
||||||
|
integrity sha512-H6xsIBfQ94aESBG8jGHXQ7i5AEpy5ZeVaLDOisDICiTCKpqEfr34/KmTrspKQNoLKNu9gTkovlpQcUi630AKiQ==
|
||||||
|
dependencies:
|
||||||
|
inherits "~2.0.1"
|
||||||
|
readable-stream "~2.0.0"
|
||||||
|
typedarray "~0.0.5"
|
||||||
|
|
||||||
concurrently@^5.1.0:
|
concurrently@^5.1.0:
|
||||||
version "5.1.0"
|
version "5.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-5.1.0.tgz#05523986ba7aaf4b58a49ddd658fab88fa783132"
|
resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-5.1.0.tgz#05523986ba7aaf4b58a49ddd658fab88fa783132"
|
||||||
@@ -6993,6 +7002,11 @@ lcid@^2.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
invert-kv "^2.0.0"
|
invert-kv "^2.0.0"
|
||||||
|
|
||||||
|
leaflet@^1.8.0:
|
||||||
|
version "1.8.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.8.0.tgz#4615db4a22a304e8e692cae9270b983b38a2055e"
|
||||||
|
integrity sha512-gwhMjFCQiYs3x/Sf+d49f10ERXaEFCPr+nVTryhAW8DWbMGqJqt9G4XuIaHmFW08zYvhgdzqXGr8AlW8v8dQkA==
|
||||||
|
|
||||||
left-pad@^1.3.0:
|
left-pad@^1.3.0:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e"
|
resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e"
|
||||||
@@ -7520,6 +7534,11 @@ minimist@^1.2.3, minimist@^1.2.5:
|
|||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
||||||
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
||||||
|
|
||||||
|
minimist@~1.2.0:
|
||||||
|
version "1.2.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
||||||
|
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
|
||||||
|
|
||||||
minipass-collect@^1.0.2:
|
minipass-collect@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617"
|
resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617"
|
||||||
@@ -8841,6 +8860,11 @@ printj@~1.1.0, printj@~1.1.2:
|
|||||||
resolved "https://registry.yarnpkg.com/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222"
|
resolved "https://registry.yarnpkg.com/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222"
|
||||||
integrity sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==
|
integrity sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==
|
||||||
|
|
||||||
|
process-nextick-args@~1.0.6:
|
||||||
|
version "1.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3"
|
||||||
|
integrity sha512-yN0WQmuCX63LP/TMvAg31nvT6m4vDqJEiiv2CAZqWOGNWutc9DfDk1NPYYmKUFmaVM2UwDowH4u5AHWYP/jxKw==
|
||||||
|
|
||||||
process-nextick-args@~2.0.0:
|
process-nextick-args@~2.0.0:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
|
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
|
||||||
@@ -9156,6 +9180,18 @@ readable-stream@~1.1.9:
|
|||||||
isarray "0.0.1"
|
isarray "0.0.1"
|
||||||
string_decoder "~0.10.x"
|
string_decoder "~0.10.x"
|
||||||
|
|
||||||
|
readable-stream@~2.0.0:
|
||||||
|
version "2.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e"
|
||||||
|
integrity sha512-TXcFfb63BQe1+ySzsHZI/5v1aJPCShfqvWJ64ayNImXMsN1Cd0YGk/wm8KB7/OeessgPc9QvS9Zou8QTkFzsLw==
|
||||||
|
dependencies:
|
||||||
|
core-util-is "~1.0.0"
|
||||||
|
inherits "~2.0.1"
|
||||||
|
isarray "~1.0.0"
|
||||||
|
process-nextick-args "~1.0.6"
|
||||||
|
string_decoder "~0.10.x"
|
||||||
|
util-deprecate "~1.0.1"
|
||||||
|
|
||||||
readdirp@^2.2.1:
|
readdirp@^2.2.1:
|
||||||
version "2.2.1"
|
version "2.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525"
|
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525"
|
||||||
@@ -10960,7 +10996,7 @@ typedarray-to-buffer@^3.1.5:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-typedarray "^1.0.0"
|
is-typedarray "^1.0.0"
|
||||||
|
|
||||||
typedarray@^0.0.6:
|
typedarray@^0.0.6, typedarray@~0.0.5:
|
||||||
version "0.0.6"
|
version "0.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||||
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
||||||
@@ -11332,6 +11368,14 @@ webpack@^4.42.0:
|
|||||||
watchpack "^1.6.0"
|
watchpack "^1.6.0"
|
||||||
webpack-sources "^1.4.1"
|
webpack-sources "^1.4.1"
|
||||||
|
|
||||||
|
wellknown@^0.5.0:
|
||||||
|
version "0.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/wellknown/-/wellknown-0.5.0.tgz#09ae9871fa826cf0a6ec1537ef00c379d78d7101"
|
||||||
|
integrity sha1-Ca6YcfqCbPCm7BU37wDDedeNcQE=
|
||||||
|
dependencies:
|
||||||
|
concat-stream "~1.5.0"
|
||||||
|
minimist "~1.2.0"
|
||||||
|
|
||||||
whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5:
|
whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5:
|
||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0"
|
resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0"
|
||||||
|
|||||||
Reference in New Issue
Block a user