package com.revolsys.swing.field; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTextArea; import javax.swing.SwingUtilities; import javax.swing.event.CaretEvent; import javax.swing.event.CaretListener; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.JTextComponent; import org.jdesktop.swingx.VerticalLayout; import com.akiban.sql.StandardException; import com.akiban.sql.parser.BetweenOperatorNode; import com.akiban.sql.parser.BinaryArithmeticOperatorNode; import com.akiban.sql.parser.BinaryLogicalOperatorNode; import com.akiban.sql.parser.BinaryOperatorNode; import com.akiban.sql.parser.CastNode; import com.akiban.sql.parser.ColumnReference; import com.akiban.sql.parser.ConstantNode; import com.akiban.sql.parser.CursorNode; import com.akiban.sql.parser.InListOperatorNode; import com.akiban.sql.parser.IsNullNode; import com.akiban.sql.parser.LikeEscapeOperatorNode; import com.akiban.sql.parser.NodeTypes; import com.akiban.sql.parser.NotNode; import com.akiban.sql.parser.NumericConstantNode; import com.akiban.sql.parser.ResultSetNode; import com.akiban.sql.parser.RowConstructorNode; import com.akiban.sql.parser.SQLParser; import com.akiban.sql.parser.SQLParserException; import com.akiban.sql.parser.SelectNode; import com.akiban.sql.parser.SimpleStringOperatorNode; import com.akiban.sql.parser.StatementNode; import com.akiban.sql.parser.UserTypeConstantNode; import com.akiban.sql.parser.ValueNode; import com.akiban.sql.parser.ValueNodeList; import com.revolsys.awt.WebColors; import com.revolsys.datatype.DataType; import com.revolsys.datatype.DataTypes; import com.revolsys.identifier.Identifier; import com.revolsys.logging.Logs; import com.revolsys.record.code.CodeTable; import com.revolsys.record.query.And; import com.revolsys.record.query.Between; import com.revolsys.record.query.Cast; import com.revolsys.record.query.CollectionValue; import com.revolsys.record.query.Column; import com.revolsys.record.query.Condition; import com.revolsys.record.query.ILike; import com.revolsys.record.query.In; import com.revolsys.record.query.IsNotNull; import com.revolsys.record.query.IsNull; import com.revolsys.record.query.Not; import com.revolsys.record.query.Or; import com.revolsys.record.query.Q; import com.revolsys.record.query.QueryValue; import com.revolsys.record.query.Value; import com.revolsys.record.query.functions.Function; import com.revolsys.record.schema.FieldDefinition; import com.revolsys.record.schema.RecordDefinition; import com.revolsys.swing.Borders; import com.revolsys.swing.action.enablecheck.EnableCheck; import com.revolsys.swing.action.enablecheck.ObjectPropertyEnableCheck; import com.revolsys.swing.component.BasePanel; import com.revolsys.swing.component.ValueField; import com.revolsys.swing.layout.GroupLayouts; import com.revolsys.swing.map.layer.record.AbstractRecordLayer; import com.revolsys.swing.toolbar.ToolBar; import com.revolsys.util.Property; import com.revolsys.util.Strings; public class QueryWhereConditionField extends ValueField implements MouseListener, CaretListener, ItemListener, PropertyChangeListener, DocumentListener { private static final long serialVersionUID = 1L; private JComponent searchField; private final JPanel searchFieldPanel; private CodeTable codeTable; private final ComboBox<FieldDefinition> fieldNamesList; private final AbstractRecordLayer layer; private final PropertyChangeListener listener; private final Condition originalFilter; private final RecordDefinition recordDefinition; private final Color selectionColor; private final String sqlPrefix; private final JTextArea statusLabel; private boolean valid; private boolean validating; private final TextPane whereTextField; private boolean hasSearchText; private boolean fieldComparable; private boolean likeEnabled; public QueryWhereConditionField(final AbstractRecordLayer layer, final PropertyChangeListener listener, final Condition filter) { this(layer, listener, filter, null); } public QueryWhereConditionField(final AbstractRecordLayer layer, final PropertyChangeListener listener, final Condition filter, final String query) { super(new BorderLayout()); setTitle("Advanced Search"); this.layer = layer; this.originalFilter = filter; this.listener = listener; this.recordDefinition = layer.getRecordDefinition(); final List<FieldDefinition> fieldDefinitions = this.recordDefinition.getFields(); this.fieldNamesList = ComboBox.newComboBox("fieldNames", fieldDefinitions, (final Object field) -> { return ((FieldDefinition)field).getTitle(); }); this.fieldNamesList.setShowErrorIcon(false); this.fieldNamesList.addItemListener(this); this.fieldNamesList.addMouseListener(this); this.searchFieldPanel = new JPanel(); this.searchFieldPanel.setOpaque(false); final ObjectPropertyEnableCheck hasSearchText = new ObjectPropertyEnableCheck(this, "hasSearchText", true); final EnableCheck fieldCompareEnabled = hasSearchText .and(new ObjectPropertyEnableCheck(this, "fieldComparable")); final ToolBar operatorToolBar = new ToolBar(); operatorToolBar.setOpaque(false); operatorToolBar.setBorderPainted(true); for (final String binaryOperator : Arrays.asList("=", "<>")) { final Runnable runnable = () -> actionAddBinaryCondition(binaryOperator); operatorToolBar.addButton("binary", binaryOperator, hasSearchText, runnable) .setBorderPainted(true); } for (final String binaryOperator : Arrays.asList("<", "<=", ">", ">=")) { final Runnable runnable = () -> actionAddBinaryCondition(binaryOperator); operatorToolBar.addButton("binary", binaryOperator, fieldCompareEnabled, runnable) .setBorderPainted(true); } operatorToolBar.addButton("like", "LIKE", hasSearchText.and(this, "likeEnabled"), this::actionAddLikeCondition).setBorderPainted(true); for (final String rightUnaryOperator : Arrays.asList("IS NULL", "IS NOT NULL")) { final Runnable runnable = () -> actionAddRightUnaryCondition(rightUnaryOperator); operatorToolBar.addButton("rightUnary", rightUnaryOperator, runnable).setBorderPainted(true); } operatorToolBar.addButton("in", "IN", hasSearchText, this::actionAddInCondition) .setBorderPainted(true); final ToolBar buttonsToolBar = new ToolBar(); buttonsToolBar.setOpaque(false); buttonsToolBar.setBorderPainted(true); buttonsToolBar.addButton("relational", "AND", () -> insertText("AND")).setBorderPainted(true); buttonsToolBar.addButton("relational", "OR", () -> insertText("OR")).setBorderPainted(true); buttonsToolBar.addButton("relational", "NOT", () -> insertText("NOT")).setBorderPainted(true); buttonsToolBar.addButton("grouping", "( )", () -> insertText("( )")).setBorderPainted(true); buttonsToolBar.addButton("math", "+", () -> insertText("+")).setBorderPainted(true); buttonsToolBar.addButton("math", "-", () -> insertText("-")).setBorderPainted(true); buttonsToolBar.addButton("math", "*", () -> insertText("*")).setBorderPainted(true); buttonsToolBar.addButton("math", "/", () -> insertText("/")).setBorderPainted(true); final BasePanel widgetPanel = new BasePanel(new VerticalLayout(5), // GroupLayouts.panelColumns(this.fieldNamesList), // operatorToolBar, // this.searchFieldPanel, // buttonsToolBar); this.whereTextField = new TextPane(5, 20); this.whereTextField.setFont(new Font("Monospaced", Font.PLAIN, 11)); this.selectionColor = this.whereTextField.getSelectionColor(); this.whereTextField.addCaretListener(this); final JScrollPane whereScroll = new JScrollPane(this.whereTextField); // whereTextField.setContentType("text/sql"); // Requires the above scroll // pane this.sqlPrefix = "SELECT * FROM " + this.recordDefinition.getPath().substring(1).replace('/', '.') + " WHERE"; final JPanel filterTextPanel = new JPanel(new BorderLayout()); filterTextPanel.setOpaque(false); filterTextPanel.add(new JLabel(this.sqlPrefix), BorderLayout.NORTH); filterTextPanel.add(whereScroll, BorderLayout.CENTER); final ToolBar statusToolBar = new ToolBar(); statusToolBar.setOpaque(false); final JButton verifyButton = statusToolBar.addButton("default", "Verify", this::verifyCondition); verifyButton.setBorderPainted(true); final JPanel queryPanel = new JPanel(new BorderLayout()); Borders.titled(queryPanel, "Query"); queryPanel.setOpaque(false); queryPanel.add(widgetPanel, BorderLayout.NORTH); queryPanel.add(filterTextPanel, BorderLayout.CENTER); queryPanel.add(statusToolBar, BorderLayout.SOUTH); queryPanel.setPreferredSize(new Dimension(500, 400)); this.statusLabel = new TextArea(); this.statusLabel.setEditable(false); final JScrollPane statusPane = new JScrollPane(this.statusLabel); statusPane.setPreferredSize(new Dimension(500, 100)); final JPanel statusPanel = new JPanel(new BorderLayout()); Borders.titled(statusPanel, "Messages"); statusPanel.setOpaque(false); statusPanel.add(statusPane, BorderLayout.CENTER); final JSplitPane topBottom = new JSplitPane(JSplitPane.VERTICAL_SPLIT, queryPanel, statusPanel); topBottom.setResizeWeight(0.7); add(topBottom, BorderLayout.CENTER); setPreferredSize(new Dimension(800, 500)); setFieldValue(filter); if (filter != null) { this.whereTextField.setText(filter.toFormattedString()); } if (Property.hasValue(query)) { this.whereTextField.setText(query); } final String searchField = layer.getProperty("searchField"); FieldDefinition searchFieldDefinition = this.recordDefinition.getField(searchField); if (searchFieldDefinition == null) { searchFieldDefinition = fieldDefinitions.get(0); } this.fieldNamesList.setSelectedItem(searchFieldDefinition); setSearchField(searchFieldDefinition); } public QueryWhereConditionField(final AbstractRecordLayer layer, final PropertyChangeListener listener, final String query) { this(layer, listener, null, query); } private void actionAddBinaryCondition(final String operator) { final FieldDefinition fieldDefinition = this.fieldNamesList.getSelectedItem(); if (fieldDefinition != null) { Object fieldValue = ((Field)this.searchField).getFieldValue(); if (fieldValue != null) { final int position = this.whereTextField.getCaretPosition(); DataType fieldType = fieldDefinition.getDataType(); if (this.codeTable == null) { try { fieldValue = fieldDefinition.toFieldValue(fieldValue); } catch (final Throwable e) { setInvalidMessage("'" + fieldValue + "' is not a valid " + fieldDefinition.getDataType().getValidationName()); return; } } else { final List<Object> values = this.codeTable .getValues(Identifier.newIdentifier(fieldValue)); if (values.size() == 1) { fieldValue = values.get(0); } else { fieldValue = Strings.toString(":", values); } if (fieldValue != null) { fieldType = DataTypes.STRING; } } if (fieldValue != null) { final Document document = this.whereTextField.getDocument(); final StringBuilder text = new StringBuilder(); if (position > 0) { text.append(" "); } text.append(fieldDefinition.getName()); text.append(" "); text.append(operator); text.append(" "); appendValue(text, fieldType, fieldValue); text.append(" "); try { document.insertString(position, text.toString(), null); } catch (final BadLocationException e) { Logs.error(this, "Error inserting text: " + text, e); } } } } } private void actionAddInCondition() { final FieldDefinition fieldDefinition = this.fieldNamesList.getSelectedItem(); if (fieldDefinition != null) { Object fieldValue = ((Field)this.searchField).getFieldValue(); if (Property.hasValue(fieldValue)) { int position = this.whereTextField.getCaretPosition(); DataType fieldType = fieldDefinition.getDataType(); if (fieldValue != null) { if (this.codeTable == null) { try { fieldValue = fieldDefinition.toFieldValue(fieldValue); } catch (final Throwable e) { setInvalidMessage( "'" + fieldValue + "' is not a valid " + fieldType.getValidationName()); return; } } else { fieldValue = this.codeTable.getValue(fieldValue); if (fieldValue != null) { fieldType = DataTypes.STRING; } } if (fieldValue != null) { final StringBuilder text = new StringBuilder(); try { final Document document = this.whereTextField.getDocument(); final String currentText = document.getText(0, position); final Matcher matcher = Pattern.compile("(?:.+ )?" + fieldDefinition.getName() + " IN \\((?:.+,\\s*)*'?((?:[^']|'')*)'?\\) $").matcher(currentText); if (matcher.matches()) { final String previousValue = matcher.group(1).replace("''", "'"); if (!DataType.equal(fieldValue, previousValue)) { position -= 2; text.append(", "); appendValue(text, fieldType, fieldValue); } } else { if (position > 0) { text.append(" "); } text.append(fieldDefinition.getName()); text.append(" IN ("); appendValue(text, fieldType, fieldValue); text.append(") "); } document.insertString(position, text.toString(), null); } catch (final BadLocationException e) { Logs.error(this, "Error inserting text: " + text, e); } } } } } } private void actionAddLikeCondition() { final FieldDefinition fieldDefinition = this.fieldNamesList.getSelectedItem(); if (fieldDefinition != null) { final Object fieldValue = ((Field)this.searchField).getFieldValue(); if (fieldValue != null) { if (this.codeTable == null) { final int position = this.whereTextField.getCaretPosition(); if (fieldValue != null) { final String valueString = fieldDefinition.toString(fieldValue); final Document document = this.whereTextField.getDocument(); final StringBuilder text = new StringBuilder(); if (position > 0) { text.append(" "); } text.append(fieldDefinition.getName()); text.append(" LIKE '%"); text.append(valueString.replaceAll("'", "''")); text.append("%' "); try { document.insertString(position, text.toString(), null); } catch (final BadLocationException e) { Logs.error(this, "Error inserting text: " + text, e); } } } } } } private void actionAddRightUnaryCondition(final String operator) { final FieldDefinition fieldDefinition = this.fieldNamesList.getSelectedItem(); if (fieldDefinition != null) { final int position = this.whereTextField.getCaretPosition(); final Document document = this.whereTextField.getDocument(); final StringBuilder text = new StringBuilder(); if (position > 0) { text.append(" "); } text.append(fieldDefinition.getName()); text.append(" "); text.append(operator); text.append(" "); try { document.insertString(position, text.toString(), null); } catch (final BadLocationException e) { Logs.error(this, "Error inserting text: " + text, e); } } } public void appendValue(final StringBuilder text, final DataType type, final Object value) { final String valueString = type.toString(value); if (DataTypes.SQL_DATE == type) { text.append("{d '" + valueString + "'}"); } else if (DataTypes.TIME == type) { text.append("{t '" + valueString + "'}"); } else if (DataTypes.TIMESTAMP == type || DataTypes.DATE == type) { text.append("{ts '" + valueString + "'}"); } else if (DataTypes.DATE_TIME == type) { text.append("{ts '" + valueString + "'}"); } else if (Number.class.isAssignableFrom(type.getJavaClass())) { text.append(valueString); } else { text.append("'"); text.append(valueString.replaceAll("'", "''")); text.append("'"); } } @Override public void caretUpdate(final CaretEvent e) { if (!this.validating) { this.whereTextField.setSelectionColor(this.selectionColor); repaint(); } } @Override public void changedUpdate(final DocumentEvent e) { updateHasSearchText(); } public void insertText(final String operator) { if (Property.hasValue(operator)) { int position = this.whereTextField.getCaretPosition(); String previousText; try { previousText = this.whereTextField.getText(0, position); } catch (final BadLocationException e) { previousText = ""; } if (!Property.hasValue(previousText) || !previousText.matches(".*" + operator.replaceAll("\\(", "\\\\(") .replaceAll("\\)", "\\\\)") .replaceAll("\\*", "\\\\*") .replaceAll("\\+", "\\\\+") + "\\s*$")) { final Document document = this.whereTextField.getDocument(); try { if (Property.hasValue(previousText) && !previousText.substring(previousText.length() - 1).matches("\\s$")) { document.insertString(position++, " ", null); } document.insertString(position, operator + " ", null); } catch (final BadLocationException e) { } } } this.whereTextField.requestFocusInWindow(); } @Override public void insertUpdate(final DocumentEvent e) { updateHasSearchText(); } public boolean isFieldComparable() { return this.fieldComparable; } public boolean isHasSearchText() { return this.hasSearchText; } public boolean isLikeEnabled() { return this.likeEnabled; } @Override public void itemStateChanged(final ItemEvent event) { if (event.getSource() == this.fieldNamesList) { if (event.getStateChange() == ItemEvent.SELECTED) { final FieldDefinition fieldDefinition = (FieldDefinition)event.getItem(); setSearchField(fieldDefinition); } } } @Override public void mouseClicked(final MouseEvent event) { if (event.getSource() == this.fieldNamesList) { if (SwingUtilities.isLeftMouseButton(event) && event.getClickCount() == 2) { final FieldDefinition fieldDefinition = this.fieldNamesList.getSelectedItem(); if (Property.hasValue(fieldDefinition)) { final String fieldName = fieldDefinition.getName(); int position = this.whereTextField.getCaretPosition(); String previousText; try { previousText = this.whereTextField.getText(0, position); } catch (final BadLocationException e) { previousText = ""; } if (!Property.hasValue(previousText) || !previousText.matches(".*\"?" + fieldName + "\"?\\s*$")) { final Document document = this.whereTextField.getDocument(); try { if (Property.hasValue(previousText) && !previousText.substring(previousText.length() - 1).matches("\\s$")) { document.insertString(position++, " ", null); } if (fieldName.matches("[A-Z][_A-Z1-9]*")) { document.insertString(position, fieldName + " ", null); } else { document.insertString(position, "\"" + fieldName + "\" ", null); } } catch (final BadLocationException e) { } } } } this.whereTextField.requestFocusInWindow(); } } @Override public void mouseEntered(final MouseEvent e) { } @Override public void mouseExited(final MouseEvent e) { } @Override public void mousePressed(final MouseEvent e) { } @Override public void mouseReleased(final MouseEvent e) { } @Override public void propertyChange(final PropertyChangeEvent event) { final Object source = event.getSource(); if (source == this.searchField) { updateHasSearchText(); } } @Override public void removeUpdate(final DocumentEvent e) { updateHasSearchText(); } @Override public void save() { super.save(); final Condition condition = getFieldValue(); this.listener .propertyChange(new PropertyChangeEvent(this, "filter", this.originalFilter, condition)); } @Override public void save(final JDialog dialog) { verifyCondition(); if (this.valid) { super.save(dialog); } else { JOptionPane.showMessageDialog(this, "<html><p>Cannot save the advanced query as the SQL is valid.<p></p>Fix the SQL or use the cancel button on the Advanced Search window to cancel the changes.<p></html>", "SQL Invalid", JOptionPane.ERROR_MESSAGE); } } @Override public boolean setFieldValue(final Object value) { if (value instanceof Condition) { return super.setFieldValue(value); } else if (Property.hasValue(value)) { return false; } else { return super.setFieldValue(value); } } public void setHasSearchText(final boolean hasSearchText) { final boolean oldValue = this.hasSearchText; this.hasSearchText = hasSearchText; firePropertyChange("hasSearchText", oldValue, hasSearchText); } protected void setInvalidMessage(final int offset, final String message) { if (offset >= 0) { this.whereTextField.setSelectionColor(WebColors.Pink); this.whereTextField.setSelectionStart(offset); final Document document = this.whereTextField.getDocument(); this.whereTextField.setSelectionEnd(document.getLength()); this.whereTextField.requestFocusInWindow(); } setInvalidMessage(message); } protected void setInvalidMessage(final String message) { this.statusLabel.setForeground(WebColors.Red); this.statusLabel.append(message + "\n"); this.valid = false; this.statusLabel.setCaretPosition(0); } private void setSearchField(final FieldDefinition fieldDefinition) { if (this.searchField != null) { Property.removeListener(this.searchField, this); if (this.searchField instanceof JTextComponent) { final JTextComponent textField = (JTextComponent)this.searchField; textField.getDocument().removeDocumentListener(this); } } this.searchFieldPanel.removeAll(); final String fieldName = fieldDefinition.getName(); this.codeTable = this.recordDefinition.getCodeTableByFieldName(fieldName); this.searchField = this.layer.newSearchField(fieldDefinition, this.codeTable); final boolean oldFieldComparable = this.fieldComparable; this.fieldComparable = false; final boolean oldLikeEnabled = this.likeEnabled; this.likeEnabled = false; if (this.codeTable == null) { final Class<?> fieldClass = fieldDefinition.getTypeClass(); if (Number.class.isAssignableFrom(fieldClass) || Date.class.isAssignableFrom(fieldClass)) { this.fieldComparable = true; } else if (String.class.isAssignableFrom(fieldClass)) { this.likeEnabled = true; } } firePropertyChange("fieldComparable", oldFieldComparable, this.fieldComparable); firePropertyChange("likeEnabled", oldLikeEnabled, this.likeEnabled); this.searchFieldPanel.add(this.searchField); setHasSearchText(((Field)this.searchField).isHasValidValue()); Property.addListener(this.searchField, this); if (this.searchField instanceof JTextComponent) { final JTextComponent textField = (JTextComponent)this.searchField; textField.getDocument().addDocumentListener(this); } GroupLayouts.makeColumns(this.searchFieldPanel, false); } @SuppressWarnings("unchecked") private <V extends QueryValue> V toQueryValue(final ValueNode expression) { if (expression instanceof BetweenOperatorNode) { final BetweenOperatorNode betweenExpression = (BetweenOperatorNode)expression; final ValueNode leftValueNode = betweenExpression.getLeftOperand(); final ValueNodeList rightOperandList = betweenExpression.getRightOperandList(); final ValueNode betweenExpressionStart = rightOperandList.get(0); final ValueNode betweenExpressionEnd = rightOperandList.get(1); if (!(leftValueNode instanceof ColumnReference)) { setInvalidMessage("Between operator must use a column name not: " + leftValueNode); return null; } if (!(betweenExpressionStart instanceof NumericConstantNode)) { setInvalidMessage("Between min value must be a number not: " + betweenExpressionStart); return null; } if (!(betweenExpressionEnd instanceof NumericConstantNode)) { setInvalidMessage("Between max value must be a number not: " + betweenExpressionEnd); return null; } final Column column = toQueryValue(leftValueNode); final Value min = toQueryValue(betweenExpressionStart); final Value max = toQueryValue(betweenExpressionEnd); final FieldDefinition fieldDefinition = this.recordDefinition.getField(column.getName()); min.convert(fieldDefinition); max.convert(fieldDefinition); return (V)new Between(column, min, max); } else if (expression instanceof BinaryLogicalOperatorNode) { final BinaryLogicalOperatorNode binaryOperatorNode = (BinaryLogicalOperatorNode)expression; final String operator = binaryOperatorNode.getOperator().toUpperCase(); final ValueNode leftValueNode = binaryOperatorNode.getLeftOperand(); final ValueNode rightValueNode = binaryOperatorNode.getRightOperand(); final QueryValue leftValue = toQueryValue(leftValueNode); if (leftValue instanceof Condition) { final Condition leftCondition = (Condition)leftValue; final QueryValue rightValue = toQueryValue(rightValueNode); if (rightValue instanceof Condition) { final Condition rightCondition = (Condition)rightValue; if ("AND".equals(operator)) { return (V)new And(leftCondition, rightCondition); } else if ("OR".equals(operator)) { return (V)new Or(leftCondition, rightCondition); } else { setInvalidMessage("Binary logical operator " + operator + " not supported."); return null; } } else { setInvalidMessage("Right side of " + operator + " must be a condition (e.g. column_name = 'value') not: " + rightValue); return null; } } else { setInvalidMessage("Left side of " + operator + " must be a condition (e.g. column_name = 'value') not: " + leftValue); return null; } } else if (expression instanceof BinaryOperatorNode) { final BinaryOperatorNode binaryOperatorNode = (BinaryOperatorNode)expression; final String operator = binaryOperatorNode.getOperator(); final ValueNode leftValueNode = binaryOperatorNode.getLeftOperand(); final ValueNode rightValueNode = binaryOperatorNode.getRightOperand(); if (QueryValue.SUPPORTED_BINARY_OPERATORS.contains(operator.toUpperCase())) { final QueryValue leftCondition = toQueryValue(leftValueNode); QueryValue rightCondition = toQueryValue(rightValueNode); if (leftCondition instanceof Column) { if (rightCondition instanceof Value) { final Object value = ((Value)rightCondition).getValue(); if (value == null) { setInvalidMessage( "Values can't be null for " + operator + " use IS NULL or IS NOT NULL instead."); } else { final Column column = (Column)leftCondition; final String name = column.getName(); final FieldDefinition fieldDefinition = this.recordDefinition.getField(name); final CodeTable codeTable = this.recordDefinition.getCodeTableByFieldName(name); if (codeTable == null || fieldDefinition == this.recordDefinition.getIdField()) { try { final Object convertedValue = fieldDefinition.toFieldValueException(value); if (convertedValue == null) { setInvalidMessage("Values can't be null for " + operator + " use IS NULL or IS NOT NULL instead."); return null; } else { rightCondition = new Value(fieldDefinition, convertedValue); } } catch (final Throwable t) { setInvalidMessage(name + "='" + value + "' is not a valid " + fieldDefinition.getDataType().getValidationName()); } } else { Object id; if (value instanceof String) { final String string = (String)value; final String[] values = string.split(":"); id = codeTable.getIdentifier((Object[])values); } else { id = codeTable.getIdentifier(value); } if (id == null) { setInvalidMessage(name + "='" + value + "' could not be found in the code table " + codeTable.getName()); } else { rightCondition = new Value(fieldDefinition, id); } } } } } if (expression instanceof BinaryArithmeticOperatorNode) { final QueryValue arithmaticCondition = Q.arithmatic(leftCondition, operator, rightCondition); return (V)arithmaticCondition; } else { final Condition binaryCondition = Q.binary(leftCondition, operator, rightCondition); return (V)binaryCondition; } } else { setInvalidMessage("Unsupported binary operator " + operator); } } else if (expression instanceof ColumnReference) { final ColumnReference column = (ColumnReference)expression; String columnName = column.getColumnName(); columnName = columnName.replaceAll("\"", ""); final FieldDefinition fieldDefinition = this.recordDefinition.getField(columnName); if (fieldDefinition == null) { setInvalidMessage("Invalid field name " + columnName); } else { return (V)new Column(fieldDefinition); } } else if (expression instanceof LikeEscapeOperatorNode) { final LikeEscapeOperatorNode likeEscapeOperatorNode = (LikeEscapeOperatorNode)expression; final ValueNode leftValueNode = likeEscapeOperatorNode.getReceiver(); final ValueNode rightValueNode = likeEscapeOperatorNode.getLeftOperand(); final QueryValue leftCondition = toQueryValue(leftValueNode); final QueryValue rightCondition = toQueryValue(rightValueNode); return (V)new ILike(leftCondition, rightCondition); } else if (expression instanceof NotNode) { final NotNode notNode = (NotNode)expression; final ValueNode operand = notNode.getOperand(); final Condition condition = toQueryValue(operand); return (V)new Not(condition); } else if (expression instanceof InListOperatorNode) { final InListOperatorNode inListOperatorNode = (InListOperatorNode)expression; final ValueNode leftOperand = inListOperatorNode.getLeftOperand(); final QueryValue leftCondition = toQueryValue(leftOperand); final List<QueryValue> conditions = new ArrayList<>(); final RowConstructorNode itemsList = inListOperatorNode.getRightOperandList(); for (final ValueNode itemValueNode : itemsList.getNodeList()) { final QueryValue itemCondition = toQueryValue(itemValueNode); conditions.add(itemCondition); } return (V)new In(leftCondition, new CollectionValue(conditions)); } else if (expression instanceof IsNullNode) { final IsNullNode isNullNode = (IsNullNode)expression; final ValueNode operand = isNullNode.getOperand(); final QueryValue value = toQueryValue(operand); if (isNullNode.getNodeType() == NodeTypes.IS_NOT_NULL_NODE) { return (V)new IsNotNull(value); } else { return (V)new IsNull(value); } // } else if (expression instanceof Parenthesis) { // final Parenthesis parenthesis = (Parenthesis)expression; // final ValueNode parenthesisValueNode = parenthesis.getExpression(); // final Condition condition = toCondition(parenthesisExpression); // final ParenthesisCondition parenthesisCondition = new // ParenthesisCondition( // condition); // if (parenthesis.isNot()) { // return (V)Q.not(parenthesisCondition); // } else { // return (V)parenthesisCondition; // } } else if (expression instanceof RowConstructorNode) { final RowConstructorNode rowConstructorNode = (RowConstructorNode)expression; final ValueNodeList values = rowConstructorNode.getNodeList(); final ValueNode valueNode = values.get(0); return (V)toQueryValue(valueNode); } else if (expression instanceof UserTypeConstantNode) { final UserTypeConstantNode constant = (UserTypeConstantNode)expression; final Object objectValue = constant.getObjectValue(); return (V)new Value(objectValue); } else if (expression instanceof ConstantNode) { final ConstantNode constant = (ConstantNode)expression; final Object value = constant.getValue(); return (V)new Value(value); } else if (expression instanceof SimpleStringOperatorNode) { final SimpleStringOperatorNode operatorNode = (SimpleStringOperatorNode)expression; final String functionName = operatorNode.getMethodName().toUpperCase(); final ValueNode operand = operatorNode.getOperand(); final QueryValue condition = toQueryValue(operand); return (V)new Function(functionName, condition); } else if (expression instanceof CastNode) { final CastNode castNode = (CastNode)expression; final String typeName = castNode.getType().getSQLstring(); final ValueNode operand = castNode.getCastOperand(); final QueryValue condition = toQueryValue(operand); return (V)new Cast(condition, typeName); } else if (expression == null) { return null; } else { setInvalidMessage("Unsupported expression" + expression.getClass() + " " + expression); } return null; } protected void updateHasSearchText() { final boolean fieldValid = ((Field)this.searchField).isHasValidValue(); setHasSearchText(fieldValid); } public void verifyCondition() { this.validating = true; this.valid = true; this.statusLabel.setText(""); try { final String whereClause = this.whereTextField.getText(); if (Property.hasValue(whereClause)) { final String sql = "SELECT * FROM X WHERE " + "\n" + whereClause; try { final StatementNode statement = new SQLParser().parseStatement(sql); if (statement instanceof CursorNode) { final CursorNode selectStatement = (CursorNode)statement; final ResultSetNode resultSetNode = selectStatement.getResultSetNode(); if (resultSetNode instanceof SelectNode) { final SelectNode selectNode = (SelectNode)resultSetNode; final ValueNode where = selectNode.getWhereClause(); final QueryValue queryValue = toQueryValue(where); if (queryValue instanceof Condition) { final Condition condition = (Condition)queryValue; if (this.valid) { setFieldValue(condition); this.statusLabel.setForeground(WebColors.DarkGreen); this.statusLabel.setText("Valid"); } } } } } catch (final SQLParserException e) { final int offset = e.getErrorPosition(); setInvalidMessage(offset - this.sqlPrefix.length(), "Error parsing SQL: " + e.getMessage()); } catch (final StandardException e) { Logs.error(this, "Error parsing SQL: " + whereClause, e); } } else { setFieldValue(Condition.ALL); this.statusLabel.setForeground(WebColors.DarkGreen); this.statusLabel.setText("Valid"); } } finally { if (!this.valid) { setFieldValue(null); } this.validating = false; } } }