From 0a0ce6ad98164fc8369a80713fd02c0fd7f1a57b Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Fri, 10 Jun 2022 20:25:55 +0200 Subject: [PATCH 01/14] v5.0.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 88192deb3..a5083d7e2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "5.0.3-beta.5", + "version": "5.0.3", "name": "dbgate-all", "workspaces": [ "packages/*", From 343cf84a58d2a896d77dd2c89d98c2e714600ae2 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sat, 11 Jun 2022 09:45:23 +0200 Subject: [PATCH 02/14] map - show geometry in MySQL --- packages/datalib/src/GridDisplay.ts | 23 +++- packages/datalib/src/TableGridDisplay.ts | 9 +- packages/tools/src/stringTools.ts | 15 +++ packages/types/dialect.d.ts | 3 + packages/web/package.json | 4 +- packages/web/src/celldata/MapCellView.svelte | 111 ++++++++++++++++++ .../web/src/widgets/CellDataWidget.svelte | 22 ++++ .../src/frontend/drivers.js | 29 +++++ yarn.lock | 46 +++++++- 9 files changed, 248 insertions(+), 14 deletions(-) create mode 100644 packages/web/src/celldata/MapCellView.svelte diff --git a/packages/datalib/src/GridDisplay.ts b/packages/datalib/src/GridDisplay.ts index c3f5476fd..7f012324d 100644 --- a/packages/datalib/src/GridDisplay.ts +++ b/packages/datalib/src/GridDisplay.ts @@ -483,6 +483,22 @@ export abstract class GridDisplay { 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) { if (!columns) return null; const orderColumnName = columns[0].columnName; @@ -492,12 +508,7 @@ export abstract class GridDisplay { name: _.pick(name, ['schemaName', 'pureName']), alias: 'basetbl', }, - columns: columns.map(col => ({ - exprType: 'column', - alias: col.columnName, - source: { alias: 'basetbl' }, - ...col, - })), + columns: columns.map(col => this.createColumnExpression(col, { alias: 'basetbl' })), orderBy: [ { exprType: 'column', diff --git a/packages/datalib/src/TableGridDisplay.ts b/packages/datalib/src/TableGridDisplay.ts index fc0472071..9252c5ab2 100644 --- a/packages/datalib/src/TableGridDisplay.ts +++ b/packages/datalib/src/TableGridDisplay.ts @@ -267,12 +267,9 @@ export class TableGridDisplay extends GridDisplay { ) { for (const column of columns) { if (this.addAllExpandedColumnsToSelected || this.config.addedColumns.includes(column.uniqueName)) { - select.columns.push({ - exprType: 'column', - columnName: column.columnName, - alias: column.uniqueName, - source: { name: column, alias: parentAlias }, - }); + select.columns.push( + this.createColumnExpression(column, { name: column, alias: parentAlias }, column.uniqueName) + ); displayedColumnInfo[column.uniqueName] = { ...column, sourceAlias: parentAlias, diff --git a/packages/tools/src/stringTools.ts b/packages/tools/src/stringTools.ts index dc8fa14dd..9e3d2d0f4 100644 --- a/packages/tools/src/stringTools.ts +++ b/packages/tools/src/stringTools.ts @@ -84,3 +84,18 @@ export function getIconForRedisType(type) { return null; } } + +export function isWktGeometry(s) { + if (!_isString(s)) return false; + + return ( + s.startsWith('POINT(') || + s.startsWith('LINESTRING(') || + s.startsWith('POLYGON(') || + s.startsWith('MULTIPOINT(') || + s.startsWith('MULTILINESTRING(') || + s.startsWith('MULTIPOLYGON(') || + s.startsWith('GEOMCOLLECTION(') || + s.startsWith('GEOMETRYCOLLECTION(') + ); +} diff --git a/packages/types/dialect.d.ts b/packages/types/dialect.d.ts index d42366159..70b241f1f 100644 --- a/packages/types/dialect.d.ts +++ b/packages/types/dialect.d.ts @@ -34,4 +34,7 @@ export interface SqlDialect { disableExplicitTransaction?: boolean; predefinedDataTypes: string[]; + + // create sql-tree expression + createColumnViewExpression(columnName: string, dataType: string, source: { alias: string }, alias?: string): any; } diff --git a/packages/web/package.json b/packages/web/package.json index 8a28f23d0..371109fea 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -57,6 +57,8 @@ "dependencies": { "chartjs-plugin-zoom": "^1.2.0", "date-fns": "^2.28.0", - "interval-operations": "^1.0.7" + "interval-operations": "^1.0.7", + "leaflet": "^1.8.0", + "wellknown": "^0.5.0" } } diff --git a/packages/web/src/celldata/MapCellView.svelte b/packages/web/src/celldata/MapCellView.svelte new file mode 100644 index 000000000..b38a42dcc --- /dev/null +++ b/packages/web/src/celldata/MapCellView.svelte @@ -0,0 +1,111 @@ + + +
diff --git a/packages/web/src/widgets/CellDataWidget.svelte b/packages/web/src/widgets/CellDataWidget.svelte index 06e391bdf..0829726ea 100644 --- a/packages/web/src/widgets/CellDataWidget.svelte +++ b/packages/web/src/widgets/CellDataWidget.svelte @@ -1,4 +1,6 @@ -
+
{ + await tick(); + map.invalidateSize(); + }} +/> From 6e1a1edac0f18b5751937021a482be7788ec58e9 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sat, 11 Jun 2022 17:57:18 +0200 Subject: [PATCH 06/14] map on standalone tab --- packages/web/src/celldata/MapCellView.svelte | 149 +--------------- packages/web/src/datagrid/DataGridCore.svelte | 28 +++ packages/web/src/elements/MapView.svelte | 164 ++++++++++++++++++ packages/web/src/icons/FontIcon.svelte | 1 + packages/web/src/tabs/MapTab.svelte | 8 + packages/web/src/tabs/index.js | 2 + .../web/src/widgets/CellDataWidget.svelte | 13 +- 7 files changed, 208 insertions(+), 157 deletions(-) create mode 100644 packages/web/src/elements/MapView.svelte create mode 100644 packages/web/src/tabs/MapTab.svelte diff --git a/packages/web/src/celldata/MapCellView.svelte b/packages/web/src/celldata/MapCellView.svelte index 824a080dc..e0d080c5f 100644 --- a/packages/web/src/celldata/MapCellView.svelte +++ b/packages/web/src/celldata/MapCellView.svelte @@ -1,152 +1,7 @@ -
{ - await tick(); - map.invalidateSize(); - }} -/> + diff --git a/packages/web/src/datagrid/DataGridCore.svelte b/packages/web/src/datagrid/DataGridCore.svelte index f2406e4e5..607533ce0 100644 --- a/packages/web/src/datagrid/DataGridCore.svelte +++ b/packages/web/src/datagrid/DataGridCore.svelte @@ -112,6 +112,14 @@ onClick: () => getCurrentDataGrid().editJsonDocument(), }); + registerCommand({ + id: 'dataGrid.openSelectionInMap', + category: 'Data grid', + name: 'Open selection in map', + testEnabled: () => getCurrentDataGrid() != null, // ?.openSelectionInMapEnabled(), + onClick: () => getCurrentDataGrid().openSelectionInMap(), + }); + registerCommand({ id: 'dataGrid.viewJsonDocument', category: 'Data grid', @@ -306,6 +314,8 @@ import { apiCall } from '../utility/api'; import getElectron from '../utility/getElectron'; import { isCtrlOrCommandKey, isMac } from '../utility/common'; + import { selectionCouldBeShownOnMap } from '../elements/MapView.svelte'; + import ErrorMessageModal from '../modals/ErrorMessageModal.svelte'; export let onLoadNextData = undefined; export let grider = undefined; @@ -530,6 +540,23 @@ 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() { const electron = getElectron(); if (electron && selectedCells.length == 1) { @@ -1426,6 +1453,7 @@ { command: 'dataGrid.generateSqlFromData' }, { command: 'dataGrid.openFreeTable' }, { command: 'dataGrid.openChartFromSelection' }, + { command: 'dataGrid.openSelectionInMap', hideDisabled: true }, { placeTag: 'chart' } ); diff --git a/packages/web/src/elements/MapView.svelte b/packages/web/src/elements/MapView.svelte new file mode 100644 index 000000000..9a27766d4 --- /dev/null +++ b/packages/web/src/elements/MapView.svelte @@ -0,0 +1,164 @@ + + + + +
{ + await tick(); + map.invalidateSize(); + }} +/> diff --git a/packages/web/src/icons/FontIcon.svelte b/packages/web/src/icons/FontIcon.svelte index f599e019e..c7ac644d5 100644 --- a/packages/web/src/icons/FontIcon.svelte +++ b/packages/web/src/icons/FontIcon.svelte @@ -165,6 +165,7 @@ 'img sort-asc': 'mdi mdi-sort-alphabetical-ascending 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 link': 'mdi mdi-link', diff --git a/packages/web/src/tabs/MapTab.svelte b/packages/web/src/tabs/MapTab.svelte new file mode 100644 index 000000000..0c21add56 --- /dev/null +++ b/packages/web/src/tabs/MapTab.svelte @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/packages/web/src/tabs/index.js b/packages/web/src/tabs/index.js index e629a1fbf..ae85d6635 100644 --- a/packages/web/src/tabs/index.js +++ b/packages/web/src/tabs/index.js @@ -24,6 +24,7 @@ import * as DiagramTab from './DiagramTab.svelte'; import * as DbKeyDetailTab from './DbKeyDetailTab.svelte'; import * as QueryDataTab from './QueryDataTab.svelte'; import * as ConnectionTab from './ConnectionTab.svelte'; +import * as MapTab from './MapTab.svelte'; export default { TableDataTab, @@ -52,4 +53,5 @@ export default { DbKeyDetailTab, QueryDataTab, ConnectionTab, + MapTab, }; diff --git a/packages/web/src/widgets/CellDataWidget.svelte b/packages/web/src/widgets/CellDataWidget.svelte index 78bc1550b..4d7b9c0a0 100644 --- a/packages/web/src/widgets/CellDataWidget.svelte +++ b/packages/web/src/widgets/CellDataWidget.svelte @@ -1,5 +1,5 @@ + + + + + + + +
+ + + `; +}; + +module.exports = getMapExport; diff --git a/packages/web/src/elements/MapView.svelte b/packages/web/src/elements/MapView.svelte index 9a27766d4..2a45e8ef6 100644 --- a/packages/web/src/elements/MapView.svelte +++ b/packages/web/src/elements/MapView.svelte @@ -22,8 +22,13 @@ import 'leaflet/dist/leaflet.css'; import leaflet from 'leaflet'; import wellknown from 'wellknown'; - import { isWktGeometry } from 'dbgate-tools'; + 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; @@ -31,6 +36,7 @@ let map; let selectionLayers = []; + let geoJson; function createColumnsTable(cells) { return `${cells.map(cell => ``).join('\n')}
${cell.column}${cell.value}
`; @@ -87,7 +93,7 @@ return; } - const geoJson = { + geoJson = { type: 'FeatureCollection', features, }; @@ -107,10 +113,10 @@ return leaflet.circleMarker(latlng, { radius: 7, weight: 2, - fillColor: '#ff7800', - color: '#ff7800', - opacity: 0.8, - fillOpacity: 0.4, + fillColor: '#ff0000', + color: '#ff0000', + opacity: 0.9, + fillOpacity: 0.9, }); }, onEachFeature: (feature, layer) => { @@ -151,10 +157,40 @@ selection; addSelectionToMap(); } + + function createMenu() { + return [ + { + text: 'Open on new tab', + onClick: () => { + openNewTab({ + title: 'Map', + icon: 'img map', + tabComponent: 'MapTab', + props: { + selection, + }, + }); + }, + }, + { + text: 'Save to file', + onClick: () => { + saveFileToDisk(async filePath => { + await apiCall('files/export-map', { + geoJson, + filePath, + }); + }); + }, + }, + ]; + }
{ From f2402cadb05975e87e3d2a8b0324f2d2d24573c7 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sat, 11 Jun 2022 18:41:17 +0200 Subject: [PATCH 08/14] show tooltip on map --- packages/api/src/utility/getMapExport.js | 3 ++- packages/web/src/elements/MapView.svelte | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/api/src/utility/getMapExport.js b/packages/api/src/utility/getMapExport.js index a4f7608c9..194354be4 100644 --- a/packages/api/src/utility/getMapExport.js +++ b/packages/api/src/utility/getMapExport.js @@ -18,7 +18,7 @@ const getMapExport = (geoJson) => { leaflet .tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, - attribution: '© OpenStreetMap', + attribution: 'DbGate | © OpenStreetMap', }) .addTo(map); @@ -47,6 +47,7 @@ const getMapExport = (geoJson) => { // does this feature have a property named popupContent? if (feature.properties && feature.properties.popupContent) { layer.bindPopup(feature.properties.popupContent); + layer.bindTooltip(feature.properties.popupContent); } }, }) diff --git a/packages/web/src/elements/MapView.svelte b/packages/web/src/elements/MapView.svelte index 2a45e8ef6..80161f05e 100644 --- a/packages/web/src/elements/MapView.svelte +++ b/packages/web/src/elements/MapView.svelte @@ -123,6 +123,7 @@ // does this feature have a property named popupContent? if (feature.properties && feature.properties.popupContent) { layer.bindPopup(feature.properties.popupContent); + layer.bindTooltip(feature.properties.popupContent); } }, }) From fa0680a8eef385688905834b8d500ef8a18b2cbd Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sat, 11 Jun 2022 18:43:19 +0200 Subject: [PATCH 09/14] changed export map name --- packages/web/src/elements/MapView.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web/src/elements/MapView.svelte b/packages/web/src/elements/MapView.svelte index 80161f05e..c6eff57a7 100644 --- a/packages/web/src/elements/MapView.svelte +++ b/packages/web/src/elements/MapView.svelte @@ -175,7 +175,7 @@ }, }, { - text: 'Save to file', + text: 'Export to HTML file', onClick: () => { saveFileToDisk(async filePath => { await apiCall('files/export-map', { From 34496ced0ef23657bc4c99bbe31d50d917f4d1dc Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sat, 11 Jun 2022 19:19:50 +0200 Subject: [PATCH 10/14] support for geograpghy view in mssql --- packages/sqltree/src/dumpSqlExpression.ts | 11 +++++++++-- packages/sqltree/src/evaluateExpression.ts | 3 +++ packages/sqltree/src/types.ts | 8 ++++++++ packages/tools/src/stringTools.ts | 12 +++--------- .../dbgate-plugin-mssql/src/frontend/driver.js | 17 +++++++++++++++++ 5 files changed, 40 insertions(+), 11 deletions(-) diff --git a/packages/sqltree/src/dumpSqlExpression.ts b/packages/sqltree/src/dumpSqlExpression.ts index 433bb740f..7cd279a2f 100644 --- a/packages/sqltree/src/dumpSqlExpression.ts +++ b/packages/sqltree/src/dumpSqlExpression.ts @@ -35,17 +35,24 @@ export function dumpSqlExpression(dmp: SqlDumper, expr: Expression) { dmp.put(')'); 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': dmp.transform(expr.transform, () => dumpSqlExpression(dmp, expr.expr)); break; case 'rowNumber': - dmp.put(" ^row_number() ^over (^order ^by "); + dmp.put(' ^row_number() ^over (^order ^by '); dmp.putCollection(', ', expr.orderBy, x => { dumpSqlExpression(dmp, x); dmp.put(' %k', x.direction); }); - dmp.put(")"); + dmp.put(')'); break; } } diff --git a/packages/sqltree/src/evaluateExpression.ts b/packages/sqltree/src/evaluateExpression.ts index 5be138ec8..2f99943c6 100644 --- a/packages/sqltree/src/evaluateExpression.ts +++ b/packages/sqltree/src/evaluateExpression.ts @@ -20,6 +20,9 @@ export function evaluateExpression(expr: Expression, values) { case 'call': return null; + case 'methodCall': + return null; + case 'transform': return null; } diff --git a/packages/sqltree/src/types.ts b/packages/sqltree/src/types.ts index 44f80b824..4f7996c2c 100644 --- a/packages/sqltree/src/types.ts +++ b/packages/sqltree/src/types.ts @@ -155,6 +155,13 @@ export interface CallExpression { argsPrefix?: string; // DISTINCT in case of COUNT DISTINCT } +export interface MethodCallExpression { + exprType: 'methodCall'; + method: string; + args: Expression[]; + thisObject: Expression; +} + export interface TranformExpression { exprType: 'transform'; expr: Expression; @@ -172,6 +179,7 @@ export type Expression = | PlaceholderExpression | RawExpression | CallExpression + | MethodCallExpression | TranformExpression | RowNumberExpression; export type OrderByExpression = Expression & { direction: 'ASC' | 'DESC' }; diff --git a/packages/tools/src/stringTools.ts b/packages/tools/src/stringTools.ts index 9e3d2d0f4..0b5f7bbf1 100644 --- a/packages/tools/src/stringTools.ts +++ b/packages/tools/src/stringTools.ts @@ -88,14 +88,8 @@ export function getIconForRedisType(type) { export function isWktGeometry(s) { if (!_isString(s)) return false; - return ( - s.startsWith('POINT(') || - s.startsWith('LINESTRING(') || - s.startsWith('POLYGON(') || - s.startsWith('MULTIPOINT(') || - s.startsWith('MULTILINESTRING(') || - s.startsWith('MULTIPOLYGON(') || - s.startsWith('GEOMCOLLECTION(') || - s.startsWith('GEOMETRYCOLLECTION(') + // return !!s.match(/^POINT\s*\(|/) + return !!s.match( + /^POINT\s*\(|^LINESTRING\s*\(|^POLYGON\s*\(|^MULTIPOINT\s*\(|^MULTILINESTRING\s*\(|^MULTIPOLYGON\s*\(|^GEOMCOLLECTION\s*\(|^GEOMETRYCOLLECTION\s*\(/ ); } diff --git a/plugins/dbgate-plugin-mssql/src/frontend/driver.js b/plugins/dbgate-plugin-mssql/src/frontend/driver.js index 3acb4612e..3f7c8b56d 100644 --- a/plugins/dbgate-plugin-mssql/src/frontend/driver.js +++ b/plugins/dbgate-plugin-mssql/src/frontend/driver.js @@ -2,6 +2,8 @@ const { driverBase } = global.DBGATE_TOOLS; const MsSqlDumper = require('./MsSqlDumper'); const { mssqlSplitterOptions } = require('dbgate-query-splitter/lib/options'); +const spatialTypes = ['GEOGRAPHY']; + /** @type {import('dbgate-types').SqlDialect} */ const dialect = { limitSelect: true, @@ -70,6 +72,21 @@ const dialect = { 'image', '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} */ From 2944d0fa39feb4d3ab702d8593bfeecce4a7a049 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sat, 11 Jun 2022 22:21:09 +0200 Subject: [PATCH 11/14] postgis - analyse geo columns, show in map --- .../src/backend/Analyser.js | 51 ++++++++++++++----- .../src/backend/sql/geographyColumns.js | 8 +++ .../src/backend/sql/geometryColumns.js | 8 +++ .../src/backend/sql/index.js | 4 ++ .../src/frontend/drivers.js | 19 +++++++ 5 files changed, 78 insertions(+), 12 deletions(-) create mode 100644 plugins/dbgate-plugin-postgres/src/backend/sql/geographyColumns.js create mode 100644 plugins/dbgate-plugin-postgres/src/backend/sql/geometryColumns.js diff --git a/plugins/dbgate-plugin-postgres/src/backend/Analyser.js b/plugins/dbgate-plugin-postgres/src/backend/Analyser.js index 01485b0c2..1fd2f0f51 100644 --- a/plugins/dbgate-plugin-postgres/src/backend/Analyser.js +++ b/plugins/dbgate-plugin-postgres/src/backend/Analyser.js @@ -11,21 +11,36 @@ function normalizeTypeName(dataType) { return dataType; } -function getColumnInfo({ - is_nullable, - column_name, - data_type, - char_max_length, - numeric_precision, - numeric_ccale, - default_value, -}) { +function getColumnInfo( + { is_nullable, column_name, data_type, char_max_length, numeric_precision, numeric_ccale, default_value }, + table = undefined, + geometryColumns = undefined, + geographyColumns = undefined +) { const normDataType = normalizeTypeName(data_type); let fullDataType = normDataType; if (char_max_length && isTypeString(normDataType)) fullDataType = `${normDataType}(${char_max_length})`; if (numeric_precision && numeric_ccale && isTypeNumeric(normDataType)) fullDataType = `${normDataType}(${numeric_precision},${numeric_ccale})`; 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 { columnName: column_name, dataType: fullDataType, @@ -145,6 +160,18 @@ class Analyser extends DatabaseAnalyser { : await this.driver.query(this.pool, this.createQuery('indexcols', ['tables'])); this.feedback({ analysingMessage: 'Loading unique names' }); 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.driver.query(this.pool, 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.driver.query(this.pool, this.createQuery('geographyColumns', ['tables'])); + } + this.feedback({ analysingMessage: 'Finalizing DB structure' }); const columnColumnsMapped = fkColumns.rows.map(x => ({ @@ -179,7 +206,7 @@ class Analyser extends DatabaseAnalyser { ...newTable, columns: columns.rows .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), foreignKeys: DatabaseAnalyser.extractForeignKeys(newTable, columnColumnsMapped), 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}`, columns: columns.rows .filter(col => col.pure_name == view.pure_name && col.schema_name == view.schema_name) - .map(getColumnInfo), + .map(col => getColumnInfo(col)), })), matviews: matviews ? 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}`, columns: matviewColumns.rows .filter(col => col.pure_name == matview.pure_name && col.schema_name == matview.schema_name) - .map(getColumnInfo), + .map(col => getColumnInfo(col)), })) : undefined, procedures: routines.rows diff --git a/plugins/dbgate-plugin-postgres/src/backend/sql/geographyColumns.js b/plugins/dbgate-plugin-postgres/src/backend/sql/geographyColumns.js new file mode 100644 index 000000000..343d9c5d6 --- /dev/null +++ b/plugins/dbgate-plugin-postgres/src/backend/sql/geographyColumns.js @@ -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 +`; \ No newline at end of file diff --git a/plugins/dbgate-plugin-postgres/src/backend/sql/geometryColumns.js b/plugins/dbgate-plugin-postgres/src/backend/sql/geometryColumns.js new file mode 100644 index 000000000..94b8d5516 --- /dev/null +++ b/plugins/dbgate-plugin-postgres/src/backend/sql/geometryColumns.js @@ -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 +`; \ No newline at end of file diff --git a/plugins/dbgate-plugin-postgres/src/backend/sql/index.js b/plugins/dbgate-plugin-postgres/src/backend/sql/index.js index b029fb6f2..4c2f500a4 100644 --- a/plugins/dbgate-plugin-postgres/src/backend/sql/index.js +++ b/plugins/dbgate-plugin-postgres/src/backend/sql/index.js @@ -13,6 +13,8 @@ const matviewColumns = require('./matviewColumns'); const indexes = require('./indexes'); const indexcols = require('./indexcols'); const uniqueNames = require('./uniqueNames'); +const geometryColumns = require('./geometryColumns'); +const geographyColumns = require('./geographyColumns'); const fk_keyColumnUsage = require('./fk_key_column_usage'); const fk_referentialConstraints = require('./fk_referential_constraints'); @@ -37,4 +39,6 @@ module.exports = { indexes, indexcols, uniqueNames, + geometryColumns, + geographyColumns, }; diff --git a/plugins/dbgate-plugin-postgres/src/frontend/drivers.js b/plugins/dbgate-plugin-postgres/src/frontend/drivers.js index d533ea3ea..5d60186ff 100644 --- a/plugins/dbgate-plugin-postgres/src/frontend/drivers.js +++ b/plugins/dbgate-plugin-postgres/src/frontend/drivers.js @@ -2,6 +2,8 @@ const { driverBase } = global.DBGATE_TOOLS; const Dumper = require('./Dumper'); const { postgreSplitterOptions } = require('dbgate-query-splitter/lib/options'); +const spatialTypes = ['GEOGRAPHY']; + /** @type {import('dbgate-types').SqlDialect} */ const dialect = { rangeSelect: true, @@ -78,6 +80,23 @@ const dialect = { 'uuid', '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 = { From 42200ec04a313eb8e98338edf82a02ae4176c17e Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sat, 11 Jun 2022 22:21:35 +0200 Subject: [PATCH 12/14] v5.0.4-beta.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a5083d7e2..3f0baabf4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "5.0.3", + "version": "5.0.4-beta.1", "name": "dbgate-all", "workspaces": [ "packages/*", From 6794b79d0e3094e090f0a7cb37ead4d9fba11389 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sun, 12 Jun 2022 07:30:44 +0200 Subject: [PATCH 13/14] postgre fix --- plugins/dbgate-plugin-postgres/src/backend/Analyser.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/dbgate-plugin-postgres/src/backend/Analyser.js b/plugins/dbgate-plugin-postgres/src/backend/Analyser.js index 1fd2f0f51..90d8563ec 100644 --- a/plugins/dbgate-plugin-postgres/src/backend/Analyser.js +++ b/plugins/dbgate-plugin-postgres/src/backend/Analyser.js @@ -162,14 +162,14 @@ class Analyser extends DatabaseAnalyser { 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'))) { + if (views.rows.find(x => x.pure_name == 'geometry_columns' && x.schema_name == 'public')) { this.feedback({ analysingMessage: 'Loading geometry columns' }); - geometryColumns = await this.driver.query(this.pool, this.createQuery('geometryColumns', ['tables'])); + 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'))) { + if (views.rows.find(x => x.pure_name == 'geography_columns' && x.schema_name == 'public')) { this.feedback({ analysingMessage: 'Loading geography columns' }); - geographyColumns = await this.driver.query(this.pool, this.createQuery('geographyColumns', ['tables'])); + geographyColumns = await this.safeQuery(this.createQuery('geographyColumns', ['tables'])); } this.feedback({ analysingMessage: 'Finalizing DB structure' }); From c104122a501b7748b1ae7832aea4b514caee001a Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sun, 12 Jun 2022 08:15:35 +0200 Subject: [PATCH 14/14] default value support in table yaml files #296 --- .../__tests__/deploy-database.spec.js | 29 +++++++++++++++++++ integration-tests/engines.js | 4 +++ packages/tools/src/yamlModelConv.ts | 4 +++ 3 files changed, 37 insertions(+) diff --git a/integration-tests/__tests__/deploy-database.spec.js b/integration-tests/__tests__/deploy-database.spec.js index 8b41b322e..416f904f5 100644 --- a/integration-tests/__tests__/deploy-database.spec.js +++ b/integration-tests/__tests__/deploy-database.spec.js @@ -297,4 +297,33 @@ describe('Deploy database', () => { 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'); + }) + ); }); diff --git a/integration-tests/engines.js b/integration-tests/engines.js index 5e8b57c00..8f10950d5 100644 --- a/integration-tests/engines.js +++ b/integration-tests/engines.js @@ -141,6 +141,10 @@ const filterLocal = [ '-CockroachDB', ]; +const enginesPostgre = engines.filter(x => x.label == 'PostgreSQL'); + module.exports = process.env.CITEST ? engines.filter(x => !x.skipOnCI) : engines.filter(x => filterLocal.find(y => x.label == y)); + +module.exports.enginesPostgre = enginesPostgre; diff --git a/packages/tools/src/yamlModelConv.ts b/packages/tools/src/yamlModelConv.ts index d22fbd4f1..bf0c57982 100644 --- a/packages/tools/src/yamlModelConv.ts +++ b/packages/tools/src/yamlModelConv.ts @@ -1,4 +1,5 @@ import { ColumnInfo, TableInfo, ForeignKeyInfo, DatabaseInfo } from 'dbgate-types'; +import { StringNullableChain } from 'lodash'; import _cloneDeep from 'lodash/cloneDeep'; import _compact from 'lodash/compact'; import { DatabaseAnalyser } from './DatabaseAnalyser'; @@ -11,6 +12,7 @@ export interface ColumnInfoYaml { autoIncrement?: boolean; references?: string; primaryKey?: boolean; + default?: string; } export interface DatabaseModelFile { @@ -39,6 +41,7 @@ function columnInfoToYaml(column: ColumnInfo, table: TableInfo): ColumnInfoYaml const res: ColumnInfoYaml = { name: column.columnName, type: column.dataType, + default: column.defaultValue, }; if (column.autoIncrement) res.autoIncrement = 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, autoIncrement: column.autoIncrement, notNull: column.notNull || (table.primaryKey && table.primaryKey.includes(column.name)), + defaultValue: column.default, }; return res; }