Announcement

Collapse
No announcement yet.
X
  • Filter
  • Time
Clear All
new posts

    Problem with isUnique validator and updates

    Is there anyway to prevent the isUnique validator from returning true when editing an existing record with the field that is set isUnique and saving (updating) the changes? I'd like to use isUnique and be able to update a record with the same name, but prevent the update from overwriting another name (keep enforcing the uniqueness).

    I'm running SmartGWT Power 2.3 on Firefox 3.6 with an Oracle 10g database.

    I've attached a picture (ping.png) of what I'm seeing and here is code to reproduce this situation.

    Here is the db schema:
    Code:
    DROP TABLE Ping;
    
    CREATE TABLE Ping
      (
        id            number(10)     not null,
        name          varchar2(51)   unique not null,
        other         varchar2(21),
        CONSTRAINT PK_PING primary key(id)
      );
    
    drop sequence ping_seq;
    create sequence ping_seq start with 1 increment by 1 nocache;
    Here is the ds.xml:

    Code:
    <DataSource 
    	schema="PING"
    	dbName="Oracle"
    	tableName="PING"
    	ID="PING"
    	serverType="sql"
    	serverConstructor="com.smartgwt.sample.server.datasource.Ping"
    >
    	<fields>
    		<field sequenceName="PING_SEQ" primaryKey="true" name="id" type="sequence" hidden="true"></field>
    		<field name="name" length="51" type="text">
    			<validators>
    				<validator type="isUnique" requiresServer="true" />
    			</validators>
    		</field>
    		<field name="other" length="21" type="text"></field>
    	</fields>
    
    	<operationBindings>
    		<operationBinding operationType="add"></operationBinding>
    		<operationBinding operationType="update"></operationBinding>
    		<operationBinding operationType="remove"></operationBinding>
    		<operationBinding operationType="fetch"></operationBinding>
    	</operationBindings>
    
    </DataSource>
    Here is test driver:
    Code:
    package com.smartgwt.sample.client;
    
    import com.google.gwt.core.client.EntryPoint;
    import com.smartgwt.client.data.DataSource;
    import com.smartgwt.client.data.Record;
    import com.smartgwt.client.widgets.Canvas;
    import com.smartgwt.client.widgets.IButton;
    import com.smartgwt.client.widgets.events.ClickEvent;
    import com.smartgwt.client.widgets.events.ClickHandler;
    import com.smartgwt.client.widgets.form.DynamicForm;
    import com.smartgwt.client.widgets.grid.ListGrid;
    import com.smartgwt.client.widgets.grid.events.RecordClickEvent;
    import com.smartgwt.client.widgets.grid.events.RecordClickHandler;
    import com.smartgwt.client.widgets.layout.HLayout;
    import com.smartgwt.client.widgets.layout.VStack;
    
    public class Ping implements EntryPoint {
    	private ListGrid grid;
    	private DynamicForm form;
    	private IButton saveBtn;
    	private IButton newBtn;
    
    
    	public void onModuleLoad() 
    	{
    		Canvas mainCanvas = new Canvas();
    		mainCanvas.setHeight100();
    		mainCanvas.setWidth100();
    		mainCanvas.setBackgroundColor("green");
    
    		DataSource ds = DataSource.get("PING");
    
    		VStack vstack = new VStack();
    		vstack.setLeft(10);
    		vstack.setTop(10);
    		vstack.setWidth("80%");
    		vstack.setHeight("80%");
    		vstack.setMembersMargin(10);
    
    		grid = new ListGrid();
    		grid.setDataSource(ds);
    		grid.setWidth(300);
    		grid.setHeight(200);
    		grid.fetchData();
    		grid.addRecordClickHandler(new RecordClickHandler() {
    			public void onRecordClick(RecordClickEvent event) {
    				Record record = event.getRecord();
    				form.editRecord(record);
    				form.enable();
    				saveBtn.enable();
    				newBtn.disable();
    			}
    		});
    
    		vstack.addMember(grid);
    
    		form = new DynamicForm();
    		form.setDataSource(ds);
    		form.setNumCols(2);
    		form.setAutoFocus(false);
    		form.disable();
    		vstack.addMember(form);
    
    		HLayout hLayout = new HLayout(10);
    		hLayout.setMembersMargin(10);
    		hLayout.setHeight(22);
    
    		saveBtn = new IButton("Save");
    		saveBtn.disable();
    		saveBtn.addClickHandler(new ClickHandler() {
    			public void onClick(ClickEvent event) {
    				form.saveData();
    				if (!form.hasErrors()) {
    					saveBtn.disable();
    				} else {
    					form.clearValues();
    				}
    			}
    		});
    		hLayout.addMember(saveBtn);
    
    		newBtn = new IButton("New");
    		newBtn.enable();
    		newBtn.addClickHandler(new ClickHandler() {
    			public void onClick(ClickEvent event) {
    				form.enable();
    				form.editNewRecord();
    				saveBtn.enable();
    				newBtn.disable();
    			}
    		});
    		hLayout.addMember(newBtn);
    
    		IButton clearBtn = new IButton("Clear");
    		clearBtn.addClickHandler(new ClickHandler() {
    			public void onClick(ClickEvent event) {
    				form.disable();
    				form.clearValues();
    				saveBtn.disable();
    				newBtn.enable();
    			}
    		});
    		hLayout.addMember(clearBtn);
    
    		vstack.addMember(hLayout);
    
    
    		mainCanvas.addChild(vstack);
    
    		mainCanvas.draw();
    
    	}
    
    }
    Steps to reproduce this issue:

    Create a new record and save. Select the record from the ListGrid to edit it. Change the 'Other' data and save. The "isUnique" validator will trigger and prevent the record from being saved even though I am updating the same record.

    I would expect isUnique not to be triggered if the record primary key matched the record I was trying to update and the isUnique field, name in this case, was identical.
    Attached Files

    #2
    Can you clarify - are you saying that:

    1) when editing an existing record, you're seeing the "isUnique" validator prevent any saving, because the value is no longer unique since the record already exists

    2) when editing an existing record, you're seeing the "isUnique" validator prevents changing to a new value, even though the new value is unique

    2) something else?

    Comment


      #3
      1) when editing an existing record, you're seeing the "isUnique" validator prevent any saving, because the value is no longer unique since the record already exists
      Yes, this is the problem I'm seeing.

      2) when editing an existing record, you're seeing the "isUnique" validator prevents changing to a new value, even though the new value is unique
      This works as expected. The "isUnique" validated field can change as long as a new unique name is used.

      Comment


        #4
        Looking this over, it's a pretty major overhaul to make this validator work in the case that you want it to - specifically, both client- and server-side validation systems would need to have the context that this is validation for a possible future "update" operation, and neither subsystem knows or uses this information currently.

        So, we'll add a note to isUnique indicating it can currently only be used for fields that aren't changed (like usernames). If you need this type of validator, you could write a serverCustom validator that uses, for example, the presence of "dsRequest.oldValues" to distinguish update vs add.

        Comment


          #5
          ...can currently only be used for fields that aren't changed (like usernames).
          Could you clarify, do you mean fields or records?

          Updating the isUnique field is not a problem. Updating other fields in the record while keeping the unique field the same as it previously was doesn't seem to work.

          The way the current behavior seems to be is if there is an isUnique validator set on any field, then that record can not be updated.

          Comment


            #6
            Right, the effect being, you can only add it as a validator to a form that is going to create a new record.

            Your best option is an equivalent serverCustom validator instead, along the lines of (this is pseudo-code):

            Code:
            DSRequest request = new DSRequest("ping", "fetch");
            request.setCriteria("uniqueField", newRecord.uniqueField);
            Map oldRecord = request.execute().getRecord();
            return (oldRecord == null || 
                      (oldRecord.pkField == newRecord.pkField &&
                       oldRecord.uniqueField == newRecord.uniqueField))

            Comment


              #7
              Writing this pseudocode make us realize that a relatively simple framework fix is possible, so this has now been fixed and will appear in the next nightly.

              Comment


                #8
                I'm not seeing any change in behavior with my demo app using the smartgwtpower-2.3.zip from the nightly build of 2010-08-20. Did your change make it into that build?

                Comment


                  #9
                  It should have, yes, and you should be able to see the difference in behavior by doing an editRecord() call in the isUnique validator sample with values from the existing sample record.

                  Check that the logs you see at servlet engine startup have the right date.

                  Then also check that when you are attempting to save, you see the primary key being transmitted along with the current value for the isUnique field. Should be automatic, but if they were missing, the fix won't have an effect.

                  Comment


                    #10
                    The log shows the right version. The DSRequest payload is passing the primary key and noting that it is an update, but I am still not having any success.

                    Here's the ds.xml, with 'id' set to be the primaryKey:

                    Code:
                    <DataSource 
                    	schema="PING"
                    	dbName="Oracle"
                    	tableName="PING"
                    	ID="PING"
                    	serverType="sql"
                    >
                    	<fields>
                    		<field sequenceName="PING_SEQ" primaryKey="true" name="id" type="sequence" hidden="true"></field>
                    		<field name="name" length="51" type="text">
                    			<validators>
                    				<validator type="isUnique" requiresServer="true" />
                    			</validators>
                    		</field>
                    		<field name="other" length="21" type="text"></field>
                    	</fields>
                    </DataSource>
                    Here's the version from log:

                    Code:
                    2010-08-20 12:06:30,268 INFO  [STDOUT] (main) === 2010-08-20 12:06:30,268 [main] INFO  ISCInit - Isomorphic SmartClient Framework (SC_SNAPSHOT-2010-08-20/PowerEdition Deployment 2010-08-20) - Initialization Complete
                    Here's what's being logged for validation and update:

                    Code:
                    2010-08-20 12:08:03,684 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:03,684 [80-5] INFO  Compression - /Ping/Ping/sc/IDACall: 216 -> 161 bytes
                    2010-08-20 12:08:14,630 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,630 [80-5] INFO  RequestContext - URL: '/Ping/Ping/sc/IDACall', User-Agent: 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8': Moz (Gecko) with Accept-Encoding header
                    2010-08-20 12:08:14,634 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,634 [80-5] DEBUG XML - Parsed XML from (in memory stream): 2ms
                    2010-08-20 12:08:14,636 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,636 [80-5] DEBUG RPCManager - Processing 1 requests.
                    2010-08-20 12:08:14,637 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,637 [80-5] DEBUG RPCManager - Request #1 (DSRequest) payload: {
                        values:{
                            id:4,
                            name:"D",
                            _selection_1:true,
                            other:"other"
                        },
                        operationConfig:{
                            dataSource:"PING",
                            operationType:"validate"
                        },
                        validationMode:"full",
                        appID:"builtinApplication",
                        operation:"PING_validate",
                        oldValues:{
                            id:4,
                            name:"D",
                            _selection_1:true,
                            other:"other"
                        },
                        criteria:{
                        }
                    }
                    2010-08-20 12:08:14,637 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,637 [80-5] INFO  IDACall - Performing 1 operation(s)
                    2010-08-20 12:08:14,638 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,638 [80-5] DEBUG AppBase - [builtinApplication.PING_validate] No userTypes defined, allowing anyone access to all operations for this application
                    2010-08-20 12:08:14,638 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,638 [80-5] DEBUG AppBase - [builtinApplication.PING_validate] No public zero-argument method named '_PING_validate' found, performing generic datasource operation
                    2010-08-20 12:08:14,639 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,639 [80-5] DEBUG AppBase - [builtinApplication.PING_validate, builtinApplication.null] No userTypes defined, allowing anyone access to all operations for this application
                    2010-08-20 12:08:14,639 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,639 [80-5] DEBUG AppBase - [builtinApplication.PING_validate, builtinApplication.null] No public zero-argument method named '_null' found, performing generic datasource operation
                    2010-08-20 12:08:14,639 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,639 [80-5] INFO  SQLDataSource - [builtinApplication.PING_validate, builtinApplication.null] Performing fetch operation with
                            criteria: {name:"D"}    values: {name:"D"}
                    2010-08-20 12:08:14,640 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,640 [80-5] INFO  SQLDataSource - [builtinApplication.PING_validate, builtinApplication.null] derived query: SELECT $defaultSelectClause FROM $defaultTableClause WHERE $defaultWhereClause
                    2010-08-20 12:08:14,675 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,674 [80-5] DEBUG PoolableSQLConnectionFactory - [builtinApplication.PING_validate, builtinApplication.null] Returning pooled Connection
                    2010-08-20 12:08:14,676 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,676 [80-5] INFO  SQLDriver - [builtinApplication.PING_validate, builtinApplication.null] Executing SQL query on 'Oracle': SELECT PING.id, PING.other, PING.name FROM PING.PING WHERE (PING.name='D')
                    2010-08-20 12:08:14,678 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,678 [80-5] INFO  DSResponse - [builtinApplication.PING_validate, builtinApplication.null] DSResponse: List with 1 items
                    2010-08-20 12:08:14,681 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,681 [80-5] DEBUG ValidationContext - [builtinApplication.PING_validate] Adding validation errors at path '/PING/name': {errorMessage=Value must be unique}
                    2010-08-20 12:08:14,685 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,681 [80-5] INFO  Validation - [builtinApplication.PING_validate] Validation error: [
                        {
                            recordPath:"/PING",
                            name:{
                                errorMessage:"Value must be unique"
                            }
                        }
                    ]
                    2010-08-20 12:08:14,686 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,685 [80-5] DEBUG RPCManager - Content type for RPC transaction: text/plain; charset=UTF-8
                    2010-08-20 12:08:14,686 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,686 [80-5] DEBUG RPCManager - non-DMI response, dropExtraFields: false
                    2010-08-20 12:08:14,687 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,687 [80-5] INFO  Compression - /Ping/Ping/sc/IDACall: 181 -> 162 bytes
                    2010-08-20 12:08:14,712 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,712 [80-5] INFO  RequestContext - URL: '/Ping/Ping/sc/IDACall', User-Agent: 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8': Moz (Gecko) with Accept-Encoding header
                    2010-08-20 12:08:14,715 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,715 [80-5] DEBUG XML - Parsed XML from (in memory stream): 2ms
                    2010-08-20 12:08:14,718 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,718 [80-5] DEBUG RPCManager - Processing 1 requests.
                    2010-08-20 12:08:14,718 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,718 [80-5] DEBUG RPCManager - Request #1 (DSRequest) payload: {
                        criteria:{
                            id:4
                        },
                        values:{
                            id:4,
                            name:"D",
                            _selection_1:true,
                            other:"other"
                        },
                        operationConfig:{
                            dataSource:"PING",
                            operationType:"update"
                        },
                        componentId:"isc_DynamicForm_0",
                        appID:"builtinApplication",
                        operation:"PING_update",
                        oldValues:{
                            id:4,
                            name:"D",
                            _selection_1:true
                        }
                    }
                    2010-08-20 12:08:14,719 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,719 [80-5] INFO  IDACall - Performing 1 operation(s)
                    2010-08-20 12:08:14,719 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,719 [80-5] DEBUG AppBase - [builtinApplication.PING_update] No userTypes defined, allowing anyone access to all operations for this application
                    2010-08-20 12:08:14,720 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,720 [80-5] DEBUG AppBase - [builtinApplication.PING_update] No public zero-argument method named '_PING_update' found, performing generic datasource operation
                    2010-08-20 12:08:14,721 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,721 [80-5] DEBUG AppBase - [builtinApplication.PING_update, builtinApplication.null] No userTypes defined, allowing anyone access to all operations for this application
                    2010-08-20 12:08:14,721 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,721 [80-5] DEBUG AppBase - [builtinApplication.PING_update, builtinApplication.null] No public zero-argument method named '_null' found, performing generic datasource operation
                    2010-08-20 12:08:14,722 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,722 [80-5] INFO  SQLDataSource - [builtinApplication.PING_update, builtinApplication.null] Performing fetch operation with
                            criteria: {name:"D"}    values: {name:"D"}
                    2010-08-20 12:08:14,722 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,722 [80-5] INFO  SQLDataSource - [builtinApplication.PING_update, builtinApplication.null] derived query: SELECT $defaultSelectClause FROM $defaultTableClause WHERE $defaultWhereClause
                    2010-08-20 12:08:14,757 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,757 [80-5] DEBUG PoolableSQLConnectionFactory - [builtinApplication.PING_update, builtinApplication.null] Returning pooled Connection
                    2010-08-20 12:08:14,758 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,758 [80-5] INFO  SQLDriver - [builtinApplication.PING_update, builtinApplication.null] Executing SQL query on 'Oracle': SELECT PING.id, PING.other, PING.name FROM PING.PING WHERE (PING.name='D')
                    2010-08-20 12:08:14,760 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,760 [80-5] INFO  DSResponse - [builtinApplication.PING_update, builtinApplication.null] DSResponse: List with 1 items
                    2010-08-20 12:08:14,761 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,761 [80-5] DEBUG ValidationContext - [builtinApplication.PING_update] Adding validation errors at path '/PING/name': {errorMessage=Value must be unique}
                    2010-08-20 12:08:14,761 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,761 [80-5] INFO  Validation - [builtinApplication.PING_update] Validation error: [
                        {
                            recordPath:"/PING",
                            name:{
                                errorMessage:"Value must be unique"
                            }
                        }
                    ]
                    2010-08-20 12:08:14,762 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,762 [80-5] DEBUG RPCManager - Content type for RPC transaction: text/plain; charset=UTF-8
                    2010-08-20 12:08:14,762 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,762 [80-5] DEBUG RPCManager - non-DMI response, dropExtraFields: false
                    2010-08-20 12:08:14,763 INFO  [STDOUT] (http-0.0.0.0-8080-5) === 2010-08-20 12:08:14,763 [80-5] INFO  Compression - /Ping/Ping/sc/IDACall: 181 -> 162 bytes
                    I am seeing still the rejection of the update with the isUnique validation set in the ds.xml.

                    Comment


                      #11
                      To eliminate the possibility of there somehow being staleness, can you open the Showcase and navigate to showcase/#validation_unique sample, then execute this test from the "Evaluate JS Expression" area of the Developer Console:

                      Code:
                      var boundForm = isc_DynamicForm_0;
                      boundForm.editRecord({userId:1, email:"kamirov@server.com", firstName:"Dave", surname:"Barker"})
                      boundForm.validate()
                      You should see no validation error. Be sure to go directly there or the form's ID (isc_DynamicForm_0) won't match.

                      Comment


                        #12
                        After Eval JS, this is the output in the Log Messages:

                        Code:
                        13:45:15.179:INFO:Log:isc.Page is loaded
                        Evaluator: result of 'var boundForm = isc_DynamicForm_0;...' (25ms):
                        true
                        And the screen shows the 'Value must be unique' warning (see attached).
                        Attached Files

                        Comment


                          #13
                          You clearly don't have the new code. We're trying to figure out if this is a build issue or something else - can you confirm exactly which file you downloaded - a licensed Power nightly from smartclient.com/builds, after logging in with your SmartClient.com account?

                          Comment


                            #14
                            Yes, build taken from:
                            http://www.smartclient.com/builds/SmartGWT/2.x/PowerEdition/2010-08-20

                            Here's the cksum:
                            Code:
                            bash-3.2# cksum smartgwtpower-2.3.zip
                            1271428462 119281477 smartgwtpower-2.3.zip

                            Comment


                              #15
                              I've tried the Evaluate JS Expression again on the 2010-08-24 nightly build for Power edition and the old "isUnique" behavior is still in that build. Were you able to track down "if this is a build issue or something else"?

                              Comment

                              Working...
                              X