mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 17:02:49 +03:00
parent
00227728cb
commit
6c20ca8a55
@ -40,6 +40,40 @@ export const passMTCheckRoute = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const passMTRenameTable = () => {
|
||||||
|
cy.get(getElementFromAlias('heading-edit-table')).click();
|
||||||
|
cy.get(getElementFromAlias('heading-edit-table-input'))
|
||||||
|
.clear()
|
||||||
|
.type(getTableName(3, testName));
|
||||||
|
cy.get(getElementFromAlias('heading-edit-table-save')).click();
|
||||||
|
cy.wait(10000);
|
||||||
|
validateCT(getTableName(3, testName), 'success');
|
||||||
|
cy.get(getElementFromAlias('heading-edit-table')).click();
|
||||||
|
cy.get(getElementFromAlias('heading-edit-table-input'))
|
||||||
|
.clear()
|
||||||
|
.type(getTableName(0, testName));
|
||||||
|
cy.get(getElementFromAlias('heading-edit-table-save')).click();
|
||||||
|
cy.wait(10000);
|
||||||
|
validateCT(getTableName(0, testName), 'success');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const passMTRenameColumn = () => {
|
||||||
|
cy.get(getElementFromAlias('edit-id')).click();
|
||||||
|
cy.get(getElementFromAlias('edit-col-name'))
|
||||||
|
.clear()
|
||||||
|
.type(getColName(3));
|
||||||
|
cy.get(getElementFromAlias('save-button')).click();
|
||||||
|
cy.wait(2500);
|
||||||
|
validateColumn(getTableName(0, testName), [getColName(3)], 'success');
|
||||||
|
cy.get(getElementFromAlias(`edit-${getColName(3)}`)).click();
|
||||||
|
cy.get(getElementFromAlias('edit-col-name'))
|
||||||
|
.clear()
|
||||||
|
.type('id');
|
||||||
|
cy.get(getElementFromAlias('save-button')).click();
|
||||||
|
cy.wait(2500);
|
||||||
|
validateColumn(getTableName(0, testName), ['id'], 'success');
|
||||||
|
};
|
||||||
|
|
||||||
export const passMTMoveToTable = () => {
|
export const passMTMoveToTable = () => {
|
||||||
cy.get(getElementFromAlias(getTableName(0, testName))).click();
|
cy.get(getElementFromAlias(getTableName(0, testName))).click();
|
||||||
cy.url().should(
|
cy.url().should(
|
||||||
|
@ -14,6 +14,8 @@ import {
|
|||||||
failMCWithWrongDefaultValue,
|
failMCWithWrongDefaultValue,
|
||||||
passCreateForeignKey,
|
passCreateForeignKey,
|
||||||
passRemoveForeignKey,
|
passRemoveForeignKey,
|
||||||
|
passMTRenameTable,
|
||||||
|
passMTRenameColumn,
|
||||||
} from './spec';
|
} from './spec';
|
||||||
|
|
||||||
import { testMode } from '../../../helpers/common';
|
import { testMode } from '../../../helpers/common';
|
||||||
@ -36,6 +38,8 @@ export const runModifyTableTests = () => {
|
|||||||
it('Creating a table', passMTCreateTable);
|
it('Creating a table', passMTCreateTable);
|
||||||
it('Moving to the table', passMTMoveToTable);
|
it('Moving to the table', passMTMoveToTable);
|
||||||
it('Modify table button opens the correct route', passMTCheckRoute);
|
it('Modify table button opens the correct route', passMTCheckRoute);
|
||||||
|
it('Pass renaming table', passMTRenameTable);
|
||||||
|
it('Pass renaming column', passMTRenameColumn);
|
||||||
it('Fails to add column without column name', failMTWithoutColName);
|
it('Fails to add column without column name', failMTWithoutColName);
|
||||||
it('Fails without type selected', failMTWithoutColType);
|
it('Fails without type selected', failMTWithoutColType);
|
||||||
it('Add a column', passMTAddColumn);
|
it('Add a column', passMTAddColumn);
|
||||||
|
@ -9,9 +9,8 @@ import {
|
|||||||
const delRel = (table, relname) => {
|
const delRel = (table, relname) => {
|
||||||
cy.get(getElementFromAlias(table)).click();
|
cy.get(getElementFromAlias(table)).click();
|
||||||
cy.get(getElementFromAlias('table-relationships')).click();
|
cy.get(getElementFromAlias('table-relationships')).click();
|
||||||
cy.get(getElementFromAlias(`remove-button-${relname}`))
|
cy.get(getElementFromAlias(`relationship-toggle-editor-${relname}`)).click();
|
||||||
.first()
|
cy.get(getElementFromAlias(`relationship-remove-${relname}`)).click();
|
||||||
.click();
|
|
||||||
cy.on('window:alert', str => {
|
cy.on('window:alert', str => {
|
||||||
expect(str === 'Are you sure?').to.be.true;
|
expect(str === 'Are you sure?').to.be.true;
|
||||||
});
|
});
|
||||||
@ -176,7 +175,7 @@ export const passRTAddSuggestedRel = () => {
|
|||||||
.clear()
|
.clear()
|
||||||
.type('author');
|
.type('author');
|
||||||
cy.get(getElementFromAlias('obj-rel-save-0')).click();
|
cy.get(getElementFromAlias('obj-rel-save-0')).click();
|
||||||
cy.wait(15000);
|
cy.wait(5000);
|
||||||
validateColumn(
|
validateColumn(
|
||||||
'article_table_rt',
|
'article_table_rt',
|
||||||
['title', { name: 'author', columns: ['name'] }],
|
['title', { name: 'author', columns: ['name'] }],
|
||||||
@ -189,7 +188,34 @@ export const passRTAddSuggestedRel = () => {
|
|||||||
.clear()
|
.clear()
|
||||||
.type('comments');
|
.type('comments');
|
||||||
cy.get(getElementFromAlias('arr-rel-save-0')).click();
|
cy.get(getElementFromAlias('arr-rel-save-0')).click();
|
||||||
cy.wait(15000);
|
cy.wait(5000);
|
||||||
|
validateColumn(
|
||||||
|
'article_table_rt',
|
||||||
|
['title', { name: 'comments', columns: ['comment'] }],
|
||||||
|
'success'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const passRTRenameRelationship = () => {
|
||||||
|
cy.get(getElementFromAlias('relationship-toggle-editor-comments')).click();
|
||||||
|
cy.get(getElementFromAlias('relationship-name-input-comments'))
|
||||||
|
.clear()
|
||||||
|
.type('comments_renamed');
|
||||||
|
cy.get(getElementFromAlias('relationship-save-comments')).click();
|
||||||
|
cy.wait(5000);
|
||||||
|
validateColumn(
|
||||||
|
'article_table_rt',
|
||||||
|
['title', { name: 'comments_renamed', columns: ['comment'] }],
|
||||||
|
'success'
|
||||||
|
);
|
||||||
|
cy.get(
|
||||||
|
getElementFromAlias('relationship-toggle-editor-comments_renamed')
|
||||||
|
).click();
|
||||||
|
cy.get(getElementFromAlias('relationship-name-input-comments_renamed'))
|
||||||
|
.clear()
|
||||||
|
.type('comments');
|
||||||
|
cy.get(getElementFromAlias('relationship-save-comments_renamed')).click();
|
||||||
|
cy.wait(5000);
|
||||||
validateColumn(
|
validateColumn(
|
||||||
'article_table_rt',
|
'article_table_rt',
|
||||||
['title', { name: 'comments', columns: ['comment'] }],
|
['title', { name: 'comments', columns: ['comment'] }],
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
passRTAddSuggestedRel,
|
passRTAddSuggestedRel,
|
||||||
failRTAddSuggestedRel,
|
failRTAddSuggestedRel,
|
||||||
checkAddManualRelationshipsButton,
|
checkAddManualRelationshipsButton,
|
||||||
|
passRTRenameRelationship,
|
||||||
} from './spec';
|
} from './spec';
|
||||||
import { testMode } from '../../../helpers/common';
|
import { testMode } from '../../../helpers/common';
|
||||||
import { setMetaData } from '../../validators/validators';
|
import { setMetaData } from '../../validators/validators';
|
||||||
@ -42,6 +43,7 @@ export const runRelationshipsTests = () => {
|
|||||||
it('Deleting the relationships', passRTDeleteRelationships);
|
it('Deleting the relationships', passRTDeleteRelationships);
|
||||||
it('Adding Suggested Relationships Error', failRTAddSuggestedRel);
|
it('Adding Suggested Relationships Error', failRTAddSuggestedRel);
|
||||||
it('Adding Suggested Relationships', passRTAddSuggestedRel);
|
it('Adding Suggested Relationships', passRTAddSuggestedRel);
|
||||||
|
it('Rename relationships', passRTRenameRelationship);
|
||||||
it('Deleting the relationships', passRTDeleteRelationships);
|
it('Deleting the relationships', passRTDeleteRelationships);
|
||||||
it('Deleting testing tables', passRTDeleteTables);
|
it('Deleting testing tables', passRTDeleteTables);
|
||||||
});
|
});
|
||||||
|
@ -397,10 +397,8 @@ export const passVAddManualObjRel = () => {
|
|||||||
export const passVDeleteRelationships = () => {
|
export const passVDeleteRelationships = () => {
|
||||||
cy.get(getElementFromAlias('author_average_rating_vt')).click();
|
cy.get(getElementFromAlias('author_average_rating_vt')).click();
|
||||||
cy.get(getElementFromAlias('table-relationships')).click();
|
cy.get(getElementFromAlias('table-relationships')).click();
|
||||||
cy.get('button')
|
cy.get(getElementFromAlias('relationship-toggle-editor-author')).click();
|
||||||
.contains('Remove')
|
cy.get(getElementFromAlias('relationship-remove-author')).click();
|
||||||
.first()
|
|
||||||
.click();
|
|
||||||
cy.on('window:alert', str => {
|
cy.on('window:alert', str => {
|
||||||
expect(str === 'Are you sure?').to.be.true;
|
expect(str === 'Are you sure?').to.be.true;
|
||||||
});
|
});
|
||||||
|
@ -736,7 +736,7 @@ code
|
|||||||
.yellow_button
|
.yellow_button
|
||||||
{
|
{
|
||||||
background-color: #FEC53D;
|
background-color: #FEC53D;
|
||||||
border-radius: 5px;
|
border-radius: 3px;
|
||||||
color: #606060;
|
color: #606060;
|
||||||
border: 1px solid #FEC53D;
|
border: 1px solid #FEC53D;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
@ -861,7 +861,51 @@ code
|
|||||||
{
|
{
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
padding-bottom: 20px
|
||||||
|
}
|
||||||
|
|
||||||
|
.editable_heading_text
|
||||||
|
{
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
padding-bottom: 20px;
|
padding-bottom: 20px;
|
||||||
|
h2 {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
i {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.editable_heading_textbox
|
||||||
|
{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
input {
|
||||||
|
width: 30%;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.editable_heading_action
|
||||||
|
{
|
||||||
|
margin-left: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
i {
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.editable_heading_action_item
|
||||||
|
{
|
||||||
|
margin-right: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
text-decoration: underline;
|
||||||
|
font-weight: normal;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.header_project_name
|
.header_project_name
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,92 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styles from '../Common.scss';
|
||||||
|
|
||||||
|
class Heading extends React.Component {
|
||||||
|
state = {
|
||||||
|
text: this.props.currentValue,
|
||||||
|
isEditting: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
handleTextChange = e => {
|
||||||
|
this.setState({ text: e.target.value });
|
||||||
|
};
|
||||||
|
|
||||||
|
toggleEditting = () => {
|
||||||
|
this.setState({ isEditting: !this.state.isEditting });
|
||||||
|
};
|
||||||
|
|
||||||
|
handleKeyPress = e => {
|
||||||
|
if (this.state.isEditting) {
|
||||||
|
if (e.charCode === 13) {
|
||||||
|
this.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
save = () => {
|
||||||
|
if (this.props.loading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.props.save(this.state.text);
|
||||||
|
};
|
||||||
|
|
||||||
|
render = () => {
|
||||||
|
const { editable, currentValue, save, loading, property } = this.props;
|
||||||
|
|
||||||
|
const { text, isEditting } = this.state;
|
||||||
|
|
||||||
|
if (!editable) {
|
||||||
|
return <h2 className={styles.heading_text}>{currentValue}</h2>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!save) {
|
||||||
|
console.warn('In EditableHeading, please provide a prop save');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isEditting) {
|
||||||
|
return (
|
||||||
|
<div className={styles.editable_heading_text}>
|
||||||
|
<h2>{currentValue}</h2>
|
||||||
|
<div
|
||||||
|
onClick={this.toggleEditting}
|
||||||
|
className={styles.editable_heading_action}
|
||||||
|
data-test={`heading-edit-${property}`}
|
||||||
|
>
|
||||||
|
<i className="fa fa-edit" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.editable_heading_textbox}>
|
||||||
|
<input
|
||||||
|
onChange={this.handleTextChange}
|
||||||
|
className={`${styles.add_pad_left} form-control`}
|
||||||
|
type="text"
|
||||||
|
onKeyPress={this.handleKeyPress}
|
||||||
|
value={text}
|
||||||
|
data-test={`heading-edit-${property}-input`}
|
||||||
|
/>
|
||||||
|
<div className={styles.editable_heading_action}>
|
||||||
|
<div
|
||||||
|
className={styles.editable_heading_action_item}
|
||||||
|
onClick={this.save}
|
||||||
|
data-test={`heading-edit-${property}-save`}
|
||||||
|
>
|
||||||
|
{loading ? 'Saving...' : 'Save'}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={styles.editable_heading_action_item}
|
||||||
|
onClick={this.toggleEditting}
|
||||||
|
data-test={`heading-edit-${property}-cancel`}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Heading;
|
@ -8,6 +8,7 @@ const gqlTableErrorNotif = [
|
|||||||
custom:
|
custom:
|
||||||
'Table name cannot contain special characters. It can have alphabets, numbers (cannot start with numbers) and _ (can start with _)',
|
'Table name cannot contain special characters. It can have alphabets, numbers (cannot start with numbers) and _ (can start with _)',
|
||||||
},
|
},
|
||||||
|
'Error renaming table!',
|
||||||
];
|
];
|
||||||
|
|
||||||
const gqlColumnErrorNotif = [
|
const gqlColumnErrorNotif = [
|
||||||
@ -18,6 +19,18 @@ const gqlColumnErrorNotif = [
|
|||||||
custom:
|
custom:
|
||||||
'Column name cannot contain special characters. It can have alphabets, numbers (cannot start with numbers) and _ (can start with _)',
|
'Column name cannot contain special characters. It can have alphabets, numbers (cannot start with numbers) and _ (can start with _)',
|
||||||
},
|
},
|
||||||
|
'Error renaming column!',
|
||||||
|
];
|
||||||
|
|
||||||
|
const gqlViewErrorNotif = [
|
||||||
|
'Error creating view!',
|
||||||
|
'View name cannot contain special characters',
|
||||||
|
'',
|
||||||
|
{
|
||||||
|
custom:
|
||||||
|
'View name cannot contain special characters. It can have alphabets, numbers (cannot start with numbers) and _ (can start with _)',
|
||||||
|
},
|
||||||
|
'Error renaming view!',
|
||||||
];
|
];
|
||||||
|
|
||||||
const gqlRelErrorNotif = [
|
const gqlRelErrorNotif = [
|
||||||
@ -28,7 +41,13 @@ const gqlRelErrorNotif = [
|
|||||||
custom:
|
custom:
|
||||||
'Relationship name cannot contain special characters. It can have alphabets, numbers (cannot start with numbers) and _ (can start with _)',
|
'Relationship name cannot contain special characters. It can have alphabets, numbers (cannot start with numbers) and _ (can start with _)',
|
||||||
},
|
},
|
||||||
|
'Error renaming relationship!',
|
||||||
];
|
];
|
||||||
|
|
||||||
export default gqlPattern;
|
export default gqlPattern;
|
||||||
export { gqlTableErrorNotif, gqlColumnErrorNotif, gqlRelErrorNotif };
|
export {
|
||||||
|
gqlTableErrorNotif,
|
||||||
|
gqlViewErrorNotif,
|
||||||
|
gqlColumnErrorNotif,
|
||||||
|
gqlRelErrorNotif,
|
||||||
|
};
|
||||||
|
@ -482,7 +482,8 @@ const makeMigrationCall = (
|
|||||||
customOnError,
|
customOnError,
|
||||||
requestMsg,
|
requestMsg,
|
||||||
successMsg,
|
successMsg,
|
||||||
errorMsg
|
errorMsg,
|
||||||
|
shouldSkipSchemaReload
|
||||||
) => {
|
) => {
|
||||||
const upQuery = {
|
const upQuery = {
|
||||||
type: 'bulk',
|
type: 'bulk',
|
||||||
@ -519,14 +520,16 @@ const makeMigrationCall = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onSuccess = () => {
|
const onSuccess = () => {
|
||||||
if (globals.consoleMode === 'cli') {
|
if (!shouldSkipSchemaReload) {
|
||||||
dispatch(loadMigrationStatus()); // don't call for server mode
|
if (globals.consoleMode === 'cli') {
|
||||||
|
dispatch(loadMigrationStatus()); // don't call for server mode
|
||||||
|
}
|
||||||
|
dispatch(loadSchema());
|
||||||
}
|
}
|
||||||
dispatch(loadSchema());
|
|
||||||
customOnSuccess();
|
|
||||||
if (successMsg) {
|
if (successMsg) {
|
||||||
dispatch(showSuccessNotification(successMsg));
|
dispatch(showSuccessNotification(successMsg));
|
||||||
}
|
}
|
||||||
|
customOnSuccess();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onError = err => {
|
const onError = err => {
|
||||||
|
@ -1,23 +1,44 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
|
import globals from '../../../../Globals';
|
||||||
|
import { changeTableOrViewName } from '../TableModify/ModifyActions';
|
||||||
|
import EditableHeading from '../../../Common/EditableHeading/EditableHeading';
|
||||||
|
import { tabNameMap } from '../utils';
|
||||||
|
|
||||||
const ViewHeader = ({ tableName, tabName, currentSchema, migrationMode }) => {
|
const ViewHeader = ({
|
||||||
|
tableName,
|
||||||
|
tabName,
|
||||||
|
currentSchema,
|
||||||
|
migrationMode,
|
||||||
|
dispatch,
|
||||||
|
allowRename,
|
||||||
|
}) => {
|
||||||
const styles = require('../TableCommon/Table.scss');
|
const styles = require('../TableCommon/Table.scss');
|
||||||
let capitalised = tabName;
|
let capitalised = tabName;
|
||||||
capitalised = capitalised[0].toUpperCase() + capitalised.slice(1);
|
capitalised = capitalised[0].toUpperCase() + capitalised.slice(1);
|
||||||
let activeTab;
|
const activeTab = tabNameMap[tabName];
|
||||||
if (tabName === 'view') {
|
const viewRenameCallback = newName => {
|
||||||
activeTab = 'Browse Rows';
|
const currentPath = window.location.pathname.replace(
|
||||||
} else if (tabName === 'insert') {
|
new RegExp(globals.urlPrefix, 'g'),
|
||||||
activeTab = 'Insert Row';
|
''
|
||||||
} else if (tabName === 'modify') {
|
);
|
||||||
activeTab = 'Modify';
|
const newPath = currentPath.replace(
|
||||||
} else if (tabName === 'relationships') {
|
/(\/schema\/.*)\/views\/(\w*)(\/.*)?/,
|
||||||
activeTab = 'Relationships';
|
`$1/views/${newName}$3`
|
||||||
} else if (tabName === 'permissions') {
|
);
|
||||||
activeTab = 'Permissions';
|
window.location.replace(
|
||||||
}
|
`${window.location.origin}${globals.urlPrefix}${newPath}`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveViewNameChange = newName => {
|
||||||
|
dispatch(
|
||||||
|
changeTableOrViewName(false, tableName, newName, () =>
|
||||||
|
viewRenameCallback(newName)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Helmet title={capitalised + ' - ' + tableName + ' - Data | Hasura'} />
|
<Helmet title={capitalised + ' - ' + tableName + ' - Data | Hasura'} />
|
||||||
@ -40,7 +61,14 @@ const ViewHeader = ({ tableName, tabName, currentSchema, migrationMode }) => {
|
|||||||
</Link>{' '}
|
</Link>{' '}
|
||||||
<i className="fa fa-angle-right" aria-hidden="true" /> {activeTab}
|
<i className="fa fa-angle-right" aria-hidden="true" /> {activeTab}
|
||||||
</div>
|
</div>
|
||||||
<h2 className={styles.heading_text}>{tableName}</h2>
|
<EditableHeading
|
||||||
|
currentValue={tableName}
|
||||||
|
save={saveViewNameChange}
|
||||||
|
loading={false}
|
||||||
|
editable={tabName === 'modify' && allowRename}
|
||||||
|
dispatch={dispatch}
|
||||||
|
property="view"
|
||||||
|
/>
|
||||||
<div className={styles.nav}>
|
<div className={styles.nav}>
|
||||||
<ul className="nav nav-pills">
|
<ul className="nav nav-pills">
|
||||||
<li
|
<li
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import globals from '../../../../Globals';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
|
import { changeTableOrViewName } from '../TableModify/ModifyActions';
|
||||||
|
import EditableHeading from '../../../Common/EditableHeading/EditableHeading';
|
||||||
|
import { tabNameMap } from '../utils';
|
||||||
|
|
||||||
const TableHeader = ({
|
const TableHeader = ({
|
||||||
tableName,
|
tableName,
|
||||||
@ -8,6 +12,8 @@ const TableHeader = ({
|
|||||||
count,
|
count,
|
||||||
migrationMode,
|
migrationMode,
|
||||||
currentSchema,
|
currentSchema,
|
||||||
|
dispatch,
|
||||||
|
allowRename,
|
||||||
}) => {
|
}) => {
|
||||||
const styles = require('./Table.scss');
|
const styles = require('./Table.scss');
|
||||||
let capitalised = tabName;
|
let capitalised = tabName;
|
||||||
@ -16,18 +22,30 @@ const TableHeader = ({
|
|||||||
if (!(count === null || count === undefined)) {
|
if (!(count === null || count === undefined)) {
|
||||||
showCount = '(' + count + ')';
|
showCount = '(' + count + ')';
|
||||||
}
|
}
|
||||||
let activeTab;
|
const activeTab = tabNameMap[tabName];
|
||||||
if (tabName === 'view') {
|
|
||||||
activeTab = 'Browse Rows';
|
const tableRenameCallback = newName => {
|
||||||
} else if (tabName === 'insert') {
|
const currentPath = window.location.pathname.replace(
|
||||||
activeTab = 'Insert Row';
|
new RegExp(globals.urlPrefix, 'g'),
|
||||||
} else if (tabName === 'modify') {
|
''
|
||||||
activeTab = 'Modify';
|
);
|
||||||
} else if (tabName === 'relationships') {
|
const newPath = currentPath.replace(
|
||||||
activeTab = 'Relationships';
|
/(\/schema\/.*)\/tables\/(\w*)(\/.*)?/,
|
||||||
} else if (tabName === 'permissions') {
|
`$1/tables/${newName}$3`
|
||||||
activeTab = 'Permissions';
|
);
|
||||||
}
|
window.location.replace(
|
||||||
|
`${window.location.origin}${globals.urlPrefix}${newPath}`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveTableNameChange = newName => {
|
||||||
|
dispatch(
|
||||||
|
changeTableOrViewName(true, tableName, newName, () =>
|
||||||
|
tableRenameCallback(newName)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Helmet title={capitalised + ' - ' + tableName + ' - Data | Hasura'} />
|
<Helmet title={capitalised + ' - ' + tableName + ' - Data | Hasura'} />
|
||||||
@ -52,7 +70,14 @@ const TableHeader = ({
|
|||||||
</Link>{' '}
|
</Link>{' '}
|
||||||
<i className="fa fa-angle-right" aria-hidden="true" /> {activeTab}
|
<i className="fa fa-angle-right" aria-hidden="true" /> {activeTab}
|
||||||
</div>
|
</div>
|
||||||
<h2 className={styles.heading_text}>{tableName}</h2>
|
<EditableHeading
|
||||||
|
currentValue={tableName}
|
||||||
|
save={saveTableNameChange}
|
||||||
|
loading={false}
|
||||||
|
editable={tabName === 'modify' && allowRename}
|
||||||
|
dispatch={dispatch}
|
||||||
|
property="table"
|
||||||
|
/>
|
||||||
<div className={styles.nav}>
|
<div className={styles.nav}>
|
||||||
<ul className="nav nav-pills">
|
<ul className="nav nav-pills">
|
||||||
<li
|
<li
|
||||||
|
@ -50,9 +50,6 @@
|
|||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
}
|
}
|
||||||
tr {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
td {
|
td {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
@ -124,3 +121,8 @@ a.expanded {
|
|||||||
.relationshipTopPadding {
|
.relationshipTopPadding {
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.relEditButtons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction:row;
|
||||||
|
}
|
405
console/src/components/Services/Data/TableModify/ColumnEditor.js
Normal file
405
console/src/components/Services/Data/TableModify/ColumnEditor.js
Normal file
@ -0,0 +1,405 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Link } from 'react-router';
|
||||||
|
import {
|
||||||
|
fkRefTableChange,
|
||||||
|
fkRColChange,
|
||||||
|
toggleFKCheckBox,
|
||||||
|
isColumnUnique,
|
||||||
|
deleteConstraintSql,
|
||||||
|
} from '../TableModify/ModifyActions';
|
||||||
|
import dataTypes from '../Common/DataTypes';
|
||||||
|
import { convertListToDictUsingKV } from '../../../../utils/data';
|
||||||
|
import {
|
||||||
|
INTEGER,
|
||||||
|
SERIAL,
|
||||||
|
BIGINT,
|
||||||
|
BIGSERIAL,
|
||||||
|
UUID,
|
||||||
|
JSONDTYPE,
|
||||||
|
JSONB,
|
||||||
|
TIMESTAMP,
|
||||||
|
TIME,
|
||||||
|
} from '../../../../constants';
|
||||||
|
import Button from '../../../Common/Button/Button';
|
||||||
|
|
||||||
|
const appPrefix = '/data';
|
||||||
|
|
||||||
|
const ColumnEditor = ({
|
||||||
|
column,
|
||||||
|
onSubmit,
|
||||||
|
onDelete,
|
||||||
|
allSchemas,
|
||||||
|
fkAdd,
|
||||||
|
tableName,
|
||||||
|
dispatch,
|
||||||
|
currentSchema,
|
||||||
|
columnComment,
|
||||||
|
allowRename,
|
||||||
|
}) => {
|
||||||
|
// eslint-disable-line no-unused-vars
|
||||||
|
const c = column;
|
||||||
|
const styles = require('./Modify.scss');
|
||||||
|
let [iname, inullable, iunique, idefault, icomment, itype] = [
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
];
|
||||||
|
// NOTE: the datatypes is filtered of serial and bigserial where hasuraDatatype === null
|
||||||
|
const refTable = fkAdd.refTable;
|
||||||
|
const tableSchema = allSchemas.find(t => t.table_name === tableName);
|
||||||
|
const rcol = fkAdd.rcol;
|
||||||
|
const typeMap = convertListToDictUsingKV(
|
||||||
|
'hasuraDatatype',
|
||||||
|
'value',
|
||||||
|
dataTypes.filter(dataType => dataType.hasuraDatatype)
|
||||||
|
);
|
||||||
|
const refSchema = allSchemas.find(t => t.table_name === refTable);
|
||||||
|
// const allTableNamesExceptCurrent = allSchemas.filter(t => t.table_name !== tableName);
|
||||||
|
const allTableNames = allSchemas.map(t => t.table_name);
|
||||||
|
allTableNames.sort();
|
||||||
|
|
||||||
|
const refColumnNames = refSchema
|
||||||
|
? refSchema.columns.map(col => col.column_name)
|
||||||
|
: [];
|
||||||
|
refColumnNames.sort();
|
||||||
|
const onFKRefTableChange = e => {
|
||||||
|
dispatch(fkRefTableChange(e.target.value));
|
||||||
|
};
|
||||||
|
const onFKRefColumnChange = e => {
|
||||||
|
dispatch(fkRColChange(e.target.value));
|
||||||
|
};
|
||||||
|
const checkExistingForeignKey = () => {
|
||||||
|
const numFk = tableSchema.foreign_key_constraints.length;
|
||||||
|
let fkName = '';
|
||||||
|
const onDeleteFK = e => {
|
||||||
|
e.preventDefault();
|
||||||
|
const isOk = confirm('Are you sure?');
|
||||||
|
if (isOk) {
|
||||||
|
dispatch(deleteConstraintSql(tableName, fkName));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (numFk > 0) {
|
||||||
|
for (let i = 0; i < numFk; i++) {
|
||||||
|
const fk = tableSchema.foreign_key_constraints[i];
|
||||||
|
if (
|
||||||
|
Object.keys(fk.column_mapping).toString() === c.column_name.toString()
|
||||||
|
) {
|
||||||
|
fkName = fk.constraint_name;
|
||||||
|
return (
|
||||||
|
<div className={`${styles.display_flex} form-group`}>
|
||||||
|
<label className="col-xs-3 text-right">Foreign Key</label>
|
||||||
|
<div className="col-xs-9">
|
||||||
|
<h5>
|
||||||
|
<span>{fk.ref_table} :: </span>
|
||||||
|
<span className={styles.add_mar_right}>
|
||||||
|
{Object.keys(fk.column_mapping)
|
||||||
|
.map(l => fk.column_mapping[l])
|
||||||
|
.join(',')}
|
||||||
|
</span>
|
||||||
|
<Link
|
||||||
|
to={`${appPrefix}/schema/${currentSchema}/tables/${tableName}/relationships`}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
color="white"
|
||||||
|
size="sm"
|
||||||
|
type="button"
|
||||||
|
data-test="add-rel-mod"
|
||||||
|
>
|
||||||
|
+Add relationship
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
color="red"
|
||||||
|
size="sm"
|
||||||
|
onClick={onDeleteFK}
|
||||||
|
data-test="remove-constraint-button"
|
||||||
|
>
|
||||||
|
{' '}
|
||||||
|
Remove Constraint{' '}
|
||||||
|
</Button>{' '}
|
||||||
|
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className={`${styles.display_flex} form-group`}>
|
||||||
|
<label className="col-xs-3 text-right">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={fkAdd.fkCheckBox}
|
||||||
|
onChange={e => {
|
||||||
|
dispatch(toggleFKCheckBox(e.target.checked));
|
||||||
|
}}
|
||||||
|
value="ForeignKey"
|
||||||
|
data-test="foreign-key-checkbox"
|
||||||
|
/>{' '}
|
||||||
|
Foreign Key
|
||||||
|
</label>
|
||||||
|
<div className="col-xs-6">
|
||||||
|
<select
|
||||||
|
className={`${styles.fkSelect} ${styles.fkInEdit} ${
|
||||||
|
styles.fkInEditLeft
|
||||||
|
} input-sm form-control`}
|
||||||
|
disabled={fkAdd.fkCheckBox === false}
|
||||||
|
value={refTable}
|
||||||
|
onChange={onFKRefTableChange}
|
||||||
|
data-test="ref-table"
|
||||||
|
>
|
||||||
|
<option disabled value="">
|
||||||
|
Reference table
|
||||||
|
</option>
|
||||||
|
{allTableNames.map((tName, i) => (
|
||||||
|
<option key={i} value={tName}>
|
||||||
|
{tName}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<select
|
||||||
|
className={`${styles.fkSelect} ${
|
||||||
|
styles.fkInEdit
|
||||||
|
} input-sm form-control`}
|
||||||
|
disabled={fkAdd.fkCheckBox === false}
|
||||||
|
value={rcol}
|
||||||
|
onChange={onFKRefColumnChange}
|
||||||
|
data-test="ref-col"
|
||||||
|
>
|
||||||
|
<option disabled value="">
|
||||||
|
Reference column
|
||||||
|
</option>
|
||||||
|
{refColumnNames.map((co, i) => (
|
||||||
|
<option key={i} value={co}>
|
||||||
|
{co}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
let isPrimaryKey = false;
|
||||||
|
const isUnique = isColumnUnique(tableSchema, c.column_name);
|
||||||
|
|
||||||
|
if (
|
||||||
|
tableSchema.primary_key &&
|
||||||
|
tableSchema.primary_key.columns.includes(c.column_name)
|
||||||
|
) {
|
||||||
|
isPrimaryKey = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const additionalOptions = [];
|
||||||
|
let finalDefaultValue = typeMap[c.data_type];
|
||||||
|
if (!typeMap[c.data_type]) {
|
||||||
|
finalDefaultValue = c.data_type;
|
||||||
|
additionalOptions.push(
|
||||||
|
<option value={finalDefaultValue} key={finalDefaultValue}>
|
||||||
|
{c.data_type}
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const generateAlterOptions = datatypeOptions => {
|
||||||
|
return dataTypes.map(datatype => {
|
||||||
|
if (datatypeOptions.includes(datatype.value)) {
|
||||||
|
return (
|
||||||
|
<option
|
||||||
|
value={datatype.value}
|
||||||
|
key={datatype.name}
|
||||||
|
title={datatype.description}
|
||||||
|
>
|
||||||
|
{datatype.name}
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const modifyAlterOptions = columntype => {
|
||||||
|
const integerOptions = [
|
||||||
|
'integer',
|
||||||
|
'serial',
|
||||||
|
'bigint',
|
||||||
|
'bigserial',
|
||||||
|
'numeric',
|
||||||
|
'text',
|
||||||
|
];
|
||||||
|
const bigintOptions = ['bigint', 'bigserial', 'text', 'numeric'];
|
||||||
|
const uuidOptions = ['uuid', 'text'];
|
||||||
|
const jsonOptions = ['json', 'jsonb', 'text'];
|
||||||
|
const timestampOptions = ['timestamptz', 'text'];
|
||||||
|
const timeOptions = ['timetz', 'text'];
|
||||||
|
switch (columntype) {
|
||||||
|
case INTEGER:
|
||||||
|
return generateAlterOptions(integerOptions);
|
||||||
|
|
||||||
|
case SERIAL:
|
||||||
|
return generateAlterOptions(integerOptions);
|
||||||
|
|
||||||
|
case BIGINT:
|
||||||
|
return generateAlterOptions(bigintOptions);
|
||||||
|
|
||||||
|
case BIGSERIAL:
|
||||||
|
return generateAlterOptions(bigintOptions);
|
||||||
|
|
||||||
|
case UUID:
|
||||||
|
return generateAlterOptions(uuidOptions);
|
||||||
|
|
||||||
|
case JSONDTYPE:
|
||||||
|
return generateAlterOptions(jsonOptions);
|
||||||
|
|
||||||
|
case JSONB:
|
||||||
|
return generateAlterOptions(jsonOptions);
|
||||||
|
|
||||||
|
case TIMESTAMP:
|
||||||
|
return generateAlterOptions(timestampOptions);
|
||||||
|
|
||||||
|
case TIME:
|
||||||
|
return generateAlterOptions(timeOptions);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return generateAlterOptions([columntype, 'text']);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`${styles.colEditor} container-fluid`}>
|
||||||
|
<form
|
||||||
|
className="form-horizontal"
|
||||||
|
onSubmit={e => {
|
||||||
|
e.preventDefault();
|
||||||
|
onSubmit(
|
||||||
|
itype.value,
|
||||||
|
inullable.value,
|
||||||
|
iunique.value,
|
||||||
|
idefault.value,
|
||||||
|
icomment.value,
|
||||||
|
column,
|
||||||
|
allowRename ? iname.value : null
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{allowRename && (
|
||||||
|
<div className={`${styles.display_flex} form-group`}>
|
||||||
|
<label className="col-xs-3 text-right">Name</label>
|
||||||
|
<div className="col-xs-6">
|
||||||
|
<input
|
||||||
|
ref={n => (iname = n)}
|
||||||
|
className="input-sm form-control"
|
||||||
|
defaultValue={column.column_name}
|
||||||
|
type="text"
|
||||||
|
data-test="edit-col-name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className={`${styles.display_flex} form-group`}>
|
||||||
|
<label className="col-xs-3 text-right">Type</label>
|
||||||
|
<div className="col-xs-6">
|
||||||
|
<select
|
||||||
|
ref={n => (itype = n)}
|
||||||
|
className="input-sm form-control"
|
||||||
|
defaultValue={finalDefaultValue}
|
||||||
|
disabled={isPrimaryKey}
|
||||||
|
>
|
||||||
|
{modifyAlterOptions(column.data_type)}
|
||||||
|
{additionalOptions}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={`${styles.display_flex} form-group`}>
|
||||||
|
<label className="col-xs-3 text-right">Nullable</label>
|
||||||
|
<div className="col-xs-6">
|
||||||
|
<select
|
||||||
|
ref={n => (inullable = n)}
|
||||||
|
className="input-sm form-control"
|
||||||
|
defaultValue={c.is_nullable === 'NO' ? 'false' : 'true'}
|
||||||
|
disabled={isPrimaryKey}
|
||||||
|
data-test="edit-col-nullable"
|
||||||
|
>
|
||||||
|
<option value="true">True</option>
|
||||||
|
<option value="false">False</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={`${styles.display_flex} form-group`}>
|
||||||
|
<label className="col-xs-3 text-right">Unique</label>
|
||||||
|
<div className="col-xs-6">
|
||||||
|
<select
|
||||||
|
ref={n => (iunique = n)}
|
||||||
|
className="input-sm form-control"
|
||||||
|
defaultValue={isUnique.toString()}
|
||||||
|
disabled={isPrimaryKey}
|
||||||
|
data-test="edit-col-unique"
|
||||||
|
>
|
||||||
|
<option value="true">True</option>
|
||||||
|
<option value="false">False</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={`${styles.display_flex} form-group`}>
|
||||||
|
<label className="col-xs-3 text-right">Default</label>
|
||||||
|
<div className="col-xs-6">
|
||||||
|
<input
|
||||||
|
ref={n => (idefault = n)}
|
||||||
|
className="input-sm form-control"
|
||||||
|
defaultValue={c.column_default ? c.column_default : null}
|
||||||
|
type="text"
|
||||||
|
disabled={isPrimaryKey}
|
||||||
|
data-test="edit-col-default"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={`${styles.display_flex} form-group`}>
|
||||||
|
<label className="col-xs-3 text-right">Comment</label>
|
||||||
|
<div className="col-xs-6">
|
||||||
|
<input
|
||||||
|
ref={n => (icomment = n)}
|
||||||
|
className="input-sm form-control"
|
||||||
|
defaultValue={columnComment ? columnComment.result[1] : null}
|
||||||
|
type="text"
|
||||||
|
data-test="edit-col-comment"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{checkExistingForeignKey()}
|
||||||
|
<div className="row">
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
color="yellow"
|
||||||
|
className={styles.button_mar_right}
|
||||||
|
size="sm"
|
||||||
|
data-test="save-button"
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
{!isPrimaryKey ? (
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
color="red"
|
||||||
|
size="sm"
|
||||||
|
onClick={e => {
|
||||||
|
e.preventDefault();
|
||||||
|
onDelete();
|
||||||
|
}}
|
||||||
|
data-test="remove-button"
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div className="row">
|
||||||
|
<br />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ColumnEditor;
|
@ -336,7 +336,6 @@ hr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.chevron_mar_right {
|
.chevron_mar_right {
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
@ -16,6 +16,11 @@ import {
|
|||||||
import dataHeaders from '../Common/Headers';
|
import dataHeaders from '../Common/Headers';
|
||||||
import { UPDATE_MIGRATION_STATUS_ERROR } from '../../../Main/Actions';
|
import { UPDATE_MIGRATION_STATUS_ERROR } from '../../../Main/Actions';
|
||||||
import { getAllUnTrackedRelations } from '../TableRelationships/Actions';
|
import { getAllUnTrackedRelations } from '../TableRelationships/Actions';
|
||||||
|
import gqlPattern, {
|
||||||
|
gqlTableErrorNotif,
|
||||||
|
gqlViewErrorNotif,
|
||||||
|
gqlColumnErrorNotif,
|
||||||
|
} from '../Common/GraphQLValidation';
|
||||||
|
|
||||||
const TOGGLE_ACTIVE_COLUMN = 'ModifyTable/TOGGLE_ACTIVE_COLUMN';
|
const TOGGLE_ACTIVE_COLUMN = 'ModifyTable/TOGGLE_ACTIVE_COLUMN';
|
||||||
const RESET = 'ModifyTable/RESET';
|
const RESET = 'ModifyTable/RESET';
|
||||||
@ -23,6 +28,8 @@ const RESET = 'ModifyTable/RESET';
|
|||||||
const VIEW_DEF_REQUEST_SUCCESS = 'ModifyTable/VIEW_DEF_REQUEST_SUCCESS';
|
const VIEW_DEF_REQUEST_SUCCESS = 'ModifyTable/VIEW_DEF_REQUEST_SUCCESS';
|
||||||
const VIEW_DEF_REQUEST_ERROR = 'ModifyTable/VIEW_DEF_REQUEST_ERROR';
|
const VIEW_DEF_REQUEST_ERROR = 'ModifyTable/VIEW_DEF_REQUEST_ERROR';
|
||||||
|
|
||||||
|
const SAVE_NEW_TABLE_NAME = 'ModifyTable/SAVE_NEW_TABLE_NAME';
|
||||||
|
|
||||||
const TABLE_COMMENT_EDIT = 'ModifyTable/TABLE_COMMENT_EDIT';
|
const TABLE_COMMENT_EDIT = 'ModifyTable/TABLE_COMMENT_EDIT';
|
||||||
const TABLE_COMMENT_INPUT_EDIT = 'ModifyTable/TABLE_COMMENT_INPUT_EDIT';
|
const TABLE_COMMENT_INPUT_EDIT = 'ModifyTable/TABLE_COMMENT_INPUT_EDIT';
|
||||||
const FK_SET_REF_TABLE = 'ModifyTable/FK_SET_REF_TABLE';
|
const FK_SET_REF_TABLE = 'ModifyTable/FK_SET_REF_TABLE';
|
||||||
@ -33,6 +40,78 @@ const FK_ADD_FORM_ERROR = 'ModifyTable/FK_ADD_FORM_ERROR';
|
|||||||
const FK_RESET = 'ModifyTable/FK_RESET';
|
const FK_RESET = 'ModifyTable/FK_RESET';
|
||||||
const TOGGLE_FK_CHECKBOX = 'ModifyTable/TOGGLE_FK_CHECKBOX';
|
const TOGGLE_FK_CHECKBOX = 'ModifyTable/TOGGLE_FK_CHECKBOX';
|
||||||
|
|
||||||
|
const changeTableOrViewName = (isTable, oldName, newName, callback) => {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const property = isTable ? 'table' : 'view';
|
||||||
|
dispatch({ type: SAVE_NEW_TABLE_NAME });
|
||||||
|
if (oldName === newName) {
|
||||||
|
return dispatch(
|
||||||
|
showErrorNotification(
|
||||||
|
`Renaming ${property} failed`,
|
||||||
|
`The ${property} name is already ${oldName}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!gqlPattern.test(newName)) {
|
||||||
|
const gqlValidationError = isTable
|
||||||
|
? gqlTableErrorNotif
|
||||||
|
: gqlViewErrorNotif;
|
||||||
|
return dispatch(
|
||||||
|
showErrorNotification(
|
||||||
|
gqlValidationError[4],
|
||||||
|
gqlValidationError[1],
|
||||||
|
gqlValidationError[2],
|
||||||
|
gqlValidationError[3]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const currentSchema = getState().tables.currentSchema;
|
||||||
|
const migrateUp = [
|
||||||
|
{
|
||||||
|
type: 'run_sql',
|
||||||
|
args: {
|
||||||
|
sql: `alter ${property} "${currentSchema}"."${oldName}" rename to "${newName}";`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const migrateDown = [
|
||||||
|
{
|
||||||
|
type: 'run_sql',
|
||||||
|
args: {
|
||||||
|
sql: `alter ${property} "${currentSchema}"."${newName}" rename to "${oldName}";`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
// apply migrations
|
||||||
|
const migrationName = `rename_${property}_` + currentSchema + '_' + oldName;
|
||||||
|
|
||||||
|
const requestMsg = `Renaming ${property}...`;
|
||||||
|
const successMsg = `Renaming ${property} successful`;
|
||||||
|
const errorMsg = `Renaming ${property} failed`;
|
||||||
|
|
||||||
|
const customOnSuccess = () => {
|
||||||
|
callback();
|
||||||
|
};
|
||||||
|
const customOnError = err => {
|
||||||
|
dispatch({ type: UPDATE_MIGRATION_STATUS_ERROR, data: err });
|
||||||
|
};
|
||||||
|
|
||||||
|
makeMigrationCall(
|
||||||
|
dispatch,
|
||||||
|
getState,
|
||||||
|
migrateUp,
|
||||||
|
migrateDown,
|
||||||
|
migrationName,
|
||||||
|
customOnSuccess,
|
||||||
|
customOnError,
|
||||||
|
requestMsg,
|
||||||
|
successMsg,
|
||||||
|
errorMsg,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// TABLE MODIFY
|
// TABLE MODIFY
|
||||||
const deleteTableSql = tableName => {
|
const deleteTableSql = tableName => {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
@ -727,7 +806,8 @@ const saveColumnChangesSql = (
|
|||||||
unique,
|
unique,
|
||||||
def,
|
def,
|
||||||
comment,
|
comment,
|
||||||
column
|
column,
|
||||||
|
newName
|
||||||
) => {
|
) => {
|
||||||
// eslint-disable-line no-unused-vars
|
// eslint-disable-line no-unused-vars
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
@ -1222,6 +1302,32 @@ const saveColumnChangesSql = (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* rename column */
|
||||||
|
if (newName && colName !== newName) {
|
||||||
|
if (!gqlPattern.test(newName)) {
|
||||||
|
return dispatch(
|
||||||
|
showErrorNotification(
|
||||||
|
gqlColumnErrorNotif[4],
|
||||||
|
gqlColumnErrorNotif[1],
|
||||||
|
gqlColumnErrorNotif[2],
|
||||||
|
gqlColumnErrorNotif[3]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
schemaChangesUp.push({
|
||||||
|
type: 'run_sql',
|
||||||
|
args: {
|
||||||
|
sql: `alter table "${currentSchema}"."${tableName}" rename column "${colName}" to "${newName}";`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
schemaChangesDown.push({
|
||||||
|
type: 'run_sql',
|
||||||
|
args: {
|
||||||
|
sql: `alter table "${currentSchema}"."${tableName}" rename column "${newName}" to "${colName}";`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Apply migrations
|
// Apply migrations
|
||||||
const migrationName =
|
const migrationName =
|
||||||
'alter_table_' +
|
'alter_table_' +
|
||||||
@ -1269,7 +1375,8 @@ const saveColChangesWithFkSql = (
|
|||||||
unique,
|
unique,
|
||||||
def,
|
def,
|
||||||
comment,
|
comment,
|
||||||
column
|
column,
|
||||||
|
newName
|
||||||
) => {
|
) => {
|
||||||
// ALTER TABLE <table> ALTER COLUMN <column> TYPE <column_type>;
|
// ALTER TABLE <table> ALTER COLUMN <column> TYPE <column_type>;
|
||||||
const colType = type;
|
const colType = type;
|
||||||
@ -1772,6 +1879,22 @@ const saveColChangesWithFkSql = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* rename column */
|
||||||
|
if (newName && newName !== colName) {
|
||||||
|
schemaChangesUp.push({
|
||||||
|
type: 'run_sql',
|
||||||
|
args: {
|
||||||
|
sql: `alter table "${currentSchema}"."${tableName}" rename column "${colName}" to "${newName}";`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
schemaChangesDown.push({
|
||||||
|
type: 'run_sql',
|
||||||
|
args: {
|
||||||
|
sql: `alter table "${currentSchema}"."${tableName}" rename column "${newName}" to "${colName}";`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Apply migrations
|
// Apply migrations
|
||||||
const migrationName =
|
const migrationName =
|
||||||
'alter_table_' +
|
'alter_table_' +
|
||||||
@ -1825,6 +1948,8 @@ export {
|
|||||||
TOGGLE_FK_CHECKBOX,
|
TOGGLE_FK_CHECKBOX,
|
||||||
TABLE_COMMENT_EDIT,
|
TABLE_COMMENT_EDIT,
|
||||||
TABLE_COMMENT_INPUT_EDIT,
|
TABLE_COMMENT_INPUT_EDIT,
|
||||||
|
SAVE_NEW_TABLE_NAME,
|
||||||
|
changeTableOrViewName,
|
||||||
fetchViewDefinition,
|
fetchViewDefinition,
|
||||||
handleMigrationErrors,
|
handleMigrationErrors,
|
||||||
saveColumnChangesSql,
|
saveColumnChangesSql,
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import { Link } from 'react-router';
|
|
||||||
import TableHeader from '../TableCommon/TableHeader';
|
import TableHeader from '../TableCommon/TableHeader';
|
||||||
import {
|
import {
|
||||||
activateCommentEdit,
|
activateCommentEdit,
|
||||||
@ -8,27 +7,19 @@ import {
|
|||||||
saveTableCommentSql,
|
saveTableCommentSql,
|
||||||
} from './ModifyActions';
|
} from './ModifyActions';
|
||||||
import {
|
import {
|
||||||
fkRefTableChange,
|
|
||||||
fkLColChange,
|
fkLColChange,
|
||||||
fkRColChange,
|
|
||||||
toggleFKCheckBox,
|
|
||||||
saveColChangesWithFkSql,
|
|
||||||
isColumnUnique,
|
|
||||||
deleteTableSql,
|
deleteTableSql,
|
||||||
deleteConstraintSql,
|
|
||||||
addColSql,
|
addColSql,
|
||||||
untrackTableSql,
|
untrackTableSql,
|
||||||
RESET,
|
RESET,
|
||||||
TOGGLE_ACTIVE_COLUMN,
|
TOGGLE_ACTIVE_COLUMN,
|
||||||
saveColumnChangesSql,
|
saveColumnChangesSql,
|
||||||
|
saveColChangesWithFkSql,
|
||||||
deleteColumnSql,
|
deleteColumnSql,
|
||||||
} from '../TableModify/ModifyActions';
|
} from '../TableModify/ModifyActions';
|
||||||
import { ordinalColSort } from '../utils';
|
import { ordinalColSort } from '../utils';
|
||||||
import dataTypes from '../Common/DataTypes';
|
import dataTypes from '../Common/DataTypes';
|
||||||
import {
|
import { convertListToDict } from '../../../../utils/data';
|
||||||
convertListToDictUsingKV,
|
|
||||||
convertListToDict,
|
|
||||||
} from '../../../../utils/data';
|
|
||||||
import {
|
import {
|
||||||
setTable,
|
setTable,
|
||||||
fetchTableComment,
|
fetchTableComment,
|
||||||
@ -36,20 +27,9 @@ import {
|
|||||||
} from '../DataActions';
|
} from '../DataActions';
|
||||||
import { showErrorNotification } from '../Notification';
|
import { showErrorNotification } from '../Notification';
|
||||||
import gqlPattern, { gqlColumnErrorNotif } from '../Common/GraphQLValidation';
|
import gqlPattern, { gqlColumnErrorNotif } from '../Common/GraphQLValidation';
|
||||||
import {
|
|
||||||
INTEGER,
|
|
||||||
SERIAL,
|
|
||||||
BIGINT,
|
|
||||||
BIGSERIAL,
|
|
||||||
UUID,
|
|
||||||
JSONDTYPE,
|
|
||||||
JSONB,
|
|
||||||
TIMESTAMP,
|
|
||||||
TIME,
|
|
||||||
} from '../../../../constants';
|
|
||||||
import Button from '../../../Common/Button/Button';
|
import Button from '../../../Common/Button/Button';
|
||||||
|
import ColumnEditor from './ColumnEditor';
|
||||||
const appPrefix = '/data';
|
import semverCheck from '../../../../helpers/semver';
|
||||||
|
|
||||||
const alterTypeOptions = dataTypes.map((datatype, index) => (
|
const alterTypeOptions = dataTypes.map((datatype, index) => (
|
||||||
<option value={datatype.value} key={index} title={datatype.description}>
|
<option value={datatype.value} key={index} title={datatype.description}>
|
||||||
@ -57,375 +37,42 @@ const alterTypeOptions = dataTypes.map((datatype, index) => (
|
|||||||
</option>
|
</option>
|
||||||
));
|
));
|
||||||
|
|
||||||
const ColumnEditor = ({
|
class ModifyTable extends React.Component {
|
||||||
column,
|
state = {
|
||||||
onSubmit,
|
supportTableColumnRename: false,
|
||||||
onDelete,
|
|
||||||
allSchemas,
|
|
||||||
fkAdd,
|
|
||||||
tableName,
|
|
||||||
dispatch,
|
|
||||||
currentSchema,
|
|
||||||
columnComment,
|
|
||||||
}) => {
|
|
||||||
// eslint-disable-line no-unused-vars
|
|
||||||
const c = column;
|
|
||||||
const styles = require('./Modify.scss');
|
|
||||||
let [inullable, iunique, idefault, icomment, itype] = [
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
];
|
|
||||||
// NOTE: the datatypes is filtered of serial and bigserial where hasuraDatatype === null
|
|
||||||
const refTable = fkAdd.refTable;
|
|
||||||
const tableSchema = allSchemas.find(t => t.table_name === tableName);
|
|
||||||
const rcol = fkAdd.rcol;
|
|
||||||
const typeMap = convertListToDictUsingKV(
|
|
||||||
'hasuraDatatype',
|
|
||||||
'value',
|
|
||||||
dataTypes.filter(dataType => dataType.hasuraDatatype)
|
|
||||||
);
|
|
||||||
const refSchema = allSchemas.find(t => t.table_name === refTable);
|
|
||||||
// const allTableNamesExceptCurrent = allSchemas.filter(t => t.table_name !== tableName);
|
|
||||||
const allTableNames = allSchemas.map(t => t.table_name);
|
|
||||||
allTableNames.sort();
|
|
||||||
|
|
||||||
const refColumnNames = refSchema
|
|
||||||
? refSchema.columns.map(col => col.column_name)
|
|
||||||
: [];
|
|
||||||
refColumnNames.sort();
|
|
||||||
const onFKRefTableChange = e => {
|
|
||||||
dispatch(fkRefTableChange(e.target.value));
|
|
||||||
};
|
|
||||||
const onFKRefColumnChange = e => {
|
|
||||||
dispatch(fkRColChange(e.target.value));
|
|
||||||
};
|
|
||||||
const checkExistingForeignKey = () => {
|
|
||||||
const numFk = tableSchema.foreign_key_constraints.length;
|
|
||||||
let fkName = '';
|
|
||||||
const onDeleteFK = e => {
|
|
||||||
e.preventDefault();
|
|
||||||
const isOk = confirm('Are you sure?');
|
|
||||||
if (isOk) {
|
|
||||||
dispatch(deleteConstraintSql(tableName, fkName));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (numFk > 0) {
|
|
||||||
for (let i = 0; i < numFk; i++) {
|
|
||||||
const fk = tableSchema.foreign_key_constraints[i];
|
|
||||||
if (
|
|
||||||
Object.keys(fk.column_mapping).toString() === c.column_name.toString()
|
|
||||||
) {
|
|
||||||
fkName = fk.constraint_name;
|
|
||||||
return (
|
|
||||||
<div className={`${styles.display_flex} form-group`}>
|
|
||||||
<label className="col-xs-3 text-right">Foreign Key</label>
|
|
||||||
<div className="col-xs-9">
|
|
||||||
<h5>
|
|
||||||
<span>{fk.ref_table} :: </span>
|
|
||||||
<span className={styles.add_mar_right}>
|
|
||||||
{Object.keys(fk.column_mapping)
|
|
||||||
.map(l => fk.column_mapping[l])
|
|
||||||
.join(',')}
|
|
||||||
</span>
|
|
||||||
<Link
|
|
||||||
to={`${appPrefix}/schema/${currentSchema}/tables/${tableName}/relationships`}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
color="white"
|
|
||||||
size="sm"
|
|
||||||
type="button"
|
|
||||||
data-test="add-rel-mod"
|
|
||||||
>
|
|
||||||
+Add relationship
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
color="red"
|
|
||||||
size="sm"
|
|
||||||
onClick={onDeleteFK}
|
|
||||||
data-test="remove-constraint-button"
|
|
||||||
>
|
|
||||||
{' '}
|
|
||||||
Remove Constraint{' '}
|
|
||||||
</Button>{' '}
|
|
||||||
|
|
||||||
</h5>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className={`${styles.display_flex} form-group`}>
|
|
||||||
<label className="col-xs-3 text-right">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={fkAdd.fkCheckBox}
|
|
||||||
onChange={e => {
|
|
||||||
dispatch(toggleFKCheckBox(e.target.checked));
|
|
||||||
}}
|
|
||||||
value="ForeignKey"
|
|
||||||
data-test="foreign-key-checkbox"
|
|
||||||
/>{' '}
|
|
||||||
Foreign Key
|
|
||||||
</label>
|
|
||||||
<div className="col-xs-6">
|
|
||||||
<select
|
|
||||||
className={`${styles.fkSelect} ${styles.fkInEdit} ${
|
|
||||||
styles.fkInEditLeft
|
|
||||||
} input-sm form-control`}
|
|
||||||
disabled={fkAdd.fkCheckBox === false}
|
|
||||||
value={refTable}
|
|
||||||
onChange={onFKRefTableChange}
|
|
||||||
data-test="ref-table"
|
|
||||||
>
|
|
||||||
<option disabled value="">
|
|
||||||
Reference table
|
|
||||||
</option>
|
|
||||||
{allTableNames.map((tName, i) => (
|
|
||||||
<option key={i} value={tName}>
|
|
||||||
{tName}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
<select
|
|
||||||
className={`${styles.fkSelect} ${
|
|
||||||
styles.fkInEdit
|
|
||||||
} input-sm form-control`}
|
|
||||||
disabled={fkAdd.fkCheckBox === false}
|
|
||||||
value={rcol}
|
|
||||||
onChange={onFKRefColumnChange}
|
|
||||||
data-test="ref-col"
|
|
||||||
>
|
|
||||||
<option disabled value="">
|
|
||||||
Reference column
|
|
||||||
</option>
|
|
||||||
{refColumnNames.map((co, i) => (
|
|
||||||
<option key={i} value={co}>
|
|
||||||
{co}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
let isPrimaryKey = false;
|
|
||||||
const isUnique = isColumnUnique(tableSchema, c.column_name);
|
|
||||||
|
|
||||||
if (
|
|
||||||
tableSchema.primary_key &&
|
|
||||||
tableSchema.primary_key.columns.includes(c.column_name)
|
|
||||||
) {
|
|
||||||
isPrimaryKey = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const additionalOptions = [];
|
|
||||||
let finalDefaultValue = typeMap[c.data_type];
|
|
||||||
if (!typeMap[c.data_type]) {
|
|
||||||
finalDefaultValue = c.data_type;
|
|
||||||
additionalOptions.push(
|
|
||||||
<option value={finalDefaultValue} key={finalDefaultValue}>
|
|
||||||
{c.data_type}
|
|
||||||
</option>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const generateAlterOptions = datatypeOptions => {
|
|
||||||
return dataTypes.map(datatype => {
|
|
||||||
if (datatypeOptions.includes(datatype.value)) {
|
|
||||||
return (
|
|
||||||
<option
|
|
||||||
value={datatype.value}
|
|
||||||
key={datatype.name}
|
|
||||||
title={datatype.description}
|
|
||||||
>
|
|
||||||
{datatype.name}
|
|
||||||
</option>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const modifyAlterOptions = columntype => {
|
|
||||||
const integerOptions = [
|
|
||||||
'integer',
|
|
||||||
'serial',
|
|
||||||
'bigint',
|
|
||||||
'bigserial',
|
|
||||||
'numeric',
|
|
||||||
'text',
|
|
||||||
];
|
|
||||||
const bigintOptions = ['bigint', 'bigserial', 'text', 'numeric'];
|
|
||||||
const uuidOptions = ['uuid', 'text'];
|
|
||||||
const jsonOptions = ['json', 'jsonb', 'text'];
|
|
||||||
const timestampOptions = ['timestamptz', 'text'];
|
|
||||||
const timeOptions = ['timetz', 'text'];
|
|
||||||
switch (columntype) {
|
|
||||||
case INTEGER:
|
|
||||||
return generateAlterOptions(integerOptions);
|
|
||||||
|
|
||||||
case SERIAL:
|
|
||||||
return generateAlterOptions(integerOptions);
|
|
||||||
|
|
||||||
case BIGINT:
|
|
||||||
return generateAlterOptions(bigintOptions);
|
|
||||||
|
|
||||||
case BIGSERIAL:
|
|
||||||
return generateAlterOptions(bigintOptions);
|
|
||||||
|
|
||||||
case UUID:
|
|
||||||
return generateAlterOptions(uuidOptions);
|
|
||||||
|
|
||||||
case JSONDTYPE:
|
|
||||||
return generateAlterOptions(jsonOptions);
|
|
||||||
|
|
||||||
case JSONB:
|
|
||||||
return generateAlterOptions(jsonOptions);
|
|
||||||
|
|
||||||
case TIMESTAMP:
|
|
||||||
return generateAlterOptions(timestampOptions);
|
|
||||||
|
|
||||||
case TIME:
|
|
||||||
return generateAlterOptions(timeOptions);
|
|
||||||
|
|
||||||
default:
|
|
||||||
return generateAlterOptions([columntype, 'text']);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={`${styles.colEditor} container-fluid`}>
|
|
||||||
<form
|
|
||||||
className="form-horizontal"
|
|
||||||
onSubmit={e => {
|
|
||||||
e.preventDefault();
|
|
||||||
onSubmit(
|
|
||||||
itype.value,
|
|
||||||
inullable.value,
|
|
||||||
iunique.value,
|
|
||||||
idefault.value,
|
|
||||||
icomment.value,
|
|
||||||
column
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className={`${styles.display_flex} form-group`}>
|
|
||||||
<label className="col-xs-3 text-right">Type</label>
|
|
||||||
<div className="col-xs-6">
|
|
||||||
<select
|
|
||||||
ref={n => (itype = n)}
|
|
||||||
className="input-sm form-control"
|
|
||||||
defaultValue={finalDefaultValue}
|
|
||||||
disabled={isPrimaryKey}
|
|
||||||
>
|
|
||||||
{modifyAlterOptions(column.data_type)}
|
|
||||||
{additionalOptions}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={`${styles.display_flex} form-group`}>
|
|
||||||
<label className="col-xs-3 text-right">Nullable</label>
|
|
||||||
<div className="col-xs-6">
|
|
||||||
<select
|
|
||||||
ref={n => (inullable = n)}
|
|
||||||
className="input-sm form-control"
|
|
||||||
defaultValue={c.is_nullable === 'NO' ? 'false' : 'true'}
|
|
||||||
disabled={isPrimaryKey}
|
|
||||||
data-test="edit-col-nullable"
|
|
||||||
>
|
|
||||||
<option value="true">True</option>
|
|
||||||
<option value="false">False</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={`${styles.display_flex} form-group`}>
|
|
||||||
<label className="col-xs-3 text-right">Unique</label>
|
|
||||||
<div className="col-xs-6">
|
|
||||||
<select
|
|
||||||
ref={n => (iunique = n)}
|
|
||||||
className="input-sm form-control"
|
|
||||||
defaultValue={isUnique.toString()}
|
|
||||||
disabled={isPrimaryKey}
|
|
||||||
data-test="edit-col-unique"
|
|
||||||
>
|
|
||||||
<option value="true">True</option>
|
|
||||||
<option value="false">False</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={`${styles.display_flex} form-group`}>
|
|
||||||
<label className="col-xs-3 text-right">Default</label>
|
|
||||||
<div className="col-xs-6">
|
|
||||||
<input
|
|
||||||
ref={n => (idefault = n)}
|
|
||||||
className="input-sm form-control"
|
|
||||||
defaultValue={c.column_default ? c.column_default : null}
|
|
||||||
type="text"
|
|
||||||
disabled={isPrimaryKey}
|
|
||||||
data-test="edit-col-default"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={`${styles.display_flex} form-group`}>
|
|
||||||
<label className="col-xs-3 text-right">Comment</label>
|
|
||||||
<div className="col-xs-6">
|
|
||||||
<input
|
|
||||||
ref={n => (icomment = n)}
|
|
||||||
className="input-sm form-control"
|
|
||||||
defaultValue={columnComment ? columnComment.result[1] : null}
|
|
||||||
type="text"
|
|
||||||
data-test="edit-col-comment"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{checkExistingForeignKey()}
|
|
||||||
<div className="row">
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
color="yellow"
|
|
||||||
className={styles.button_mar_right}
|
|
||||||
size="sm"
|
|
||||||
data-test="save-button"
|
|
||||||
>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
{!isPrimaryKey ? (
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
color="red"
|
|
||||||
size="sm"
|
|
||||||
onClick={e => {
|
|
||||||
e.preventDefault();
|
|
||||||
onDelete();
|
|
||||||
}}
|
|
||||||
data-test="remove-button"
|
|
||||||
>
|
|
||||||
Remove
|
|
||||||
</Button>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<div className="row">
|
|
||||||
<br />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
class ModifyTable extends Component {
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { dispatch } = this.props;
|
const { dispatch, serverVersion } = this.props;
|
||||||
dispatch({ type: RESET });
|
dispatch({ type: RESET });
|
||||||
dispatch(setTable(this.props.tableName));
|
dispatch(setTable(this.props.tableName));
|
||||||
dispatch(fetchTableComment(this.props.tableName));
|
dispatch(fetchTableComment(this.props.tableName));
|
||||||
|
if (serverVersion) {
|
||||||
|
this.checkTableColumnRenameSupport(serverVersion);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
if (
|
||||||
|
nextProps.serverVersion &&
|
||||||
|
nextProps.serverVersion !== this.props.serverVersion
|
||||||
|
) {
|
||||||
|
this.checkTableColumnRenameSupport(nextProps.serverVersion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkTableColumnRenameSupport = serverVersion => {
|
||||||
|
try {
|
||||||
|
if (semverCheck('tableColumnRename', serverVersion)) {
|
||||||
|
this.setState({
|
||||||
|
supportTableColumnRename: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
tableName,
|
tableName,
|
||||||
@ -455,7 +102,15 @@ class ModifyTable extends Component {
|
|||||||
let colEditor = null;
|
let colEditor = null;
|
||||||
let bg = '';
|
let bg = '';
|
||||||
const colName = c.column_name;
|
const colName = c.column_name;
|
||||||
const onSubmit = (type, nullable, unique, def, comment, column) => {
|
const onSubmit = (
|
||||||
|
type,
|
||||||
|
nullable,
|
||||||
|
unique,
|
||||||
|
def,
|
||||||
|
comment,
|
||||||
|
column,
|
||||||
|
newName
|
||||||
|
) => {
|
||||||
// dispatch(saveColumnChangesSql(tableName, colName, type, nullable, def, column));
|
// dispatch(saveColumnChangesSql(tableName, colName, type, nullable, def, column));
|
||||||
if (fkAdd.fkCheckBox === true) {
|
if (fkAdd.fkCheckBox === true) {
|
||||||
dispatch(fkLColChange(column.column_name));
|
dispatch(fkLColChange(column.column_name));
|
||||||
@ -468,7 +123,8 @@ class ModifyTable extends Component {
|
|||||||
unique,
|
unique,
|
||||||
def,
|
def,
|
||||||
comment,
|
comment,
|
||||||
column
|
column,
|
||||||
|
newName
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -481,7 +137,8 @@ class ModifyTable extends Component {
|
|||||||
unique,
|
unique,
|
||||||
def,
|
def,
|
||||||
comment,
|
comment,
|
||||||
column
|
column,
|
||||||
|
newName
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -512,6 +169,7 @@ class ModifyTable extends Component {
|
|||||||
allSchemas={allSchemas}
|
allSchemas={allSchemas}
|
||||||
currentSchema={currentSchema}
|
currentSchema={currentSchema}
|
||||||
columnComment={columnComment}
|
columnComment={columnComment}
|
||||||
|
allowRename={this.state.supportTableColumnRename}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -526,6 +184,7 @@ class ModifyTable extends Component {
|
|||||||
allSchemas={allSchemas}
|
allSchemas={allSchemas}
|
||||||
currentSchema={currentSchema}
|
currentSchema={currentSchema}
|
||||||
columnComment={columnComment}
|
columnComment={columnComment}
|
||||||
|
allowRename={this.state.supportTableColumnRename}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -717,6 +376,7 @@ class ModifyTable extends Component {
|
|||||||
tabName="modify"
|
tabName="modify"
|
||||||
migrationMode={migrationMode}
|
migrationMode={migrationMode}
|
||||||
currentSchema={currentSchema}
|
currentSchema={currentSchema}
|
||||||
|
allowRename={this.state.supportTableColumnRename}
|
||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
<div className={`container-fluid ${styles.padd_left_remove}`}>
|
<div className={`container-fluid ${styles.padd_left_remove}`}>
|
||||||
@ -872,12 +532,14 @@ ModifyTable.propTypes = {
|
|||||||
lastFormError: PropTypes.object,
|
lastFormError: PropTypes.object,
|
||||||
lastSuccess: PropTypes.bool,
|
lastSuccess: PropTypes.bool,
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
|
serverVersion: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state, ownProps) => ({
|
const mapStateToProps = (state, ownProps) => ({
|
||||||
tableName: ownProps.params.table,
|
tableName: ownProps.params.table,
|
||||||
allSchemas: state.tables.allSchemas,
|
allSchemas: state.tables.allSchemas,
|
||||||
migrationMode: state.main.migrationMode,
|
migrationMode: state.main.migrationMode,
|
||||||
|
serverVersion: state.main.serverVersion,
|
||||||
currentSchema: state.tables.currentSchema,
|
currentSchema: state.tables.currentSchema,
|
||||||
tableComment: state.tables.tableComment,
|
tableComment: state.tables.tableComment,
|
||||||
columnComment: state.tables.columnComment,
|
columnComment: state.tables.columnComment,
|
||||||
|
@ -16,16 +16,44 @@ import {
|
|||||||
import { ordinalColSort } from '../utils';
|
import { ordinalColSort } from '../utils';
|
||||||
import { setTable, fetchTableComment } from '../DataActions';
|
import { setTable, fetchTableComment } from '../DataActions';
|
||||||
import Button from '../../../Common/Button/Button';
|
import Button from '../../../Common/Button/Button';
|
||||||
|
import semverCheck from '../../../../helpers/semver';
|
||||||
|
|
||||||
class ModifyView extends Component {
|
class ModifyView extends Component {
|
||||||
|
state = {
|
||||||
|
supportTableColumnRename: false,
|
||||||
|
};
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { dispatch } = this.props;
|
const { dispatch, serverVersion } = this.props;
|
||||||
dispatch({ type: RESET });
|
dispatch({ type: RESET });
|
||||||
dispatch(setTable(this.props.tableName));
|
dispatch(setTable(this.props.tableName));
|
||||||
dispatch(fetchViewDefinition(this.props.tableName, false));
|
dispatch(fetchViewDefinition(this.props.tableName, false));
|
||||||
dispatch(fetchTableComment(this.props.tableName));
|
dispatch(fetchTableComment(this.props.tableName));
|
||||||
|
if (serverVersion) {
|
||||||
|
this.checkTableColumnRenameSupport(serverVersion);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
if (
|
||||||
|
nextProps.serverVersion &&
|
||||||
|
nextProps.serverVersion !== this.props.serverVersion
|
||||||
|
) {
|
||||||
|
this.checkTableColumnRenameSupport(nextProps.serverVersion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkTableColumnRenameSupport = serverVersion => {
|
||||||
|
try {
|
||||||
|
if (semverCheck('tableColumnRename', serverVersion)) {
|
||||||
|
this.setState({
|
||||||
|
supportTableColumnRename: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
modifyViewDefinition = viewName => {
|
modifyViewDefinition = viewName => {
|
||||||
// fetch the definition
|
// fetch the definition
|
||||||
this.props.dispatch(fetchViewDefinition(viewName, true));
|
this.props.dispatch(fetchViewDefinition(viewName, true));
|
||||||
@ -191,6 +219,7 @@ class ModifyView extends Component {
|
|||||||
tabName="modify"
|
tabName="modify"
|
||||||
currentSchema={currentSchema}
|
currentSchema={currentSchema}
|
||||||
migrationMode={migrationMode}
|
migrationMode={migrationMode}
|
||||||
|
allowRename={this.state.supportTableColumnRename}
|
||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
<div className={'container-fluid ' + styles.padd_left_remove}>
|
<div className={'container-fluid ' + styles.padd_left_remove}>
|
||||||
@ -261,6 +290,7 @@ ModifyView.propTypes = {
|
|||||||
lastError: PropTypes.object,
|
lastError: PropTypes.object,
|
||||||
lastSuccess: PropTypes.bool,
|
lastSuccess: PropTypes.bool,
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
|
serverVersion: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state, ownProps) => {
|
const mapStateToProps = (state, ownProps) => {
|
||||||
@ -271,6 +301,7 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
currentSchema: state.tables.currentSchema,
|
currentSchema: state.tables.currentSchema,
|
||||||
tableComment: state.tables.tableComment,
|
tableComment: state.tables.tableComment,
|
||||||
migrationMode: state.main.migrationMode,
|
migrationMode: state.main.migrationMode,
|
||||||
|
serverVersion: state.main.serverVersion,
|
||||||
...state.tables.modify,
|
...state.tables.modify,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -36,6 +36,56 @@ const relTypeChange = isObjRel => ({
|
|||||||
});
|
});
|
||||||
const relRTableChange = rTable => ({ type: REL_SET_RTABLE, rTable });
|
const relRTableChange = rTable => ({ type: REL_SET_RTABLE, rTable });
|
||||||
|
|
||||||
|
const saveRenameRelationship = (oldName, newName, tableName, callback) => {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const currentSchema = getState().tables.currentSchema;
|
||||||
|
const migrateUp = [
|
||||||
|
{
|
||||||
|
type: 'rename_relationship',
|
||||||
|
args: {
|
||||||
|
table: tableName,
|
||||||
|
name: oldName,
|
||||||
|
new_name: newName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const migrateDown = [
|
||||||
|
{
|
||||||
|
type: 'rename_relationship',
|
||||||
|
args: {
|
||||||
|
table: tableName,
|
||||||
|
name: newName,
|
||||||
|
new_name: oldName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
// Apply migrations
|
||||||
|
const migrationName = `rename_relationship_${oldName}_to_${newName}_schema_${currentSchema}_table_${tableName}`;
|
||||||
|
|
||||||
|
const requestMsg = 'Renaming relationship...';
|
||||||
|
const successMsg = 'Relationship renamed';
|
||||||
|
const errorMsg = 'Renaming relationship failed';
|
||||||
|
|
||||||
|
const customOnSuccess = () => {
|
||||||
|
callback();
|
||||||
|
};
|
||||||
|
const customOnError = () => {};
|
||||||
|
|
||||||
|
makeMigrationCall(
|
||||||
|
dispatch,
|
||||||
|
getState,
|
||||||
|
migrateUp,
|
||||||
|
migrateDown,
|
||||||
|
migrationName,
|
||||||
|
customOnSuccess,
|
||||||
|
customOnError,
|
||||||
|
requestMsg,
|
||||||
|
successMsg,
|
||||||
|
errorMsg
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const generateRelationshipsQuery = (
|
const generateRelationshipsQuery = (
|
||||||
tableName,
|
tableName,
|
||||||
relName,
|
relName,
|
||||||
@ -487,4 +537,5 @@ export {
|
|||||||
autoAddRelName,
|
autoAddRelName,
|
||||||
formRelName,
|
formRelName,
|
||||||
getAllUnTrackedRelations,
|
getAllUnTrackedRelations,
|
||||||
|
saveRenameRelationship,
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,154 @@
|
|||||||
|
/* eslint-disable jsx-a11y/no-autofocus */
|
||||||
|
import React from 'react';
|
||||||
|
import { getRelationshipLine } from './utils';
|
||||||
|
import Button from '../../../Common/Button/Button';
|
||||||
|
import { deleteRelMigrate, saveRenameRelationship } from './Actions';
|
||||||
|
import { showErrorNotification } from '../Notification';
|
||||||
|
import gqlPattern, { gqlRelErrorNotif } from '../Common/GraphQLValidation';
|
||||||
|
import styles from '../TableModify/Modify.scss';
|
||||||
|
|
||||||
|
class RelationshipEditor extends React.Component {
|
||||||
|
state = {
|
||||||
|
isEditting: false,
|
||||||
|
text: this.props.relName,
|
||||||
|
};
|
||||||
|
|
||||||
|
handleTextChange = e => {
|
||||||
|
this.setState({
|
||||||
|
text: e.target.value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
toggleEditor = () => {
|
||||||
|
this.setState({
|
||||||
|
isEditting: !this.state.isEditting,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleKeyPress = e => {
|
||||||
|
if (this.state.isEditting) {
|
||||||
|
if (e.charCode === 13) {
|
||||||
|
this.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
save = () => {
|
||||||
|
const { tableName, relName, dispatch } = this.props;
|
||||||
|
const { text } = this.state;
|
||||||
|
if (text === relName) {
|
||||||
|
return dispatch(
|
||||||
|
showErrorNotification(
|
||||||
|
'Renaming relationship failed',
|
||||||
|
`The relationship name is already ${relName}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!gqlPattern.test(text)) {
|
||||||
|
return dispatch(
|
||||||
|
showErrorNotification(
|
||||||
|
gqlRelErrorNotif[4],
|
||||||
|
gqlRelErrorNotif[1],
|
||||||
|
gqlRelErrorNotif[2],
|
||||||
|
gqlRelErrorNotif[3]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
dispatch(
|
||||||
|
saveRenameRelationship(relName, text, tableName, this.toggleEditor)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
dispatch,
|
||||||
|
tableName,
|
||||||
|
relName,
|
||||||
|
relConfig,
|
||||||
|
isObjRel,
|
||||||
|
tableStyles,
|
||||||
|
allowRename,
|
||||||
|
} = this.props;
|
||||||
|
const { text, isEditting } = this.state;
|
||||||
|
const { lcol, rtable, rcol } = relConfig;
|
||||||
|
const onDelete = e => {
|
||||||
|
e.preventDefault();
|
||||||
|
const isOk = confirm('Are you sure?');
|
||||||
|
if (isOk) {
|
||||||
|
dispatch(
|
||||||
|
deleteRelMigrate(tableName, relName, lcol, rtable, rcol, isObjRel)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const collapsed = () => (
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
color={allowRename ? 'white' : 'red'}
|
||||||
|
size={allowRename ? 'xs' : 'sm'}
|
||||||
|
onClick={allowRename ? this.toggleEditor : onDelete}
|
||||||
|
data-test={
|
||||||
|
allowRename
|
||||||
|
? `relationship-toggle-editor-${relName}`
|
||||||
|
: `relationship-remove-${relName}`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{allowRename ? 'Edit' : 'Remove'}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<b>{relName}</b>
|
||||||
|
<div className={tableStyles.relationshipTopPadding}>
|
||||||
|
{getRelationshipLine(isObjRel, lcol, rcol, rtable)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const expanded = () => (
|
||||||
|
<div className={styles.activeEdit}>
|
||||||
|
<div className={tableStyles.add_mar_top}>
|
||||||
|
<Button
|
||||||
|
color="white"
|
||||||
|
size="xs"
|
||||||
|
onClick={this.toggleEditor}
|
||||||
|
data-test={`relationship-toggle-editor-${relName}`}
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className={tableStyles.relationshipTopPadding}>
|
||||||
|
<div>{getRelationshipLine(isObjRel, lcol, rcol, rtable)}</div>
|
||||||
|
<input
|
||||||
|
onChange={this.handleTextChange}
|
||||||
|
className={`form-control ${styles.add_mar_top_small}`}
|
||||||
|
type="text"
|
||||||
|
value={text}
|
||||||
|
data-test={`relationship-name-input-${relName}`}
|
||||||
|
onKeyPress={this.handleKeyPress}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={tableStyles.relEditButtons}>
|
||||||
|
<Button
|
||||||
|
className={styles.add_mar_right}
|
||||||
|
color="yellow"
|
||||||
|
size="xs"
|
||||||
|
onClick={this.save}
|
||||||
|
data-test={`relationship-save-${relName}`}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="red"
|
||||||
|
size="xs"
|
||||||
|
onClick={onDelete}
|
||||||
|
data-test={`relationship-remove-${relName}`}
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return <td>{isEditting ? expanded() : collapsed()}</td>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default RelationshipEditor;
|
@ -3,7 +3,6 @@ import React, { Component } from 'react';
|
|||||||
import TableHeader from '../TableCommon/TableHeader';
|
import TableHeader from '../TableCommon/TableHeader';
|
||||||
import { RESET } from '../TableModify/ModifyActions';
|
import { RESET } from '../TableModify/ModifyActions';
|
||||||
import {
|
import {
|
||||||
deleteRelMigrate,
|
|
||||||
addNewRelClicked,
|
addNewRelClicked,
|
||||||
addRelNewFromStateMigrate,
|
addRelNewFromStateMigrate,
|
||||||
relSelectionChanged,
|
relSelectionChanged,
|
||||||
@ -16,10 +15,13 @@ import { findAllFromRel } from '../utils';
|
|||||||
import { showErrorNotification } from '../Notification';
|
import { showErrorNotification } from '../Notification';
|
||||||
import { setTable } from '../DataActions';
|
import { setTable } from '../DataActions';
|
||||||
import gqlPattern, { gqlRelErrorNotif } from '../Common/GraphQLValidation';
|
import gqlPattern, { gqlRelErrorNotif } from '../Common/GraphQLValidation';
|
||||||
|
import { getRelationshipLine } from './utils';
|
||||||
|
|
||||||
import AddManualRelationship from './AddManualRelationship';
|
import AddManualRelationship from './AddManualRelationship';
|
||||||
import suggestedRelationshipsRaw from './autoRelations';
|
import suggestedRelationshipsRaw from './autoRelations';
|
||||||
|
import RelationshipEditor from './RelationshipEditor';
|
||||||
import Button from '../../../Common/Button/Button';
|
import Button from '../../../Common/Button/Button';
|
||||||
|
import semverCheck from '../../../../helpers/semver';
|
||||||
|
|
||||||
/* Gets the complete list of relationships and converts it to a list of object, which looks like so :
|
/* Gets the complete list of relationships and converts it to a list of object, which looks like so :
|
||||||
{
|
{
|
||||||
@ -43,64 +45,6 @@ const getObjArrayRelationshipList = relationships => {
|
|||||||
return requiredList;
|
return requiredList;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* This function sets the styling to the way the relationship looks, for eg: id -> user::user_id */
|
|
||||||
const getRelationshipLine = (isObjRel, lcol, rcol, rTable) => {
|
|
||||||
const finalRTable = rTable.name ? rTable.name : rTable;
|
|
||||||
return isObjRel ? (
|
|
||||||
<span>
|
|
||||||
|
|
||||||
{lcol.join(',')}
|
|
||||||
→
|
|
||||||
{rTable} :: {rcol.join(',')}
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<span>
|
|
||||||
|
|
||||||
{finalRTable} :: {rcol.join(',')}
|
|
||||||
→
|
|
||||||
{lcol.join(',')}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const relationshipView = (
|
|
||||||
dispatch,
|
|
||||||
tableName,
|
|
||||||
relName,
|
|
||||||
{ lcol, rtable, rcol },
|
|
||||||
isObjRel,
|
|
||||||
tableStyles
|
|
||||||
) => {
|
|
||||||
const onDelete = e => {
|
|
||||||
e.preventDefault();
|
|
||||||
const isOk = confirm('Are you sure?');
|
|
||||||
if (isOk) {
|
|
||||||
dispatch(
|
|
||||||
deleteRelMigrate(tableName, relName, lcol, rtable, rcol, isObjRel)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<td>
|
|
||||||
<div>
|
|
||||||
<Button
|
|
||||||
color="red"
|
|
||||||
size="sm"
|
|
||||||
onClick={onDelete}
|
|
||||||
data-test={`remove-button-${relName}`}
|
|
||||||
>
|
|
||||||
Remove
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<b>{relName}</b>
|
|
||||||
<div className={tableStyles.relationshipTopPadding}>
|
|
||||||
{getRelationshipLine(isObjRel, lcol, rcol, rtable)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const addRelationshipCellView = (
|
const addRelationshipCellView = (
|
||||||
dispatch,
|
dispatch,
|
||||||
rel,
|
rel,
|
||||||
@ -179,7 +123,7 @@ const addRelationshipCellView = (
|
|||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
color="yellow"
|
color="yellow"
|
||||||
size="sm"
|
size="xs"
|
||||||
data-test={
|
data-test={
|
||||||
relMetaData[0] === 'object'
|
relMetaData[0] === 'object'
|
||||||
? `obj-rel-save-${relMetaData[1]}`
|
? `obj-rel-save-${relMetaData[1]}`
|
||||||
@ -365,10 +309,39 @@ const AddRelationship = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
class Relationships extends Component {
|
class Relationships extends Component {
|
||||||
|
state = {
|
||||||
|
supportRename: false,
|
||||||
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.dispatch({ type: RESET });
|
const { dispatch, serverVersion } = this.props;
|
||||||
this.props.dispatch(setTable(this.props.tableName));
|
dispatch({ type: RESET });
|
||||||
|
dispatch(setTable(this.props.tableName));
|
||||||
|
if (serverVersion) {
|
||||||
|
this.checkRenameSupport(serverVersion);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
if (
|
||||||
|
nextProps.serverVersion &&
|
||||||
|
nextProps.serverVersion !== this.props.serverVersion
|
||||||
|
) {
|
||||||
|
this.checkRenameSupport(nextProps.serverVersion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkRenameSupport = serverVersion => {
|
||||||
|
try {
|
||||||
|
if (semverCheck('tableColumnRename', serverVersion)) {
|
||||||
|
this.setState({
|
||||||
|
supportRename: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
tableName,
|
tableName,
|
||||||
@ -432,26 +405,36 @@ class Relationships extends Component {
|
|||||||
{getObjArrayRelationshipList(tableSchema.relationships).map(
|
{getObjArrayRelationshipList(tableSchema.relationships).map(
|
||||||
(rel, i) => {
|
(rel, i) => {
|
||||||
const column1 = rel.objRel ? (
|
const column1 = rel.objRel ? (
|
||||||
relationshipView(
|
<RelationshipEditor
|
||||||
dispatch,
|
dispatch={dispatch}
|
||||||
tableName,
|
tableName={tableName}
|
||||||
rel.objRel.rel_name,
|
relName={rel.objRel.rel_name}
|
||||||
findAllFromRel(allSchemas, tableSchema, rel.objRel),
|
relConfig={findAllFromRel(
|
||||||
true,
|
allSchemas,
|
||||||
tableStyles
|
tableSchema,
|
||||||
)
|
rel.objRel
|
||||||
|
)}
|
||||||
|
isObjRel
|
||||||
|
tableStyles={tableStyles}
|
||||||
|
allowRename={this.state.supportRename}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<td />
|
<td />
|
||||||
);
|
);
|
||||||
const column2 = rel.arrRel ? (
|
const column2 = rel.arrRel ? (
|
||||||
relationshipView(
|
<RelationshipEditor
|
||||||
dispatch,
|
dispatch={dispatch}
|
||||||
tableName,
|
tableName={tableName}
|
||||||
rel.arrRel.rel_name,
|
relName={rel.arrRel.rel_name}
|
||||||
findAllFromRel(allSchemas, tableSchema, rel.arrRel),
|
relConfig={findAllFromRel(
|
||||||
false,
|
allSchemas,
|
||||||
tableStyles
|
tableSchema,
|
||||||
)
|
rel.arrRel
|
||||||
|
)}
|
||||||
|
isObjRel={false}
|
||||||
|
tableStyles={tableStyles}
|
||||||
|
allowRename={this.state.supportRename}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<td />
|
<td />
|
||||||
);
|
);
|
||||||
@ -565,12 +548,14 @@ Relationships.propTypes = {
|
|||||||
lastFormError: PropTypes.object,
|
lastFormError: PropTypes.object,
|
||||||
lastSuccess: PropTypes.bool,
|
lastSuccess: PropTypes.bool,
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
|
serverVersion: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state, ownProps) => ({
|
const mapStateToProps = (state, ownProps) => ({
|
||||||
tableName: ownProps.params.table,
|
tableName: ownProps.params.table,
|
||||||
allSchemas: state.tables.allSchemas,
|
allSchemas: state.tables.allSchemas,
|
||||||
migrationMode: state.main.migrationMode,
|
migrationMode: state.main.migrationMode,
|
||||||
|
serverVersion: state.main.serverVersion,
|
||||||
currentSchema: state.tables.currentSchema,
|
currentSchema: state.tables.currentSchema,
|
||||||
schemaList: state.tables.schemaList,
|
schemaList: state.tables.schemaList,
|
||||||
...state.tables.modify,
|
...state.tables.modify,
|
||||||
|
@ -2,12 +2,14 @@ import React, { Component } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import ViewHeader from '../TableBrowseRows/ViewHeader';
|
import ViewHeader from '../TableBrowseRows/ViewHeader';
|
||||||
import { RESET } from '../TableModify/ModifyActions';
|
import { RESET } from '../TableModify/ModifyActions';
|
||||||
import { deleteRelMigrate, addNewRelClicked } from './Actions';
|
import { addNewRelClicked } from './Actions';
|
||||||
import { findAllFromRel } from '../utils';
|
import { findAllFromRel } from '../utils';
|
||||||
import { setTable, UPDATE_REMOTE_SCHEMA_MANUAL_REL } from '../DataActions';
|
import { setTable, UPDATE_REMOTE_SCHEMA_MANUAL_REL } from '../DataActions';
|
||||||
|
|
||||||
import AddRelationship from './AddManualRelationship';
|
import AddRelationship from './AddManualRelationship';
|
||||||
import Button from '../../../Common/Button/Button';
|
import Button from '../../../Common/Button/Button';
|
||||||
|
import RelationshipEditor from './RelationshipEditor';
|
||||||
|
import semverCheck from '../../../../helpers/semver';
|
||||||
|
|
||||||
/* Gets the complete list of relationships and converts it to a list of object, which looks like so :
|
/* Gets the complete list of relationships and converts it to a list of object, which looks like so :
|
||||||
{
|
{
|
||||||
@ -31,70 +33,44 @@ const getObjArrayRelationshipList = relationships => {
|
|||||||
return requiredList;
|
return requiredList;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* This function sets the styling to the way the relationship looks, for eg: id -> user::user_id */
|
|
||||||
const getRelationshipLine = (isObjRel, lcol, rcol, rTable) => {
|
|
||||||
const getGrayText = value => <i>{value}</i>;
|
|
||||||
return isObjRel ? (
|
|
||||||
<span>
|
|
||||||
|
|
||||||
{getGrayText(lcol)}
|
|
||||||
→
|
|
||||||
{rTable} :: {rcol}
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<span>
|
|
||||||
|
|
||||||
{rTable} :: {rcol}
|
|
||||||
→
|
|
||||||
{getGrayText(lcol)}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const relationshipView = (
|
|
||||||
dispatch,
|
|
||||||
tableName,
|
|
||||||
relName,
|
|
||||||
{ lcol, rtable, rcol },
|
|
||||||
isObjRel,
|
|
||||||
tableStyles
|
|
||||||
) => {
|
|
||||||
const onDelete = e => {
|
|
||||||
e.preventDefault();
|
|
||||||
const isOk = confirm('Are you sure?');
|
|
||||||
if (isOk) {
|
|
||||||
dispatch(
|
|
||||||
deleteRelMigrate(tableName, relName, lcol, rtable, rcol, isObjRel)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<td>
|
|
||||||
<div>
|
|
||||||
<Button size="sm" color="red" onClick={onDelete}>
|
|
||||||
Remove
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<b>{relName}</b>
|
|
||||||
<div className={tableStyles.relationshipTopPadding}>
|
|
||||||
{getRelationshipLine(isObjRel, lcol, rcol, rtable)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
class RelationshipsView extends Component {
|
class RelationshipsView extends Component {
|
||||||
|
state = {
|
||||||
|
supportRename: false,
|
||||||
|
};
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.dispatch({ type: RESET });
|
const { dispatch, serverVersion, currentSchema, tableName } = this.props;
|
||||||
this.props.dispatch(setTable(this.props.tableName));
|
dispatch({ type: RESET });
|
||||||
|
dispatch(setTable(tableName));
|
||||||
// Sourcing the current schema into manual relationship
|
// Sourcing the current schema into manual relationship
|
||||||
this.props.dispatch({
|
dispatch({
|
||||||
type: UPDATE_REMOTE_SCHEMA_MANUAL_REL,
|
type: UPDATE_REMOTE_SCHEMA_MANUAL_REL,
|
||||||
data: this.props.currentSchema,
|
data: currentSchema,
|
||||||
});
|
});
|
||||||
|
if (serverVersion) {
|
||||||
|
this.checkRenameSupport(serverVersion);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
if (
|
||||||
|
nextProps.serverVersion &&
|
||||||
|
nextProps.serverVersion !== this.props.serverVersion
|
||||||
|
) {
|
||||||
|
this.checkRenameSupport(nextProps.serverVersion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkRenameSupport = serverVersion => {
|
||||||
|
try {
|
||||||
|
if (semverCheck('tableColumnRename', serverVersion)) {
|
||||||
|
this.setState({
|
||||||
|
supportRename: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
tableName,
|
tableName,
|
||||||
@ -159,26 +135,36 @@ class RelationshipsView extends Component {
|
|||||||
{getObjArrayRelationshipList(tableSchema.relationships).map(
|
{getObjArrayRelationshipList(tableSchema.relationships).map(
|
||||||
(rel, i) => {
|
(rel, i) => {
|
||||||
const column1 = rel.objRel ? (
|
const column1 = rel.objRel ? (
|
||||||
relationshipView(
|
<RelationshipEditor
|
||||||
dispatch,
|
dispatch={dispatch}
|
||||||
tableName,
|
tableName={tableName}
|
||||||
rel.objRel.rel_name,
|
relName={rel.objRel.rel_name}
|
||||||
findAllFromRel(allSchemas, tableSchema, rel.objRel),
|
relConfig={findAllFromRel(
|
||||||
true,
|
allSchemas,
|
||||||
tableStyles
|
tableSchema,
|
||||||
)
|
rel.objRel
|
||||||
|
)}
|
||||||
|
isObjRel
|
||||||
|
tableStyles={tableStyles}
|
||||||
|
allowRename={this.state.supportRename}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<td />
|
<td />
|
||||||
);
|
);
|
||||||
const column2 = rel.arrRel ? (
|
const column2 = rel.arrRel ? (
|
||||||
relationshipView(
|
<RelationshipEditor
|
||||||
dispatch,
|
dispatch={dispatch}
|
||||||
tableName,
|
tableName={tableName}
|
||||||
rel.arrRel.rel_name,
|
relName={rel.arrRel.rel_name}
|
||||||
findAllFromRel(allSchemas, tableSchema, rel.arrRel),
|
relConfig={findAllFromRel(
|
||||||
false,
|
allSchemas,
|
||||||
tableStyles
|
tableSchema,
|
||||||
)
|
rel.arrRel
|
||||||
|
)}
|
||||||
|
isObjRel={false}
|
||||||
|
tableStyles={tableStyles}
|
||||||
|
allowRename={this.state.supportRename}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<td />
|
<td />
|
||||||
);
|
);
|
||||||
@ -263,6 +249,7 @@ RelationshipsView.propTypes = {
|
|||||||
lastFormError: PropTypes.object,
|
lastFormError: PropTypes.object,
|
||||||
lastSuccess: PropTypes.bool,
|
lastSuccess: PropTypes.bool,
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
|
serverVersion: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state, ownProps) => ({
|
const mapStateToProps = (state, ownProps) => ({
|
||||||
@ -270,6 +257,7 @@ const mapStateToProps = (state, ownProps) => ({
|
|||||||
allSchemas: state.tables.allSchemas,
|
allSchemas: state.tables.allSchemas,
|
||||||
currentSchema: state.tables.currentSchema,
|
currentSchema: state.tables.currentSchema,
|
||||||
migrationMode: state.main.migrationMode,
|
migrationMode: state.main.migrationMode,
|
||||||
|
serverVersion: state.main.serverVersion,
|
||||||
schemaList: state.tables.schemaList,
|
schemaList: state.tables.schemaList,
|
||||||
...state.tables.modify,
|
...state.tables.modify,
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
/* This function sets the styling to the way the relationship looks, for eg: id -> user::user_id */
|
||||||
|
export const getRelationshipLine = (isObjRel, lcol, rcol, rTable) => {
|
||||||
|
const finalRTable = rTable.name ? rTable.name : rTable;
|
||||||
|
return isObjRel ? (
|
||||||
|
<span>
|
||||||
|
|
||||||
|
{lcol.join(',')}
|
||||||
|
→
|
||||||
|
{rTable} :: {rcol.join(',')}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span>
|
||||||
|
|
||||||
|
{finalRTable} :: {rcol.join(',')}
|
||||||
|
→
|
||||||
|
{lcol.join(',')}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
@ -1,3 +1,11 @@
|
|||||||
|
const tabNameMap = {
|
||||||
|
view: 'Browse Rows',
|
||||||
|
insert: 'Insert Row',
|
||||||
|
modify: 'Modify',
|
||||||
|
relationships: 'Relationships',
|
||||||
|
permissions: 'Permissions',
|
||||||
|
};
|
||||||
|
|
||||||
const ordinalColSort = (a, b) => {
|
const ordinalColSort = (a, b) => {
|
||||||
if (a.ordinal_position < b.ordinal_position) {
|
if (a.ordinal_position < b.ordinal_position) {
|
||||||
return -1;
|
return -1;
|
||||||
@ -137,4 +145,5 @@ export {
|
|||||||
getIngForm,
|
getIngForm,
|
||||||
escapeRegExp,
|
escapeRegExp,
|
||||||
getTableName,
|
getTableName,
|
||||||
|
tabNameMap,
|
||||||
};
|
};
|
||||||
|
@ -14,6 +14,7 @@ const componentsSemver = {
|
|||||||
insertPermRestrictColumns: '1.0.0-alpha28',
|
insertPermRestrictColumns: '1.0.0-alpha28',
|
||||||
permHideUpsertSection: '1.0.0-alpha32',
|
permHideUpsertSection: '1.0.0-alpha32',
|
||||||
customFunctionSection: '1.0.0-alpha36',
|
customFunctionSection: '1.0.0-alpha36',
|
||||||
|
tableColumnRename: '1.0.0-alpha39',
|
||||||
triggerRetryTimeout: '1.0.0-alpha38',
|
triggerRetryTimeout: '1.0.0-alpha38',
|
||||||
permUpdatePresets: '1.0.0-alpha38',
|
permUpdatePresets: '1.0.0-alpha38',
|
||||||
};
|
};
|
||||||
|
@ -166,8 +166,11 @@ library
|
|||||||
, Hasura.RQL.DDL.Permission.Triggers
|
, Hasura.RQL.DDL.Permission.Triggers
|
||||||
, Hasura.RQL.DDL.Permission
|
, Hasura.RQL.DDL.Permission
|
||||||
, Hasura.RQL.DDL.Relationship
|
, Hasura.RQL.DDL.Relationship
|
||||||
|
, Hasura.RQL.DDL.Relationship.Rename
|
||||||
|
, Hasura.RQL.DDL.Relationship.Types
|
||||||
, Hasura.RQL.DDL.QueryTemplate
|
, Hasura.RQL.DDL.QueryTemplate
|
||||||
, Hasura.RQL.DDL.Schema.Table
|
, Hasura.RQL.DDL.Schema.Table
|
||||||
|
, Hasura.RQL.DDL.Schema.Rename
|
||||||
, Hasura.RQL.DDL.Schema.Function
|
, Hasura.RQL.DDL.Schema.Function
|
||||||
, Hasura.RQL.DDL.Schema.Diff
|
, Hasura.RQL.DDL.Schema.Diff
|
||||||
, Hasura.RQL.DDL.Metadata
|
, Hasura.RQL.DDL.Metadata
|
||||||
@ -233,6 +236,7 @@ library
|
|||||||
, Hasura.Logging
|
, Hasura.Logging
|
||||||
, Network.URI.Extended
|
, Network.URI.Extended
|
||||||
, Ops
|
, Ops
|
||||||
|
, Migrate
|
||||||
|
|
||||||
other-modules: Hasura.Server.Auth.JWT.Internal
|
other-modules: Hasura.Server.Auth.JWT.Internal
|
||||||
, Hasura.Server.Auth.JWT.Logging
|
, Hasura.Server.Auth.JWT.Logging
|
||||||
@ -318,7 +322,8 @@ executable graphql-engine
|
|||||||
, connection
|
, connection
|
||||||
, string-conversions
|
, string-conversions
|
||||||
|
|
||||||
other-modules: Ops
|
other-modules: Ops
|
||||||
|
, Migrate
|
||||||
|
|
||||||
if flag(developer)
|
if flag(developer)
|
||||||
ghc-prof-options: -rtsopts -fprof-auto -fno-prof-count-entries
|
ghc-prof-options: -rtsopts -fprof-auto -fno-prof-count-entries
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
module Main where
|
module Main where
|
||||||
|
|
||||||
|
import Migrate (migrateCatalog)
|
||||||
import Ops
|
import Ops
|
||||||
|
|
||||||
import Control.Monad.STM (atomically)
|
import Control.Monad.STM (atomically)
|
||||||
|
276
server/src-exec/Migrate.hs
Normal file
276
server/src-exec/Migrate.hs
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
module Migrate
|
||||||
|
( curCatalogVer
|
||||||
|
, migrateCatalog
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Data.Time.Clock (UTCTime)
|
||||||
|
import Language.Haskell.TH.Syntax (Q, TExp, unTypeQ)
|
||||||
|
|
||||||
|
import Hasura.Prelude
|
||||||
|
import Hasura.RQL.DDL.Schema.Table
|
||||||
|
import Hasura.RQL.Types
|
||||||
|
import Hasura.Server.Query
|
||||||
|
|
||||||
|
import qualified Data.Aeson as A
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import qualified Data.Yaml.TH as Y
|
||||||
|
|
||||||
|
import qualified Database.PG.Query as Q
|
||||||
|
|
||||||
|
curCatalogVer :: T.Text
|
||||||
|
curCatalogVer = "10"
|
||||||
|
|
||||||
|
migrateMetadata
|
||||||
|
:: (MonadTx m, HasHttpManager m, CacheRWM m, UserInfoM m, MonadIO m)
|
||||||
|
=> Bool -> RQLQuery -> m ()
|
||||||
|
migrateMetadata buildSC rqlQuery = do
|
||||||
|
-- Build schema cache from 'hdb_catalog' only if current
|
||||||
|
-- metadata migration depends on metadata added in previous versions
|
||||||
|
when buildSC $ buildSchemaCache
|
||||||
|
-- run the RQL query to Migrate metadata
|
||||||
|
void $ runQueryM rqlQuery
|
||||||
|
|
||||||
|
setAsSystemDefinedFor2 :: (MonadTx m) => m ()
|
||||||
|
setAsSystemDefinedFor2 =
|
||||||
|
liftTx $ Q.catchE defaultTxErrorHandler $
|
||||||
|
Q.multiQ [Q.sql|
|
||||||
|
UPDATE hdb_catalog.hdb_table
|
||||||
|
SET is_system_defined = 'true'
|
||||||
|
WHERE table_schema = 'hdb_catalog'
|
||||||
|
AND ( table_name = 'event_triggers'
|
||||||
|
OR table_name = 'event_log'
|
||||||
|
OR table_name = 'event_invocation_logs'
|
||||||
|
);
|
||||||
|
UPDATE hdb_catalog.hdb_relationship
|
||||||
|
SET is_system_defined = 'true'
|
||||||
|
WHERE table_schema = 'hdb_catalog'
|
||||||
|
AND ( table_name = 'event_triggers'
|
||||||
|
OR table_name = 'event_log'
|
||||||
|
OR table_name = 'event_invocation_logs'
|
||||||
|
);
|
||||||
|
|]
|
||||||
|
|
||||||
|
setAsSystemDefinedFor5 :: (MonadTx m) => m ()
|
||||||
|
setAsSystemDefinedFor5 =
|
||||||
|
liftTx $ Q.catchE defaultTxErrorHandler $
|
||||||
|
Q.multiQ [Q.sql|
|
||||||
|
UPDATE hdb_catalog.hdb_table
|
||||||
|
SET is_system_defined = 'true'
|
||||||
|
WHERE table_schema = 'hdb_catalog'
|
||||||
|
AND table_name = 'remote_schemas';
|
||||||
|
|]
|
||||||
|
|
||||||
|
setAsSystemDefinedFor8 :: (MonadTx m) => m ()
|
||||||
|
setAsSystemDefinedFor8 =
|
||||||
|
liftTx $ Q.catchE defaultTxErrorHandler $
|
||||||
|
Q.multiQ [Q.sql|
|
||||||
|
UPDATE hdb_catalog.hdb_table
|
||||||
|
SET is_system_defined = 'true'
|
||||||
|
WHERE table_schema = 'hdb_catalog'
|
||||||
|
AND ( table_name = 'hdb_function_agg'
|
||||||
|
OR table_name = 'hdb_function'
|
||||||
|
);
|
||||||
|
UPDATE hdb_catalog.hdb_relationship
|
||||||
|
SET is_system_defined = 'true'
|
||||||
|
WHERE table_schema = 'hdb_catalog'
|
||||||
|
AND table_name = 'hdb_function_agg';
|
||||||
|
|]
|
||||||
|
|
||||||
|
setAsSystemDefinedFor9 :: (MonadTx m) => m ()
|
||||||
|
setAsSystemDefinedFor9 =
|
||||||
|
liftTx $ Q.catchE defaultTxErrorHandler $
|
||||||
|
Q.multiQ [Q.sql|
|
||||||
|
UPDATE hdb_catalog.hdb_table
|
||||||
|
SET is_system_defined = 'true'
|
||||||
|
WHERE table_schema = 'hdb_catalog'
|
||||||
|
AND table_name = 'hdb_version';
|
||||||
|
|]
|
||||||
|
|
||||||
|
getCatalogVersion
|
||||||
|
:: (MonadTx m)
|
||||||
|
=> m T.Text
|
||||||
|
getCatalogVersion = do
|
||||||
|
res <- liftTx $ Q.withQE defaultTxErrorHandler [Q.sql|
|
||||||
|
SELECT version FROM hdb_catalog.hdb_version
|
||||||
|
|] () False
|
||||||
|
return $ runIdentity $ Q.getRow res
|
||||||
|
|
||||||
|
from08To1 :: (MonadTx m) => m ()
|
||||||
|
from08To1 = liftTx $ Q.catchE defaultTxErrorHandler $ do
|
||||||
|
Q.unitQ "ALTER TABLE hdb_catalog.hdb_relationship ADD COLUMN comment TEXT NULL" () False
|
||||||
|
Q.unitQ "ALTER TABLE hdb_catalog.hdb_permission ADD COLUMN comment TEXT NULL" () False
|
||||||
|
Q.unitQ "ALTER TABLE hdb_catalog.hdb_query_template ADD COLUMN comment TEXT NULL" () False
|
||||||
|
Q.unitQ [Q.sql|
|
||||||
|
UPDATE hdb_catalog.hdb_query_template
|
||||||
|
SET template_defn =
|
||||||
|
json_build_object('type', 'select', 'args', template_defn->'select');
|
||||||
|
|] () False
|
||||||
|
|
||||||
|
from1To2
|
||||||
|
:: (MonadTx m, HasHttpManager m, CacheRWM m, UserInfoM m, MonadIO m)
|
||||||
|
=> m ()
|
||||||
|
from1To2 = do
|
||||||
|
-- Migrate database
|
||||||
|
Q.Discard () <- liftTx $ Q.multiQE defaultTxErrorHandler
|
||||||
|
$(Q.sqlFromFile "src-rsr/migrate_from_1.sql")
|
||||||
|
migrateMetadata False migrateMetadataFrom1
|
||||||
|
-- Set as system defined
|
||||||
|
setAsSystemDefinedFor2
|
||||||
|
where
|
||||||
|
migrateMetadataFrom1 =
|
||||||
|
$(unTypeQ (Y.decodeFile "src-rsr/migrate_metadata_from_1.yaml" :: Q (TExp RQLQuery)))
|
||||||
|
|
||||||
|
from2To3 :: (MonadTx m) => m ()
|
||||||
|
from2To3 = liftTx $ Q.catchE defaultTxErrorHandler $ do
|
||||||
|
Q.unitQ "ALTER TABLE hdb_catalog.event_triggers ADD COLUMN headers JSON" () False
|
||||||
|
Q.unitQ "ALTER TABLE hdb_catalog.event_log ADD COLUMN next_retry_at TIMESTAMP" () False
|
||||||
|
Q.unitQ "CREATE INDEX ON hdb_catalog.event_log (trigger_id)" () False
|
||||||
|
Q.unitQ "CREATE INDEX ON hdb_catalog.event_invocation_logs (event_id)" () False
|
||||||
|
|
||||||
|
-- custom resolver
|
||||||
|
from4To5
|
||||||
|
:: (MonadTx m, HasHttpManager m, CacheRWM m, UserInfoM m, MonadIO m)
|
||||||
|
=> m ()
|
||||||
|
from4To5 = do
|
||||||
|
Q.Discard () <- liftTx $ Q.multiQE defaultTxErrorHandler
|
||||||
|
$(Q.sqlFromFile "src-rsr/migrate_from_4_to_5.sql")
|
||||||
|
migrateMetadata False migrateMetadataFrom4
|
||||||
|
-- Set as system defined
|
||||||
|
setAsSystemDefinedFor5
|
||||||
|
where
|
||||||
|
migrateMetadataFrom4 =
|
||||||
|
$(unTypeQ (Y.decodeFile "src-rsr/migrate_metadata_from_4_to_5.yaml" :: Q (TExp RQLQuery)))
|
||||||
|
|
||||||
|
|
||||||
|
from3To4 :: (MonadTx m) => m ()
|
||||||
|
from3To4 = liftTx $ Q.catchE defaultTxErrorHandler $ do
|
||||||
|
Q.unitQ "ALTER TABLE hdb_catalog.event_triggers ADD COLUMN configuration JSON" () False
|
||||||
|
eventTriggers <- map uncurryEventTrigger <$> Q.listQ [Q.sql|
|
||||||
|
SELECT e.name, e.definition::json, e.webhook, e.num_retries, e.retry_interval, e.headers::json
|
||||||
|
FROM hdb_catalog.event_triggers e
|
||||||
|
|] () False
|
||||||
|
forM_ eventTriggers updateEventTrigger3To4
|
||||||
|
Q.unitQ "ALTER TABLE hdb_catalog.event_triggers\
|
||||||
|
\ DROP COLUMN definition\
|
||||||
|
\, DROP COLUMN query\
|
||||||
|
\, DROP COLUMN webhook\
|
||||||
|
\, DROP COLUMN num_retries\
|
||||||
|
\, DROP COLUMN retry_interval\
|
||||||
|
\, DROP COLUMN headers" () False
|
||||||
|
where
|
||||||
|
uncurryEventTrigger (trn, Q.AltJ tDef, w, nr, rint, Q.AltJ headers) =
|
||||||
|
EventTriggerConf trn tDef (Just w) Nothing (RetryConf nr rint Nothing) headers
|
||||||
|
updateEventTrigger3To4 etc@(EventTriggerConf name _ _ _ _ _) = Q.unitQ [Q.sql|
|
||||||
|
UPDATE hdb_catalog.event_triggers
|
||||||
|
SET
|
||||||
|
configuration = $1
|
||||||
|
WHERE name = $2
|
||||||
|
|] (Q.AltJ $ A.toJSON etc, name) True
|
||||||
|
|
||||||
|
from5To6 :: (MonadTx m) => m ()
|
||||||
|
from5To6 = liftTx $ do
|
||||||
|
-- Migrate database
|
||||||
|
Q.Discard () <- Q.multiQE defaultTxErrorHandler
|
||||||
|
$(Q.sqlFromFile "src-rsr/migrate_from_5_to_6.sql")
|
||||||
|
return ()
|
||||||
|
|
||||||
|
from6To7 :: (MonadTx m) => m ()
|
||||||
|
from6To7 = liftTx $ do
|
||||||
|
-- Migrate database
|
||||||
|
Q.Discard () <- Q.multiQE defaultTxErrorHandler
|
||||||
|
$(Q.sqlFromFile "src-rsr/migrate_from_6_to_7.sql")
|
||||||
|
return ()
|
||||||
|
|
||||||
|
from7To8
|
||||||
|
:: (MonadTx m, HasHttpManager m, CacheRWM m, UserInfoM m, MonadIO m)
|
||||||
|
=> m ()
|
||||||
|
from7To8 = do
|
||||||
|
-- Migrate database
|
||||||
|
Q.Discard () <- liftTx $ Q.multiQE defaultTxErrorHandler
|
||||||
|
$(Q.sqlFromFile "src-rsr/migrate_from_7_to_8.sql")
|
||||||
|
-- Migrate metadata
|
||||||
|
-- Building schema cache is required since this metadata migration
|
||||||
|
-- involves in creating object relationship to hdb_catalog.hdb_table
|
||||||
|
migrateMetadata True migrateMetadataFrom7
|
||||||
|
setAsSystemDefinedFor8
|
||||||
|
where
|
||||||
|
migrateMetadataFrom7 =
|
||||||
|
$(unTypeQ (Y.decodeFile "src-rsr/migrate_metadata_from_7_to_8.yaml" :: Q (TExp RQLQuery)))
|
||||||
|
|
||||||
|
-- alter hdb_version table and track it (telemetry changes)
|
||||||
|
from8To9
|
||||||
|
:: (MonadTx m, HasHttpManager m, CacheRWM m, UserInfoM m, MonadIO m)
|
||||||
|
=> m ()
|
||||||
|
from8To9 = do
|
||||||
|
Q.Discard () <- liftTx $ Q.multiQE defaultTxErrorHandler
|
||||||
|
$(Q.sqlFromFile "src-rsr/migrate_from_8_to_9.sql")
|
||||||
|
-- Migrate metadata
|
||||||
|
migrateMetadata False migrateMetadataFrom8
|
||||||
|
-- Set as system defined
|
||||||
|
setAsSystemDefinedFor9
|
||||||
|
where
|
||||||
|
migrateMetadataFrom8 =
|
||||||
|
$(unTypeQ (Y.decodeFile "src-rsr/migrate_metadata_from_8_to_9.yaml" :: Q (TExp RQLQuery)))
|
||||||
|
|
||||||
|
-- alter foreign keys on hdb_relationship and hdb_permission table to have ON UPDATE CASCADE
|
||||||
|
from9To10 :: (MonadTx m) => m ()
|
||||||
|
from9To10 = liftTx $ do
|
||||||
|
-- Migrate database
|
||||||
|
Q.Discard () <- Q.multiQE defaultTxErrorHandler
|
||||||
|
$(Q.sqlFromFile "src-rsr/migrate_from_9_to_10.sql")
|
||||||
|
return ()
|
||||||
|
|
||||||
|
migrateCatalog
|
||||||
|
:: (MonadTx m, CacheRWM m, MonadIO m, UserInfoM m, HasHttpManager m)
|
||||||
|
=> UTCTime -> m String
|
||||||
|
migrateCatalog migrationTime = do
|
||||||
|
preVer <- getCatalogVersion
|
||||||
|
if | preVer == curCatalogVer ->
|
||||||
|
return "already at the latest version"
|
||||||
|
| preVer == "0.8" -> from08ToCurrent
|
||||||
|
| preVer == "1" -> from1ToCurrent
|
||||||
|
| preVer == "2" -> from2ToCurrent
|
||||||
|
| preVer == "3" -> from3ToCurrent
|
||||||
|
| preVer == "4" -> from4ToCurrent
|
||||||
|
| preVer == "5" -> from5ToCurrent
|
||||||
|
| preVer == "6" -> from6ToCurrent
|
||||||
|
| preVer == "7" -> from7ToCurrent
|
||||||
|
| preVer == "8" -> from8ToCurrent
|
||||||
|
| preVer == "9" -> from9ToCurrent
|
||||||
|
| otherwise -> throw400 NotSupported $
|
||||||
|
"unsupported version : " <> preVer
|
||||||
|
where
|
||||||
|
from9ToCurrent = from9To10 >> postMigrate
|
||||||
|
|
||||||
|
from8ToCurrent = from8To9 >> from9ToCurrent
|
||||||
|
|
||||||
|
from7ToCurrent = from7To8 >> from8ToCurrent
|
||||||
|
|
||||||
|
from6ToCurrent = from6To7 >> from7ToCurrent
|
||||||
|
|
||||||
|
from5ToCurrent = from5To6 >> from6ToCurrent
|
||||||
|
|
||||||
|
from4ToCurrent = from4To5 >> from5ToCurrent
|
||||||
|
|
||||||
|
from3ToCurrent = from3To4 >> from4ToCurrent
|
||||||
|
|
||||||
|
from2ToCurrent = from2To3 >> from3ToCurrent
|
||||||
|
|
||||||
|
from1ToCurrent = from1To2 >> from2ToCurrent
|
||||||
|
|
||||||
|
from08ToCurrent = from08To1 >> from1ToCurrent
|
||||||
|
|
||||||
|
postMigrate = do
|
||||||
|
-- update the catalog version
|
||||||
|
updateVersion
|
||||||
|
-- try building the schema cache
|
||||||
|
buildSchemaCache
|
||||||
|
return $ "successfully migrated to " ++ show curCatalogVer
|
||||||
|
|
||||||
|
updateVersion =
|
||||||
|
liftTx $ Q.unitQE defaultTxErrorHandler [Q.sql|
|
||||||
|
UPDATE "hdb_catalog"."hdb_version"
|
||||||
|
SET "version" = $1,
|
||||||
|
"upgraded_on" = $2
|
||||||
|
|] (curCatalogVer, migrationTime) False
|
@ -1,12 +1,12 @@
|
|||||||
module Ops
|
module Ops
|
||||||
( initCatalogSafe
|
( initCatalogSafe
|
||||||
, cleanCatalog
|
, cleanCatalog
|
||||||
, migrateCatalog
|
|
||||||
, execQuery
|
, execQuery
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Data.Time.Clock (UTCTime)
|
import Data.Time.Clock (UTCTime)
|
||||||
import Language.Haskell.TH.Syntax (Q, TExp, unTypeQ)
|
import Language.Haskell.TH.Syntax (Q, TExp, unTypeQ)
|
||||||
|
import Migrate (curCatalogVer)
|
||||||
|
|
||||||
import Hasura.Prelude
|
import Hasura.Prelude
|
||||||
import Hasura.RQL.DDL.Schema.Table
|
import Hasura.RQL.DDL.Schema.Table
|
||||||
@ -22,9 +22,6 @@ import qualified Data.Yaml.TH as Y
|
|||||||
import qualified Database.PG.Query as Q
|
import qualified Database.PG.Query as Q
|
||||||
import qualified Database.PG.Query.Connection as Q
|
import qualified Database.PG.Query.Connection as Q
|
||||||
|
|
||||||
curCatalogVer :: T.Text
|
|
||||||
curCatalogVer = "9"
|
|
||||||
|
|
||||||
initCatalogSafe
|
initCatalogSafe
|
||||||
:: (QErrM m, UserInfoM m, CacheRWM m, MonadTx m, MonadIO m, HasHttpManager m)
|
:: (QErrM m, UserInfoM m, CacheRWM m, MonadTx m, MonadIO m, HasHttpManager m)
|
||||||
=> UTCTime -> m String
|
=> UTCTime -> m String
|
||||||
@ -116,15 +113,6 @@ initCatalogStrict createSchema initTime = do
|
|||||||
|] (Identity sn) False
|
|] (Identity sn) False
|
||||||
|
|
||||||
|
|
||||||
migrateMetadata
|
|
||||||
:: (MonadTx m, HasHttpManager m, CacheRWM m, UserInfoM m, MonadIO m)
|
|
||||||
=> RQLQuery -> m ()
|
|
||||||
migrateMetadata rqlQuery = do
|
|
||||||
-- build schema cache
|
|
||||||
buildSchemaCache
|
|
||||||
-- run the RQL query to migrate metadata
|
|
||||||
void $ runQueryM rqlQuery
|
|
||||||
|
|
||||||
setAllAsSystemDefined :: (MonadTx m) => m ()
|
setAllAsSystemDefined :: (MonadTx m) => m ()
|
||||||
setAllAsSystemDefined = liftTx $ Q.catchE defaultTxErrorHandler $ do
|
setAllAsSystemDefined = liftTx $ Q.catchE defaultTxErrorHandler $ do
|
||||||
Q.unitQ "UPDATE hdb_catalog.hdb_table SET is_system_defined = 'true'" () False
|
Q.unitQ "UPDATE hdb_catalog.hdb_table SET is_system_defined = 'true'" () False
|
||||||
@ -132,261 +120,12 @@ setAllAsSystemDefined = liftTx $ Q.catchE defaultTxErrorHandler $ do
|
|||||||
Q.unitQ "UPDATE hdb_catalog.hdb_permission SET is_system_defined = 'true'" () False
|
Q.unitQ "UPDATE hdb_catalog.hdb_permission SET is_system_defined = 'true'" () False
|
||||||
Q.unitQ "UPDATE hdb_catalog.hdb_query_template SET is_system_defined = 'true'" () False
|
Q.unitQ "UPDATE hdb_catalog.hdb_query_template SET is_system_defined = 'true'" () False
|
||||||
|
|
||||||
setAsSystemDefinedFor2 :: (MonadTx m) => m ()
|
|
||||||
setAsSystemDefinedFor2 =
|
|
||||||
liftTx $ Q.catchE defaultTxErrorHandler $
|
|
||||||
Q.multiQ [Q.sql|
|
|
||||||
UPDATE hdb_catalog.hdb_table
|
|
||||||
SET is_system_defined = 'true'
|
|
||||||
WHERE table_schema = 'hdb_catalog'
|
|
||||||
AND ( table_name = 'event_triggers'
|
|
||||||
OR table_name = 'event_log'
|
|
||||||
OR table_name = 'event_invocation_logs'
|
|
||||||
);
|
|
||||||
UPDATE hdb_catalog.hdb_relationship
|
|
||||||
SET is_system_defined = 'true'
|
|
||||||
WHERE table_schema = 'hdb_catalog'
|
|
||||||
AND ( table_name = 'event_triggers'
|
|
||||||
OR table_name = 'event_log'
|
|
||||||
OR table_name = 'event_invocation_logs'
|
|
||||||
);
|
|
||||||
|]
|
|
||||||
|
|
||||||
setAsSystemDefinedFor5 :: (MonadTx m) => m ()
|
|
||||||
setAsSystemDefinedFor5 =
|
|
||||||
liftTx $ Q.catchE defaultTxErrorHandler $
|
|
||||||
Q.multiQ [Q.sql|
|
|
||||||
UPDATE hdb_catalog.hdb_table
|
|
||||||
SET is_system_defined = 'true'
|
|
||||||
WHERE table_schema = 'hdb_catalog'
|
|
||||||
AND table_name = 'remote_schemas';
|
|
||||||
|]
|
|
||||||
|
|
||||||
setAsSystemDefinedFor8 :: (MonadTx m) => m ()
|
|
||||||
setAsSystemDefinedFor8 =
|
|
||||||
liftTx $ Q.catchE defaultTxErrorHandler $
|
|
||||||
Q.multiQ [Q.sql|
|
|
||||||
UPDATE hdb_catalog.hdb_table
|
|
||||||
SET is_system_defined = 'true'
|
|
||||||
WHERE table_schema = 'hdb_catalog'
|
|
||||||
AND ( table_name = 'hdb_function_agg'
|
|
||||||
OR table_name = 'hdb_function'
|
|
||||||
);
|
|
||||||
UPDATE hdb_catalog.hdb_relationship
|
|
||||||
SET is_system_defined = 'true'
|
|
||||||
WHERE table_schema = 'hdb_catalog'
|
|
||||||
AND table_name = 'hdb_function_agg';
|
|
||||||
|]
|
|
||||||
|
|
||||||
setAsSystemDefinedFor9 :: (MonadTx m) => m ()
|
|
||||||
setAsSystemDefinedFor9 =
|
|
||||||
liftTx $ Q.catchE defaultTxErrorHandler $
|
|
||||||
Q.multiQ [Q.sql|
|
|
||||||
UPDATE hdb_catalog.hdb_table
|
|
||||||
SET is_system_defined = 'true'
|
|
||||||
WHERE table_schema = 'hdb_catalog'
|
|
||||||
AND table_name = 'hdb_version';
|
|
||||||
|]
|
|
||||||
|
|
||||||
|
|
||||||
cleanCatalog :: (MonadTx m) => m ()
|
cleanCatalog :: (MonadTx m) => m ()
|
||||||
cleanCatalog = liftTx $ Q.catchE defaultTxErrorHandler $ do
|
cleanCatalog = liftTx $ Q.catchE defaultTxErrorHandler $ do
|
||||||
-- This is where the generated views and triggers are stored
|
-- This is where the generated views and triggers are stored
|
||||||
Q.unitQ "DROP SCHEMA IF EXISTS hdb_views CASCADE" () False
|
Q.unitQ "DROP SCHEMA IF EXISTS hdb_views CASCADE" () False
|
||||||
Q.unitQ "DROP SCHEMA hdb_catalog CASCADE" () False
|
Q.unitQ "DROP SCHEMA hdb_catalog CASCADE" () False
|
||||||
|
|
||||||
getCatalogVersion
|
|
||||||
:: (MonadTx m)
|
|
||||||
=> m T.Text
|
|
||||||
getCatalogVersion = do
|
|
||||||
res <- liftTx $ Q.withQE defaultTxErrorHandler [Q.sql|
|
|
||||||
SELECT version FROM hdb_catalog.hdb_version
|
|
||||||
|] () False
|
|
||||||
return $ runIdentity $ Q.getRow res
|
|
||||||
|
|
||||||
from08To1 :: (MonadTx m) => m ()
|
|
||||||
from08To1 = liftTx $ Q.catchE defaultTxErrorHandler $ do
|
|
||||||
Q.unitQ "ALTER TABLE hdb_catalog.hdb_relationship ADD COLUMN comment TEXT NULL" () False
|
|
||||||
Q.unitQ "ALTER TABLE hdb_catalog.hdb_permission ADD COLUMN comment TEXT NULL" () False
|
|
||||||
Q.unitQ "ALTER TABLE hdb_catalog.hdb_query_template ADD COLUMN comment TEXT NULL" () False
|
|
||||||
Q.unitQ [Q.sql|
|
|
||||||
UPDATE hdb_catalog.hdb_query_template
|
|
||||||
SET template_defn =
|
|
||||||
json_build_object('type', 'select', 'args', template_defn->'select');
|
|
||||||
|] () False
|
|
||||||
|
|
||||||
from1To2
|
|
||||||
:: (MonadTx m, HasHttpManager m, CacheRWM m, UserInfoM m, MonadIO m)
|
|
||||||
=> m ()
|
|
||||||
from1To2 = do
|
|
||||||
-- migrate database
|
|
||||||
Q.Discard () <- liftTx $ Q.multiQE defaultTxErrorHandler
|
|
||||||
$(Q.sqlFromFile "src-rsr/migrate_from_1.sql")
|
|
||||||
migrateMetadata migrateMetadataFrom1
|
|
||||||
-- set as system defined
|
|
||||||
setAsSystemDefinedFor2
|
|
||||||
where
|
|
||||||
migrateMetadataFrom1 =
|
|
||||||
$(unTypeQ (Y.decodeFile "src-rsr/migrate_metadata_from_1.yaml" :: Q (TExp RQLQuery)))
|
|
||||||
|
|
||||||
from2To3 :: (MonadTx m) => m ()
|
|
||||||
from2To3 = liftTx $ Q.catchE defaultTxErrorHandler $ do
|
|
||||||
Q.unitQ "ALTER TABLE hdb_catalog.event_triggers ADD COLUMN headers JSON" () False
|
|
||||||
Q.unitQ "ALTER TABLE hdb_catalog.event_log ADD COLUMN next_retry_at TIMESTAMP" () False
|
|
||||||
Q.unitQ "CREATE INDEX ON hdb_catalog.event_log (trigger_id)" () False
|
|
||||||
Q.unitQ "CREATE INDEX ON hdb_catalog.event_invocation_logs (event_id)" () False
|
|
||||||
|
|
||||||
-- custom resolver
|
|
||||||
from4To5
|
|
||||||
:: (MonadTx m, HasHttpManager m, CacheRWM m, UserInfoM m, MonadIO m)
|
|
||||||
=> m ()
|
|
||||||
from4To5 = do
|
|
||||||
Q.Discard () <- liftTx $ Q.multiQE defaultTxErrorHandler
|
|
||||||
$(Q.sqlFromFile "src-rsr/migrate_from_4_to_5.sql")
|
|
||||||
migrateMetadata migrateMetadataFrom4
|
|
||||||
-- set as system defined
|
|
||||||
setAsSystemDefinedFor5
|
|
||||||
where
|
|
||||||
migrateMetadataFrom4 =
|
|
||||||
$(unTypeQ (Y.decodeFile "src-rsr/migrate_metadata_from_4_to_5.yaml" :: Q (TExp RQLQuery)))
|
|
||||||
|
|
||||||
from3To4 :: (MonadTx m) => m ()
|
|
||||||
from3To4 = liftTx $ Q.catchE defaultTxErrorHandler $ do
|
|
||||||
Q.unitQ "ALTER TABLE hdb_catalog.event_triggers ADD COLUMN configuration JSON" () False
|
|
||||||
eventTriggers <- map uncurryEventTrigger <$> Q.listQ [Q.sql|
|
|
||||||
SELECT e.name, e.definition::json, e.webhook, e.num_retries, e.retry_interval, e.headers::json
|
|
||||||
FROM hdb_catalog.event_triggers e
|
|
||||||
|] () False
|
|
||||||
forM_ eventTriggers updateEventTrigger3To4
|
|
||||||
Q.unitQ "ALTER TABLE hdb_catalog.event_triggers\
|
|
||||||
\ DROP COLUMN definition\
|
|
||||||
\, DROP COLUMN query\
|
|
||||||
\, DROP COLUMN webhook\
|
|
||||||
\, DROP COLUMN num_retries\
|
|
||||||
\, DROP COLUMN retry_interval\
|
|
||||||
\, DROP COLUMN headers" () False
|
|
||||||
where
|
|
||||||
uncurryEventTrigger (trn, Q.AltJ tDef, w, nr, rint, Q.AltJ headers) =
|
|
||||||
EventTriggerConf trn tDef (Just w) Nothing (RetryConf nr rint Nothing) headers
|
|
||||||
updateEventTrigger3To4 etc@(EventTriggerConf name _ _ _ _ _) = Q.unitQ [Q.sql|
|
|
||||||
UPDATE hdb_catalog.event_triggers
|
|
||||||
SET
|
|
||||||
configuration = $1
|
|
||||||
WHERE name = $2
|
|
||||||
|] (Q.AltJ $ A.toJSON etc, name) True
|
|
||||||
|
|
||||||
from5To6 :: (MonadTx m) => m ()
|
|
||||||
from5To6 = liftTx $ do
|
|
||||||
-- migrate database
|
|
||||||
Q.Discard () <- Q.multiQE defaultTxErrorHandler
|
|
||||||
$(Q.sqlFromFile "src-rsr/migrate_from_5_to_6.sql")
|
|
||||||
return ()
|
|
||||||
|
|
||||||
from6To7 :: (MonadTx m) => m ()
|
|
||||||
from6To7 = liftTx $ do
|
|
||||||
-- migrate database
|
|
||||||
Q.Discard () <- Q.multiQE defaultTxErrorHandler
|
|
||||||
$(Q.sqlFromFile "src-rsr/migrate_from_6_to_7.sql")
|
|
||||||
return ()
|
|
||||||
|
|
||||||
from7To8
|
|
||||||
:: (MonadTx m, HasHttpManager m, CacheRWM m, UserInfoM m, MonadIO m)
|
|
||||||
=> m ()
|
|
||||||
from7To8 = do
|
|
||||||
-- migrate database
|
|
||||||
Q.Discard () <- liftTx $ Q.multiQE defaultTxErrorHandler
|
|
||||||
$(Q.sqlFromFile "src-rsr/migrate_from_7_to_8.sql")
|
|
||||||
-- migrate metadata
|
|
||||||
migrateMetadata migrateMetadataFrom7
|
|
||||||
setAsSystemDefinedFor8
|
|
||||||
where
|
|
||||||
migrateMetadataFrom7 =
|
|
||||||
$(unTypeQ (Y.decodeFile "src-rsr/migrate_metadata_from_7_to_8.yaml" :: Q (TExp RQLQuery)))
|
|
||||||
|
|
||||||
-- alter hdb_version table and track it (telemetry changes)
|
|
||||||
from8To9
|
|
||||||
:: (MonadTx m, HasHttpManager m, CacheRWM m, UserInfoM m, MonadIO m)
|
|
||||||
=> m ()
|
|
||||||
from8To9 = do
|
|
||||||
Q.Discard () <- liftTx $ Q.multiQE defaultTxErrorHandler
|
|
||||||
$(Q.sqlFromFile "src-rsr/migrate_from_8_to_9.sql")
|
|
||||||
void $ runQueryM migrateMetadataFrom8
|
|
||||||
-- set as system defined
|
|
||||||
setAsSystemDefinedFor9
|
|
||||||
where
|
|
||||||
migrateMetadataFrom8 =
|
|
||||||
$(unTypeQ (Y.decodeFile "src-rsr/migrate_metadata_from_8_to_9.yaml" :: Q (TExp RQLQuery)))
|
|
||||||
|
|
||||||
|
|
||||||
migrateCatalog
|
|
||||||
:: (MonadTx m, CacheRWM m, MonadIO m, UserInfoM m, HasHttpManager m)
|
|
||||||
=> UTCTime -> m String
|
|
||||||
migrateCatalog migrationTime = do
|
|
||||||
preVer <- getCatalogVersion
|
|
||||||
if | preVer == curCatalogVer ->
|
|
||||||
return "already at the latest version"
|
|
||||||
| preVer == "0.8" -> from08ToCurrent
|
|
||||||
| preVer == "1" -> from1ToCurrent
|
|
||||||
| preVer == "2" -> from2ToCurrent
|
|
||||||
| preVer == "3" -> from3ToCurrent
|
|
||||||
| preVer == "4" -> from4ToCurrent
|
|
||||||
| preVer == "5" -> from5ToCurrent
|
|
||||||
| preVer == "6" -> from6ToCurrent
|
|
||||||
| preVer == "7" -> from7ToCurrent
|
|
||||||
| preVer == "8" -> from8ToCurrent
|
|
||||||
| otherwise -> throw400 NotSupported $
|
|
||||||
"unsupported version : " <> preVer
|
|
||||||
where
|
|
||||||
from8ToCurrent = do
|
|
||||||
from8To9
|
|
||||||
postMigrate
|
|
||||||
|
|
||||||
from7ToCurrent = do
|
|
||||||
from7To8
|
|
||||||
from8ToCurrent
|
|
||||||
|
|
||||||
from6ToCurrent = do
|
|
||||||
from6To7
|
|
||||||
from7ToCurrent
|
|
||||||
|
|
||||||
from5ToCurrent = do
|
|
||||||
from5To6
|
|
||||||
from6ToCurrent
|
|
||||||
|
|
||||||
from4ToCurrent = do
|
|
||||||
from4To5
|
|
||||||
from5ToCurrent
|
|
||||||
|
|
||||||
from3ToCurrent = do
|
|
||||||
from3To4
|
|
||||||
from4ToCurrent
|
|
||||||
|
|
||||||
from2ToCurrent = do
|
|
||||||
from2To3
|
|
||||||
from3ToCurrent
|
|
||||||
|
|
||||||
from1ToCurrent = do
|
|
||||||
from1To2
|
|
||||||
from2ToCurrent
|
|
||||||
|
|
||||||
from08ToCurrent = do
|
|
||||||
from08To1
|
|
||||||
from1ToCurrent
|
|
||||||
|
|
||||||
postMigrate = do
|
|
||||||
-- update the catalog version
|
|
||||||
updateVersion
|
|
||||||
-- try building the schema cache
|
|
||||||
buildSchemaCache
|
|
||||||
return $ "successfully migrated to " ++ show curCatalogVer
|
|
||||||
|
|
||||||
updateVersion =
|
|
||||||
liftTx $ Q.unitQE defaultTxErrorHandler [Q.sql|
|
|
||||||
UPDATE "hdb_catalog"."hdb_version"
|
|
||||||
SET "version" = $1,
|
|
||||||
"upgraded_on" = $2
|
|
||||||
|] (curCatalogVer, migrationTime) False
|
|
||||||
|
|
||||||
execQuery
|
execQuery
|
||||||
:: (MonadTx m, CacheRWM m, MonadIO m, UserInfoM m, HasHttpManager m)
|
:: (MonadTx m, CacheRWM m, MonadIO m, UserInfoM m, HasHttpManager m)
|
||||||
=> BL.ByteString -> m BL.ByteString
|
=> BL.ByteString -> m BL.ByteString
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
{-# LANGUAGE QuasiQuotes #-}
|
|
||||||
module Hasura.RQL.DDL.Metadata
|
module Hasura.RQL.DDL.Metadata
|
||||||
( TableMeta
|
( TableMeta
|
||||||
|
|
||||||
|
@ -38,6 +38,7 @@ module Hasura.RQL.DDL.Permission
|
|||||||
, addPermP1
|
, addPermP1
|
||||||
, addPermP2
|
, addPermP2
|
||||||
|
|
||||||
|
, dropView
|
||||||
, DropPerm
|
, DropPerm
|
||||||
, runDropPerm
|
, runDropPerm
|
||||||
|
|
||||||
|
@ -114,6 +114,21 @@ savePermToCatalog pt (QualifiedObject sn tn) (PermDef rn qdef mComment) =
|
|||||||
VALUES ($1, $2, $3, $4, $5 :: jsonb, $6)
|
VALUES ($1, $2, $3, $4, $5 :: jsonb, $6)
|
||||||
|] (sn, tn, rn, permTypeToCode pt, Q.AltJ qdef, mComment) True
|
|] (sn, tn, rn, permTypeToCode pt, Q.AltJ qdef, mComment) True
|
||||||
|
|
||||||
|
updatePermDefInCatalog
|
||||||
|
:: (ToJSON a)
|
||||||
|
=> PermType
|
||||||
|
-> QualifiedTable
|
||||||
|
-> RoleName
|
||||||
|
-> a
|
||||||
|
-> Q.TxE QErr ()
|
||||||
|
updatePermDefInCatalog pt (QualifiedObject sn tn) rn qdef =
|
||||||
|
Q.unitQE defaultTxErrorHandler [Q.sql|
|
||||||
|
UPDATE hdb_catalog.hdb_permission
|
||||||
|
SET perm_def = $1 :: jsonb
|
||||||
|
WHERE table_schema = $2 AND table_name = $3
|
||||||
|
AND role_name = $4 AND perm_type = $5
|
||||||
|
|] (Q.AltJ qdef, sn, tn, rn, permTypeToCode pt) True
|
||||||
|
|
||||||
dropPermFromCatalog
|
dropPermFromCatalog
|
||||||
:: QualifiedTable
|
:: QualifiedTable
|
||||||
-> RoleName
|
-> RoleName
|
||||||
|
@ -1,89 +1,32 @@
|
|||||||
module Hasura.RQL.DDL.Relationship where
|
module Hasura.RQL.DDL.Relationship
|
||||||
|
( objRelP2Setup
|
||||||
|
, objRelP2
|
||||||
|
, arrRelP2Setup
|
||||||
|
, arrRelP2
|
||||||
|
, delRelFromCatalog
|
||||||
|
, validateRelP1
|
||||||
|
, runCreateObjRel
|
||||||
|
, runCreateArrRel
|
||||||
|
, runDropRel
|
||||||
|
, runSetRelComment
|
||||||
|
, module Hasura.RQL.DDL.Relationship.Types
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
import qualified Database.PG.Query as Q
|
import qualified Database.PG.Query as Q
|
||||||
import Hasura.Prelude
|
import Hasura.Prelude
|
||||||
import Hasura.RQL.DDL.Deps
|
import Hasura.RQL.DDL.Deps
|
||||||
import Hasura.RQL.DDL.Permission (purgePerm)
|
import Hasura.RQL.DDL.Permission (purgePerm)
|
||||||
|
import Hasura.RQL.DDL.Relationship.Types
|
||||||
import Hasura.RQL.Types
|
import Hasura.RQL.Types
|
||||||
import Hasura.SQL.Types
|
import Hasura.SQL.Types
|
||||||
|
|
||||||
import Data.Aeson.Casing
|
|
||||||
import Data.Aeson.TH
|
|
||||||
import Data.Aeson.Types
|
import Data.Aeson.Types
|
||||||
import qualified Data.HashMap.Strict as HM
|
import qualified Data.HashMap.Strict as HM
|
||||||
import qualified Data.Map.Strict as M
|
import qualified Data.Map.Strict as M
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import Data.Tuple (swap)
|
import Data.Tuple (swap)
|
||||||
import Instances.TH.Lift ()
|
import Instances.TH.Lift ()
|
||||||
import Language.Haskell.TH.Syntax (Lift)
|
|
||||||
|
|
||||||
data RelDef a
|
|
||||||
= RelDef
|
|
||||||
{ rdName :: !RelName
|
|
||||||
, rdUsing :: !a
|
|
||||||
, rdComment :: !(Maybe T.Text)
|
|
||||||
} deriving (Show, Eq, Lift)
|
|
||||||
|
|
||||||
$(deriveFromJSON (aesonDrop 2 snakeCase){omitNothingFields=True} ''RelDef)
|
|
||||||
|
|
||||||
instance (ToJSON a) => ToJSON (RelDef a) where
|
|
||||||
toJSON = object . toAesonPairs
|
|
||||||
|
|
||||||
instance (ToJSON a) => ToAesonPairs (RelDef a) where
|
|
||||||
toAesonPairs (RelDef rn ru rc) =
|
|
||||||
[ "name" .= rn
|
|
||||||
, "using" .= ru
|
|
||||||
, "comment" .= rc
|
|
||||||
]
|
|
||||||
|
|
||||||
data RelManualConfig
|
|
||||||
= RelManualConfig
|
|
||||||
{ rmTable :: !QualifiedTable
|
|
||||||
, rmColumns :: !(M.Map PGCol PGCol)
|
|
||||||
} deriving (Show, Eq, Lift)
|
|
||||||
|
|
||||||
instance FromJSON RelManualConfig where
|
|
||||||
parseJSON (Object v) =
|
|
||||||
RelManualConfig
|
|
||||||
<$> v .: "remote_table"
|
|
||||||
<*> v .: "column_mapping"
|
|
||||||
|
|
||||||
parseJSON _ =
|
|
||||||
fail "manual_configuration should be an object"
|
|
||||||
|
|
||||||
instance ToJSON RelManualConfig where
|
|
||||||
toJSON (RelManualConfig qt cm) =
|
|
||||||
object [ "remote_table" .= qt
|
|
||||||
, "column_mapping" .= cm
|
|
||||||
]
|
|
||||||
|
|
||||||
data RelUsing a b
|
|
||||||
= RUFKeyOn a
|
|
||||||
| RUManual b
|
|
||||||
deriving (Show, Eq, Lift)
|
|
||||||
|
|
||||||
instance (ToJSON a, ToJSON b) => ToJSON (RelUsing a b) where
|
|
||||||
toJSON (RUFKeyOn fkey) =
|
|
||||||
object [ "foreign_key_constraint_on" .= fkey ]
|
|
||||||
toJSON (RUManual manual) =
|
|
||||||
object [ "manual_configuration" .= manual ]
|
|
||||||
|
|
||||||
instance (FromJSON a, FromJSON b) => FromJSON (RelUsing a b) where
|
|
||||||
parseJSON (Object o) = do
|
|
||||||
let fkeyOnM = HM.lookup "foreign_key_constraint_on" o
|
|
||||||
manualM = HM.lookup "manual_configuration" o
|
|
||||||
let msgFrag = "one of foreign_key_constraint_on/manual_configuration should be present"
|
|
||||||
case (fkeyOnM, manualM) of
|
|
||||||
(Nothing, Nothing) -> fail $ "atleast " <> msgFrag
|
|
||||||
(Just a, Nothing) -> RUFKeyOn <$> parseJSON a
|
|
||||||
(Nothing, Just b) -> RUManual <$> parseJSON b
|
|
||||||
_ -> fail $ "only " <> msgFrag
|
|
||||||
parseJSON _ =
|
|
||||||
fail "using should be an object"
|
|
||||||
|
|
||||||
newtype ObjRelManualConfig =
|
|
||||||
ObjRelManualConfig { getObjRelMapping :: RelManualConfig }
|
|
||||||
deriving (Show, Eq, FromJSON, ToJSON, Lift)
|
|
||||||
|
|
||||||
validateManualConfig
|
validateManualConfig
|
||||||
:: (QErrM m, CacheRM m)
|
:: (QErrM m, CacheRM m)
|
||||||
@ -134,11 +77,6 @@ checkForColConfilct tabInfo f =
|
|||||||
]
|
]
|
||||||
Nothing -> return ()
|
Nothing -> return ()
|
||||||
|
|
||||||
type ObjRelUsing = RelUsing PGCol ObjRelManualConfig
|
|
||||||
type ObjRelDef = RelDef ObjRelUsing
|
|
||||||
|
|
||||||
type CreateObjRel = WithTable ObjRelDef
|
|
||||||
|
|
||||||
objRelP1
|
objRelP1
|
||||||
:: (QErrM m, CacheRM m)
|
:: (QErrM m, CacheRM m)
|
||||||
=> TableInfo
|
=> TableInfo
|
||||||
@ -230,22 +168,6 @@ runCreateObjRel defn = do
|
|||||||
createObjRelP1 defn
|
createObjRelP1 defn
|
||||||
createObjRelP2 defn
|
createObjRelP2 defn
|
||||||
|
|
||||||
data ArrRelUsingFKeyOn
|
|
||||||
= ArrRelUsingFKeyOn
|
|
||||||
{ arufTable :: !QualifiedTable
|
|
||||||
, arufColumn :: !PGCol
|
|
||||||
} deriving (Show, Eq, Lift)
|
|
||||||
|
|
||||||
$(deriveJSON (aesonDrop 4 snakeCase){omitNothingFields=True} ''ArrRelUsingFKeyOn)
|
|
||||||
|
|
||||||
newtype ArrRelManualConfig =
|
|
||||||
ArrRelManualConfig { getArrRelMapping :: RelManualConfig }
|
|
||||||
deriving (Show, Eq, FromJSON, ToJSON, Lift)
|
|
||||||
|
|
||||||
type ArrRelUsing = RelUsing ArrRelUsingFKeyOn ArrRelManualConfig
|
|
||||||
type ArrRelDef = RelDef ArrRelUsing
|
|
||||||
type CreateArrRel = WithTable ArrRelDef
|
|
||||||
|
|
||||||
createArrRelP1 :: (UserInfoM m, QErrM m, CacheRM m) => CreateArrRel -> m ()
|
createArrRelP1 :: (UserInfoM m, QErrM m, CacheRM m) => CreateArrRel -> m ()
|
||||||
createArrRelP1 (WithTable qt rd) = do
|
createArrRelP1 (WithTable qt rd) = do
|
||||||
adminOnly
|
adminOnly
|
||||||
@ -333,15 +255,6 @@ runCreateArrRel defn = do
|
|||||||
createArrRelP1 defn
|
createArrRelP1 defn
|
||||||
createArrRelP2 defn
|
createArrRelP2 defn
|
||||||
|
|
||||||
data DropRel
|
|
||||||
= DropRel
|
|
||||||
{ drTable :: !QualifiedTable
|
|
||||||
, drRelationship :: !RelName
|
|
||||||
, drCascade :: !(Maybe Bool)
|
|
||||||
} deriving (Show, Eq, Lift)
|
|
||||||
|
|
||||||
$(deriveJSON (aesonDrop 2 snakeCase){omitNothingFields=True} ''DropRel)
|
|
||||||
|
|
||||||
dropRelP1 :: (UserInfoM m, QErrM m, CacheRM m) => DropRel -> m [SchemaObjId]
|
dropRelP1 :: (UserInfoM m, QErrM m, CacheRM m) => DropRel -> m [SchemaObjId]
|
||||||
dropRelP1 (DropRel qt rn cascade) = do
|
dropRelP1 (DropRel qt rn cascade) = do
|
||||||
adminOnly
|
adminOnly
|
||||||
@ -390,20 +303,13 @@ delRelFromCatalog (QualifiedObject sn tn) rn =
|
|||||||
AND rel_name = $3
|
AND rel_name = $3
|
||||||
|] (sn, tn, rn) True
|
|] (sn, tn, rn) True
|
||||||
|
|
||||||
data SetRelComment
|
validateRelP1
|
||||||
= SetRelComment
|
:: (UserInfoM m, QErrM m, CacheRM m)
|
||||||
{ arTable :: !QualifiedTable
|
=> QualifiedTable -> RelName -> m RelInfo
|
||||||
, arRelationship :: !RelName
|
validateRelP1 qt rn = do
|
||||||
, arComment :: !(Maybe T.Text)
|
|
||||||
} deriving (Show, Eq, Lift)
|
|
||||||
|
|
||||||
$(deriveJSON (aesonDrop 2 snakeCase){omitNothingFields=True} ''SetRelComment)
|
|
||||||
|
|
||||||
setRelCommentP1 :: (UserInfoM m, QErrM m, CacheRM m) => SetRelComment -> m ()
|
|
||||||
setRelCommentP1 (SetRelComment qt rn _) = do
|
|
||||||
adminOnly
|
adminOnly
|
||||||
tabInfo <- askTabInfo qt
|
tabInfo <- askTabInfo qt
|
||||||
void $ askRelType (tiFieldInfoMap tabInfo) rn ""
|
askRelType (tiFieldInfoMap tabInfo) rn ""
|
||||||
|
|
||||||
setRelCommentP2
|
setRelCommentP2
|
||||||
:: (QErrM m, MonadTx m)
|
:: (QErrM m, MonadTx m)
|
||||||
@ -416,8 +322,10 @@ runSetRelComment
|
|||||||
:: (QErrM m, CacheRWM m, MonadTx m , UserInfoM m)
|
:: (QErrM m, CacheRWM m, MonadTx m , UserInfoM m)
|
||||||
=> SetRelComment -> m RespBody
|
=> SetRelComment -> m RespBody
|
||||||
runSetRelComment defn = do
|
runSetRelComment defn = do
|
||||||
setRelCommentP1 defn
|
void $ validateRelP1 qt rn
|
||||||
setRelCommentP2 defn
|
setRelCommentP2 defn
|
||||||
|
where
|
||||||
|
SetRelComment qt rn _ = defn
|
||||||
|
|
||||||
setRelComment :: SetRelComment
|
setRelComment :: SetRelComment
|
||||||
-> Q.TxE QErr ()
|
-> Q.TxE QErr ()
|
||||||
|
42
server/src-lib/Hasura/RQL/DDL/Relationship/Rename.hs
Normal file
42
server/src-lib/Hasura/RQL/DDL/Relationship/Rename.hs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
module Hasura.RQL.DDL.Relationship.Rename
|
||||||
|
(runRenameRel)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Hasura.Prelude
|
||||||
|
import Hasura.RQL.DDL.Relationship (validateRelP1)
|
||||||
|
import Hasura.RQL.DDL.Relationship.Types
|
||||||
|
import Hasura.RQL.DDL.Schema.Rename (renameRelInCatalog)
|
||||||
|
import Hasura.RQL.DDL.Schema.Table (buildSchemaCache)
|
||||||
|
import Hasura.RQL.Types
|
||||||
|
import Hasura.SQL.Types
|
||||||
|
|
||||||
|
import qualified Data.HashMap.Strict as HM
|
||||||
|
|
||||||
|
renameRelP2
|
||||||
|
:: (QErrM m, MonadTx m, CacheRWM m, MonadIO m, HasHttpManager m)
|
||||||
|
=> QualifiedTable -> RelName -> RelInfo -> m ()
|
||||||
|
renameRelP2 qt newRN relInfo = do
|
||||||
|
tabInfo <- askTabInfo qt
|
||||||
|
-- check for conflicts in fieldInfoMap
|
||||||
|
case HM.lookup (fromRel newRN) $ tiFieldInfoMap tabInfo of
|
||||||
|
Nothing -> return ()
|
||||||
|
Just _ ->
|
||||||
|
throw400 AlreadyExists $ "cannot rename relationship " <> oldRN
|
||||||
|
<<> " to " <> newRN <<> " in table " <> qt <<>
|
||||||
|
" as a column/relationship with the name already exists"
|
||||||
|
-- update catalog
|
||||||
|
renameRelInCatalog qt oldRN newRN
|
||||||
|
-- update schema cache
|
||||||
|
buildSchemaCache
|
||||||
|
where
|
||||||
|
oldRN = riName relInfo
|
||||||
|
|
||||||
|
runRenameRel
|
||||||
|
:: (QErrM m, CacheRWM m, MonadTx m , UserInfoM m, MonadIO m, HasHttpManager m)
|
||||||
|
=> RenameRel -> m RespBody
|
||||||
|
runRenameRel defn = do
|
||||||
|
ri <- validateRelP1 qt rn
|
||||||
|
renameRelP2 qt newRN ri
|
||||||
|
return successMsg
|
||||||
|
where
|
||||||
|
RenameRel qt rn newRN = defn
|
130
server/src-lib/Hasura/RQL/DDL/Relationship/Types.hs
Normal file
130
server/src-lib/Hasura/RQL/DDL/Relationship/Types.hs
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
module Hasura.RQL.DDL.Relationship.Types where
|
||||||
|
|
||||||
|
import Hasura.Prelude
|
||||||
|
import Hasura.RQL.Types
|
||||||
|
import Hasura.SQL.Types
|
||||||
|
|
||||||
|
import Data.Aeson.Casing
|
||||||
|
import Data.Aeson.TH
|
||||||
|
import Data.Aeson.Types
|
||||||
|
import qualified Data.HashMap.Strict as HM
|
||||||
|
import qualified Data.Map.Strict as M
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import Instances.TH.Lift ()
|
||||||
|
import Language.Haskell.TH.Syntax (Lift)
|
||||||
|
|
||||||
|
data RelDef a
|
||||||
|
= RelDef
|
||||||
|
{ rdName :: !RelName
|
||||||
|
, rdUsing :: !a
|
||||||
|
, rdComment :: !(Maybe T.Text)
|
||||||
|
} deriving (Show, Eq, Lift)
|
||||||
|
|
||||||
|
$(deriveFromJSON (aesonDrop 2 snakeCase){omitNothingFields=True} ''RelDef)
|
||||||
|
|
||||||
|
instance (ToJSON a) => ToJSON (RelDef a) where
|
||||||
|
toJSON = object . toAesonPairs
|
||||||
|
|
||||||
|
instance (ToJSON a) => ToAesonPairs (RelDef a) where
|
||||||
|
toAesonPairs (RelDef rn ru rc) =
|
||||||
|
[ "name" .= rn
|
||||||
|
, "using" .= ru
|
||||||
|
, "comment" .= rc
|
||||||
|
]
|
||||||
|
|
||||||
|
data RelManualConfig
|
||||||
|
= RelManualConfig
|
||||||
|
{ rmTable :: !QualifiedTable
|
||||||
|
, rmColumns :: !(M.Map PGCol PGCol)
|
||||||
|
} deriving (Show, Eq, Lift)
|
||||||
|
|
||||||
|
instance FromJSON RelManualConfig where
|
||||||
|
parseJSON (Object v) =
|
||||||
|
RelManualConfig
|
||||||
|
<$> v .: "remote_table"
|
||||||
|
<*> v .: "column_mapping"
|
||||||
|
|
||||||
|
parseJSON _ =
|
||||||
|
fail "manual_configuration should be an object"
|
||||||
|
|
||||||
|
instance ToJSON RelManualConfig where
|
||||||
|
toJSON (RelManualConfig qt cm) =
|
||||||
|
object [ "remote_table" .= qt
|
||||||
|
, "column_mapping" .= cm
|
||||||
|
]
|
||||||
|
|
||||||
|
data RelUsing a b
|
||||||
|
= RUFKeyOn a
|
||||||
|
| RUManual b
|
||||||
|
deriving (Show, Eq, Lift)
|
||||||
|
|
||||||
|
instance (ToJSON a, ToJSON b) => ToJSON (RelUsing a b) where
|
||||||
|
toJSON (RUFKeyOn fkey) =
|
||||||
|
object [ "foreign_key_constraint_on" .= fkey ]
|
||||||
|
toJSON (RUManual manual) =
|
||||||
|
object [ "manual_configuration" .= manual ]
|
||||||
|
|
||||||
|
instance (FromJSON a, FromJSON b) => FromJSON (RelUsing a b) where
|
||||||
|
parseJSON (Object o) = do
|
||||||
|
let fkeyOnM = HM.lookup "foreign_key_constraint_on" o
|
||||||
|
manualM = HM.lookup "manual_configuration" o
|
||||||
|
let msgFrag = "one of foreign_key_constraint_on/manual_configuration should be present"
|
||||||
|
case (fkeyOnM, manualM) of
|
||||||
|
(Nothing, Nothing) -> fail $ "atleast " <> msgFrag
|
||||||
|
(Just a, Nothing) -> RUFKeyOn <$> parseJSON a
|
||||||
|
(Nothing, Just b) -> RUManual <$> parseJSON b
|
||||||
|
_ -> fail $ "only " <> msgFrag
|
||||||
|
parseJSON _ =
|
||||||
|
fail "using should be an object"
|
||||||
|
|
||||||
|
newtype ArrRelManualConfig =
|
||||||
|
ArrRelManualConfig { getArrRelMapping :: RelManualConfig }
|
||||||
|
deriving (Show, Eq, FromJSON, ToJSON, Lift)
|
||||||
|
|
||||||
|
data ArrRelUsingFKeyOn
|
||||||
|
= ArrRelUsingFKeyOn
|
||||||
|
{ arufTable :: !QualifiedTable
|
||||||
|
, arufColumn :: !PGCol
|
||||||
|
} deriving (Show, Eq, Lift)
|
||||||
|
|
||||||
|
$(deriveJSON (aesonDrop 4 snakeCase){omitNothingFields=True} ''ArrRelUsingFKeyOn)
|
||||||
|
|
||||||
|
type ArrRelUsing = RelUsing ArrRelUsingFKeyOn ArrRelManualConfig
|
||||||
|
type ArrRelDef = RelDef ArrRelUsing
|
||||||
|
type CreateArrRel = WithTable ArrRelDef
|
||||||
|
|
||||||
|
newtype ObjRelManualConfig =
|
||||||
|
ObjRelManualConfig { getObjRelMapping :: RelManualConfig }
|
||||||
|
deriving (Show, Eq, FromJSON, ToJSON, Lift)
|
||||||
|
|
||||||
|
type ObjRelUsing = RelUsing PGCol ObjRelManualConfig
|
||||||
|
type ObjRelDef = RelDef ObjRelUsing
|
||||||
|
|
||||||
|
type CreateObjRel = WithTable ObjRelDef
|
||||||
|
|
||||||
|
data DropRel
|
||||||
|
= DropRel
|
||||||
|
{ drTable :: !QualifiedTable
|
||||||
|
, drRelationship :: !RelName
|
||||||
|
, drCascade :: !(Maybe Bool)
|
||||||
|
} deriving (Show, Eq, Lift)
|
||||||
|
|
||||||
|
$(deriveJSON (aesonDrop 2 snakeCase){omitNothingFields=True} ''DropRel)
|
||||||
|
|
||||||
|
data SetRelComment
|
||||||
|
= SetRelComment
|
||||||
|
{ arTable :: !QualifiedTable
|
||||||
|
, arRelationship :: !RelName
|
||||||
|
, arComment :: !(Maybe T.Text)
|
||||||
|
} deriving (Show, Eq, Lift)
|
||||||
|
|
||||||
|
$(deriveJSON (aesonDrop 2 snakeCase){omitNothingFields=True} ''SetRelComment)
|
||||||
|
|
||||||
|
data RenameRel
|
||||||
|
= RenameRel
|
||||||
|
{ rrTable :: !QualifiedTable
|
||||||
|
, rrName :: !RelName
|
||||||
|
, rrNewName :: !RelName
|
||||||
|
} deriving (Show, Eq, Lift)
|
||||||
|
|
||||||
|
$(deriveJSON (aesonDrop 2 snakeCase) ''RenameRel)
|
@ -27,6 +27,7 @@ import Hasura.SQL.Types
|
|||||||
|
|
||||||
import qualified Database.PG.Query as Q
|
import qualified Database.PG.Query as Q
|
||||||
|
|
||||||
|
import Control.Arrow ((***))
|
||||||
import Data.Aeson.Casing
|
import Data.Aeson.Casing
|
||||||
import Data.Aeson.TH
|
import Data.Aeson.TH
|
||||||
|
|
||||||
@ -166,8 +167,7 @@ getTableDiff oldtm newtm =
|
|||||||
= PGColInfo colName colType isNullable
|
= PGColInfo colName colType isNullable
|
||||||
|
|
||||||
alteredCols =
|
alteredCols =
|
||||||
flip map (filter (uncurry (/=)) existingCols) $ \(pcmo, pcmn) ->
|
flip map (filter (uncurry (/=)) existingCols) $ pcmToPci *** pcmToPci
|
||||||
(pcmToPci pcmo, pcmToPci pcmn)
|
|
||||||
|
|
||||||
droppedFKeyConstraints = map cmName $
|
droppedFKeyConstraints = map cmName $
|
||||||
filter (isForeignKey . cmType) $ getDifference cmOid
|
filter (isForeignKey . cmType) $ getDifference cmOid
|
||||||
@ -225,7 +225,7 @@ getSchemaChangeDeps schemaDiff = do
|
|||||||
where
|
where
|
||||||
SchemaDiff droppedTables alteredTables = schemaDiff
|
SchemaDiff droppedTables alteredTables = schemaDiff
|
||||||
|
|
||||||
isDirectDep (SOTableObj tn _) = tn `HS.member` (HS.fromList droppedTables)
|
isDirectDep (SOTableObj tn _) = tn `HS.member` HS.fromList droppedTables
|
||||||
isDirectDep _ = False
|
isDirectDep _ = False
|
||||||
|
|
||||||
data FunctionMeta
|
data FunctionMeta
|
||||||
|
381
server/src-lib/Hasura/RQL/DDL/Schema/Rename.hs
Normal file
381
server/src-lib/Hasura/RQL/DDL/Schema/Rename.hs
Normal file
@ -0,0 +1,381 @@
|
|||||||
|
module Hasura.RQL.DDL.Schema.Rename
|
||||||
|
( renameTableInCatalog
|
||||||
|
, renameColInCatalog
|
||||||
|
, renameRelInCatalog
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Control.Arrow ((***))
|
||||||
|
import Hasura.Prelude
|
||||||
|
import Hasura.RQL.DDL.Permission
|
||||||
|
import Hasura.RQL.DDL.Permission.Internal
|
||||||
|
import Hasura.RQL.DDL.Relationship.Types
|
||||||
|
import Hasura.RQL.Types
|
||||||
|
import Hasura.SQL.Types
|
||||||
|
|
||||||
|
import qualified Data.HashMap.Strict as M
|
||||||
|
import qualified Data.Map.Strict as Map
|
||||||
|
import qualified Database.PG.Query as Q
|
||||||
|
|
||||||
|
import Data.Aeson
|
||||||
|
|
||||||
|
data RenameItem a
|
||||||
|
= RenameItem
|
||||||
|
{ _riTable :: !QualifiedTable
|
||||||
|
, _riOld :: !a
|
||||||
|
, _riNew :: !a
|
||||||
|
} deriving (Show, Eq)
|
||||||
|
|
||||||
|
type RenameCol = RenameItem PGCol
|
||||||
|
data RenameField
|
||||||
|
= RFCol !RenameCol
|
||||||
|
| RFRel !(RenameItem RelName)
|
||||||
|
deriving (Show, Eq)
|
||||||
|
|
||||||
|
type RenameTable = (QualifiedTable, QualifiedTable)
|
||||||
|
|
||||||
|
otherDeps :: QErrM m => Text -> SchemaObjId -> m ()
|
||||||
|
otherDeps errMsg = \case
|
||||||
|
SOQTemplate name ->
|
||||||
|
throw400 NotSupported $
|
||||||
|
"found dependant query template " <> name <<> "; " <> errMsg
|
||||||
|
d ->
|
||||||
|
throw500 $ "unexpected dependancy "
|
||||||
|
<> reportSchemaObj d <> "; " <> errMsg
|
||||||
|
|
||||||
|
|
||||||
|
renameTableInCatalog
|
||||||
|
:: (MonadTx m, CacheRM m)
|
||||||
|
=> QualifiedTable -> QualifiedTable -> m ()
|
||||||
|
renameTableInCatalog newQT oldQT = do
|
||||||
|
sc <- askSchemaCache
|
||||||
|
let allDeps = getDependentObjs sc $ SOTable oldQT
|
||||||
|
-- update all dependant schema objects
|
||||||
|
forM_ allDeps $ \case
|
||||||
|
SOTableObj refQT (TORel rn) ->
|
||||||
|
updateRelDefs refQT rn (oldQT, newQT)
|
||||||
|
-- table names are not specified in permission definitions
|
||||||
|
SOTableObj _ (TOPerm _ _) -> return ()
|
||||||
|
d -> otherDeps errMsg d
|
||||||
|
-- -- Update table name in hdb_catalog
|
||||||
|
liftTx $ Q.catchE defaultTxErrorHandler updateTableInCatalog
|
||||||
|
where
|
||||||
|
QualifiedObject nsn ntn = newQT
|
||||||
|
QualifiedObject osn otn = oldQT
|
||||||
|
errMsg = "cannot rename table " <> oldQT <<> " to " <>> newQT
|
||||||
|
updateTableInCatalog =
|
||||||
|
Q.unitQ [Q.sql|
|
||||||
|
UPDATE "hdb_catalog"."hdb_table"
|
||||||
|
SET table_schema = $1, table_name = $2
|
||||||
|
WHERE table_schema = $3 AND table_name = $4
|
||||||
|
|] (nsn, ntn, osn, otn) False
|
||||||
|
|
||||||
|
renameColInCatalog
|
||||||
|
:: (MonadTx m, CacheRM m)
|
||||||
|
=> PGCol -> PGCol -> QualifiedTable -> TableInfo -> m ()
|
||||||
|
renameColInCatalog oCol nCol qt ti = do
|
||||||
|
sc <- askSchemaCache
|
||||||
|
-- Check if any relation exists with new column name
|
||||||
|
assertFldNotExists
|
||||||
|
-- Fetch dependent objects
|
||||||
|
let depObjs = getDependentObjs sc $ SOTableObj qt $ TOCol oCol
|
||||||
|
renameFld = RFCol $ RenameItem qt oCol nCol
|
||||||
|
-- Update dependent objects
|
||||||
|
forM_ depObjs $ \case
|
||||||
|
SOTableObj refQT (TOPerm role pt) ->
|
||||||
|
updatePermFlds refQT role pt renameFld
|
||||||
|
SOTableObj refQT (TORel rn) ->
|
||||||
|
updateColInRel refQT rn $ RenameItem qt oCol nCol
|
||||||
|
d -> otherDeps errMsg d
|
||||||
|
where
|
||||||
|
errMsg = "cannot rename column " <> oCol <<> " to " <>> nCol
|
||||||
|
assertFldNotExists =
|
||||||
|
case M.lookup (fromPGCol oCol) $ tiFieldInfoMap ti of
|
||||||
|
Just (FIRelationship _) ->
|
||||||
|
throw400 AlreadyExists $ "cannot rename column " <> oCol
|
||||||
|
<<> " to " <> nCol <<> " in table " <> qt <<>
|
||||||
|
" as a relationship with the name already exists"
|
||||||
|
_ -> return ()
|
||||||
|
|
||||||
|
renameRelInCatalog
|
||||||
|
:: (MonadTx m, CacheRM m)
|
||||||
|
=> QualifiedTable -> RelName -> RelName -> m ()
|
||||||
|
renameRelInCatalog qt oldRN newRN = do
|
||||||
|
sc <- askSchemaCache
|
||||||
|
let depObjs = getDependentObjs sc $ SOTableObj qt $ TORel oldRN
|
||||||
|
renameFld = RFRel $ RenameItem qt oldRN newRN
|
||||||
|
|
||||||
|
forM_ depObjs $ \case
|
||||||
|
SOTableObj refQT (TOPerm role pt) ->
|
||||||
|
updatePermFlds refQT role pt renameFld
|
||||||
|
d -> otherDeps errMsg d
|
||||||
|
liftTx updateRelName
|
||||||
|
where
|
||||||
|
errMsg = "cannot rename relationship " <> oldRN <<> " to " <>> newRN
|
||||||
|
QualifiedObject sn tn = qt
|
||||||
|
updateRelName =
|
||||||
|
Q.unitQE defaultTxErrorHandler [Q.sql|
|
||||||
|
UPDATE hdb_catalog.hdb_relationship
|
||||||
|
SET rel_name = $1
|
||||||
|
WHERE table_schema = $2
|
||||||
|
AND table_name = $3
|
||||||
|
AND rel_name = $4
|
||||||
|
|] (newRN, sn, tn, oldRN) True
|
||||||
|
|
||||||
|
|
||||||
|
-- update table names in relationship definition
|
||||||
|
updateRelDefs
|
||||||
|
:: (MonadTx m, CacheRM m)
|
||||||
|
=> QualifiedTable -> RelName -> RenameTable -> m ()
|
||||||
|
updateRelDefs qt rn renameTable = do
|
||||||
|
fim <- askFieldInfoMap qt
|
||||||
|
ri <- askRelType fim rn ""
|
||||||
|
case riType ri of
|
||||||
|
ObjRel -> updateObjRelDef qt rn renameTable
|
||||||
|
ArrRel -> updateArrRelDef qt rn renameTable
|
||||||
|
|
||||||
|
|
||||||
|
updateObjRelDef
|
||||||
|
:: (MonadTx m)
|
||||||
|
=> QualifiedTable -> RelName -> RenameTable -> m ()
|
||||||
|
updateObjRelDef qt rn (oldQT, newQT) = do
|
||||||
|
oldDefV <- liftTx $ getRelDef qt rn
|
||||||
|
oldDef :: ObjRelUsing <- decodeValue oldDefV
|
||||||
|
let newDef = case oldDef of
|
||||||
|
RUFKeyOn _ -> oldDef
|
||||||
|
RUManual (ObjRelManualConfig (RelManualConfig dbQT rmCols)) ->
|
||||||
|
let updQT = bool oldQT newQT $ oldQT == dbQT
|
||||||
|
in RUManual $ ObjRelManualConfig $ RelManualConfig updQT rmCols
|
||||||
|
liftTx $ updateRel qt rn $ toJSON newDef
|
||||||
|
|
||||||
|
updateArrRelDef
|
||||||
|
:: (MonadTx m)
|
||||||
|
=> QualifiedTable -> RelName -> RenameTable -> m ()
|
||||||
|
updateArrRelDef qt rn (oldQT, newQT) = do
|
||||||
|
oldDefV <- liftTx $ getRelDef qt rn
|
||||||
|
oldDef <- decodeValue oldDefV
|
||||||
|
let newDef = case oldDef of
|
||||||
|
RUFKeyOn (ArrRelUsingFKeyOn dbQT c) ->
|
||||||
|
let updQT = getUpdQT dbQT
|
||||||
|
in RUFKeyOn $ ArrRelUsingFKeyOn updQT c
|
||||||
|
RUManual (ArrRelManualConfig (RelManualConfig dbQT rmCols)) ->
|
||||||
|
let updQT = getUpdQT dbQT
|
||||||
|
in RUManual $ ArrRelManualConfig $ RelManualConfig updQT rmCols
|
||||||
|
liftTx $ updateRel qt rn $ toJSON newDef
|
||||||
|
where
|
||||||
|
getUpdQT dbQT = bool oldQT newQT $ oldQT == dbQT
|
||||||
|
|
||||||
|
-- | update fields in premissions
|
||||||
|
updatePermFlds :: (MonadTx m, CacheRM m)
|
||||||
|
=> QualifiedTable -> RoleName -> PermType -> RenameField -> m ()
|
||||||
|
updatePermFlds refQT rn pt rf = do
|
||||||
|
Q.AltJ pDef <- liftTx fetchPermDef
|
||||||
|
case pt of
|
||||||
|
PTInsert -> do
|
||||||
|
perm <- decodeValue pDef
|
||||||
|
updateInsPermFlds refQT rf rn perm
|
||||||
|
PTSelect -> do
|
||||||
|
perm <- decodeValue pDef
|
||||||
|
updateSelPermFlds refQT rf rn perm
|
||||||
|
PTUpdate -> do
|
||||||
|
perm <- decodeValue pDef
|
||||||
|
updateUpdPermFlds refQT rf rn perm
|
||||||
|
PTDelete -> do
|
||||||
|
perm <- decodeValue pDef
|
||||||
|
updateDelPermFlds refQT rf rn perm
|
||||||
|
where
|
||||||
|
QualifiedObject sn tn = refQT
|
||||||
|
fetchPermDef =
|
||||||
|
runIdentity . Q.getRow <$>
|
||||||
|
Q.withQE defaultTxErrorHandler [Q.sql|
|
||||||
|
SELECT perm_def::json
|
||||||
|
FROM hdb_catalog.hdb_permission
|
||||||
|
WHERE table_schema = $1
|
||||||
|
AND table_name = $2
|
||||||
|
AND role_name = $3
|
||||||
|
AND perm_type = $4
|
||||||
|
|] (sn, tn, rn, permTypeToCode pt) True
|
||||||
|
|
||||||
|
updateInsPermFlds
|
||||||
|
:: (MonadTx m, CacheRM m)
|
||||||
|
=> QualifiedTable -> RenameField -> RoleName -> InsPerm -> m ()
|
||||||
|
updateInsPermFlds qt rf rn (InsPerm chk preset cols) = do
|
||||||
|
updBoolExp <- updateBoolExp qt rf chk
|
||||||
|
liftTx $ updatePermDefInCatalog PTInsert qt rn $
|
||||||
|
InsPerm updBoolExp updPresetM updColsM
|
||||||
|
where
|
||||||
|
updPresetM = updatePreset qt rf <$> preset
|
||||||
|
updColsM = updateCols qt rf <$> cols
|
||||||
|
|
||||||
|
updateSelPermFlds
|
||||||
|
:: (MonadTx m, CacheRM m)
|
||||||
|
=> QualifiedTable -> RenameField -> RoleName -> SelPerm -> m ()
|
||||||
|
updateSelPermFlds refQT rf rn (SelPerm cols fltr limit aggAllwd) = do
|
||||||
|
updBoolExp <- updateBoolExp refQT rf fltr
|
||||||
|
liftTx $ updatePermDefInCatalog PTSelect refQT rn $
|
||||||
|
SelPerm updCols updBoolExp limit aggAllwd
|
||||||
|
where
|
||||||
|
updCols = updateCols refQT rf cols
|
||||||
|
|
||||||
|
updateUpdPermFlds
|
||||||
|
:: (MonadTx m, CacheRM m)
|
||||||
|
=> QualifiedTable -> RenameField -> RoleName -> UpdPerm -> m ()
|
||||||
|
updateUpdPermFlds refQT rf rn (UpdPerm cols preset fltr) = do
|
||||||
|
updBoolExp <- updateBoolExp refQT rf fltr
|
||||||
|
liftTx $ updatePermDefInCatalog PTUpdate refQT rn $
|
||||||
|
UpdPerm updCols updPresetM updBoolExp
|
||||||
|
where
|
||||||
|
updCols = updateCols refQT rf cols
|
||||||
|
updPresetM = updatePreset refQT rf <$> preset
|
||||||
|
|
||||||
|
updateDelPermFlds
|
||||||
|
:: (MonadTx m, CacheRM m)
|
||||||
|
=> QualifiedTable -> RenameField -> RoleName -> DelPerm -> m ()
|
||||||
|
updateDelPermFlds refQT rf rn (DelPerm fltr) = do
|
||||||
|
updBoolExp <- updateBoolExp refQT rf fltr
|
||||||
|
liftTx $ updatePermDefInCatalog PTDelete refQT rn $
|
||||||
|
DelPerm updBoolExp
|
||||||
|
|
||||||
|
updatePreset
|
||||||
|
:: QualifiedTable -> RenameField -> ColVals -> ColVals
|
||||||
|
updatePreset qt rf obj =
|
||||||
|
case rf of
|
||||||
|
RFCol (RenameItem opQT oCol nCol) ->
|
||||||
|
if qt == opQT then updatePreset' oCol nCol
|
||||||
|
else obj
|
||||||
|
_ -> obj
|
||||||
|
|
||||||
|
where
|
||||||
|
updatePreset' oCol nCol =
|
||||||
|
M.fromList updItems
|
||||||
|
where
|
||||||
|
updItems= map procObjItem $ M.toList obj
|
||||||
|
procObjItem (pgCol, v) =
|
||||||
|
let isUpdated = pgCol == oCol
|
||||||
|
updCol = bool pgCol nCol isUpdated
|
||||||
|
in (updCol, v)
|
||||||
|
|
||||||
|
updateCols
|
||||||
|
:: QualifiedTable -> RenameField -> PermColSpec -> PermColSpec
|
||||||
|
updateCols qt rf permSpec =
|
||||||
|
case rf of
|
||||||
|
RFCol (RenameItem opQT oCol nCol) ->
|
||||||
|
if qt == opQT then updateCols' oCol nCol permSpec
|
||||||
|
else permSpec
|
||||||
|
_ -> permSpec
|
||||||
|
where
|
||||||
|
updateCols' oCol nCol cols = case cols of
|
||||||
|
PCStar -> cols
|
||||||
|
PCCols c -> PCCols $ flip map c $
|
||||||
|
\col -> if col == oCol then nCol else col
|
||||||
|
|
||||||
|
updateBoolExp
|
||||||
|
:: (QErrM m, CacheRM m)
|
||||||
|
=> QualifiedTable -> RenameField -> BoolExp -> m BoolExp
|
||||||
|
updateBoolExp qt rf =
|
||||||
|
fmap BoolExp . traverse (updateColExp qt rf) . unBoolExp
|
||||||
|
|
||||||
|
updateColExp
|
||||||
|
:: (QErrM m, CacheRM m)
|
||||||
|
=> QualifiedTable -> RenameField -> ColExp-> m ColExp
|
||||||
|
updateColExp qt rf (ColExp fld val) =
|
||||||
|
ColExp updatedFld <$> updatedVal
|
||||||
|
where
|
||||||
|
updatedFld = bool fld nFld $ opQT == qt && oFld == fld
|
||||||
|
updatedVal = do
|
||||||
|
fim <- askFieldInfoMap qt
|
||||||
|
fi <- askFieldInfo fim fld
|
||||||
|
case fi of
|
||||||
|
FIColumn _ -> return val
|
||||||
|
FIRelationship ri -> do
|
||||||
|
let remTable = riRTable ri
|
||||||
|
be <- decodeValue val
|
||||||
|
ube <- updateBoolExp remTable rf be
|
||||||
|
return $ toJSON ube
|
||||||
|
|
||||||
|
(oFld, nFld, opQT) = case rf of
|
||||||
|
RFCol (RenameItem tn oCol nCol) -> (fromPGCol oCol, fromPGCol nCol, tn)
|
||||||
|
RFRel (RenameItem tn oRel nRel) -> (fromRel oRel, fromRel nRel, tn)
|
||||||
|
|
||||||
|
-- rename columns in relationship definitions
|
||||||
|
updateColInRel
|
||||||
|
:: (MonadTx m, CacheRM m)
|
||||||
|
=> QualifiedTable -> RelName -> RenameCol -> m ()
|
||||||
|
updateColInRel fromQT rn rnCol = do
|
||||||
|
fim <- askFieldInfoMap fromQT
|
||||||
|
ri <- askRelType fim rn ""
|
||||||
|
let toQT = riRTable ri
|
||||||
|
oldDefV <- liftTx $ getRelDef fromQT rn
|
||||||
|
newDefV <- case riType ri of
|
||||||
|
ObjRel -> fmap toJSON $
|
||||||
|
updateColInObjRel fromQT toQT rnCol <$> decodeValue oldDefV
|
||||||
|
ArrRel -> fmap toJSON $
|
||||||
|
updateColInArrRel fromQT toQT rnCol <$> decodeValue oldDefV
|
||||||
|
liftTx $ updateRel fromQT rn newDefV
|
||||||
|
|
||||||
|
updateColInObjRel
|
||||||
|
:: QualifiedTable -> QualifiedTable
|
||||||
|
-> RenameCol -> ObjRelUsing -> ObjRelUsing
|
||||||
|
updateColInObjRel fromQT toQT rnCol = \case
|
||||||
|
RUFKeyOn col -> RUFKeyOn $ updateColRel fromQT col rnCol
|
||||||
|
RUManual (ObjRelManualConfig manConfig) ->
|
||||||
|
RUManual $ ObjRelManualConfig $
|
||||||
|
updateRelManualConfig fromQT toQT rnCol manConfig
|
||||||
|
|
||||||
|
updateColInArrRel
|
||||||
|
:: QualifiedTable -> QualifiedTable
|
||||||
|
-> RenameCol -> ArrRelUsing -> ArrRelUsing
|
||||||
|
updateColInArrRel fromQT toQT rnCol = \case
|
||||||
|
RUFKeyOn (ArrRelUsingFKeyOn t c) ->
|
||||||
|
let updCol = updateColRel toQT c rnCol
|
||||||
|
in RUFKeyOn $ ArrRelUsingFKeyOn t updCol
|
||||||
|
RUManual (ArrRelManualConfig manConfig) ->
|
||||||
|
RUManual $ ArrRelManualConfig $
|
||||||
|
updateRelManualConfig fromQT toQT rnCol manConfig
|
||||||
|
|
||||||
|
type ColMap = Map.Map PGCol PGCol
|
||||||
|
|
||||||
|
updateColRel
|
||||||
|
:: QualifiedTable -> PGCol -> RenameCol -> PGCol
|
||||||
|
updateColRel qt col rnCol =
|
||||||
|
if opQT == qt && col == oCol then nCol else col
|
||||||
|
where
|
||||||
|
RenameItem opQT oCol nCol = rnCol
|
||||||
|
|
||||||
|
updateRelManualConfig
|
||||||
|
:: QualifiedTable -> QualifiedTable
|
||||||
|
-> RenameCol -> RelManualConfig -> RelManualConfig
|
||||||
|
updateRelManualConfig fromQT toQT rnCol manConfig =
|
||||||
|
RelManualConfig tn $ updateColMap fromQT toQT rnCol colMap
|
||||||
|
where
|
||||||
|
RelManualConfig tn colMap = manConfig
|
||||||
|
|
||||||
|
updateColMap
|
||||||
|
:: QualifiedTable -> QualifiedTable
|
||||||
|
-> RenameCol -> ColMap -> ColMap
|
||||||
|
updateColMap fromQT toQT rnCol colMap =
|
||||||
|
Map.fromList $ map (modCol fromQT *** modCol toQT) (Map.toList colMap)
|
||||||
|
where
|
||||||
|
RenameItem qt oCol nCol = rnCol
|
||||||
|
modCol colQt col = if colQt == qt && col == oCol then nCol else col
|
||||||
|
|
||||||
|
-- database functions for relationships
|
||||||
|
getRelDef :: QualifiedTable -> RelName -> Q.TxE QErr Value
|
||||||
|
getRelDef (QualifiedObject sn tn) rn =
|
||||||
|
Q.getAltJ . runIdentity . Q.getRow <$> Q.withQE defaultTxErrorHandler
|
||||||
|
[Q.sql|
|
||||||
|
SELECT rel_def::json FROM hdb_catalog.hdb_relationship
|
||||||
|
WHERE table_schema = $1 AND table_name = $2
|
||||||
|
AND rel_name = $3
|
||||||
|
|] (sn, tn, rn) True
|
||||||
|
|
||||||
|
updateRel :: QualifiedTable -> RelName -> Value -> Q.TxE QErr ()
|
||||||
|
updateRel (QualifiedObject sn tn) rn relDef =
|
||||||
|
Q.unitQE defaultTxErrorHandler [Q.sql|
|
||||||
|
UPDATE hdb_catalog.hdb_relationship
|
||||||
|
SET rel_def = $1 :: jsonb
|
||||||
|
WHERE table_schema = $2
|
||||||
|
AND table_name = $3
|
||||||
|
AND rel_name = $4
|
||||||
|
|] (Q.AltJ relDef, sn , tn, rn) True
|
||||||
|
|
@ -10,6 +10,7 @@ import Hasura.RQL.DDL.Relationship
|
|||||||
import Hasura.RQL.DDL.RemoteSchema
|
import Hasura.RQL.DDL.RemoteSchema
|
||||||
import Hasura.RQL.DDL.Schema.Diff
|
import Hasura.RQL.DDL.Schema.Diff
|
||||||
import Hasura.RQL.DDL.Schema.Function
|
import Hasura.RQL.DDL.Schema.Function
|
||||||
|
import Hasura.RQL.DDL.Schema.Rename
|
||||||
import Hasura.RQL.DDL.Subscribe
|
import Hasura.RQL.DDL.Subscribe
|
||||||
import Hasura.RQL.DDL.Utils
|
import Hasura.RQL.DDL.Utils
|
||||||
import Hasura.RQL.Types
|
import Hasura.RQL.Types
|
||||||
@ -30,7 +31,6 @@ import qualified Data.HashMap.Strict as M
|
|||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import qualified Data.Text.Encoding as TE
|
import qualified Data.Text.Encoding as TE
|
||||||
import qualified Database.PostgreSQL.LibPQ as PQ
|
import qualified Database.PostgreSQL.LibPQ as PQ
|
||||||
import qualified Language.GraphQL.Draft.Syntax as G
|
|
||||||
|
|
||||||
delTableFromCatalog :: QualifiedTable -> Q.Tx ()
|
delTableFromCatalog :: QualifiedTable -> Q.Tx ()
|
||||||
delTableFromCatalog (QualifiedObject sn tn) =
|
delTableFromCatalog (QualifiedObject sn tn) =
|
||||||
@ -82,7 +82,8 @@ trackExistingTableOrViewP2
|
|||||||
trackExistingTableOrViewP2 vn isSystemDefined = do
|
trackExistingTableOrViewP2 vn isSystemDefined = do
|
||||||
sc <- askSchemaCache
|
sc <- askSchemaCache
|
||||||
let defGCtx = scDefaultRemoteGCtx sc
|
let defGCtx = scDefaultRemoteGCtx sc
|
||||||
GS.checkConflictingNode defGCtx (G.Name tn)
|
tn = GS.qualObjectToName vn
|
||||||
|
GS.checkConflictingNode defGCtx tn
|
||||||
|
|
||||||
trackExistingTableOrViewP2Setup vn isSystemDefined
|
trackExistingTableOrViewP2Setup vn isSystemDefined
|
||||||
liftTx $ Q.catchE defaultTxErrorHandler $
|
liftTx $ Q.catchE defaultTxErrorHandler $
|
||||||
@ -92,12 +93,6 @@ trackExistingTableOrViewP2 vn isSystemDefined = do
|
|||||||
refreshGCtxMapInSchema
|
refreshGCtxMapInSchema
|
||||||
|
|
||||||
return successMsg
|
return successMsg
|
||||||
where
|
|
||||||
getSchemaN = getSchemaTxt . qSchema
|
|
||||||
getTableN = getTableTxt . qName
|
|
||||||
tn = case getSchemaN vn of
|
|
||||||
"public" -> getTableN vn
|
|
||||||
_ -> getSchemaN vn <> "_" <> getTableN vn
|
|
||||||
|
|
||||||
runTrackTableQ
|
runTrackTableQ
|
||||||
:: ( QErrM m, CacheRWM m, MonadTx m
|
:: ( QErrM m, CacheRWM m, MonadTx m
|
||||||
@ -134,53 +129,69 @@ purgeDep schemaObjId = case schemaObjId of
|
|||||||
_ -> throw500 $
|
_ -> throw500 $
|
||||||
"unexpected dependent object : " <> reportSchemaObj schemaObjId
|
"unexpected dependent object : " <> reportSchemaObj schemaObjId
|
||||||
|
|
||||||
processTableChanges
|
processTableChanges :: (MonadTx m, CacheRWM m)
|
||||||
:: (QErrM m, CacheRWM m) => TableInfo -> TableDiff -> m ()
|
=> TableInfo -> TableDiff -> m Bool
|
||||||
processTableChanges ti tableDiff = do
|
processTableChanges ti tableDiff = do
|
||||||
|
-- If table rename occurs then don't replace constraints and
|
||||||
when (isJust mNewName) $
|
-- process dropped/added columns, because schema reload happens eventually
|
||||||
throw400 NotSupported $ "table renames are not yet supported : " <>> tn
|
|
||||||
|
|
||||||
-- replace constraints
|
|
||||||
replaceConstraints
|
|
||||||
|
|
||||||
-- for all the dropped columns
|
|
||||||
forM_ droppedCols $ \droppedCol ->
|
|
||||||
-- Drop the column from the cache
|
|
||||||
delColFromCache droppedCol tn
|
|
||||||
|
|
||||||
-- In the newly added columns check that there is no conflict with relationships
|
|
||||||
forM_ addedCols $ \colInfo@(PGColInfo colName _ _) ->
|
|
||||||
case M.lookup (fromPGCol colName) $ tiFieldInfoMap ti of
|
|
||||||
Just (FIRelationship _) ->
|
|
||||||
throw400 AlreadyExists $ "cannot add column " <> colName
|
|
||||||
<<> " in table " <> tn <<>
|
|
||||||
" as a relationship with the name already exists"
|
|
||||||
_ -> addColToCache colName colInfo tn
|
|
||||||
|
|
||||||
sc <- askSchemaCache
|
sc <- askSchemaCache
|
||||||
-- for rest of the columns
|
let tn = tiName ti
|
||||||
forM_ alteredCols $ \(PGColInfo oColName oColTy _, nci@(PGColInfo nColName nColTy _)) ->
|
withOldTabName = do
|
||||||
if | oColName /= nColName ->
|
-- replace constraints
|
||||||
throw400 NotSupported $ "column renames are not yet supported : " <>
|
replaceConstraints tn
|
||||||
tn <<> "." <>> oColName
|
-- for all the dropped columns
|
||||||
| oColTy /= nColTy -> do
|
procDroppedCols tn
|
||||||
let colId = SOTableObj tn $ TOCol oColName
|
-- for all added columns
|
||||||
depObjs = getDependentObjsWith (== "on_type") sc colId
|
procAddedCols tn
|
||||||
if null depObjs
|
-- for all altered columns
|
||||||
then updateFldInCache oColName nci
|
procAlteredCols sc tn
|
||||||
else throw400 DependencyError $ "cannot change type of column " <> oColName <<> " in table "
|
|
||||||
<> tn <<> " because of the following dependencies : " <>
|
withNewTabName newTN = do
|
||||||
reportSchemaObjs depObjs
|
let tnGQL = GS.qualObjectToName newTN
|
||||||
| otherwise -> return ()
|
defGCtx = scDefaultRemoteGCtx sc
|
||||||
|
-- check for GraphQL schema conflicts on new name
|
||||||
|
GS.checkConflictingNode defGCtx tnGQL
|
||||||
|
void $ procAlteredCols sc tn
|
||||||
|
-- update new table in catalog
|
||||||
|
renameTableInCatalog newTN tn
|
||||||
|
return True
|
||||||
|
|
||||||
|
maybe withOldTabName withNewTabName mNewName
|
||||||
|
|
||||||
where
|
where
|
||||||
updateFldInCache cn ci = do
|
|
||||||
delColFromCache cn tn
|
|
||||||
addColToCache cn ci tn
|
|
||||||
replaceConstraints = flip modTableInCache tn $ \tInfo ->
|
|
||||||
return $ tInfo {tiUniqOrPrimConstraints = constraints}
|
|
||||||
tn = tiName ti
|
|
||||||
TableDiff mNewName droppedCols addedCols alteredCols _ constraints = tableDiff
|
TableDiff mNewName droppedCols addedCols alteredCols _ constraints = tableDiff
|
||||||
|
replaceConstraints tn = flip modTableInCache tn $ \tInfo ->
|
||||||
|
return $ tInfo {tiUniqOrPrimConstraints = constraints}
|
||||||
|
|
||||||
|
procDroppedCols tn =
|
||||||
|
forM_ droppedCols $ \droppedCol ->
|
||||||
|
-- Drop the column from the cache
|
||||||
|
delColFromCache droppedCol tn
|
||||||
|
|
||||||
|
procAddedCols tn =
|
||||||
|
-- In the newly added columns check that there is no conflict with relationships
|
||||||
|
forM_ addedCols $ \pci@(PGColInfo colName _ _) ->
|
||||||
|
case M.lookup (fromPGCol colName) $ tiFieldInfoMap ti of
|
||||||
|
Just (FIRelationship _) ->
|
||||||
|
throw400 AlreadyExists $ "cannot add column " <> colName
|
||||||
|
<<> " in table " <> tn <<>
|
||||||
|
" as a relationship with the name already exists"
|
||||||
|
_ -> addColToCache colName pci tn
|
||||||
|
|
||||||
|
procAlteredCols sc tn = fmap or $
|
||||||
|
forM alteredCols $ \(PGColInfo oColName oColTy _, PGColInfo nColName nColTy _) ->
|
||||||
|
if | oColName /= nColName -> do
|
||||||
|
renameColInCatalog oColName nColName tn ti
|
||||||
|
return True
|
||||||
|
| oColTy /= nColTy -> do
|
||||||
|
let colId = SOTableObj tn $ TOCol oColName
|
||||||
|
depObjs = getDependentObjsWith (== "on_type") sc colId
|
||||||
|
unless (null depObjs) $ throw400 DependencyError $
|
||||||
|
"cannot change type of column " <> oColName <<> " in table "
|
||||||
|
<> tn <<> " because of the following dependencies : " <>
|
||||||
|
reportSchemaObjs depObjs
|
||||||
|
return False
|
||||||
|
| otherwise -> return False
|
||||||
|
|
||||||
delTableAndDirectDeps
|
delTableAndDirectDeps
|
||||||
:: (QErrM m, CacheRWM m, MonadTx m) => QualifiedTable -> m ()
|
:: (QErrM m, CacheRWM m, MonadTx m) => QualifiedTable -> m ()
|
||||||
@ -201,14 +212,13 @@ delTableAndDirectDeps qtn@(QualifiedObject sn tn) = do
|
|||||||
delTableFromCatalog qtn
|
delTableFromCatalog qtn
|
||||||
delTableFromCache qtn
|
delTableFromCache qtn
|
||||||
|
|
||||||
processSchemaChanges
|
processSchemaChanges :: (MonadTx m, CacheRWM m) => SchemaDiff -> m Bool
|
||||||
:: (QErrM m, CacheRWM m, MonadTx m) => SchemaDiff -> m ()
|
|
||||||
processSchemaChanges schemaDiff = do
|
processSchemaChanges schemaDiff = do
|
||||||
-- Purge the dropped tables
|
-- Purge the dropped tables
|
||||||
mapM_ delTableAndDirectDeps droppedTables
|
mapM_ delTableAndDirectDeps droppedTables
|
||||||
-- Get schema cache
|
|
||||||
sc <- askSchemaCache
|
sc <- askSchemaCache
|
||||||
forM_ alteredTables $ \(oldQtn, tableDiff) -> do
|
fmap or $ forM alteredTables $ \(oldQtn, tableDiff) -> do
|
||||||
ti <- case M.lookup oldQtn $ scTables sc of
|
ti <- case M.lookup oldQtn $ scTables sc of
|
||||||
Just ti -> return ti
|
Just ti -> return ti
|
||||||
Nothing -> throw500 $ "old table metadata not found in cache : " <>> oldQtn
|
Nothing -> throw500 $ "old table metadata not found in cache : " <>> oldQtn
|
||||||
@ -473,24 +483,28 @@ execWithMDCheck (RunSQL t cascade _) = do
|
|||||||
throw400 NotSupported $
|
throw400 NotSupported $
|
||||||
"type of function " <> qf <<> " is altered to \"VOLATILE\" which is not supported now"
|
"type of function " <> qf <<> " is altered to \"VOLATILE\" which is not supported now"
|
||||||
|
|
||||||
-- update the schema cache with the changes
|
-- update the schema cache and hdb_catalog with the changes
|
||||||
processSchemaChanges schemaDiff
|
reloadRequired <- processSchemaChanges schemaDiff
|
||||||
|
|
||||||
postSc <- askSchemaCache
|
let withReload = buildSchemaCache
|
||||||
-- recreate the insert permission infra
|
withoutReload = do
|
||||||
forM_ (M.elems $ scTables postSc) $ \ti -> do
|
postSc <- askSchemaCache
|
||||||
let tn = tiName ti
|
-- recreate the insert permission infra
|
||||||
forM_ (M.elems $ tiRolePermInfoMap ti) $ \rpi ->
|
forM_ (M.elems $ scTables postSc) $ \ti -> do
|
||||||
maybe (return ()) (liftTx . buildInsInfra tn) $ _permIns rpi
|
let tn = tiName ti
|
||||||
|
forM_ (M.elems $ tiRolePermInfoMap ti) $ \rpi ->
|
||||||
|
maybe (return ()) (liftTx . buildInsInfra tn) $ _permIns rpi
|
||||||
|
|
||||||
--recreate triggers
|
--recreate triggers
|
||||||
forM_ (M.elems $ scTables postSc) $ \ti -> do
|
forM_ (M.elems $ scTables postSc) $ \ti -> do
|
||||||
let tn = tiName ti
|
let tn = tiName ti
|
||||||
cols = getCols $ tiFieldInfoMap ti
|
cols = getCols $ tiFieldInfoMap ti
|
||||||
forM_ (M.toList $ tiEventTriggerInfoMap ti) $ \(trn, eti) -> do
|
forM_ (M.toList $ tiEventTriggerInfoMap ti) $ \(trn, eti) -> do
|
||||||
let opsDef = etiOpsDef eti
|
let opsDef = etiOpsDef eti
|
||||||
trid = etiId eti
|
trid = etiId eti
|
||||||
liftTx $ mkTriggerQ trid trn tn cols opsDef
|
liftTx $ mkTriggerQ trid trn tn cols opsDef
|
||||||
|
|
||||||
|
bool withoutReload withReload reloadRequired
|
||||||
|
|
||||||
-- refresh the gCtxMap in schema cache
|
-- refresh the gCtxMap in schema cache
|
||||||
refreshGCtxMapInSchema
|
refreshGCtxMapInSchema
|
||||||
|
@ -772,7 +772,6 @@ getDependentObjsWith f sc objId =
|
|||||||
where
|
where
|
||||||
isDependency deps = not $ HS.null $ flip HS.filter deps $
|
isDependency deps = not $ HS.null $ flip HS.filter deps $
|
||||||
\(SchemaDependency depId reason) -> objId `induces` depId && f reason
|
\(SchemaDependency depId reason) -> objId `induces` depId && f reason
|
||||||
|
|
||||||
-- induces a b : is b dependent on a
|
-- induces a b : is b dependent on a
|
||||||
induces (SOTable tn1) (SOTable tn2) = tn1 == tn2
|
induces (SOTable tn1) (SOTable tn2) = tn1 == tn2
|
||||||
induces (SOTable tn1) (SOTableObj tn2 _) = tn1 == tn2
|
induces (SOTable tn1) (SOTableObj tn2 _) = tn1 == tn2
|
||||||
|
@ -3,18 +3,19 @@ module Hasura.Server.Query where
|
|||||||
import Data.Aeson
|
import Data.Aeson
|
||||||
import Data.Aeson.Casing
|
import Data.Aeson.Casing
|
||||||
import Data.Aeson.TH
|
import Data.Aeson.TH
|
||||||
import Language.Haskell.TH.Syntax (Lift)
|
import Language.Haskell.TH.Syntax (Lift)
|
||||||
|
|
||||||
import qualified Data.ByteString.Builder as BB
|
import qualified Data.ByteString.Builder as BB
|
||||||
import qualified Data.ByteString.Lazy as BL
|
import qualified Data.ByteString.Lazy as BL
|
||||||
import qualified Data.Vector as V
|
import qualified Data.Vector as V
|
||||||
import qualified Network.HTTP.Client as HTTP
|
import qualified Network.HTTP.Client as HTTP
|
||||||
|
|
||||||
import Hasura.Prelude
|
import Hasura.Prelude
|
||||||
import Hasura.RQL.DDL.Metadata
|
import Hasura.RQL.DDL.Metadata
|
||||||
import Hasura.RQL.DDL.Permission
|
import Hasura.RQL.DDL.Permission
|
||||||
import Hasura.RQL.DDL.QueryTemplate
|
import Hasura.RQL.DDL.QueryTemplate
|
||||||
import Hasura.RQL.DDL.Relationship
|
import Hasura.RQL.DDL.Relationship
|
||||||
|
import Hasura.RQL.DDL.Relationship.Rename
|
||||||
import Hasura.RQL.DDL.RemoteSchema
|
import Hasura.RQL.DDL.RemoteSchema
|
||||||
import Hasura.RQL.DDL.Schema.Function
|
import Hasura.RQL.DDL.Schema.Function
|
||||||
import Hasura.RQL.DDL.Schema.Table
|
import Hasura.RQL.DDL.Schema.Table
|
||||||
@ -23,12 +24,12 @@ import Hasura.RQL.DML.Count
|
|||||||
import Hasura.RQL.DML.Delete
|
import Hasura.RQL.DML.Delete
|
||||||
import Hasura.RQL.DML.Insert
|
import Hasura.RQL.DML.Insert
|
||||||
import Hasura.RQL.DML.QueryTemplate
|
import Hasura.RQL.DML.QueryTemplate
|
||||||
import Hasura.RQL.DML.Returning (encodeJSONVector)
|
import Hasura.RQL.DML.Returning (encodeJSONVector)
|
||||||
import Hasura.RQL.DML.Select
|
import Hasura.RQL.DML.Select
|
||||||
import Hasura.RQL.DML.Update
|
import Hasura.RQL.DML.Update
|
||||||
import Hasura.RQL.Types
|
import Hasura.RQL.Types
|
||||||
|
|
||||||
import qualified Database.PG.Query as Q
|
import qualified Database.PG.Query as Q
|
||||||
|
|
||||||
data RQLQuery
|
data RQLQuery
|
||||||
= RQAddExistingTableOrView !TrackTable
|
= RQAddExistingTableOrView !TrackTable
|
||||||
@ -42,6 +43,7 @@ data RQLQuery
|
|||||||
| RQCreateArrayRelationship !CreateArrRel
|
| RQCreateArrayRelationship !CreateArrRel
|
||||||
| RQDropRelationship !DropRel
|
| RQDropRelationship !DropRel
|
||||||
| RQSetRelationshipComment !SetRelComment
|
| RQSetRelationshipComment !SetRelComment
|
||||||
|
| RQRenameRelationship !RenameRel
|
||||||
|
|
||||||
| RQCreateInsertPermission !CreateInsPerm
|
| RQCreateInsertPermission !CreateInsPerm
|
||||||
| RQCreateSelectPermission !CreateSelPerm
|
| RQCreateSelectPermission !CreateSelPerm
|
||||||
@ -142,6 +144,7 @@ queryNeedsReload qi = case qi of
|
|||||||
RQCreateArrayRelationship _ -> True
|
RQCreateArrayRelationship _ -> True
|
||||||
RQDropRelationship _ -> True
|
RQDropRelationship _ -> True
|
||||||
RQSetRelationshipComment _ -> False
|
RQSetRelationshipComment _ -> False
|
||||||
|
RQRenameRelationship _ -> True
|
||||||
|
|
||||||
RQCreateInsertPermission _ -> True
|
RQCreateInsertPermission _ -> True
|
||||||
RQCreateSelectPermission _ -> True
|
RQCreateSelectPermission _ -> True
|
||||||
@ -201,6 +204,7 @@ runQueryM rq = withPathK "args" $ case rq of
|
|||||||
RQCreateArrayRelationship q -> runCreateArrRel q
|
RQCreateArrayRelationship q -> runCreateArrRel q
|
||||||
RQDropRelationship q -> runDropRel q
|
RQDropRelationship q -> runDropRel q
|
||||||
RQSetRelationshipComment q -> runSetRelComment q
|
RQSetRelationshipComment q -> runSetRelComment q
|
||||||
|
RQRenameRelationship q -> runRenameRel q
|
||||||
|
|
||||||
RQCreateInsertPermission q -> runCreatePerm q
|
RQCreateInsertPermission q -> runCreatePerm q
|
||||||
RQCreateSelectPermission q -> runCreatePerm q
|
RQCreateSelectPermission q -> runCreatePerm q
|
||||||
|
@ -45,7 +45,7 @@ CREATE TABLE hdb_catalog.hdb_relationship
|
|||||||
is_system_defined boolean default false,
|
is_system_defined boolean default false,
|
||||||
|
|
||||||
PRIMARY KEY (table_schema, table_name, rel_name),
|
PRIMARY KEY (table_schema, table_name, rel_name),
|
||||||
FOREIGN KEY (table_schema, table_name) REFERENCES hdb_catalog.hdb_table(table_schema, table_name)
|
FOREIGN KEY (table_schema, table_name) REFERENCES hdb_catalog.hdb_table(table_schema, table_name) ON UPDATE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE hdb_catalog.hdb_permission
|
CREATE TABLE hdb_catalog.hdb_permission
|
||||||
@ -59,7 +59,7 @@ CREATE TABLE hdb_catalog.hdb_permission
|
|||||||
is_system_defined boolean default false,
|
is_system_defined boolean default false,
|
||||||
|
|
||||||
PRIMARY KEY (table_schema, table_name, role_name, perm_type),
|
PRIMARY KEY (table_schema, table_name, role_name, perm_type),
|
||||||
FOREIGN KEY (table_schema, table_name) REFERENCES hdb_catalog.hdb_table(table_schema, table_name)
|
FOREIGN KEY (table_schema, table_name) REFERENCES hdb_catalog.hdb_table(table_schema, table_name) ON UPDATE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE VIEW hdb_catalog.hdb_permission_agg AS
|
CREATE VIEW hdb_catalog.hdb_permission_agg AS
|
||||||
|
8
server/src-rsr/migrate_from_9_to_10.sql
Normal file
8
server/src-rsr/migrate_from_9_to_10.sql
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
ALTER TABLE hdb_catalog.hdb_relationship
|
||||||
|
DROP CONSTRAINT hdb_relationship_table_schema_fkey,
|
||||||
|
ADD CONSTRAINT hdb_relationship_table_schema_fkey FOREIGN KEY (table_schema, table_name) REFERENCES hdb_catalog.hdb_table(table_schema, table_name) ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
|
||||||
|
ALTER TABLE hdb_catalog.hdb_permission
|
||||||
|
DROP CONSTRAINT hdb_permission_table_schema_fkey,
|
||||||
|
ADD CONSTRAINT hdb_permission_table_schema_fkey FOREIGN KEY (table_schema, table_name) REFERENCES hdb_catalog.hdb_table(table_schema, table_name) ON UPDATE CASCADE;
|
@ -50,9 +50,13 @@ querySpecFiles =
|
|||||||
, "upsert_role_user_error.yaml"
|
, "upsert_role_user_error.yaml"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
gqlIntrospection :: FilePath
|
||||||
|
gqlIntrospection = "introspection.yaml"
|
||||||
|
|
||||||
gqlSpecFiles :: [FilePath]
|
gqlSpecFiles :: [FilePath]
|
||||||
gqlSpecFiles =
|
gqlSpecFiles =
|
||||||
[ "introspection.yaml"
|
[ "insert_mutation/author.yaml"
|
||||||
|
, "introspection.yaml"
|
||||||
, "introspection_user_role.yaml"
|
, "introspection_user_role.yaml"
|
||||||
, "insert_mutation/author.yaml"
|
, "insert_mutation/author.yaml"
|
||||||
, "insert_mutation/author_articles_nested.yaml"
|
, "insert_mutation/author_articles_nested.yaml"
|
||||||
@ -98,6 +102,9 @@ gqlSpecFiles =
|
|||||||
, "delete_mutation/author_foreign_key_violation.yaml"
|
, "delete_mutation/author_foreign_key_violation.yaml"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
alterTable :: FilePath
|
||||||
|
alterTable = "alter_table.yaml"
|
||||||
|
|
||||||
readTestCase :: FilePath -> IO TestCase
|
readTestCase :: FilePath -> IO TestCase
|
||||||
readTestCase fpath = do
|
readTestCase fpath = do
|
||||||
res <- Y.decodeFileEither ("test/testcases/" ++ fpath)
|
res <- Y.decodeFileEither ("test/testcases/" ++ fpath)
|
||||||
@ -129,6 +136,8 @@ mkSpecs :: IO (SpecWith Application)
|
|||||||
mkSpecs = do
|
mkSpecs = do
|
||||||
ddlTc <- mapM readTestCase querySpecFiles
|
ddlTc <- mapM readTestCase querySpecFiles
|
||||||
gqlTc <- mapM readTestCase gqlSpecFiles
|
gqlTc <- mapM readTestCase gqlSpecFiles
|
||||||
|
gqlIntrospectionTc <- readTestCase gqlIntrospection
|
||||||
|
alterTabTc <- readTestCase alterTable
|
||||||
return $ do
|
return $ do
|
||||||
describe "version API" $
|
describe "version API" $
|
||||||
it "responds with version" $
|
it "responds with version" $
|
||||||
@ -146,4 +155,11 @@ mkSpecs = do
|
|||||||
|
|
||||||
describe "Query API" $ mapM_ mkSpec ddlTc
|
describe "Query API" $ mapM_ mkSpec ddlTc
|
||||||
|
|
||||||
|
describe "GraphQL Introspection" $ mkSpec gqlIntrospectionTc
|
||||||
|
|
||||||
describe "GraphQL API" $ mapM_ mkSpec gqlTc
|
describe "GraphQL API" $ mapM_ mkSpec gqlTc
|
||||||
|
|
||||||
|
describe "Alter Table" $ mkSpec alterTabTc
|
||||||
|
|
||||||
|
describe "GraphQL Introspection after altering a table"
|
||||||
|
$ mkSpec gqlIntrospectionTc
|
||||||
|
15
server/test/testcases/alter_table.yaml
Normal file
15
server/test/testcases/alter_table.yaml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
description: Runs a bulk sql query to alter a table
|
||||||
|
url: /v1/query
|
||||||
|
status: 200
|
||||||
|
query:
|
||||||
|
type: bulk
|
||||||
|
args:
|
||||||
|
- type: run_sql
|
||||||
|
args:
|
||||||
|
sql: "ALTER TABLE dollar$test RENAME TO dollar_test"
|
||||||
|
- type: run_sql
|
||||||
|
args:
|
||||||
|
sql: "ALTER TABLE dollar_test RENAME name TO name_altered"
|
||||||
|
- type: run_sql
|
||||||
|
args:
|
||||||
|
sql: "ALTER TABLE dollar_test RENAME CONSTRAINT dollar$test_pkey TO dollar_test_pkey"
|
@ -16,6 +16,20 @@
|
|||||||
column_mapping:
|
column_mapping:
|
||||||
id: author_id
|
id: author_id
|
||||||
|
|
||||||
|
#Rename relationship
|
||||||
|
|
||||||
|
- description: Rename relationship articles to articles_array
|
||||||
|
url: /v1/query
|
||||||
|
status: 200
|
||||||
|
response:
|
||||||
|
message: success
|
||||||
|
query:
|
||||||
|
type: rename_relationship
|
||||||
|
args:
|
||||||
|
table: author_view
|
||||||
|
name: articles
|
||||||
|
new_name: articles_array
|
||||||
|
|
||||||
#Drop relationship
|
#Drop relationship
|
||||||
- description: Drop object relationship
|
- description: Drop object relationship
|
||||||
url: /v1/query
|
url: /v1/query
|
||||||
@ -26,4 +40,4 @@
|
|||||||
type: drop_relationship
|
type: drop_relationship
|
||||||
args:
|
args:
|
||||||
table: author_view
|
table: author_view
|
||||||
relationship: articles
|
relationship: articles_array
|
||||||
|
@ -47,6 +47,55 @@
|
|||||||
- id
|
- id
|
||||||
- name
|
- name
|
||||||
|
|
||||||
|
#Rename object relationship
|
||||||
|
- description: Rename object relationship author to author_obj
|
||||||
|
url: /v1/query
|
||||||
|
status: 200
|
||||||
|
response:
|
||||||
|
message: success
|
||||||
|
query:
|
||||||
|
type: rename_relationship
|
||||||
|
args:
|
||||||
|
table: article
|
||||||
|
name: author
|
||||||
|
new_name: author_obj
|
||||||
|
|
||||||
|
#Select Query
|
||||||
|
|
||||||
|
- description: Nested select on article with renamed object relation
|
||||||
|
url: /v1/query
|
||||||
|
status: 200
|
||||||
|
response:
|
||||||
|
- id: 1
|
||||||
|
title: Article 1
|
||||||
|
content: Sample article content 1
|
||||||
|
author_obj:
|
||||||
|
id: 1
|
||||||
|
name: Author 1
|
||||||
|
- id: 2
|
||||||
|
title: Article 2
|
||||||
|
content: Sample article content 2
|
||||||
|
author_obj:
|
||||||
|
id: 1
|
||||||
|
name: Author 1
|
||||||
|
- id: 3
|
||||||
|
title: Article 3
|
||||||
|
content: Sample article content 3
|
||||||
|
author_obj:
|
||||||
|
id: 2
|
||||||
|
name: Author 2
|
||||||
|
query:
|
||||||
|
type: select
|
||||||
|
args:
|
||||||
|
table: article
|
||||||
|
columns:
|
||||||
|
- id
|
||||||
|
- title
|
||||||
|
- content
|
||||||
|
- name: author_obj
|
||||||
|
columns:
|
||||||
|
- id
|
||||||
|
- name
|
||||||
|
|
||||||
#Drop object relationship
|
#Drop object relationship
|
||||||
- description: Drop object relationship
|
- description: Drop object relationship
|
||||||
@ -60,4 +109,4 @@
|
|||||||
type: drop_relationship
|
type: drop_relationship
|
||||||
args:
|
args:
|
||||||
table: article
|
table: article
|
||||||
relationship: author
|
relationship: author_obj
|
||||||
|
@ -50,6 +50,56 @@
|
|||||||
- id
|
- id
|
||||||
- name
|
- name
|
||||||
|
|
||||||
|
#Rename object relationship
|
||||||
|
- description: Rename object relationship author to author_obj
|
||||||
|
url: /v1/query
|
||||||
|
status: 200
|
||||||
|
response:
|
||||||
|
message: success
|
||||||
|
query:
|
||||||
|
type: rename_relationship
|
||||||
|
args:
|
||||||
|
table: article_view
|
||||||
|
name: author
|
||||||
|
new_name: author_obj
|
||||||
|
|
||||||
|
#Select Query
|
||||||
|
|
||||||
|
- description: Nested select on article with renamed object relation
|
||||||
|
url: /v1/query
|
||||||
|
status: 200
|
||||||
|
response:
|
||||||
|
- id: 1
|
||||||
|
title: Article 1
|
||||||
|
content: Sample article content 1
|
||||||
|
author_obj:
|
||||||
|
id: 1
|
||||||
|
name: AUTHOR 1
|
||||||
|
- id: 2
|
||||||
|
title: Article 2
|
||||||
|
content: Sample article content 2
|
||||||
|
author_obj:
|
||||||
|
id: 1
|
||||||
|
name: AUTHOR 1
|
||||||
|
- id: 3
|
||||||
|
title: Article 3
|
||||||
|
content: Sample article content 3
|
||||||
|
author_obj:
|
||||||
|
id: 2
|
||||||
|
name: AUTHOR 2
|
||||||
|
query:
|
||||||
|
type: select
|
||||||
|
args:
|
||||||
|
table: article_view
|
||||||
|
columns:
|
||||||
|
- id
|
||||||
|
- title
|
||||||
|
- content
|
||||||
|
- name: author_obj
|
||||||
|
columns:
|
||||||
|
- id
|
||||||
|
- name
|
||||||
|
|
||||||
#Drop object relationship
|
#Drop object relationship
|
||||||
- description: Drop object relationship
|
- description: Drop object relationship
|
||||||
url: /v1/query
|
url: /v1/query
|
||||||
@ -62,4 +112,4 @@
|
|||||||
type: drop_relationship
|
type: drop_relationship
|
||||||
args:
|
args:
|
||||||
table: article_view
|
table: article_view
|
||||||
relationship: author
|
relationship: author_obj
|
||||||
|
@ -4,39 +4,12 @@ args:
|
|||||||
- type: run_sql
|
- type: run_sql
|
||||||
args:
|
args:
|
||||||
sql: |
|
sql: |
|
||||||
drop view article_view
|
DROP VIEW article_view;
|
||||||
|
DROP TABLE article;
|
||||||
- type: run_sql
|
DROP VIEW author_view;
|
||||||
args:
|
DROP TABLE author;
|
||||||
sql: |
|
DROP VIEW hge_tests.address_view;
|
||||||
drop table article
|
DROP TABLE hge_tests.address;
|
||||||
|
DROP VIEW hge_tests.resident_view;
|
||||||
- type: run_sql
|
DROP TABLE hge_tests.resident;
|
||||||
args:
|
cascade: true
|
||||||
sql: |
|
|
||||||
drop view author_view
|
|
||||||
|
|
||||||
- type: run_sql
|
|
||||||
args:
|
|
||||||
sql: |
|
|
||||||
drop table author
|
|
||||||
|
|
||||||
- type: run_sql
|
|
||||||
args:
|
|
||||||
sql: |
|
|
||||||
drop view hge_tests.address_view
|
|
||||||
|
|
||||||
- type: run_sql
|
|
||||||
args:
|
|
||||||
sql: |
|
|
||||||
drop table hge_tests.address
|
|
||||||
|
|
||||||
- type: run_sql
|
|
||||||
args:
|
|
||||||
sql: |
|
|
||||||
drop view hge_tests.resident_view
|
|
||||||
|
|
||||||
- type: run_sql
|
|
||||||
args:
|
|
||||||
sql: |
|
|
||||||
drop table hge_tests.resident
|
|
||||||
|
@ -9,15 +9,65 @@ args:
|
|||||||
id serial primary key,
|
id serial primary key,
|
||||||
name text unique
|
name text unique
|
||||||
);
|
);
|
||||||
|
insert into author (name) values ('Author 1'), ('Author 2');
|
||||||
|
create table article(
|
||||||
|
id serial primary key,
|
||||||
|
title text not null,
|
||||||
|
content text not null,
|
||||||
|
author_id integer not null references author(id)
|
||||||
|
);
|
||||||
|
insert into article (title, content, author_id) values
|
||||||
|
('article 1 by author 1', 'content for article 1', 1),
|
||||||
|
('article 2 by author 1', 'content for article 2', 1),
|
||||||
|
('article 1 by author 2', 'content for article 3', 2);
|
||||||
|
|
||||||
- type: track_table
|
- type: track_table
|
||||||
args:
|
args:
|
||||||
schema: public
|
schema: public
|
||||||
name: author
|
name: author
|
||||||
|
- type: track_table
|
||||||
|
args:
|
||||||
|
schema: public
|
||||||
|
name: article
|
||||||
|
|
||||||
#Insert Author table data
|
#Object relationship
|
||||||
- type: insert
|
- type: create_object_relationship
|
||||||
|
args:
|
||||||
|
table: article
|
||||||
|
name: author
|
||||||
|
using:
|
||||||
|
foreign_key_constraint_on: author_id
|
||||||
|
|
||||||
|
#Array relationship
|
||||||
|
- type: create_array_relationship
|
||||||
args:
|
args:
|
||||||
table: author
|
table: author
|
||||||
objects:
|
name: articles
|
||||||
- name: Author 1
|
using:
|
||||||
- name: Author 2
|
foreign_key_constraint_on:
|
||||||
|
table: article
|
||||||
|
column: author_id
|
||||||
|
|
||||||
|
#Article select permission for user
|
||||||
|
- type: create_select_permission
|
||||||
|
args:
|
||||||
|
table: article
|
||||||
|
role: user
|
||||||
|
permission:
|
||||||
|
columns:
|
||||||
|
- id
|
||||||
|
- title
|
||||||
|
- content
|
||||||
|
- author_id
|
||||||
|
filter:
|
||||||
|
author:
|
||||||
|
id: X-Hasura-User-Id
|
||||||
|
|
||||||
|
#Article insert permission for user
|
||||||
|
- type: create_insert_permission
|
||||||
|
args:
|
||||||
|
table: article
|
||||||
|
role: user
|
||||||
|
permission:
|
||||||
|
check:
|
||||||
|
author_id: X-Hasura-User-Id
|
||||||
|
70
server/tests-py/queries/v1/run_sql/sql_rename_columns.yaml
Normal file
70
server/tests-py/queries/v1/run_sql/sql_rename_columns.yaml
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
#Rename columns
|
||||||
|
- url: /v1/query
|
||||||
|
status: 200
|
||||||
|
query:
|
||||||
|
type: run_sql
|
||||||
|
args:
|
||||||
|
sql: |
|
||||||
|
alter table author rename column id to author_id;
|
||||||
|
|
||||||
|
#Select Queries
|
||||||
|
- url: /v1/query
|
||||||
|
status: 200
|
||||||
|
response:
|
||||||
|
- author_id: 1
|
||||||
|
name: Author 1
|
||||||
|
- author_id: 2
|
||||||
|
name: Author 2
|
||||||
|
query:
|
||||||
|
type: select
|
||||||
|
args:
|
||||||
|
table: author
|
||||||
|
columns:
|
||||||
|
- author_id
|
||||||
|
- name
|
||||||
|
|
||||||
|
- url: /v1/query
|
||||||
|
status: 200
|
||||||
|
headers:
|
||||||
|
X-Hasura-Role: user
|
||||||
|
X-Hasura-User-Id: '1'
|
||||||
|
response:
|
||||||
|
- id: 1
|
||||||
|
title: article 1 by author 1
|
||||||
|
author_id: 1
|
||||||
|
- id: 2
|
||||||
|
title: article 2 by author 1
|
||||||
|
author_id: 1
|
||||||
|
query:
|
||||||
|
type: select
|
||||||
|
args:
|
||||||
|
table: article
|
||||||
|
columns:
|
||||||
|
- id
|
||||||
|
- title
|
||||||
|
- author_id
|
||||||
|
|
||||||
|
#Revert changes
|
||||||
|
- url: /v1/query
|
||||||
|
status: 200
|
||||||
|
query:
|
||||||
|
type: run_sql
|
||||||
|
args:
|
||||||
|
sql: |
|
||||||
|
alter table author rename column author_id to id;
|
||||||
|
|
||||||
|
#Queries post revert
|
||||||
|
- url: /v1/query
|
||||||
|
status: 200
|
||||||
|
response:
|
||||||
|
- id: 1
|
||||||
|
name: Author 1
|
||||||
|
- id: 2
|
||||||
|
name: Author 2
|
||||||
|
query:
|
||||||
|
type: select
|
||||||
|
args:
|
||||||
|
table: author
|
||||||
|
columns:
|
||||||
|
- id
|
||||||
|
- name
|
55
server/tests-py/queries/v1/run_sql/sql_rename_table.yaml
Normal file
55
server/tests-py/queries/v1/run_sql/sql_rename_table.yaml
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
#Rename article table
|
||||||
|
- url: /v1/query
|
||||||
|
status: 200
|
||||||
|
query:
|
||||||
|
type: run_sql
|
||||||
|
args:
|
||||||
|
sql: |
|
||||||
|
alter table article rename to articles;
|
||||||
|
|
||||||
|
#Perform select
|
||||||
|
- url: /v1/query
|
||||||
|
status: 200
|
||||||
|
response:
|
||||||
|
- id: 1
|
||||||
|
title: article 1 by author 1
|
||||||
|
content: content for article 1
|
||||||
|
- id: 2
|
||||||
|
title: article 2 by author 1
|
||||||
|
content: content for article 2
|
||||||
|
- id: 3
|
||||||
|
title: article 1 by author 2
|
||||||
|
content: content for article 3
|
||||||
|
query:
|
||||||
|
type: select
|
||||||
|
args:
|
||||||
|
table: articles
|
||||||
|
columns:
|
||||||
|
- id
|
||||||
|
- title
|
||||||
|
- content
|
||||||
|
|
||||||
|
#Revert changes
|
||||||
|
- url: /v1/query
|
||||||
|
status: 200
|
||||||
|
query:
|
||||||
|
type: run_sql
|
||||||
|
args:
|
||||||
|
sql: |
|
||||||
|
alter table articles rename to article;
|
||||||
|
|
||||||
|
#Error
|
||||||
|
- url: /v1/query
|
||||||
|
status: 400
|
||||||
|
response:
|
||||||
|
path: "$.args.table"
|
||||||
|
error: table "articles" does not exist
|
||||||
|
code: not-exists
|
||||||
|
query:
|
||||||
|
type: select
|
||||||
|
args:
|
||||||
|
table: articles
|
||||||
|
columns:
|
||||||
|
- id
|
||||||
|
- title
|
||||||
|
- content
|
@ -0,0 +1,57 @@
|
|||||||
|
#Rename article table and id columns
|
||||||
|
- url: /v1/query
|
||||||
|
status: 200
|
||||||
|
query:
|
||||||
|
type: run_sql
|
||||||
|
args:
|
||||||
|
sql: |
|
||||||
|
alter table article rename to articles;
|
||||||
|
alter table articles rename column id to article_id;
|
||||||
|
|
||||||
|
#Perform select
|
||||||
|
- url: /v1/query
|
||||||
|
status: 200
|
||||||
|
response:
|
||||||
|
- article_id: 1
|
||||||
|
title: article 1 by author 1
|
||||||
|
content: content for article 1
|
||||||
|
- article_id: 2
|
||||||
|
title: article 2 by author 1
|
||||||
|
content: content for article 2
|
||||||
|
- article_id: 3
|
||||||
|
title: article 1 by author 2
|
||||||
|
content: content for article 3
|
||||||
|
query:
|
||||||
|
type: select
|
||||||
|
args:
|
||||||
|
table: articles
|
||||||
|
columns:
|
||||||
|
- article_id
|
||||||
|
- title
|
||||||
|
- content
|
||||||
|
|
||||||
|
#Revert changes
|
||||||
|
- url: /v1/query
|
||||||
|
status: 200
|
||||||
|
query:
|
||||||
|
type: run_sql
|
||||||
|
args:
|
||||||
|
sql: |
|
||||||
|
alter table articles rename to article;
|
||||||
|
alter table article rename column article_id to id;
|
||||||
|
|
||||||
|
#Error
|
||||||
|
- url: /v1/query
|
||||||
|
status: 400
|
||||||
|
response:
|
||||||
|
path: "$.args.table"
|
||||||
|
error: table "articles" does not exist
|
||||||
|
code: not-exists
|
||||||
|
query:
|
||||||
|
type: select
|
||||||
|
args:
|
||||||
|
table: articles
|
||||||
|
columns:
|
||||||
|
- id
|
||||||
|
- title
|
||||||
|
- content
|
@ -3,4 +3,6 @@ args:
|
|||||||
- type: run_sql
|
- type: run_sql
|
||||||
args:
|
args:
|
||||||
sql: |
|
sql: |
|
||||||
drop table author
|
drop table article;
|
||||||
|
drop table author;
|
||||||
|
cascade: true
|
||||||
|
@ -471,6 +471,15 @@ class TestRunSQL(DefaultTestQueries):
|
|||||||
def test_sql_query_as_user_error(self, hge_ctx):
|
def test_sql_query_as_user_error(self, hge_ctx):
|
||||||
check_query_f(hge_ctx, self.dir() + '/sql_query_as_user_error.yaml')
|
check_query_f(hge_ctx, self.dir() + '/sql_query_as_user_error.yaml')
|
||||||
|
|
||||||
|
def test_sql_rename_table(self, hge_ctx):
|
||||||
|
check_query_f(hge_ctx, self.dir() + '/sql_rename_table.yaml')
|
||||||
|
|
||||||
|
def test_sql_rename_columns(self, hge_ctx):
|
||||||
|
check_query_f(hge_ctx, self.dir() + '/sql_rename_columns.yaml')
|
||||||
|
|
||||||
|
def test_sql_rename_table_and_column(self, hge_ctx):
|
||||||
|
check_query_f(hge_ctx, self.dir() + '/sql_rename_table_and_column.yaml')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def dir(cls):
|
def dir(cls):
|
||||||
return "queries/v1/run_sql"
|
return "queries/v1/run_sql"
|
||||||
|
Loading…
Reference in New Issue
Block a user