This commit is contained in:
SPRINX0\prochazka
2025-02-25 10:53:19 +01:00
32 changed files with 512 additions and 110 deletions

View File

@@ -0,0 +1,7 @@
module.exports = {
mysql: true,
postgres: true,
mssql: true,
oracle: true,
// sqlite: true,
};

View File

@@ -0,0 +1,98 @@
const localconfig = require('../../.localconfig');
const { formatQueryWithoutParams } = require('dbgate-tools');
global.DBGATE_PACKAGES = {
'dbgate-tools': require('dbgate-tools'),
};
function requireEngineDriver(engine) {
const [shortName, packageName] = engine.split('@');
const plugin = require(`../../../plugins/${packageName}/src/frontend/index`);
if (plugin.drivers) {
return plugin.drivers.find(x => x.engine == engine);
}
throw new Error(`Could not find engine driver ${engine}`);
}
Cypress.on('uncaught:exception', (err, runnable) => {
// if the error message matches the one about WorkerGlobalScope importScripts
if (err.message.includes("Failed to execute 'importScripts' on 'WorkerGlobalScope'")) {
// return false to let Cypress know we intentionally want to ignore this error
return false;
}
// otherwise let Cypress throw the error
});
beforeEach(() => {
cy.visit('http://localhost:3000');
cy.viewport(1250, 900);
});
function multiTest(testName, testDefinition) {
if (localconfig.mysql) {
it(testName + ' MySQL', () => testDefinition('MySql-connection', 'mysql@dbgate-plugin-mysql'));
}
if (localconfig.postgres) {
it(testName + ' Postgres', () => testDefinition('Postgres-connection', 'postgres@dbgate-plugin-postgres'));
}
if (localconfig.mssql) {
it(testName + ' Mssql', () => testDefinition('Mssql-connection', 'mssql@dbgate-plugin-mssql'));
}
if (localconfig.oracle) {
it(testName + ' Oracle', () =>
testDefinition('Oracle-connection', 'oracle@dbgate-plugin-oracle', {
databaseName: 'C##MY_GUITAR_SHOP',
implicitTransactions: true,
})
);
}
}
describe('Mutli-sql tests', () => {
multiTest('Transactions', (connectionName, engine, options = {}) => {
const driver = requireEngineDriver(engine);
const databaseName = options.databaseName ?? 'my_guitar_shop';
const implicitTransactions = options.implicitTransactions ?? false;
cy.contains(connectionName).click();
cy.contains(databaseName).click();
cy.testid('TabsPanel_buttonNewQuery').click();
cy.wait(1000);
cy.get('body').type(
formatQueryWithoutParams(driver, "INSERT INTO ~categories (~category_id, ~category_name) VALUES (5, 'test');")
);
// rollback
if (!implicitTransactions) {
cy.testid('QueryTab_beginTransactionButton').click();
cy.contains('Begin Transaction finished');
}
cy.testid('QueryTab_executeButton').click();
cy.contains('Query execution finished');
cy.testid('QueryTab_rollbackTransactionButton').click();
cy.contains('Rollback Transaction finished');
// should contain 4 rows
cy.testid('SqlObjectList_container').contains('categories').click();
cy.contains('Guitars').click();
cy.testid('TableDataTab_refreshGrid').click();
cy.contains('Rows: 4');
// commit
cy.contains('Query #1').click();
if (!implicitTransactions) {
cy.testid('QueryTab_beginTransactionButton').click();
cy.contains('Begin Transaction finished');
}
cy.testid('QueryTab_executeButton').click();
cy.contains('Query execution finished');
cy.testid('QueryTab_commitTransactionButton').click();
cy.contains('Commit Transaction finished');
// should contain 5 rows
cy.testid('SqlObjectList_container').contains('categories').click();
cy.contains('Guitars').click();
cy.testid('TableDataTab_refreshGrid').click();
cy.contains('Rows: 5');
});
});

View File

@@ -1,16 +0,0 @@
Cypress.on('uncaught:exception', (err, runnable) => {
// if the error message matches the one about WorkerGlobalScope importScripts
if (err.message.includes("Failed to execute 'importScripts' on 'WorkerGlobalScope'")) {
// return false to let Cypress know we intentionally want to ignore this error
return false;
}
// otherwise let Cypress throw the error
});
beforeEach(() => {
cy.visit('http://localhost:3000');
cy.viewport(1250, 900);
});
describe('Data browser data', () => {
});

View File

@@ -3,7 +3,6 @@ columns:
- name: category_id - name: category_id
type: int type: int
default: null default: null
autoIncrement: true
notNull: true notNull: true
- name: category_name - name: category_name
type: varchar(255) type: varchar(255)

View File

@@ -1,10 +1,10 @@
{"__isStreamHeader":true,"pureName":"orders","tableRowCount":"9","tableEngine":"InnoDB","objectComment":"","modifyDate":"2025-02-03 02:56:32","objectId":"orders","contentHash":"2025-02-03 02:56:32","columns":[{"notNull":true,"autoIncrement":true,"columnName":"order_id","columnComment":"","dataType":"int","defaultValue":null,"isUnsigned":false,"isZerofill":false},{"notNull":true,"autoIncrement":false,"columnName":"customer_id","columnComment":"","dataType":"int","defaultValue":null,"isUnsigned":false,"isZerofill":false},{"notNull":true,"autoIncrement":false,"columnName":"order_date","columnComment":"","dataType":"datetime","defaultValue":null,"isUnsigned":false,"isZerofill":false},{"notNull":true,"autoIncrement":false,"columnName":"ship_amount","columnComment":"","dataType":"decimal(10,2)","defaultValue":null,"isUnsigned":false,"isZerofill":false},{"notNull":true,"autoIncrement":false,"columnName":"tax_amount","columnComment":"","dataType":"decimal(10,2)","defaultValue":null,"isUnsigned":false,"isZerofill":false},{"notNull":false,"autoIncrement":false,"columnName":"ship_date","columnComment":"","dataType":"datetime","defaultValue":null,"isUnsigned":false,"isZerofill":false},{"notNull":true,"autoIncrement":false,"columnName":"ship_address_id","columnComment":"","dataType":"int","defaultValue":null,"isUnsigned":false,"isZerofill":false},{"notNull":true,"autoIncrement":false,"columnName":"card_type","columnComment":"","dataType":"varchar(50)","defaultValue":null,"isUnsigned":false,"isZerofill":false},{"notNull":true,"autoIncrement":false,"columnName":"card_number","columnComment":"","dataType":"char(16)","defaultValue":null,"isUnsigned":false,"isZerofill":false},{"notNull":true,"autoIncrement":false,"columnName":"card_expires","columnComment":"","dataType":"char(7)","defaultValue":null,"isUnsigned":false,"isZerofill":false},{"notNull":true,"autoIncrement":false,"columnName":"billing_address_id","columnComment":"","dataType":"int","defaultValue":null,"isUnsigned":false,"isZerofill":false}],"primaryKey":{"constraintName":"PRIMARY","pureName":"orders","constraintType":"primaryKey","columns":[{"columnName":"order_id"}]},"foreignKeys":[{"constraintName":"orders_fk_customers","constraintType":"foreignKey","pureName":"orders","refTableName":"customers","updateAction":"NO ACTION","deleteAction":"NO ACTION","columns":[{"columnName":"customer_id","refColumnName":"customer_id"}]}],"indexes":[{"constraintName":"orders_fk_customers","indexType":"BTREE","isUnique":false,"columns":[{"columnName":"customer_id","isDescending":0}]}],"uniques":[],"engine":"mysql@dbgate-plugin-mysql"} {"__isStreamHeader":true,"pureName":"orders","tableRowCount":"9","tableEngine":"InnoDB","objectComment":"","modifyDate":"2025-02-03T02:56:32","objectId":"orders","contentHash":"2025-02-03 02:56:32","columns":[{"notNull":true,"autoIncrement":true,"columnName":"order_id","columnComment":"","dataType":"int","defaultValue":null,"isUnsigned":false,"isZerofill":false},{"notNull":true,"autoIncrement":false,"columnName":"customer_id","columnComment":"","dataType":"int","defaultValue":null,"isUnsigned":false,"isZerofill":false},{"notNull":true,"autoIncrement":false,"columnName":"order_date","columnComment":"","dataType":"datetime","defaultValue":null,"isUnsigned":false,"isZerofill":false},{"notNull":true,"autoIncrement":false,"columnName":"ship_amount","columnComment":"","dataType":"decimal(10,2)","defaultValue":null,"isUnsigned":false,"isZerofill":false},{"notNull":true,"autoIncrement":false,"columnName":"tax_amount","columnComment":"","dataType":"decimal(10,2)","defaultValue":null,"isUnsigned":false,"isZerofill":false},{"notNull":false,"autoIncrement":false,"columnName":"ship_date","columnComment":"","dataType":"datetime","defaultValue":null,"isUnsigned":false,"isZerofill":false},{"notNull":true,"autoIncrement":false,"columnName":"ship_address_id","columnComment":"","dataType":"int","defaultValue":null,"isUnsigned":false,"isZerofill":false},{"notNull":true,"autoIncrement":false,"columnName":"card_type","columnComment":"","dataType":"varchar(50)","defaultValue":null,"isUnsigned":false,"isZerofill":false},{"notNull":true,"autoIncrement":false,"columnName":"card_number","columnComment":"","dataType":"char(16)","defaultValue":null,"isUnsigned":false,"isZerofill":false},{"notNull":true,"autoIncrement":false,"columnName":"card_expires","columnComment":"","dataType":"char(7)","defaultValue":null,"isUnsigned":false,"isZerofill":false},{"notNull":true,"autoIncrement":false,"columnName":"billing_address_id","columnComment":"","dataType":"int","defaultValue":null,"isUnsigned":false,"isZerofill":false}],"primaryKey":{"constraintName":"PRIMARY","pureName":"orders","constraintType":"primaryKey","columns":[{"columnName":"order_id"}]},"foreignKeys":[{"constraintName":"orders_fk_customers","constraintType":"foreignKey","pureName":"orders","refTableName":"customers","updateAction":"NO ACTION","deleteAction":"NO ACTION","columns":[{"columnName":"customer_id","refColumnName":"customer_id"}]}],"indexes":[{"constraintName":"orders_fk_customers","indexType":"BTREE","isUnique":false,"columns":[{"columnName":"customer_id","isDescending":0}]}],"uniques":[],"engine":"mysql@dbgate-plugin-mysql"}
{"order_id":1,"customer_id":1,"order_date":"2018-03-28 09:40:28","ship_amount":"5.00","tax_amount":"32.32","ship_date":"2018-03-30 15:32:51","ship_address_id":1,"card_type":"Visa","card_number":"4111111111111111","card_expires":"04/2020","billing_address_id":2} {"order_id":1,"customer_id":1,"order_date":"2018-03-28T09:40:28","ship_amount":"5.00","tax_amount":"32.32","ship_date":"2018-03-30T15:32:51","ship_address_id":1,"card_type":"Visa","card_number":"4111111111111111","card_expires":"04/2020","billing_address_id":2}
{"order_id":2,"customer_id":2,"order_date":"2018-03-28 11:23:20","ship_amount":"5.00","tax_amount":"0.00","ship_date":"2018-03-29 12:52:14","ship_address_id":3,"card_type":"Visa","card_number":"4012888888881881","card_expires":"08/2019","billing_address_id":3} {"order_id":2,"customer_id":2,"order_date":"2018-03-28T11:23:20","ship_amount":"5.00","tax_amount":"0.00","ship_date":"2018-03-29T12:52:14","ship_address_id":3,"card_type":"Visa","card_number":"4012888888881881","card_expires":"08/2019","billing_address_id":3}
{"order_id":3,"customer_id":1,"order_date":"2018-03-29 09:44:58","ship_amount":"10.00","tax_amount":"89.92","ship_date":"2018-03-31 09:11:41","ship_address_id":1,"card_type":"Visa","card_number":"4111111111111111","card_expires":"04/2017","billing_address_id":2} {"order_id":3,"customer_id":1,"order_date":"2018-03-29T09:44:58","ship_amount":"10.00","tax_amount":"89.92","ship_date":"2018-03-31T09:11:41","ship_address_id":1,"card_type":"Visa","card_number":"4111111111111111","card_expires":"04/2017","billing_address_id":2}
{"order_id":4,"customer_id":3,"order_date":"2018-03-30 15:22:31","ship_amount":"5.00","tax_amount":"0.00","ship_date":"2018-04-03 16:32:21","ship_address_id":4,"card_type":"American Express","card_number":"378282246310005","card_expires":"04/2016","billing_address_id":4} {"order_id":4,"customer_id":3,"order_date":"2018-03-30T15:22:31","ship_amount":"5.00","tax_amount":"0.00","ship_date":"2018-04-03T16:32:21","ship_address_id":4,"card_type":"American Express","card_number":"378282246310005","card_expires":"04/2016","billing_address_id":4}
{"order_id":5,"customer_id":4,"order_date":"2018-03-31 05:43:11","ship_amount":"5.00","tax_amount":"0.00","ship_date":"2018-04-02 14:21:12","ship_address_id":5,"card_type":"Visa","card_number":"4111111111111111","card_expires":"04/2019","billing_address_id":6} {"order_id":5,"customer_id":4,"order_date":"2018-03-31T05:43:11","ship_amount":"5.00","tax_amount":"0.00","ship_date":"2018-04-02T14:21:12","ship_address_id":5,"card_type":"Visa","card_number":"4111111111111111","card_expires":"04/2019","billing_address_id":6}
{"order_id":6,"customer_id":5,"order_date":"2018-03-31 18:37:22","ship_amount":"5.00","tax_amount":"0.00","ship_date":null,"ship_address_id":7,"card_type":"Discover","card_number":"6011111111111117","card_expires":"04/2019","billing_address_id":7} {"order_id":6,"customer_id":5,"order_date":"2018-03-31T18:37:22","ship_amount":"5.00","tax_amount":"0.00","ship_date":null,"ship_address_id":7,"card_type":"Discover","card_number":"6011111111111117","card_expires":"04/2019","billing_address_id":7}
{"order_id":7,"customer_id":6,"order_date":"2018-04-01 23:11:12","ship_amount":"15.00","tax_amount":"0.00","ship_date":"2018-04-03 10:21:35","ship_address_id":8,"card_type":"MasterCard","card_number":"5555555555554444","card_expires":"04/2019","billing_address_id":8} {"order_id":7,"customer_id":6,"order_date":"2018-04-01T23:11:12","ship_amount":"15.00","tax_amount":"0.00","ship_date":"2018-04-03T10:21:35","ship_address_id":8,"card_type":"MasterCard","card_number":"5555555555554444","card_expires":"04/2019","billing_address_id":8}
{"order_id":8,"customer_id":7,"order_date":"2018-04-02 11:26:38","ship_amount":"5.00","tax_amount":"0.00","ship_date":null,"ship_address_id":9,"card_type":"Visa","card_number":"4012888888881881","card_expires":"04/2019","billing_address_id":10} {"order_id":8,"customer_id":7,"order_date":"2018-04-02T11:26:38","ship_amount":"5.00","tax_amount":"0.00","ship_date":null,"ship_address_id":9,"card_type":"Visa","card_number":"4012888888881881","card_expires":"04/2019","billing_address_id":10}
{"order_id":9,"customer_id":4,"order_date":"2018-04-03 12:22:31","ship_amount":"5.00","tax_amount":"0.00","ship_date":null,"ship_address_id":5,"card_type":"Visa","card_number":"4111111111111111","card_expires":"04/2019","billing_address_id":6} {"order_id":9,"customer_id":4,"order_date":"2018-04-03T12:22:31","ship_amount":"5.00","tax_amount":"0.00","ship_date":null,"ship_address_id":5,"card_type":"Visa","card_number":"4111111111111111","card_expires":"04/2019","billing_address_id":6}

View File

@@ -1,11 +1,11 @@
{"__isStreamHeader":true,"pureName":"products","tableRowCount":"10","tableEngine":"InnoDB","objectComment":"","modifyDate":"2025-02-03 02:56:32","objectId":"products","contentHash":"2025-02-03 02:56:32","columns":[{"notNull":true,"autoIncrement":true,"columnName":"product_id","columnComment":"","dataType":"int","defaultValue":null,"isUnsigned":false,"isZerofill":false},{"notNull":true,"autoIncrement":false,"columnName":"category_id","columnComment":"","dataType":"int","defaultValue":null,"isUnsigned":false,"isZerofill":false},{"notNull":true,"autoIncrement":false,"columnName":"product_code","columnComment":"","dataType":"varchar(10)","defaultValue":null,"isUnsigned":false,"isZerofill":false},{"notNull":true,"autoIncrement":false,"columnName":"product_name","columnComment":"","dataType":"varchar(255)","defaultValue":null,"isUnsigned":false,"isZerofill":false},{"notNull":true,"autoIncrement":false,"columnName":"description","columnComment":"","dataType":"text","defaultValue":null,"isUnsigned":false,"isZerofill":false},{"notNull":true,"autoIncrement":false,"columnName":"list_price","columnComment":"","dataType":"decimal(10,2)","defaultValue":null,"isUnsigned":false,"isZerofill":false},{"notNull":true,"autoIncrement":false,"columnName":"discount_percent","columnComment":"","dataType":"decimal(10,2)","defaultValue":"0.00","isUnsigned":false,"isZerofill":false},{"notNull":false,"autoIncrement":false,"columnName":"date_added","columnComment":"","dataType":"datetime","defaultValue":null,"isUnsigned":false,"isZerofill":false}],"primaryKey":{"constraintName":"PRIMARY","pureName":"products","constraintType":"primaryKey","columns":[{"columnName":"product_id"}]},"foreignKeys":[{"constraintName":"products_fk_categories","constraintType":"foreignKey","pureName":"products","refTableName":"categories","updateAction":"NO ACTION","deleteAction":"NO ACTION","columns":[{"columnName":"category_id","refColumnName":"category_id"}]}],"indexes":[{"constraintName":"products_fk_categories","indexType":"BTREE","isUnique":false,"columns":[{"columnName":"category_id","isDescending":0}]}],"uniques":[{"constraintName":"product_code","columns":[{"columnName":"product_code"}]}],"engine":"mysql@dbgate-plugin-mysql"} {"__isStreamHeader":true,"pureName":"products","tableRowCount":"10","tableEngine":"InnoDB","objectComment":"","modifyDate":"2025-02-03 02:56:32","objectId":"products","contentHash":"2025-02-03 02:56:32","columns":[{"notNull":true,"autoIncrement":true,"columnName":"product_id","columnComment":"","dataType":"int","defaultValue":null,"isUnsigned":false,"isZerofill":false},{"notNull":true,"autoIncrement":false,"columnName":"category_id","columnComment":"","dataType":"int","defaultValue":null,"isUnsigned":false,"isZerofill":false},{"notNull":true,"autoIncrement":false,"columnName":"product_code","columnComment":"","dataType":"varchar(10)","defaultValue":null,"isUnsigned":false,"isZerofill":false},{"notNull":true,"autoIncrement":false,"columnName":"product_name","columnComment":"","dataType":"varchar(255)","defaultValue":null,"isUnsigned":false,"isZerofill":false},{"notNull":true,"autoIncrement":false,"columnName":"description","columnComment":"","dataType":"text","defaultValue":null,"isUnsigned":false,"isZerofill":false},{"notNull":true,"autoIncrement":false,"columnName":"list_price","columnComment":"","dataType":"decimal(10,2)","defaultValue":null,"isUnsigned":false,"isZerofill":false},{"notNull":true,"autoIncrement":false,"columnName":"discount_percent","columnComment":"","dataType":"decimal(10,2)","defaultValue":"0.00","isUnsigned":false,"isZerofill":false},{"notNull":false,"autoIncrement":false,"columnName":"date_added","columnComment":"","dataType":"datetime","defaultValue":null,"isUnsigned":false,"isZerofill":false}],"primaryKey":{"constraintName":"PRIMARY","pureName":"products","constraintType":"primaryKey","columns":[{"columnName":"product_id"}]},"foreignKeys":[{"constraintName":"products_fk_categories","constraintType":"foreignKey","pureName":"products","refTableName":"categories","updateAction":"NO ACTION","deleteAction":"NO ACTION","columns":[{"columnName":"category_id","refColumnName":"category_id"}]}],"indexes":[{"constraintName":"products_fk_categories","indexType":"BTREE","isUnique":false,"columns":[{"columnName":"category_id","isDescending":0}]}],"uniques":[{"constraintName":"product_code","columns":[{"columnName":"product_code"}]}],"engine":"mysql@dbgate-plugin-mysql"}
{"product_id":1,"category_id":1,"product_code":"strat","product_name":"Fender Stratocaster","description":"The Fender Stratocaster is the electric guitar design that changed the world. New features include a tinted neck, parchment pickguard and control knobs, and a '70s-style logo. Includes select alder body, 21-fret maple neck with your choice of a rosewood or maple fretboard, 3 single-coil pickups, vintage-style tremolo, and die-cast tuning keys. This guitar features a thicker bridge block for increased sustain and a more stable point of contact with the strings. At this low price, why play anything but the real thing?\r\n\r\nFeatures:\r\n\r\n* New features:\r\n* Thicker bridge block\r\n* 3-ply parchment pick guard\r\n* Tinted neck","list_price":"699.00","discount_percent":"30.00","date_added":"2017-10-30 09:32:40"} {"product_id":1,"category_id":1,"product_code":"strat","product_name":"Fender Stratocaster","description":"The Fender Stratocaster is the electric guitar design that changed the world. New features include a tinted neck, parchment pickguard and control knobs, and a '70s-style logo. Includes select alder body, 21-fret maple neck with your choice of a rosewood or maple fretboard, 3 single-coil pickups, vintage-style tremolo, and die-cast tuning keys. This guitar features a thicker bridge block for increased sustain and a more stable point of contact with the strings. At this low price, why play anything but the real thing?\r\n\r\nFeatures:\r\n\r\n* New features:\r\n* Thicker bridge block\r\n* 3-ply parchment pick guard\r\n* Tinted neck","list_price":"699.00","discount_percent":"30.00","date_added":"2017-10-30T09:32:40"}
{"product_id":2,"category_id":1,"product_code":"les_paul","product_name":"Gibson Les Paul","description":"This Les Paul guitar offers a carved top and humbucking pickups. It has a simple yet elegant design. Cutting-yet-rich tone?the hallmark of the Les Paul?pours out of the 490R and 498T Alnico II magnet humbucker pickups, which are mounted on a carved maple top with a mahogany back. The faded finish models are equipped with BurstBucker Pro pickups and a mahogany top. This guitar includes a Gibson hardshell case (Faded and satin finish models come with a gig bag) and a limited lifetime warranty.\r\n\r\nFeatures:\r\n\r\n* Carved maple top and mahogany back (Mahogany top on faded finish models)\r\n* Mahogany neck, '59 Rounded Les Paul\r\n* Rosewood fingerboard (Ebony on Alpine white)\r\n* Tune-O-Matic bridge with stopbar\r\n* Chrome or gold hardware\r\n* 490R and 498T Alnico 2 magnet humbucker pickups (BurstBucker Pro on faded finish models)\r\n* 2 volume and 2 tone knobs, 3-way switch","list_price":"1199.00","discount_percent":"30.00","date_added":"2017-12-05 16:33:13"} {"product_id":2,"category_id":1,"product_code":"les_paul","product_name":"Gibson Les Paul","description":"This Les Paul guitar offers a carved top and humbucking pickups. It has a simple yet elegant design. Cutting-yet-rich tone?the hallmark of the Les Paul?pours out of the 490R and 498T Alnico II magnet humbucker pickups, which are mounted on a carved maple top with a mahogany back. The faded finish models are equipped with BurstBucker Pro pickups and a mahogany top. This guitar includes a Gibson hardshell case (Faded and satin finish models come with a gig bag) and a limited lifetime warranty.\r\n\r\nFeatures:\r\n\r\n* Carved maple top and mahogany back (Mahogany top on faded finish models)\r\n* Mahogany neck, '59 Rounded Les Paul\r\n* Rosewood fingerboard (Ebony on Alpine white)\r\n* Tune-O-Matic bridge with stopbar\r\n* Chrome or gold hardware\r\n* 490R and 498T Alnico 2 magnet humbucker pickups (BurstBucker Pro on faded finish models)\r\n* 2 volume and 2 tone knobs, 3-way switch","list_price":"1199.00","discount_percent":"30.00","date_added":"2017-12-05T16:33:13"}
{"product_id":3,"category_id":1,"product_code":"sg","product_name":"Gibson SG","description":"This Gibson SG electric guitar takes the best of the '62 original and adds the longer and sturdier neck joint of the late '60s models. All the classic features you'd expect from a historic guitar. Hot humbuckers go from rich, sweet lightning to warm, tingling waves of sustain. A silky-fast rosewood fretboard plays like a dream. The original-style beveled mahogany body looks like a million bucks. Plus, Tune-O-Matic bridge and chrome hardware. Limited lifetime warranty. Includes hardshell case.\r\n\r\nFeatures:\r\n\r\n* Double-cutaway beveled mahogany body\r\n* Set mahogany neck with rounded '50s profile\r\n* Bound rosewood fingerboard with trapezoid inlays\r\n* Tune-O-Matic bridge with stopbar tailpiece\r\n* Chrome hardware\r\n* 490R humbucker in the neck position\r\n* 498T humbucker in the bridge position\r\n* 2 volume knobs, 2 tone knobs, 3-way switch\r\n* 24-3/4\" scale","list_price":"2517.00","discount_percent":"52.00","date_added":"2018-02-04 11:04:31"} {"product_id":3,"category_id":1,"product_code":"sg","product_name":"Gibson SG","description":"This Gibson SG electric guitar takes the best of the '62 original and adds the longer and sturdier neck joint of the late '60s models. All the classic features you'd expect from a historic guitar. Hot humbuckers go from rich, sweet lightning to warm, tingling waves of sustain. A silky-fast rosewood fretboard plays like a dream. The original-style beveled mahogany body looks like a million bucks. Plus, Tune-O-Matic bridge and chrome hardware. Limited lifetime warranty. Includes hardshell case.\r\n\r\nFeatures:\r\n\r\n* Double-cutaway beveled mahogany body\r\n* Set mahogany neck with rounded '50s profile\r\n* Bound rosewood fingerboard with trapezoid inlays\r\n* Tune-O-Matic bridge with stopbar tailpiece\r\n* Chrome hardware\r\n* 490R humbucker in the neck position\r\n* 498T humbucker in the bridge position\r\n* 2 volume knobs, 2 tone knobs, 3-way switch\r\n* 24-3/4\" scale","list_price":"2517.00","discount_percent":"52.00","date_added":"2018-02-04T11:04:31"}
{"product_id":4,"category_id":1,"product_code":"fg700s","product_name":"Yamaha FG700S","description":"The Yamaha FG700S solid top acoustic guitar has the ultimate combo for projection and pure tone. The expertly braced spruce top speaks clearly atop the rosewood body. It has a rosewood fingerboard, rosewood bridge, die-cast tuners, body and neck binding, and a tortoise pickguard.\r\n\r\nFeatures:\r\n\r\n* Solid Sitka spruce top\r\n* Rosewood back and sides\r\n* Rosewood fingerboard\r\n* Rosewood bridge\r\n* White/black body and neck binding\r\n* Die-cast tuners\r\n* Tortoise pickguard\r\n* Limited lifetime warranty","list_price":"489.99","discount_percent":"38.00","date_added":"2018-06-01 11:12:59"} {"product_id":4,"category_id":1,"product_code":"fg700s","product_name":"Yamaha FG700S","description":"The Yamaha FG700S solid top acoustic guitar has the ultimate combo for projection and pure tone. The expertly braced spruce top speaks clearly atop the rosewood body. It has a rosewood fingerboard, rosewood bridge, die-cast tuners, body and neck binding, and a tortoise pickguard.\r\n\r\nFeatures:\r\n\r\n* Solid Sitka spruce top\r\n* Rosewood back and sides\r\n* Rosewood fingerboard\r\n* Rosewood bridge\r\n* White/black body and neck binding\r\n* Die-cast tuners\r\n* Tortoise pickguard\r\n* Limited lifetime warranty","list_price":"489.99","discount_percent":"38.00","date_added":"2018-06-01T11:12:59"}
{"product_id":5,"category_id":1,"product_code":"washburn","product_name":"Washburn D10S","description":"The Washburn D10S acoustic guitar is superbly crafted with a solid spruce top and mahogany back and sides for exceptional tone. A mahogany neck and rosewood fingerboard make fretwork a breeze, while chrome Grover-style machines keep you perfectly tuned. The Washburn D10S comes with a limited lifetime warranty.\r\n\r\nFeatures:\r\n\r\n * Spruce top\r\n * Mahogany back, sides\r\n * Mahogany neck Rosewood fingerboard\r\n * Chrome Grover-style machines","list_price":"299.00","discount_percent":"0.00","date_added":"2018-07-30 13:58:35"} {"product_id":5,"category_id":1,"product_code":"washburn","product_name":"Washburn D10S","description":"The Washburn D10S acoustic guitar is superbly crafted with a solid spruce top and mahogany back and sides for exceptional tone. A mahogany neck and rosewood fingerboard make fretwork a breeze, while chrome Grover-style machines keep you perfectly tuned. The Washburn D10S comes with a limited lifetime warranty.\r\n\r\nFeatures:\r\n\r\n * Spruce top\r\n * Mahogany back, sides\r\n * Mahogany neck Rosewood fingerboard\r\n * Chrome Grover-style machines","list_price":"299.00","discount_percent":"0.00","date_added":"2018-07-30T13:58:35"}
{"product_id":6,"category_id":1,"product_code":"rodriguez","product_name":"Rodriguez Caballero 11","description":"Featuring a carefully chosen, solid Canadian cedar top and laminated bubinga back and sides, the Caballero 11 classical guitar is a beauty to behold and play. The headstock and fretboard are of Indian rosewood. Nickel-plated tuners and Silver-plated frets are installed to last a lifetime. The body binding and wood rosette are exquisite.\r\n\r\nThe Rodriguez Guitar is hand crafted and glued to create precise balances. From the invisible careful sanding, even inside the body, that ensures the finished instrument's purity of tone, to the beautifully unique rosette inlays around the soundhole and on the back of the neck, each guitar is a credit to its luthier and worthy of being handed down from one generation to another.\r\n\r\nThe tone, resonance and beauty of fine guitars are all dependent upon the wood from which they are made. The wood used in the construction of Rodriguez guitars is carefully chosen and aged to guarantee the highest quality. No wood is purchased before the tree has been cut down, and at least 2 years must elapse before the tree is turned into lumber. The wood has to be well cut from the log. The grain must be close and absolutely vertical. The shop is totally free from humidity.","list_price":"415.00","discount_percent":"39.00","date_added":"2018-07-30 14:12:41"} {"product_id":6,"category_id":1,"product_code":"rodriguez","product_name":"Rodriguez Caballero 11","description":"Featuring a carefully chosen, solid Canadian cedar top and laminated bubinga back and sides, the Caballero 11 classical guitar is a beauty to behold and play. The headstock and fretboard are of Indian rosewood. Nickel-plated tuners and Silver-plated frets are installed to last a lifetime. The body binding and wood rosette are exquisite.\r\n\r\nThe Rodriguez Guitar is hand crafted and glued to create precise balances. From the invisible careful sanding, even inside the body, that ensures the finished instrument's purity of tone, to the beautifully unique rosette inlays around the soundhole and on the back of the neck, each guitar is a credit to its luthier and worthy of being handed down from one generation to another.\r\n\r\nThe tone, resonance and beauty of fine guitars are all dependent upon the wood from which they are made. The wood used in the construction of Rodriguez guitars is carefully chosen and aged to guarantee the highest quality. No wood is purchased before the tree has been cut down, and at least 2 years must elapse before the tree is turned into lumber. The wood has to be well cut from the log. The grain must be close and absolutely vertical. The shop is totally free from humidity.","list_price":"415.00","discount_percent":"39.00","date_added":"2018-07-30T14:12:41"}
{"product_id":7,"category_id":2,"product_code":"precision","product_name":"Fender Precision","description":"The Fender Precision bass guitar delivers the sound, look, and feel today's bass players demand. This bass features that classic P-Bass old-school design. Each Precision bass boasts contemporary features and refinements that make it an excellent value. Featuring an alder body and a split single-coil pickup, this classic electric bass guitar lives up to its Fender legacy.\r\n\r\nFeatures:\r\n\r\n* Body: Alder\r\n* Neck: Maple, modern C shape, tinted satin urethane finish\r\n* Fingerboard: Rosewood or maple (depending on color)\r\n* 9-1/2\" Radius (241 mm)\r\n* Frets: 20 Medium-jumbo frets\r\n* Pickups: 1 Standard Precision Bass split single-coil pickup (Mid)\r\n* Controls: Volume, Tone\r\n* Bridge: Standard vintage style with single groove saddles\r\n* Machine heads: Standard\r\n* Hardware: Chrome\r\n* Pickguard: 3-Ply Parchment\r\n* Scale Length: 34\" (864 mm)\r\n* Width at Nut: 1-5/8\" (41.3 mm)\r\n* Unique features: Knurled chrome P Bass knobs, Fender transition logo","list_price":"799.99","discount_percent":"30.00","date_added":"2018-06-01 11:29:35"} {"product_id":7,"category_id":2,"product_code":"precision","product_name":"Fender Precision","description":"The Fender Precision bass guitar delivers the sound, look, and feel today's bass players demand. This bass features that classic P-Bass old-school design. Each Precision bass boasts contemporary features and refinements that make it an excellent value. Featuring an alder body and a split single-coil pickup, this classic electric bass guitar lives up to its Fender legacy.\r\n\r\nFeatures:\r\n\r\n* Body: Alder\r\n* Neck: Maple, modern C shape, tinted satin urethane finish\r\n* Fingerboard: Rosewood or maple (depending on color)\r\n* 9-1/2\" Radius (241 mm)\r\n* Frets: 20 Medium-jumbo frets\r\n* Pickups: 1 Standard Precision Bass split single-coil pickup (Mid)\r\n* Controls: Volume, Tone\r\n* Bridge: Standard vintage style with single groove saddles\r\n* Machine heads: Standard\r\n* Hardware: Chrome\r\n* Pickguard: 3-Ply Parchment\r\n* Scale Length: 34\" (864 mm)\r\n* Width at Nut: 1-5/8\" (41.3 mm)\r\n* Unique features: Knurled chrome P Bass knobs, Fender transition logo","list_price":"799.99","discount_percent":"30.00","date_added":"2018-06-01T11:29:35"}
{"product_id":8,"category_id":2,"product_code":"hofner","product_name":"Hofner Icon","description":"With authentic details inspired by the original, the Hofner Icon makes the legendary violin bass available to the rest of us. Don't get the idea that this a just a \"nowhere man\" look-alike. This quality instrument features a real spruce top and beautiful flamed maple back and sides. The semi-hollow body and set neck will give you the warm, round tone you expect from the violin bass.\r\n\r\nFeatures:\r\n\r\n* Authentic details inspired by the original\r\n* Spruce top\r\n* Flamed maple back and sides\r\n* Set neck\r\n* Rosewood fretboard\r\n* 30\" scale\r\n* 22 frets\r\n* Dot inlay","list_price":"499.99","discount_percent":"25.00","date_added":"2018-07-30 14:18:33"} {"product_id":8,"category_id":2,"product_code":"hofner","product_name":"Hofner Icon","description":"With authentic details inspired by the original, the Hofner Icon makes the legendary violin bass available to the rest of us. Don't get the idea that this a just a \"nowhere man\" look-alike. This quality instrument features a real spruce top and beautiful flamed maple back and sides. The semi-hollow body and set neck will give you the warm, round tone you expect from the violin bass.\r\n\r\nFeatures:\r\n\r\n* Authentic details inspired by the original\r\n* Spruce top\r\n* Flamed maple back and sides\r\n* Set neck\r\n* Rosewood fretboard\r\n* 30\" scale\r\n* 22 frets\r\n* Dot inlay","list_price":"499.99","discount_percent":"25.00","date_added":"2018-07-30T14:18:33"}
{"product_id":9,"category_id":3,"product_code":"ludwig","product_name":"Ludwig 5-piece Drum Set with Cymbals","description":"This product includes a Ludwig 5-piece drum set and a Zildjian starter cymbal pack.\r\n\r\nWith the Ludwig drum set, you get famous Ludwig quality. This set features a bass drum, two toms, a floor tom, and a snare?each with a wrapped finish. Drum hardware includes LA214FP bass pedal, snare stand, cymbal stand, hi-hat stand, and a throne.\r\n\r\nWith the Zildjian cymbal pack, you get a 14\" crash, 18\" crash/ride, and a pair of 13\" hi-hats. Sound grooves and round hammer strikes in a simple circular pattern on the top surface of these cymbals magnify the basic sound of the distinctive alloy.\r\n\r\nFeatures:\r\n\r\n* Famous Ludwig quality\r\n* Wrapped finishes\r\n* 22\" x 16\" kick drum\r\n* 12\" x 10\" and 13\" x 11\" toms\r\n* 16\" x 16\" floor tom\r\n* 14\" x 6-1/2\" snare drum kick pedal\r\n* Snare stand\r\n* Straight cymbal stand hi-hat stand\r\n* FREE throne","list_price":"699.99","discount_percent":"30.00","date_added":"2018-07-30 12:46:40"} {"product_id":9,"category_id":3,"product_code":"ludwig","product_name":"Ludwig 5-piece Drum Set with Cymbals","description":"This product includes a Ludwig 5-piece drum set and a Zildjian starter cymbal pack.\r\n\r\nWith the Ludwig drum set, you get famous Ludwig quality. This set features a bass drum, two toms, a floor tom, and a snare?each with a wrapped finish. Drum hardware includes LA214FP bass pedal, snare stand, cymbal stand, hi-hat stand, and a throne.\r\n\r\nWith the Zildjian cymbal pack, you get a 14\" crash, 18\" crash/ride, and a pair of 13\" hi-hats. Sound grooves and round hammer strikes in a simple circular pattern on the top surface of these cymbals magnify the basic sound of the distinctive alloy.\r\n\r\nFeatures:\r\n\r\n* Famous Ludwig quality\r\n* Wrapped finishes\r\n* 22\" x 16\" kick drum\r\n* 12\" x 10\" and 13\" x 11\" toms\r\n* 16\" x 16\" floor tom\r\n* 14\" x 6-1/2\" snare drum kick pedal\r\n* Snare stand\r\n* Straight cymbal stand hi-hat stand\r\n* FREE throne","list_price":"699.99","discount_percent":"30.00","date_added":"2018-07-30T12:46:40"}
{"product_id":10,"category_id":3,"product_code":"tama","product_name":"Tama 5-Piece Drum Set with Cymbals","description":"The Tama 5-piece Drum Set is the most affordable Tama drum kit ever to incorporate so many high-end features.\r\n\r\nWith over 40 years of experience, Tama knows what drummers really want. Which is why, no matter how long you've been playing the drums, no matter what budget you have to work with, Tama has the set you need, want, and can afford. Every aspect of the modern drum kit was exhaustively examined and reexamined and then improved before it was accepted as part of the Tama design. Which is why, if you start playing Tama now as a beginner, you'll still enjoy playing it when you've achieved pro-status. That's how good these groundbreaking new drums are.\r\n\r\nOnly Tama comes with a complete set of genuine Meinl HCS cymbals. These high-quality brass cymbals are made in Germany and are sonically matched so they sound great together. They are even lathed for a more refined tonal character. The set includes 14\" hi-hats, 16\" crash cymbal, and a 20\" ride cymbal.\r\n\r\nFeatures:\r\n\r\n* 100% poplar 6-ply/7.5mm shells\r\n* Precise bearing edges\r\n* 100% glued finishes\r\n* Original small lugs\r\n* Drum heads\r\n* Accu-tune bass drum hoops\r\n* Spur brackets\r\n* Tom holder\r\n* Tom brackets","list_price":"799.99","discount_percent":"15.00","date_added":"2018-07-30 13:14:15"} {"product_id":10,"category_id":3,"product_code":"tama","product_name":"Tama 5-Piece Drum Set with Cymbals","description":"The Tama 5-piece Drum Set is the most affordable Tama drum kit ever to incorporate so many high-end features.\r\n\r\nWith over 40 years of experience, Tama knows what drummers really want. Which is why, no matter how long you've been playing the drums, no matter what budget you have to work with, Tama has the set you need, want, and can afford. Every aspect of the modern drum kit was exhaustively examined and reexamined and then improved before it was accepted as part of the Tama design. Which is why, if you start playing Tama now as a beginner, you'll still enjoy playing it when you've achieved pro-status. That's how good these groundbreaking new drums are.\r\n\r\nOnly Tama comes with a complete set of genuine Meinl HCS cymbals. These high-quality brass cymbals are made in Germany and are sonically matched so they sound great together. They are even lathed for a more refined tonal character. The set includes 14\" hi-hats, 16\" crash cymbal, and a 20\" ride cymbal.\r\n\r\nFeatures:\r\n\r\n* 100% poplar 6-ply/7.5mm shells\r\n* Precise bearing edges\r\n* 100% glued finishes\r\n* Original small lugs\r\n* Drum heads\r\n* Accu-tune bass drum hoops\r\n* Spur brackets\r\n* Tom holder\r\n* Tom brackets","list_price":"799.99","discount_percent":"15.00","date_added":"2018-07-30T13:14:15"}

View File

@@ -49,3 +49,20 @@ services:
image: redis image: redis
ports: ports:
- 16011:6379 - 16011:6379
mssql:
image: mcr.microsoft.com/mssql/server
restart: always
ports:
- 16012:1433
environment:
- ACCEPT_EULA=Y
- SA_PASSWORD=Pwd2020Db
- MSSQL_PID=Express
oracle:
image: gvenzl/oracle-xe:21-slim
environment:
ORACLE_PASSWORD: Pwd2020Db
ports:
- 16013:1521

View File

@@ -1,4 +1,4 @@
CONNECTIONS=mysql,postgres CONNECTIONS=mysql,postgres,mssql,oracle
LABEL_mysql=MySql-connection LABEL_mysql=MySql-connection
SERVER_mysql=localhost SERVER_mysql=localhost
@@ -15,3 +15,18 @@ PASSWORD_postgres=Pwd2020Db
PORT_postgres=16000 PORT_postgres=16000
ENGINE_postgres=postgres@dbgate-plugin-postgres ENGINE_postgres=postgres@dbgate-plugin-postgres
DBCONFIG_postgres=[{"name":"PgChinook","connectionColor":"red"}] DBCONFIG_postgres=[{"name":"PgChinook","connectionColor":"red"}]
LABEL_oracle=Oracle-connection
SERVER_oracle=localhost
USER_oracle=system
PASSWORD_oracle=Pwd2020Db
PORT_oracle=16013
ENGINE_oracle=oracle@dbgate-plugin-oracle
SERVICE_NAME_oracle=xe
LABEL_mssql=Mssql-connection
SERVER_mssql=localhost
USER_mssql=sa
PASSWORD_mssql=Pwd2020Db
PORT_mssql=16012
ENGINE_mssql=mssql@dbgate-plugin-mssql

View File

@@ -1,4 +1,5 @@
const path = require('path'); const path = require('path');
const localconfig = require('../.localconfig');
const dbgateApi = require('dbgate-api'); const dbgateApi = require('dbgate-api');
dbgateApi.initializeApiEnvironment(); dbgateApi.initializeApiEnvironment();
@@ -7,11 +8,15 @@ dbgateApi.registerPlugins(dbgatePluginMysql);
const dbgatePluginPostgres = require('dbgate-plugin-postgres'); const dbgatePluginPostgres = require('dbgate-plugin-postgres');
dbgateApi.registerPlugins(dbgatePluginPostgres); dbgateApi.registerPlugins(dbgatePluginPostgres);
async function createDb(connection, dropDbSql, createDbSql) { async function createDb(connection, dropDbSql, createDbSql, database = 'my_guitar_shop') {
await dbgateApi.executeQuery({ try {
connection, await dbgateApi.executeQuery({
sql: dropDbSql, connection,
}); sql: dropDbSql,
});
} catch (err) {
console.error('Failed to drop database', err);
}
await dbgateApi.executeQuery({ await dbgateApi.executeQuery({
connection, connection,
@@ -21,36 +26,69 @@ async function createDb(connection, dropDbSql, createDbSql) {
await dbgateApi.importDbFromFolder({ await dbgateApi.importDbFromFolder({
connection: { connection: {
...connection, ...connection,
database: 'my_quitar_shop', database,
}, },
folder: path.resolve(path.join(__dirname, '../data/my-guitar-shop')), folder: path.resolve(path.join(__dirname, '../data/my-guitar-shop')),
}); });
} }
async function run() { async function run() {
await createDb( if (localconfig.postgres) {
{ await createDb(
server: process.env.SERVER_postgres, {
user: process.env.USER_postgres, server: process.env.SERVER_postgres,
password: process.env.PASSWORD_postgres, user: process.env.USER_postgres,
port: process.env.PORT_postgres, password: process.env.PASSWORD_postgres,
engine: 'postgres@dbgate-plugin-postgres', port: process.env.PORT_postgres,
}, engine: 'postgres@dbgate-plugin-postgres',
'drop database if exists my_quitar_shop', },
'create database my_quitar_shop' 'drop database if exists my_guitar_shop',
); 'create database my_guitar_shop'
);
}
await createDb( if (localconfig.mysql) {
{ await createDb(
server: process.env.SERVER_mysql, {
user: process.env.USER_mysql, server: process.env.SERVER_mysql,
password: process.env.PASSWORD_mysql, user: process.env.USER_mysql,
port: process.env.PORT_mysql, password: process.env.PASSWORD_mysql,
engine: 'mysql@dbgate-plugin-mysql', port: process.env.PORT_mysql,
}, engine: 'mysql@dbgate-plugin-mysql',
'drop database if exists my_quitar_shop', },
'create database my_quitar_shop' 'drop database if exists my_guitar_shop',
); 'create database my_guitar_shop'
);
}
if (localconfig.mssql) {
await createDb(
{
server: process.env.SERVER_mssql,
user: process.env.USER_mssql,
password: process.env.PASSWORD_mssql,
port: process.env.PORT_mssql,
engine: 'mssql@dbgate-plugin-mssql',
},
'drop database if exists my_guitar_shop',
'create database my_guitar_shop'
);
}
if (localconfig.oracle) {
await createDb(
{
server: process.env.SERVER_oracle,
user: process.env.USER_oracle,
password: process.env.PASSWORD_oracle,
port: process.env.PORT_oracle,
engine: 'oracle@dbgate-plugin-oracle',
},
'DROP USER c##my_guitar_shop CASCADE',
'CREATE USER c##my_guitar_shop IDENTIFIED BY my_guitar_shop DEFAULT TABLESPACE users TEMPORARY TABLESPACE temp QUOTA 10M ON users',
'C##my_guitar_shop'
);
}
} }
dbgateApi.runScript(run); dbgateApi.runScript(run);

View File

@@ -36,7 +36,7 @@
"test:team": "start-server-and-test start:team http://localhost:3000 cy:run:team", "test:team": "start-server-and-test start:team http://localhost:3000 cy:run:team",
"test:multi-sql": "start-server-and-test start:multi-sql http://localhost:3000 cy:run:multi-sql", "test:multi-sql": "start-server-and-test start:multi-sql http://localhost:3000 cy:run:multi-sql",
"test": "yarn test:add-connection && yarn test:portal && yarn test:oauth && yarn test:browse-data && yarn test:team && yaran test:multi-sql", "test": "yarn test:add-connection && yarn test:portal && yarn test:oauth && yarn test:browse-data && yarn test:team && yarn test:multi-sql",
"test:ci": "yarn test" "test:ci": "yarn test"
}, },
"dependencies": {} "dependencies": {}

View File

@@ -1,11 +1,12 @@
const engines = require('../engines'); const engines = require('../engines');
const stream = require('stream'); const stream = require('stream');
const path = require('path');
const { testWrapper } = require('../tools'); const { testWrapper } = require('../tools');
const tableWriter = require('dbgate-api/src/shell/tableWriter'); const tableWriter = require('dbgate-api/src/shell/tableWriter');
const tableReader = require('dbgate-api/src/shell/tableReader'); const tableReader = require('dbgate-api/src/shell/tableReader');
const copyStream = require('dbgate-api/src/shell/copyStream'); const copyStream = require('dbgate-api/src/shell/copyStream');
const importDatabase = require('dbgate-api/src/shell/importDatabase'); const importDatabase = require('dbgate-api/src/shell/importDatabase');
const fakeObjectReader = require('dbgate-api/src/shell/fakeObjectReader'); const importDbFromFolder = require('dbgate-api/src/shell/importDbFromFolder');
const { runQueryOnDriver, runCommandOnDriver } = require('dbgate-tools'); const { runQueryOnDriver, runCommandOnDriver } = require('dbgate-tools');
function createImportStream() { function createImportStream() {
@@ -108,7 +109,6 @@ describe('DB Import/export', () => {
expect(res2.rows[0].cnt.toString()).toEqual('6'); expect(res2.rows[0].cnt.toString()).toEqual('6');
}) })
); );
const enginesWithDumpFile = engines.filter(x => x.dumpFile); const enginesWithDumpFile = engines.filter(x => x.dumpFile);
const hasEnginesWithDumpFile = enginesWithDumpFile.length > 0; const hasEnginesWithDumpFile = enginesWithDumpFile.length > 0;
@@ -181,4 +181,18 @@ describe('DB Import/export', () => {
expect(result).toEqual(data); expect(result).toEqual(data);
}) })
); );
test.each(engines.map(engine => [engine.label, engine]))(
'Import guitar shop - schema + data - %s',
testWrapper(async (conn, driver, engine) => {
await importDbFromFolder({
systemConnection: conn,
driver,
folder: path.join(__dirname, '../../e2e-tests/data/my-guitar-shop'),
});
const res1 = await runQueryOnDriver(conn, driver, dmp => dmp.put(`select count(*) as ~cnt from ~categories`));
expect(res1.rows[0].cnt.toString()).toEqual('4');
})
);
}); });

View File

@@ -74,9 +74,9 @@ services:
# - cockroachdb # - cockroachdb
# restart: on-failure # restart: on-failure
# oracle: oracle:
# image: gvenzl/oracle-xe:21-slim image: gvenzl/oracle-xe:21-slim
# environment: environment:
# ORACLE_PASSWORD: Pwd2020Db ORACLE_PASSWORD: Pwd2020Db
# ports: ports:
# - 15006:1521 - 15006:1521

View File

@@ -650,7 +650,7 @@ const enginesOnCi = [
const enginesOnLocal = [ const enginesOnLocal = [
// all engines, which would be run on local test // all engines, which would be run on local test
cassandraEngine, // cassandraEngine,
// mysqlEngine, // mysqlEngine,
// mariaDbEngine, // mariaDbEngine,
// postgreSqlEngine, // postgreSqlEngine,
@@ -658,7 +658,7 @@ const enginesOnLocal = [
// sqliteEngine, // sqliteEngine,
// cockroachDbEngine, // cockroachDbEngine,
// clickhouseEngine, // clickhouseEngine,
// oracleEngine, oracleEngine,
]; ];
/** @type {import('dbgate-types').TestEngineInfo[] & Record<string, import('dbgate-types').TestEngineInfo>} */ /** @type {import('dbgate-types').TestEngineInfo[] & Record<string, import('dbgate-types').TestEngineInfo>} */

View File

@@ -58,6 +58,7 @@
"install:drivers:packer": "node common/defineVolatileDependencies.js packer/build", "install:drivers:packer": "node common/defineVolatileDependencies.js packer/build",
"prepare:docker": "yarn plugins:copydist && yarn build:web && yarn build:api && yarn copy:docker:build && yarn install:drivers:docker", "prepare:docker": "yarn plugins:copydist && yarn build:web && yarn build:api && yarn copy:docker:build && yarn install:drivers:docker",
"prepare:packer": "yarn plugins:copydist && yarn build:web && yarn build:api && yarn copy:packer:build", "prepare:packer": "yarn plugins:copydist && yarn build:web && yarn build:api && yarn copy:packer:build",
"build:e2e": "yarn build:lib && yarn prepare:packer",
"start": "concurrently --kill-others-on-fail \"yarn start:api\" \"yarn start:web\"", "start": "concurrently --kill-others-on-fail \"yarn start:api\" \"yarn start:web\"",
"lib": "concurrently --kill-others-on-fail \"yarn start:sqltree\" \"yarn start:filterparser\" \"yarn start:datalib\" \"yarn start:tools\" \"yarn build:plugins:frontend:watch\"", "lib": "concurrently --kill-others-on-fail \"yarn start:sqltree\" \"yarn start:filterparser\" \"yarn start:datalib\" \"yarn start:tools\" \"yarn build:plugins:frontend:watch\"",
"ts:api": "yarn workspace dbgate-api ts", "ts:api": "yarn workspace dbgate-api ts",

View File

@@ -56,12 +56,19 @@ module.exports = {
handle_done(sesid, props) { handle_done(sesid, props) {
socket.emit(`session-done-${sesid}`); socket.emit(`session-done-${sesid}`);
if (!props.skipFinishedMessage) { if (!props.skipFinishedMessage) {
this.dispatchMessage(sesid, 'Query execution finished'); if (props.controlCommand) {
this.dispatchMessage(sesid, `${_.startCase(props.controlCommand)} finished`);
} else {
this.dispatchMessage(sesid, 'Query execution finished');
}
} }
const session = this.opened.find(x => x.sesid == sesid); const session = this.opened.find(x => x.sesid == sesid);
if (session.loadingReader_jslid) { if (session.loadingReader_jslid) {
socket.emit(`session-jslid-done-${session.loadingReader_jslid}`); socket.emit(`session-jslid-done-${session.loadingReader_jslid}`);
} }
if (props.autoCommit) {
this.executeControlCommand({ sesid, command: 'commitTransaction' });
}
if (session.killOnDone) { if (session.killOnDone) {
this.kill({ sesid }); this.kill({ sesid });
} }
@@ -131,7 +138,7 @@ module.exports = {
}, },
executeQuery_meta: true, executeQuery_meta: true,
async executeQuery({ sesid, sql }) { async executeQuery({ sesid, sql, autoCommit }) {
const session = this.opened.find(x => x.sesid == sesid); const session = this.opened.find(x => x.sesid == sesid);
if (!session) { if (!session) {
throw new Error('Invalid session'); throw new Error('Invalid session');
@@ -139,7 +146,21 @@ module.exports = {
logger.info({ sesid, sql }, 'Processing query'); logger.info({ sesid, sql }, 'Processing query');
this.dispatchMessage(sesid, 'Query execution started'); this.dispatchMessage(sesid, 'Query execution started');
session.subprocess.send({ msgtype: 'executeQuery', sql }); session.subprocess.send({ msgtype: 'executeQuery', sql, autoCommit });
return { state: 'ok' };
},
executeControlCommand_meta: true,
async executeControlCommand({ sesid, command }) {
const session = this.opened.find(x => x.sesid == sesid);
if (!session) {
throw new Error('Invalid session');
}
logger.info({ sesid, command }, 'Processing control command');
this.dispatchMessage(sesid, `${_.startCase(command)} started`);
session.subprocess.send({ msgtype: 'executeControlCommand', command });
return { state: 'ok' }; return { state: 'ok' };
}, },

View File

@@ -245,7 +245,47 @@ async function handleStopProfiler({ jslid }) {
currentProfiler = null; currentProfiler = null;
} }
async function handleExecuteQuery({ sql }) { async function handleExecuteControlCommand({ command }) {
lastActivity = new Date().getTime();
await waitConnected();
const driver = requireEngineDriver(storedConnection);
if (command == 'commitTransaction' && !allowExecuteCustomScript(driver)) {
process.send({
msgtype: 'info',
info: {
message: 'Connection without read-only sessions is read only',
severity: 'error',
},
});
process.send({ msgtype: 'done', skipFinishedMessage: true });
return;
//process.send({ msgtype: 'error', error: e.message });
}
executingScripts++;
try {
const dmp = driver.createDumper();
switch (command) {
case 'commitTransaction':
await dmp.commitTransaction();
break;
case 'rollbackTransaction':
await dmp.rollbackTransaction();
break;
case 'beginTransaction':
await dmp.beginTransaction();
break;
}
await driver.query(dbhan, dmp.s, { discardResult: true });
process.send({ msgtype: 'done', controlCommand: command });
} finally {
executingScripts--;
}
}
async function handleExecuteQuery({ sql, autoCommit }) {
lastActivity = new Date().getTime(); lastActivity = new Date().getTime();
await waitConnected(); await waitConnected();
@@ -279,7 +319,7 @@ async function handleExecuteQuery({ sql }) {
// handler.stream = stream; // handler.stream = stream;
// resultIndex = handler.resultIndex; // resultIndex = handler.resultIndex;
} }
process.send({ msgtype: 'done' }); process.send({ msgtype: 'done', autoCommit });
} finally { } finally {
executingScripts--; executingScripts--;
} }
@@ -323,6 +363,7 @@ function handlePing() {
const messageHandlers = { const messageHandlers = {
connect: handleConnect, connect: handleConnect,
executeQuery: handleExecuteQuery, executeQuery: handleExecuteQuery,
executeControlCommand: handleExecuteControlCommand,
executeReader: handleExecuteReader, executeReader: handleExecuteReader,
startProfiler: handleStartProfiler, startProfiler: handleStartProfiler,
stopProfiler: handleStopProfiler, stopProfiler: handleStopProfiler,

View File

@@ -52,13 +52,15 @@ async function importDbFromFolder({ connection, systemConnection, driver, folder
// console.log('CREATING STRUCTURE:', sql); // console.log('CREATING STRUCTURE:', sql);
await executeQuery({ connection, systemConnection: dbhan, driver, sql, logScriptItems: true }); await executeQuery({ connection, systemConnection: dbhan, driver, sql, logScriptItems: true });
for (const table of model.tables) { for (const table of modelAdapted.tables) {
const fileName = path.join(folder, `${table.pureName}.jsonl`); const fileName = path.join(folder, `${table.pureName}.jsonl`);
if (await fs.exists(fileName)) { if (await fs.exists(fileName)) {
const src = await jsonLinesReader({ fileName }); const src = await jsonLinesReader({ fileName });
const dst = await tableWriter({ const dst = await tableWriter({
connection, systemConnection: dbhan,
pureName: table.pureName, pureName: table.pureName,
driver,
targetTableStructure: table,
}); });
await copyStream(src, dst); await copyStream(src, dst);
} }

View File

@@ -15,6 +15,7 @@ const logger = getLogger('tableWriter');
* @param {boolean} options.truncate - truncate table before insert * @param {boolean} options.truncate - truncate table before insert
* @param {boolean} options.createIfNotExists - create table if not exists * @param {boolean} options.createIfNotExists - create table if not exists
* @param {boolean} options.commitAfterInsert - commit transaction after insert * @param {boolean} options.commitAfterInsert - commit transaction after insert
* @param {any} options.targetTableStructure - target table structure (don't analyse if given)
* @returns {Promise<writerType>} - writer object * @returns {Promise<writerType>} - writer object
*/ */
async function tableWriter({ connection, schemaName, pureName, driver, systemConnection, ...options }) { async function tableWriter({ connection, schemaName, pureName, driver, systemConnection, ...options }) {

View File

@@ -259,11 +259,21 @@ export class SqlDumper implements AlterProcessor {
this.putRaw(' '); this.putRaw(' ');
this.specialColumnOptions(column); this.specialColumnOptions(column);
if (includeNullable && !this.dialect?.specificNullabilityImplementation) {
this.put(column.notNull ? '^not ^null' : '^null'); if (this.dialect?.defaultValueBeforeNullability) {
} if (includeDefault && column.defaultValue?.toString()?.trim()) {
if (includeDefault && column.defaultValue?.toString()?.trim()) { this.columnDefault(column);
this.columnDefault(column); }
if (includeNullable && !this.dialect?.specificNullabilityImplementation) {
this.put(column.notNull ? '^not ^null' : '^null');
}
} else {
if (includeNullable && !this.dialect?.specificNullabilityImplementation) {
this.put(column.notNull ? '^not ^null' : '^null');
}
if (includeDefault && column.defaultValue?.toString()?.trim()) {
this.columnDefault(column);
}
} }
} }

View File

@@ -1,5 +1,6 @@
import { EngineDriver, WriteTableOptions } from 'dbgate-types'; import { EngineDriver, WriteTableOptions } from 'dbgate-types';
import _intersection from 'lodash/intersection'; import _intersection from 'lodash/intersection';
import _fromPairs from 'lodash/fromPairs';
import { getLogger } from './getLogger'; import { getLogger } from './getLogger';
import { prepareTableForImport } from './tableTransforms'; import { prepareTableForImport } from './tableTransforms';
@@ -18,6 +19,7 @@ export function createBulkInsertStreamBase(driver: EngineDriver, stream, dbhan,
writable.buffer = []; writable.buffer = [];
writable.structure = null; writable.structure = null;
writable.columnNames = null; writable.columnNames = null;
writable.columnDataTypes = null;
writable.requireFixedStructure = driver.databaseEngineTypes.includes('sql'); writable.requireFixedStructure = driver.databaseEngineTypes.includes('sql');
writable.addRow = async row => { writable.addRow = async row => {
@@ -30,7 +32,7 @@ export function createBulkInsertStreamBase(driver: EngineDriver, stream, dbhan,
}; };
writable.checkStructure = async () => { writable.checkStructure = async () => {
let structure = await driver.analyseSingleTable(dbhan, name); let structure = options.targetTableStructure ?? (await driver.analyseSingleTable(dbhan, name));
if (structure) { if (structure) {
writable.structure = structure; writable.structure = structure;
} }
@@ -45,6 +47,10 @@ export function createBulkInsertStreamBase(driver: EngineDriver, stream, dbhan,
logger.info({ sql: dmp.s }, `Creating table ${fullNameQuoted}`); logger.info({ sql: dmp.s }, `Creating table ${fullNameQuoted}`);
await driver.script(dbhan, dmp.s); await driver.script(dbhan, dmp.s);
structure = await driver.analyseSingleTable(dbhan, name); structure = await driver.analyseSingleTable(dbhan, name);
writable.structure = structure;
}
if (!writable.structure) {
throw new Error(`Error importing table - ${fullNameQuoted} not found`);
} }
if (options.truncate) { if (options.truncate) {
await driver.script(dbhan, `TRUNCATE TABLE ${fullNameQuoted}`); await driver.script(dbhan, `TRUNCATE TABLE ${fullNameQuoted}`);
@@ -54,6 +60,12 @@ export function createBulkInsertStreamBase(driver: EngineDriver, stream, dbhan,
structure.columns.map(x => x.columnName), structure.columns.map(x => x.columnName),
writable.structure.columns.map(x => x.columnName) writable.structure.columns.map(x => x.columnName)
); );
writable.columnDataTypes = _fromPairs(
writable.columnNames.map(colName => [
colName,
writable.structure.columns.find(x => x.columnName == colName)?.dataType,
])
);
}; };
writable.send = async () => { writable.send = async () => {
@@ -70,7 +82,9 @@ export function createBulkInsertStreamBase(driver: EngineDriver, stream, dbhan,
for (const row of rows) { for (const row of rows) {
if (wasRow) dmp.putRaw(',\n'); if (wasRow) dmp.putRaw(',\n');
dmp.putRaw('('); dmp.putRaw('(');
dmp.putCollection(',', writable.columnNames, col => dmp.putValue(row[col as string])); dmp.putCollection(',', writable.columnNames, col =>
dmp.putValue(row[col as string], writable.columnDataTypes?.[col as string])
);
dmp.putRaw(')'); dmp.putRaw(')');
wasRow = true; wasRow = true;
} }
@@ -86,8 +100,11 @@ export function createBulkInsertStreamBase(driver: EngineDriver, stream, dbhan,
dmp.putRaw(')\n VALUES\n'); dmp.putRaw(')\n VALUES\n');
dmp.putRaw('('); dmp.putRaw('(');
dmp.putCollection(',', writable.columnNames, col => dmp.putValue(row[col as string])); dmp.putCollection(',', writable.columnNames, col =>
dmp.putValue(row[col as string], writable.columnDataTypes?.[col as string])
);
dmp.putRaw(')'); dmp.putRaw(')');
// console.log(dmp.s);
await driver.query(dbhan, dmp.s, { discardResult: true }); await driver.query(dbhan, dmp.s, { discardResult: true });
} }
} }

View File

@@ -63,6 +63,7 @@ export interface SqlDialect {
requireFromDual?: boolean; requireFromDual?: boolean;
userDatabaseNamePrefix?: string; // c## in Oracle userDatabaseNamePrefix?: string; // c## in Oracle
upperCaseAllDbObjectNames?: boolean; upperCaseAllDbObjectNames?: boolean;
defaultValueBeforeNullability?: boolean;
predefinedDataTypes: string[]; predefinedDataTypes: string[];

View File

@@ -40,6 +40,7 @@ export interface WriteTableOptions {
truncate?: boolean; truncate?: boolean;
createIfNotExists?: boolean; createIfNotExists?: boolean;
commitAfterInsert?: boolean; commitAfterInsert?: boolean;
targetTableStructure?: TableInfo;
} }
export interface EngineAuthType { export interface EngineAuthType {
@@ -163,6 +164,7 @@ export interface EngineDriver<TClient = any> extends FilterBehaviourProvider {
profilerChartMeasures?: { label: string; field: string }[]; profilerChartMeasures?: { label: string; field: string }[];
isElectronOnly?: boolean; isElectronOnly?: boolean;
supportsTransactions?: boolean; supportsTransactions?: boolean;
implicitTransactions?: boolean; // transaction is started with first SQL command, no BEGIN TRANSACTION is needed
collectionSingularLabel?: string; collectionSingularLabel?: string;
collectionPluralLabel?: string; collectionPluralLabel?: string;

View File

@@ -213,6 +213,8 @@
'icon transaction': 'mdi mdi-code-json', 'icon transaction': 'mdi mdi-code-json',
'icon commit': 'mdi mdi-check-circle', 'icon commit': 'mdi mdi-check-circle',
'icon rollback': 'mdi mdi-close-circle', 'icon rollback': 'mdi mdi-close-circle',
'icon autocommit-on': 'mdi mdi-check-circle',
'icon autocommit-off': 'mdi mdi-check-circle-outline',
'img ok': 'mdi mdi-check-circle color-icon-green', 'img ok': 'mdi mdi-check-circle color-icon-green',
'img ok-inv': 'mdi mdi-check-circle color-icon-inv-green', 'img ok-inv': 'mdi mdi-check-circle color-icon-inv-green',

View File

@@ -68,6 +68,22 @@
testEnabled: () => getCurrentEditor()?.beginTransactionEnabled(), testEnabled: () => getCurrentEditor()?.beginTransactionEnabled(),
onClick: () => getCurrentEditor().beginTransaction(), onClick: () => getCurrentEditor().beginTransaction(),
}); });
registerCommand({
id: 'query.autocommitOffSwitch',
category: 'Query',
name: 'Auto commit: OFF',
icon: 'icon autocommit-off',
testEnabled: () => getCurrentEditor()?.autocommitOffSwitchEnabled(),
onClick: () => getCurrentEditor().autocommitOffSwitch(),
});
registerCommand({
id: 'query.autocommitOnSwitch',
category: 'Query',
name: 'Auto commit: ON',
icon: 'icon autocommit-on',
testEnabled: () => getCurrentEditor()?.autocommitOnSwitchEnabled(),
onClick: () => getCurrentEditor().autocommitOnSwitch(),
});
registerCommand({ registerCommand({
id: 'query.commitTransaction', id: 'query.commitTransaction',
category: 'Query', category: 'Query',
@@ -178,6 +194,7 @@
let isAiAssistantVisible = isProApp() && localStorage.getItem(`tabdata_isAiAssistantVisible_${tabid}`) == 'true'; let isAiAssistantVisible = isProApp() && localStorage.getItem(`tabdata_isAiAssistantVisible_${tabid}`) == 'true';
let domAiAssistant; let domAiAssistant;
let isInTransaction = false; let isInTransaction = false;
let isAutocommit = false;
onMount(() => { onMount(() => {
intervalId = setInterval(() => { intervalId = setInterval(() => {
@@ -221,6 +238,7 @@
busy; busy;
sessionId; sessionId;
isInTransaction; isInTransaction;
isAutocommit;
invalidateCommands(); invalidateCommands();
} }
@@ -309,6 +327,9 @@
executeNumber++; executeNumber++;
visibleResultTabs = true; visibleResultTabs = true;
busy = true;
timerLabel.start();
let sesid = sessionId; let sesid = sessionId;
if (!sesid) { if (!sesid) {
const resp = await apiCall('sessions/create', { const resp = await apiCall('sessions/create', {
@@ -318,11 +339,13 @@
sesid = resp.sesid; sesid = resp.sesid;
sessionId = sesid; sessionId = sesid;
} }
busy = true; if (driver?.implicitTransactions) {
timerLabel.start(); isInTransaction = true;
}
await apiCall('sessions/execute-query', { await apiCall('sessions/execute-query', {
sesid, sesid,
sql, sql,
autoCommit: driver?.implicitTransactions && isAutocommit,
}); });
await apiCall('query-history/write', { await apiCall('query-history/write', {
data: { data: {
@@ -334,6 +357,26 @@
}); });
} }
async function executeControlCommand(command) {
busy = true;
timerLabel.start();
visibleResultTabs = true;
let sesid = sessionId;
if (!sesid) {
const resp = await apiCall('sessions/create', {
conid,
database,
});
sesid = resp.sesid;
sessionId = sesid;
}
await apiCall('sessions/execute-control-command', {
sesid,
command,
});
}
export async function executeCurrent() { export async function executeCurrent() {
const cmd = domEditor.getCurrentCommandText(); const cmd = domEditor.getCurrentCommandText();
await executeCore(cmd.text, cmd.line); await executeCore(cmd.text, cmd.line);
@@ -407,32 +450,42 @@
} }
export function beginTransaction() { export function beginTransaction() {
const dmp = driver.createDumper();
dmp.beginTransaction();
executeCore(dmp.s);
isInTransaction = true; isInTransaction = true;
executeControlCommand('beginTransaction');
} }
export function beginTransactionEnabled() { export function beginTransactionEnabled() {
return driver?.supportsTransactions && !isInTransaction && !busy; return driver?.supportsTransactions && !driver?.implicitTransactions && !isInTransaction && !busy;
}
export function autocommitOffSwitchEnabled() {
return driver?.supportsTransactions && driver?.implicitTransactions && !isInTransaction && !busy && !isAutocommit;
}
export function autocommitOnSwitchEnabled() {
return driver?.supportsTransactions && driver?.implicitTransactions && !isInTransaction && !busy && isAutocommit;
} }
export function endTransactionEnabled() { export function endTransactionEnabled() {
return !!sessionId && driver?.supportsTransactions && isInTransaction && !busy; return !!sessionId && driver?.supportsTransactions && isInTransaction && !busy && !isAutocommit;
}
export function autocommitOffSwitch() {
isAutocommit = true;
}
export function autocommitOnSwitch() {
isAutocommit = false;
} }
export function commitTransaction() { export function commitTransaction() {
const dmp = driver.createDumper();
dmp.commitTransaction();
executeCore(dmp.s);
isInTransaction = false; isInTransaction = false;
executeControlCommand('commitTransaction');
} }
export function rollbackTransaction() { export function rollbackTransaction() {
const dmp = driver.createDumper();
dmp.rollbackTransaction();
executeCore(dmp.s);
isInTransaction = false; isInTransaction = false;
executeControlCommand('rollbackTransaction');
} }
const handleMesageClick = message => { const handleMesageClick = message => {
@@ -444,6 +497,9 @@
const handleSessionDone = () => { const handleSessionDone = () => {
busy = false; busy = false;
if (isAutocommit) {
isInTransaction = false;
}
timerLabel.stop(); timerLabel.stop();
}; };
@@ -665,6 +721,12 @@
data-testid="QueryTab_beginTransactionButton" data-testid="QueryTab_beginTransactionButton"
hideDisabled hideDisabled
/> />
<ToolStripCommandButton command="query.autocommitOnSwitch" data-testid="QueryTab_autocommitOnSwitch" hideDisabled />
<ToolStripCommandButton
command="query.autocommitOffSwitch"
data-testid="QueryTab_autocommitOffSwitch"
hideDisabled
/>
<ToolStripCommandButton <ToolStripCommandButton
command="query.commitTransaction" command="query.commitTransaction"
data-testid="QueryTab_commitTransactionButton" data-testid="QueryTab_commitTransactionButton"

View File

@@ -52,6 +52,23 @@ export default async function applyScriptTemplate(
connectionInfo connectionInfo
); );
} }
if (scriptTemplate == 'INSERT') {
return generateTableSql(
extensions,
props,
(dmp, tableInfo) => {
dmp.put(
'^insert ^into %f (%,i) ^values (%,v)',
tableInfo,
tableInfo.columns.map(x => x.columnName),
tableInfo.columns.map(x => null)
);
},
true,
dbinfo,
connectionInfo
);
}
if (scriptTemplate == 'CREATE OBJECT') { if (scriptTemplate == 'CREATE OBJECT') {
const objectInfo = dbinfo ? extractDbObjectInfo(dbinfo, props) : await getSqlObjectInfo(props); const objectInfo = dbinfo ? extractDbObjectInfo(dbinfo, props) : await getSqlObjectInfo(props);
if (objectInfo) { if (objectInfo) {
@@ -95,6 +112,7 @@ export function getSupportedScriptTemplates(objectTypeField: string): { label: s
return [ return [
{ label: 'CREATE TABLE', scriptTemplate: 'CREATE TABLE' }, { label: 'CREATE TABLE', scriptTemplate: 'CREATE TABLE' },
{ label: 'SELECT', scriptTemplate: 'SELECT' }, { label: 'SELECT', scriptTemplate: 'SELECT' },
{ label: 'INSERT', scriptTemplate: 'INSERT' },
]; ];
case 'views': case 'views':
return [ return [

View File

@@ -227,7 +227,12 @@
{#if !filter} {#if !filter}
<DropDownButton icon="icon plus-thick" menu={createAddMenu} /> <DropDownButton icon="icon plus-thick" menu={createAddMenu} />
{/if} {/if}
<InlineButton on:click={handleRefreshDatabase} title="Refresh database connection and object list" square> <InlineButton
on:click={handleRefreshDatabase}
title="Refresh database connection and object list"
square
data-testid="SqlObjectList_refreshButton"
>
<FontIcon icon="icon refresh" /> <FontIcon icon="icon refresh" />
</InlineButton> </InlineButton>
</SearchBoxWrapper> </SearchBoxWrapper>
@@ -245,7 +250,11 @@
<FocusedConnectionInfoWidget {conid} {database} connection={$connection} /> <FocusedConnectionInfoWidget {conid} {database} connection={$connection} />
{/if} {/if}
<WidgetsInnerContainer bind:this={domContainer} hideContent={differentFocusedDb}> <WidgetsInnerContainer
bind:this={domContainer}
hideContent={differentFocusedDb}
data-testid="SqlObjectList_container"
>
{#if ($status && ($status.name == 'pending' || $status.name == 'checkStructure' || $status.name == 'loadStructure') && $objects) || !$objects} {#if ($status && ($status.name == 'pending' || $status.name == 'checkStructure' || $status.name == 'loadStructure') && $objects) || !$objects}
<LoadingInfo message={$status?.feedback?.analysingMessage || 'Loading database structure'} /> <LoadingInfo message={$status?.feedback?.analysingMessage || 'Loading database structure'} />
{:else} {:else}

View File

@@ -8,7 +8,7 @@
} }
</script> </script>
<div on:drop bind:this={domDiv} class:hideContent><slot /></div> <div on:drop bind:this={domDiv} class:hideContent data-testid={$$props['data-testid']}><slot /></div>
<style> <style>
div { div {

View File

@@ -23,7 +23,16 @@ function createOracleBulkInsertStream(driver, stream, dbhan, name, options) {
dmp.putRaw(')\n VALUES (\n'); dmp.putRaw(')\n VALUES (\n');
dmp.put( dmp.put(
'%,s', '%,s',
writable.columnNames.map((c, i) => `:C${i}`) writable.columnNames.map((c, i) => {
if (writable.columnDataTypes?.[c]?.toLowerCase() == 'timestamp') {
return `CASE WHEN :C${i} IS NULL THEN NULL ELSE TO_TIMESTAMP(:C${i}, 'YYYY-MM-DD"T"HH24:MI:SS') END`;
}
if (writable.columnDataTypes?.[c]?.toLowerCase() == 'date') {
return `CASE WHEN :C${i} IS NULL THEN NULL ELSE TO_DATE(:C${i}, 'YYYY-MM-DD"T"HH24:MI:SS') END`;
}
return `:C${i}`;
})
); );
dmp.putRaw(')'); dmp.putRaw(')');

View File

@@ -139,6 +139,16 @@ class Dumper extends SqlDumper {
// putByteArrayValue(value) { // putByteArrayValue(value) {
// this.putRaw(`e'\\\\x${arrayToHexString(value)}'`); // this.putRaw(`e'\\\\x${arrayToHexString(value)}'`);
// } // }
putValue(value, dataType) {
if (dataType?.toLowerCase() == 'timestamp') {
this.putRaw(`TO_TIMESTAMP('${value}', 'YYYY-MM-DD"T"HH24:MI:SS')`);
} else if (dataType?.toLowerCase() == 'date') {
this.putRaw(`TO_DATE('${value}', 'YYYY-MM-DD"T"HH24:MI:SS')`);
} else {
super.putValue(value);
}
}
} }
module.exports = Dumper; module.exports = Dumper;

View File

@@ -23,6 +23,7 @@ const dialect = {
userDatabaseNamePrefix: 'C##', userDatabaseNamePrefix: 'C##',
upperCaseAllDbObjectNames: true, upperCaseAllDbObjectNames: true,
requireStandaloneSelectForScopeIdentity: true, requireStandaloneSelectForScopeIdentity: true,
defaultValueBeforeNullability: true,
createColumn: true, createColumn: true,
dropColumn: true, dropColumn: true,
@@ -102,6 +103,7 @@ const oracleDriver = {
getQuerySplitterOptions: () => oracleSplitterOptions, getQuerySplitterOptions: () => oracleSplitterOptions,
readOnlySessions: true, readOnlySessions: true,
supportsTransactions: true, supportsTransactions: true,
implicitTransactions: true,
databaseUrlPlaceholder: 'e.g. localhost:1521/orcl', databaseUrlPlaceholder: 'e.g. localhost:1521/orcl',
@@ -177,6 +179,12 @@ END trigger_name;
} }
return dialect; return dialect;
}, },
adaptDataType(dataType) {
if (dataType?.toLowerCase() == 'datetime') return 'timestamp';
if (dataType?.toLowerCase() == 'text') return 'clob';
return dataType;
},
}; };
module.exports = oracleDriver; module.exports = oracleDriver;

View File

@@ -109,3 +109,19 @@ jobs:
image: redis image: redis
ports: ports:
- 16011:6379 - 16011:6379
mssql:
image: mcr.microsoft.com/mssql/server
ports:
- 16012:1433
env:
- ACCEPT_EULA=Y
- SA_PASSWORD=Pwd2020Db
- MSSQL_PID=Express
oracle:
image: gvenzl/oracle-xe:21-slim
env:
ORACLE_PASSWORD: Pwd2020Db
ports:
- 16013:1521

View File

@@ -7,7 +7,7 @@ checkout-and-merge-pro:
repository: dbgate/dbgate-pro repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }} token: ${{ secrets.GH_TOKEN }}
path: dbgate-pro path: dbgate-pro
ref: 47dbf13a0e22955a8d6a1fb7063688a44c9c84c4 ref: 83c1c85eb2761a642c17b2f0f882970718e0afca
- name: Merge dbgate/dbgate-pro - name: Merge dbgate/dbgate-pro
run: | run: |
mkdir ../dbgate-pro mkdir ../dbgate-pro