const { driverBase } = global.DBGATE_PACKAGES['dbgate-tools'];
const Dumper = require('./Dumper');
const { postgreSplitterOptions } = require('dbgate-query-splitter/lib/options');
const postgresIcon = ''
const cockroachIcon = '';
const redshiftIcon = '';
const spatialTypes = ['GEOGRAPHY', 'GEOMETRY'];
/** @type {import('dbgate-types').SqlDialect} */
const dialect = {
rangeSelect: true,
ilike: true,
defaultSchemaName: 'public',
multipleSchema: true,
// stringEscapeChar: '\\',
stringEscapeChar: "'",
fallbackDataType: 'varchar',
anonymousPrimaryKey: false,
enableConstraintsPerTable: true,
dropColumnDependencies: ['dependencies'],
quoteIdentifier(s) {
return '"' + s + '"';
},
stringAgg: true,
createColumn: true,
dropColumn: true,
changeColumn: true,
createIndex: true,
dropIndex: true,
createForeignKey: true,
dropForeignKey: true,
createPrimaryKey: true,
dropPrimaryKey: true,
createUnique: true,
dropUnique: true,
createCheck: true,
dropCheck: true,
allowMultipleValuesInsert: true,
renameSqlObject: true,
filteredIndexes: true,
dropReferencesWhenDropTable: true,
requireStandaloneSelectForScopeIdentity: true,
predefinedDataTypes: [
'bigint',
'bigserial',
'bit',
'varbit',
'boolean',
'box',
'bytea',
'char(20)',
'varchar(250)',
'cidr',
'circle',
'date',
'double precision',
'inet',
'int',
'interval',
'json',
'jsonb',
'line',
'lseg',
'macaddr',
'macaddr8',
'money',
'numeric(10,2)',
'path',
'pg_lsn',
'pg_snapshot',
'point',
'polygon',
'real',
'smallint',
'smallserial',
'serial',
'text',
'time',
'timetz',
'timestamp',
'timestamptz',
'tsquery',
'tsvector',
'txid_snapshot',
'uuid',
'xml',
],
createColumnViewExpression(columnName, dataType, source, alias, purpose) {
if (dataType && spatialTypes.includes(dataType.toUpperCase())) {
return {
exprType: 'call',
func: 'ST_AsText',
alias: alias || columnName,
args: [
{
exprType: 'column',
columnName,
source,
},
],
};
}
if (dataType?.toLowerCase() == 'uuid' || (purpose == 'filter' && dataType?.toLowerCase()?.startsWith('json'))) {
return {
exprType: 'unaryRaw',
expr: {
exprType: 'column',
source,
columnName,
},
afterSql: '::text',
alias: alias || columnName,
};
}
},
};
const postgresDriverBase = {
...driverBase,
supportsTransactions: true,
supportsIncrementalAnalysis: true,
dumperClass: Dumper,
dialect,
// showConnectionField: (field, values) =>
// ['server', 'port', 'user', 'password', 'defaultDatabase', 'singleDatabase'].includes(field),
getQuerySplitterOptions: usage =>
usage == 'editor'
? { ...postgreSplitterOptions, ignoreComments: true, preventSingleLineSplit: true }
: usage == 'import'
? {
...postgreSplitterOptions,
copyFromStdin: true,
}
: postgreSplitterOptions,
readOnlySessions: true,
databaseUrlPlaceholder: 'e.g. postgresql://user:password@localhost:5432/default_database',
showConnectionField: (field, values) => {
const allowedFields = ['useDatabaseUrl', 'authType', 'user', 'isReadOnly', 'useSeparateSchemas'];
if (values.authType == 'awsIam') {
allowedFields.push('awsRegion', 'secretAccessKey', 'accessKeyId');
}
if (values.authType == 'socket') {
allowedFields.push('socketPath');
} else {
if (values.useDatabaseUrl) {
allowedFields.push('databaseUrl');
} else {
allowedFields.push('server', 'port');
}
}
if (values.authType != 'awsIam' && values.authType != 'socket') {
allowedFields.push('password');
}
if (!values.useDatabaseUrl) {
allowedFields.push('defaultDatabase', 'singleDatabase');
}
return allowedFields.includes(field);
},
beforeConnectionSave: connection => {
const { databaseUrl } = connection;
if (databaseUrl) {
const m = databaseUrl.match(/\/([^/]+)($|\?)/);
return {
...connection,
singleDatabase: !!m,
defaultDatabase: m ? m[1] : null,
};
}
return connection;
},
__analyserInternals: {},
getNewObjectTemplates() {
return [
{ label: 'New view', sql: 'CREATE VIEW myview\nAS\nSELECT * FROM table1' },
{ label: 'New materialized view', sql: 'CREATE MATERIALIZED VIEW myview\nAS\nSELECT * FROM table1' },
{
label: 'New procedure',
sql: `CREATE PROCEDURE myproc (arg1 INT)
LANGUAGE SQL
AS $$
SELECT * FROM table1;
$$`,
},
{
label: 'New function (plpgsql)',
sql: `CREATE FUNCTION myfunc (arg1 INT)
RETURNS INT
AS $$
BEGIN
RETURN 1;
END
$$ LANGUAGE plpgsql;`,
},
{
label: 'New trigger',
sql: `CREATE TRIGGER trigger_name
BEFORE INSERT ON table_name
FOR EACH ROW
EXECUTE FUNCTION function_name();`,
},
];
},
authTypeLabel: 'Connection mode',
defaultAuthTypeName: 'hostPort',
defaultSocketPath: '/var/run/postgresql',
supportsDatabaseBackup: true,
supportsDatabaseRestore: true,
adaptDataType(dataType) {
if (dataType?.toLowerCase() == 'datetime') return 'timestamp';
return dataType;
},
getCliConnectionArgs(connection) {
const args = [`--username=${connection.user}`, `--host=${connection.server}`];
if (connection.port) {
args.push(`--port=${connection.port}`);
}
return args;
},
getNativeOperationFormArgs(operation) {
if (operation == 'backup') {
return [
{
type: 'checkbox',
label: 'Dump only data (without structure)',
name: 'dataOnly',
default: false,
},
{
type: 'checkbox',
label: 'Dump schema only (no data)',
name: 'schemaOnly',
default: false,
},
{
type: 'checkbox',
label: 'Use SQL insert instead of COPY for rows',
name: 'insert',
default: false,
},
{
type: 'checkbox',
label: 'Prevent dumping of access privileges (grant/revoke)',
name: 'noPrivileges',
default: false,
},
{
type: 'checkbox',
label: 'Do not output commands to set ownership of objects ',
name: 'noOwner',
default: false,
},
{
type: 'text',
label: 'Custom arguments',
name: 'customArgs',
},
];
}
return null;
},
backupDatabaseCommand(connection, settings, externalTools) {
const { outputFile, database, selectedTables, skippedTables, options, argsFormat } = settings;
const command = externalTools.pg_dump || 'pg_dump';
const args = this.getCliConnectionArgs(connection, externalTools);
args.push(`--file=${outputFile}`);
args.push('--verbose');
args.push(database);
if (options.dataOnly) {
args.push(`--data-only`);
}
if (options.schemaOnly) {
args.push(`--schema-only`);
}
if (options.insert) {
args.push(`--insert`);
}
if (options.noPrivileges) {
args.push(`--no-privileges`);
}
if (options.noOwner) {
args.push(`--no-owner`);
}
if (skippedTables.length > 0) {
for (const table of selectedTables) {
args.push(
argsFormat == 'spawn'
? `--table="${table.schemaName}"."${table.pureName}"`
: `--table='"${table.schemaName}"."${table.pureName}"'`
);
}
}
if (options.customArgs?.trim()) {
const customArgs = options.customArgs.split(/\s+/).filter(arg => arg.trim() != '');
args.push(...customArgs);
}
return {
command,
args,
env: { PGPASSWORD: connection.password },
};
},
restoreDatabaseCommand(connection, settings, externalTools) {
const { inputFile, database } = settings;
const command = externalTools.psql || 'psql';
const args = this.getCliConnectionArgs(connection, externalTools);
args.push(`--dbname=${database}`);
// args.push('--verbose');
args.push(`--file=${inputFile}`);
return {
command,
args,
env: { PGPASSWORD: connection.password },
};
},
transformNativeCommandMessage(message) {
if (message.message.startsWith('INSERT ') || message.message == 'SET') {
return null;
}
if (message.message.startsWith('pg_dump: processing data for table')) {
return {
...message,
severity: 'info',
message: message.message.replace('pg_dump: processing data for table', 'Processing table'),
};
} else if (message.message.toLowerCase().includes('error:')) {
return {
...message,
severity: 'error',
};
} else {
return {
...message,
severity: 'debug',
};
}
return message;
},
};
/** @type {import('dbgate-types').EngineDriver} */
const postgresDriver = {
...postgresDriverBase,
supportsServerSummary: true,
engine: 'postgres@dbgate-plugin-postgres',
title: 'PostgreSQL',
defaultPort: 5432,
dialect: {
...dialect,
materializedViews: true,
},
icon : postgresIcon,
dialectByVersion(version) {
if (version) {
return {
...dialect,
materializedViews:
version &&
version.versionMajor != null &&
version.versionMinor != null &&
(version.versionMajor > 9 || version.versionMajor == 9 || version.versionMinor >= 3),
isFipsComplianceOn: version.isFipsComplianceOn,
};
}
return dialect;
},
};
/** @type {import('dbgate-types').EngineDriver} */
const cockroachDriver = {
...postgresDriverBase,
supportsServerSummary: true,
engine: 'cockroach@dbgate-plugin-postgres',
title: 'CockroachDB',
defaultPort: 26257,
dialect: {
...dialect,
materializedViews: true,
dropColumnDependencies: ['primaryKey', 'dependencies'],
dropPrimaryKey: false,
},
__analyserInternals: {},
icon: cockroachIcon,
};
/** @type {import('dbgate-types').EngineDriver} */
const redshiftDriver = {
...postgresDriverBase,
supportsServerSummary: true,
dialect: {
...dialect,
stringAgg: false,
},
__analyserInternals: {
skipIndexes: true,
},
engine: 'redshift@dbgate-plugin-postgres',
title: 'Amazon Redshift',
defaultPort: 5439,
premiumOnly: true,
databaseUrlPlaceholder: 'e.g. redshift-cluster-1.xxxx.redshift.amazonaws.com:5439/dev',
icon: redshiftIcon,
showConnectionField: (field, values) =>
['databaseUrl', 'user', 'password', 'isReadOnly', 'useSeparateSchemas'].includes(field),
beforeConnectionSave: connection => {
const { databaseUrl } = connection;
if (databaseUrl) {
const m = databaseUrl.match(/\/([^/]+)$/);
if (m) {
return {
...connection,
singleDatabase: true,
defaultDatabase: m[1],
// displayName: connection.displayName || `${m[1]} on Amazon Redshift`,
};
}
}
return connection;
},
};
module.exports = [postgresDriver, cockroachDriver, redshiftDriver];