Support for nullable primary key columns

This commit is contained in:
Ralf Wisser
2019-12-12 07:54:27 +01:00
parent cca0332caf
commit eed28b3bb1
12 changed files with 110 additions and 39 deletions

View File

@@ -212,7 +212,7 @@
<td>Minimum length of all paths from the row back to
any subject row.<br />
The distance of subject rows is 0.</td>
The distance of a subject row is 0.</td>
</tr>
<tr>
@@ -351,8 +351,7 @@
key is associated with the subject table. In this case, you
should define a&nbsp;key of the table in the tool's data
model&nbsp;(not in the database!) manually, using the data
model editor. Note that a key must be unique and known (i.e.
not NULL).<br />
model editor. Note that a key must be unique.<br />
<br />
(On Oracle, however, <i>rowid</i>-pseudo columns can be used
instead of primary keys)<br />

View File

@@ -137,7 +137,7 @@ public abstract class PrimaryKeyValidator {
private void throwIfErrorFound() throws SqlException {
if (errorMessage.length() > 0) {
SqlException e = new SqlException(errorMessage.toString(), errorStatements.toString(), null);
SqlException e = new SqlException("Invalid Primary Key", errorMessage.toString(), errorStatements.toString(), null);
e.setFormatted(true);
throw e;
}
@@ -151,7 +151,7 @@ public abstract class PrimaryKeyValidator {
}
pks.append(quoting.requote(pkCol.name));
}
final String sql = "Select * from " + quoting.requote(table.getName()) + " " +
final String sql = "Select " + pks + " from " + quoting.requote(table.getName()) + " " +
"Group by " + pks + " having count(*) > 1";
try {
session.executeQuery(sql, new Session.AbstractResultSetReader() {
@@ -159,7 +159,7 @@ public abstract class PrimaryKeyValidator {
public void readCurrentRow(ResultSet resultSet) throws SQLException {
addError("Primary key of table \"" + table.getName() + "\" is not unique.", sql.toString());
}
}, null, cancellationContext, 1);
}, null, cancellationContext, 1, true);
} catch (SqlException e) {
addError("Table \"" + table.getName() + "\": " + e.message, sql.toString());
}
@@ -184,7 +184,7 @@ public abstract class PrimaryKeyValidator {
public void readCurrentRow(ResultSet resultSet) throws SQLException {
addError("Primary key of table \"" + table.getName() + "\" contains null.", sql.toString());
}
}, null, cancellationContext, 1);
}, null, cancellationContext, 1, true);
} catch (SqlException e) {
addError("Table \"" + table.getName() + "\": " + e.message, sql.toString());
}

View File

@@ -463,8 +463,8 @@ public class Session {
* @param sqlQuery the query in SQL
* @param reader the reader for the result
* @param alternativeSQL query to be executed if sqlQuery fails
* @param limit row limit, 0 for unlimited
* @param context cancellation context
* @param limit row limit, 0 for unlimited
* @param withExplicitCommit if <code>true</code>, switch of autocommit and commit explicitly
*/
public long executeQuery(String sqlQuery, ResultSetReader reader, String alternativeSQL, Object context, int limit, boolean withExplicitCommit) throws SQLException {
@@ -477,8 +477,8 @@ public class Session {
* @param sqlQuery the query in SQL
* @param reader the reader for the result
* @param alternativeSQL query to be executed if sqlQuery fails
* @param limit row limit, 0 for unlimited
* @param context cancellation context
* @param limit row limit, 0 for unlimited
*/
public long executeQuery(String sqlQuery, ResultSetReader reader, String alternativeSQL, Object context, int limit) throws SQLException {
return executeQuery(sqlQuery, reader, alternativeSQL, context, limit, 0, false);
@@ -491,8 +491,8 @@ public class Session {
* @param sqlQuery the query in SQL
* @param reader the reader for the result
* @param alternativeSQL query to be executed if sqlQuery fails
* @param limit row limit, 0 for unlimited
* @param context cancellation context
* @param limit row limit, 0 for unlimited
* @param timeout the timeout in sec
* @param withExplicitCommit if <code>true</code>, switch of autocommit and commit explicitly
*/

View File

@@ -25,12 +25,18 @@ import java.sql.SQLException;
public class SqlException extends SQLException {
public final String message;
public final String errorDialogTitle;
public final String sqlStatement;
private boolean insufficientPrivileges = false;
private boolean isFormatted = false;
public SqlException(String message, String sqlStatement, Throwable t) {
this(null, message, sqlStatement, t);
}
public SqlException(String errorDialogTitle, String message, String sqlStatement, Throwable t) {
super(message, t);
this.errorDialogTitle = errorDialogTitle;
this.message = t == null? message : t.getMessage();
this.sqlStatement = sqlStatement;
}

View File

@@ -72,12 +72,10 @@ public class PrimaryKey {
* @return a match of all columns of <code>primaryKey</code>
*/
public Map<Column, Column> match(PrimaryKey primaryKey) {
// TODO check completeness of matching
if (Configuration.getInstance().getDoMinimizeUPK() || !primaryKey.needsOrderedMatch) {
Map<Column, Column> match = new HashMap<Column, Column>();
boolean minimize = Configuration.getInstance().getDoMinimizeUPK() || !primaryKey.needsOrderedMatch;
if (minimize) {
Set<Integer> assignedUPKColumns = new HashSet<Integer>();
Map<Column, Column> match = new HashMap<Column, Column>();
for (Column column: getColumns()) {
for (int i = 0; i < primaryKey.getColumns().size(); ++i) {
if (assignedUPKColumns.contains(i)) {
@@ -94,9 +92,7 @@ public class PrimaryKey {
}
}
}
return match;
} else {
Map<Column, Column> match = new HashMap<Column, Column>();
int i = 0;
for (Column column: getColumns()) {
if (i >= primaryKey.getColumns().size()) {
@@ -111,8 +107,16 @@ public class PrimaryKey {
}
}
}
return match;
}
if (match.size() != primaryKey.columns.size()) {
throw new IllegalStateException("Incomplete pk-upk-match. (" + minimize + ")\n"
+ "PK: " + primaryKey.toSQL(null) + "\n"
+ "UPK: " + toSQL(null) + "\n"
+ "Match: " + match + "\n");
}
return match;
}
public static boolean isAssignable(Column uPKColumn, Column entityColumn) {

View File

@@ -1475,9 +1475,9 @@ public class SubsettingEngine {
if (scriptFile != null && scriptFormat != ScriptFormat.XML && exportStatistic.getTotal() != exportedCount) {
String message =
"The number of rows collected (" + exportStatistic.getTotal() + ") differs from that of the exported ones (" + exportedCount + ").\n" +
"This may have been caused by an invalid primary key definition.\nPlease note that each primary key must be unique and never null.\n" +
"This may have been caused by an invalid primary key definition.\nPlease note that each primary key must be unique.\n" +
"It is recommended to check the integrity of the primary keys.\n" +
"To do this, use the cli/api-argument \"-check-primary-keys\".";
"To do this, use the menu item \"Check primary keys\" in the menu called \"DataModel\".";
if (executionContext.isAbortInCaseOfInconsistency()) {
throw new InconsistentSubsettingResultException(message);
} else {

View File

@@ -2307,7 +2307,6 @@ public abstract class ExportDialog extends javax.swing.JDialog {
}//GEN-LAST:event_independentWorkingTablesActionPerformed
private void insertActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_insertActionPerformed
// TODO add your handling code here:
}//GEN-LAST:event_insertActionPerformed
public boolean isOk() {

View File

@@ -116,6 +116,10 @@ public class SqlErrorDialog extends javax.swing.JDialog {
sendButton.addKeyListener(keyListener);
jButton1.setVisible(false);
}
} else {
if (title != null) {
setTitle(title);
}
}
if (sendButton.isVisible() && UpdateInfoManager.currentDownloadableRelease != null) {

View File

@@ -392,21 +392,46 @@
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Component class="javax.swing.JLabel" name="jLabel12">
<Component class="javax.swing.JLabel" name="warnPKChangedLabel">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.modules.form.editors2.FontEditor">
<FontInfo relative="true">
<Font component="jLabel12" property="font" relativeSize="true" size="1"/>
<Font component="warnPKChangedLabel" property="font" relativeSize="true" size="1"/>
</FontInfo>
</Property>
<Property name="foreground" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
<Color blue="0" green="0" red="cd" type="rgb"/>
</Property>
<Property name="text" type="java.lang.String" value="&lt;html&gt;Primary key has been changed. &lt;br&gt; Please keep in mind that the PK must be unique and never &lt;i&gt;null&lt;/i&gt;. &lt;br&gt; It is recommended to check the integrity of the PK.&lt;br&gt; To do that, please select the option &quot;check primary keys&quot; in the export dialog or use the button below. &lt;/html&gt;"/>
<Property name="text" type="java.lang.String" value="&lt;html&gt;Primary key has been changed.&lt;br&gt;Keep in mind that the primary key must be unique.&lt;br&gt;It is recommended to check the integrity of the primary key.&lt;br&gt;To do that, please select the option &quot;check primary keys&quot; in the export dialog or use the button below. &lt;/html&gt;"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="-1" gridY="-1" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="1.0" weightY="1.0"/>
<GridBagConstraints gridX="1" gridY="1" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="1.0" weightY="1.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JSeparator" name="warnSeparator">
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="2" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="2" insetsLeft="0" insetsBottom="2" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="warnNullablePKLabel">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.modules.form.editors2.FontEditor">
<FontInfo relative="true">
<Font component="warnNullablePKLabel" property="font" relativeSize="true" size="1"/>
</FontInfo>
</Property>
<Property name="foreground" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
<Color blue="0" green="0" red="cd" type="rgb"/>
</Property>
<Property name="text" type="java.lang.String" value="&lt;html&gt;Nullable primary key columns can have a negative impact on performance.&lt;/html&gt;"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="3" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="1.0" weightY="1.0"/>
</Constraint>
</Constraints>
</Component>

View File

@@ -72,13 +72,18 @@ public abstract class TableEditor extends javax.swing.JDialog {
private void updateTable(List<ColumnModel> model) {
List<Column> pkColumns = new ArrayList<Column>();
hasNullablePKColumn = false;
for (ColumnModel cm: model) {
if (cm.isPk) {
pkColumns.add(cm.column);
if (cm.column.isNullable) {
hasNullablePKColumn = true;
}
}
}
PrimaryKey primaryKey = new PrimaryKeyFactory(null).createPrimaryKey(pkColumns, null);
table = new Table(nameField.getText().trim(), primaryKey, false, false);
updateWarningPanel();
}
@SuppressWarnings("serial")
@@ -176,7 +181,8 @@ public abstract class TableEditor extends javax.swing.JDialog {
c.isNullable = columnIsNullable.isSelected();
element.column = c;
if (element.isPk != primaryKey1.isSelected()) {
pkChangedWarning();
pkChanged = true;
updateWarningPanel();
}
element.isPk = primaryKey1.isSelected();
}
@@ -294,7 +300,9 @@ public abstract class TableEditor extends javax.swing.JDialog {
jLabel7 = new javax.swing.JLabel();
displayName = new javax.swing.JTextField();
warnPanel = new javax.swing.JPanel();
jLabel12 = new javax.swing.JLabel();
warnPKChangedLabel = new javax.swing.JLabel();
warnSeparator = new javax.swing.JSeparator();
warnNullablePKLabel = new javax.swing.JLabel();
columnDetailsPanel.setLayout(new java.awt.GridBagLayout());
@@ -649,14 +657,33 @@ public abstract class TableEditor extends javax.swing.JDialog {
warnPanel.setBorder(javax.swing.BorderFactory.createTitledBorder("Warning"));
warnPanel.setLayout(new java.awt.GridBagLayout());
jLabel12.setFont(jLabel12.getFont().deriveFont(jLabel12.getFont().getSize()+1f));
jLabel12.setForeground(new java.awt.Color(205, 0, 0));
jLabel12.setText("<html>Primary key has been changed. <br> Please keep in mind that the PK must be unique and never <i>null</i>. <br> It is recommended to check the integrity of the PK.<br> To do that, please select the option \"check primary keys\" in the export dialog or use the button below. </html>");
warnPKChangedLabel.setFont(warnPKChangedLabel.getFont().deriveFont(warnPKChangedLabel.getFont().getSize()+1f));
warnPKChangedLabel.setForeground(new java.awt.Color(205, 0, 0));
warnPKChangedLabel.setText("<html>Primary key has been changed.<br>Keep in mind that the primary key must be unique.<br>It is recommended to check the integrity of the primary key.<br>To do that, please select the option \"check primary keys\" in the export dialog or use the button below. </html>");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 1;
gridBagConstraints.gridy = 1;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.weighty = 1.0;
warnPanel.add(jLabel12, gridBagConstraints);
warnPanel.add(warnPKChangedLabel, gridBagConstraints);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 1;
gridBagConstraints.gridy = 2;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.insets = new java.awt.Insets(2, 0, 2, 0);
warnPanel.add(warnSeparator, gridBagConstraints);
warnNullablePKLabel.setFont(warnNullablePKLabel.getFont().deriveFont(warnNullablePKLabel.getFont().getSize()+1f));
warnNullablePKLabel.setForeground(new java.awt.Color(205, 0, 0));
warnNullablePKLabel.setText("<html>Nullable primary key columns can have a negative impact on performance.</html>");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 1;
gridBagConstraints.gridy = 3;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.weighty = 1.0;
warnPanel.add(warnNullablePKLabel, gridBagConstraints);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 1;
@@ -957,10 +984,14 @@ public abstract class TableEditor extends javax.swing.JDialog {
return false;
}
private void pkChangedWarning() {
if (!warnPanel.isVisible()) {
warnPanel.setVisible(true);
}
private boolean pkChanged = false;
private boolean hasNullablePKColumn = false;
private void updateWarningPanel() {
warnPanel.setVisible(pkChanged || hasNullablePKColumn);
warnPKChangedLabel.setVisible(pkChanged);
warnSeparator.setVisible(pkChanged && hasNullablePKColumn);
warnNullablePKLabel.setVisible(hasNullablePKColumn);
}
// Variables declaration - do not modify//GEN-BEGIN:variables
@@ -980,7 +1011,6 @@ public abstract class TableEditor extends javax.swing.JDialog {
private javax.swing.JLabel jLabel1;
private javax.swing.JLabel jLabel10;
private javax.swing.JLabel jLabel11;
private javax.swing.JLabel jLabel12;
private javax.swing.JLabel jLabel2;
private javax.swing.JLabel jLabel3;
private javax.swing.JLabel jLabel4;
@@ -994,7 +1024,10 @@ public abstract class TableEditor extends javax.swing.JDialog {
private javax.swing.JCheckBox primaryKey1;
private javax.swing.JPanel slotPanel;
private javax.swing.JCheckBox upsertCheckbox;
private javax.swing.JLabel warnNullablePKLabel;
private javax.swing.JLabel warnPKChangedLabel;
private javax.swing.JPanel warnPanel;
private javax.swing.JSeparator warnSeparator;
// End of variables declaration//GEN-END:variables
private static final long serialVersionUID = -3331167410435129849L;

View File

@@ -751,6 +751,7 @@ public class UIUtil {
if (t instanceof SqlException) {
String message = ((SqlException) t).getMessage();
String sql = ((SqlException) t).sqlStatement;
String errorDialogTitle = ((SqlException) t).errorDialogTitle;
if (message != null) {
if (sql != null) {
String iMsg = message.toString() + "\n" + JailerVersion.VERSION + "\n" + sql;
@@ -758,7 +759,7 @@ public class UIUtil {
}
}
new SqlErrorDialog(parent == null ? null : parent instanceof Window? (Window) parent : SwingUtilities.getWindowAncestor(parent),
((SqlException) t).isFormatted()? message : lineWrap(message, 120).toString(), sql, ((SqlException) t).isFormatted(), true, null, false, additionalControl);
((SqlException) t).isFormatted()? message : lineWrap(message, 120).toString(), sql, ((SqlException) t).isFormatted(), true, errorDialogTitle, false, additionalControl);
return;
}
if (t instanceof CancellationException) {

View File

@@ -429,7 +429,7 @@ public abstract class SingleStageProgressListener implements ProgressListener {
warned = true;
String message =
"Warning: The number of rows collected (" + finalCollectedRows + ") differs from that of the exported ones (" + exportedRows.get() + ").\n \n" +
"This may have been caused by an invalid primary key definition.\nPlease note that each primary key must be unique and never null.\n \n" +
"This may have been caused by an invalid primary key definition.\nPlease note that each primary key must be unique.\n \n" +
"It is recommended to check the integrity of the primary keys.\n" +
"To do this, use the button below \nor the menu item \"Check primary keys\" in the menu called \"DataModel\".";
final JButton button = new JButton("Check Primary Keys");