Hi Isomorphic,
I'm using v10.1p_2016-03-27 and just noticed that the unique-check for setAllowMultiUpdate-Requests consists of two SELECT statements where it could be one and that the 2nd just takes a value from the (random) 1st row.
See this log:
I marked two lines above. Why does the first SELECT all rows of the table? Why does the 2nd statement use cmadigan@server.com? This is just the value of one row that happens to be the first by chance.
This is the testcase:
BuiltInDS.java:
Employees.ds.xml:
Employees.java:
Try button 1+2 to execute the customOperation with the multiUpdate.
Use the 3rd button to reset the entry if needed.
For me, if you change the criteriaFields="Email" in the .ds.xml to criteriaFields="EmployeeType", I even get validation errors (these are not guaranteed as they whole behavior is non-deterministic and depends on the data for the 1st row of the 1st SELECT).
In my opinion the process should be like this (for doRenameEmployeeToNormal), 1st without criteriaFields:
It is more complicated with criteriaFields for the unique-validator:
It seems that the solution for the criteriaFields-case is more general and would also work for the simpler case.
As it is unclear how many rows will be SELECTED and processed in the criteriaFields-case (one row per returned criteriaField) and this could potentially be much data, an improvement would be to do in the DB, where it belongs:
If this returns data, the unique validator would be violated.
I'm pretty sure that this change will require some time which is OK for me as I have a workaround for my code in mind.
But the way the unique validator behaves currently for setAllowMultiUpdate-Requests is definitely wrong. Perhaps it should be disabled until this gets reworked.
Best regards
Blama
I'm using v10.1p_2016-03-27 and just noticed that the unique-check for setAllowMultiUpdate-Requests consists of two SELECT statements where it could be one and that the 2nd just takes a value from the (random) 1st row.
See this log:
Code:
=== 2016-03-30 13:35:26,862 [8-34] INFO RequestContext - URL: '/builtinds/sc/IDACall', User-Agent: 'Mozilla/5.0 (Windows NT 6.3; WOW64; rv:26.0) Gecko/20100101 Firefox/26.0': Moz (Gecko) with Accept-Encoding header === 2016-03-30 13:35:26,862 [8-34] DEBUG IDACall - Header Name:Value pair: Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 === 2016-03-30 13:35:26,862 [8-34] DEBUG IDACall - Header Name:Value pair: Connection:keep-alive === 2016-03-30 13:35:26,862 [8-34] DEBUG IDACall - Header Name:Value pair: DNT:1 === 2016-03-30 13:35:26,862 [8-34] DEBUG IDACall - Header Name:Value pair: Referer:http://127.0.0.1:8888/BuiltInDS.html?gwt.codesvr=127.0.0.1:9997 === 2016-03-30 13:35:26,862 [8-34] DEBUG IDACall - Header Name:Value pair: Pragma:no-cache === 2016-03-30 13:35:26,862 [8-34] DEBUG IDACall - Header Name:Value pair: Accept-Encoding:gzip, deflate === 2016-03-30 13:35:26,862 [8-34] DEBUG IDACall - Header Name:Value pair: Cache-Control:no-cache === 2016-03-30 13:35:26,862 [8-34] DEBUG IDACall - Header Name:Value pair: User-Agent:Mozilla/5.0 (Windows NT 6.3; WOW64; rv:26.0) Gecko/20100101 Firefox/26.0 === 2016-03-30 13:35:26,862 [8-34] DEBUG IDACall - Header Name:Value pair: Content-Type:application/x-www-form-urlencoded; charset=UTF-8 === 2016-03-30 13:35:26,862 [8-34] DEBUG IDACall - Header Name:Value pair: Accept-Language:de-de,de;q=0.8,en-us;q=0.5,en;q=0.3 === 2016-03-30 13:35:26,862 [8-34] DEBUG IDACall - Header Name:Value pair: Content-Length:828 === 2016-03-30 13:35:26,862 [8-34] DEBUG IDACall - Header Name:Value pair: Cookie:isc_cState=ready; JSESSIONID=xxre83hp03zd1sbpiz5zx8wq2; GLog=%7B%0A%20%20%20%20trackRPC%3Atrue%2C%20%0A%20%20%20%20isc_pageURL%3A%22http%3A//127.0.0.1%3A8888/BuiltInDS.html%3Fgwt.codesvr%3D127.0.0.1%3A9997%22%2C%20%0A%20%20%20%20isc_pageGUID%3A%2245C1F5C0-A193-481A-9145-F6D2BEBEDFAC%22%2C%20%0A%20%20%20%20priorityDefaults%3A%7B%0A%20%20%20%20%20%20%20%20sgwtInternal%3A1%2C%20%0A%20%20%20%20%20%20%20%20Log%3A4%0A%20%20%20%20%7D%2C%20%0A%20%20%20%20defaultPriority%3A3%2C%20%0A%20%20%20%20left%3A2068%2C%20%0A%20%20%20%20top%3A131%2C%20%0A%20%20%20%20width%3A905%2C%20%0A%20%20%20%20height%3A788%0A%7D === 2016-03-30 13:35:26,862 [8-34] DEBUG IDACall - Header Name:Value pair: Host:127.0.0.1:8888 === 2016-03-30 13:35:26,862 [8-34] DEBUG IDACall - session exists: xxre83hp03zd1sbpiz5zx8wq2 === 2016-03-30 13:35:26,862 [8-34] DEBUG IDACall - remote user: null === 2016-03-30 13:35:26,864 [8-34] DEBUG XML - Parsed XML from (in memory stream): 1ms === 2016-03-30 13:35:26,864 [8-34] DEBUG ISCKeyedObjectPool - Borrowing object for 'transaction' === 2016-03-30 13:35:26,864 [8-34] DEBUG PoolableDataSourceFactory - Tried to create DataSource of type 'transaction' but null was returned === 2016-03-30 13:35:26,864 [8-34] DEBUG PoolableDataSourceFactory - Created DataSource null of type 'transaction' in the pooling flow === 2016-03-30 13:35:26,864 [8-34] DEBUG ISCKeyedObjectPool - Borrowing object for 'Object' === 2016-03-30 13:35:26,864 [8-34] DEBUG PoolableDataSourceFactory - Created DataSource 60 of type 'Object' and assigned it to thread qtp448569108-34 === 2016-03-30 13:35:26,864 [8-34] DEBUG PoolableDataSourceFactory - Created DataSource 60 of type 'Object' in the pooling flow === 2016-03-30 13:35:26,864 [8-34] DEBUG PoolableDataSourceFactory - Activated DataSource 60 of type 'Object' === 2016-03-30 13:35:26,864 [8-34] DEBUG ISCKeyedObjectPool - Borrowing object for 'List' === 2016-03-30 13:35:26,865 [8-34] DEBUG PoolableDataSourceFactory - Created DataSource 61 of type 'List' and assigned it to thread qtp448569108-34 === 2016-03-30 13:35:26,865 [8-34] DEBUG PoolableDataSourceFactory - Created DataSource 61 of type 'List' in the pooling flow === 2016-03-30 13:35:26,865 [8-34] DEBUG PoolableDataSourceFactory - Activated DataSource 61 of type 'List' === 2016-03-30 13:35:26,865 [8-34] DEBUG ISCKeyedObjectPool - Borrowing object for 'elem' === 2016-03-30 13:35:26,865 [8-34] DEBUG PoolableDataSourceFactory - Tried to create DataSource of type 'elem' but null was returned === 2016-03-30 13:35:26,865 [8-34] DEBUG PoolableDataSourceFactory - Created DataSource null of type 'elem' in the pooling flow === 2016-03-30 13:35:26,865 [8-34] DEBUG RPCManager - Processing 1 requests. === 2016-03-30 13:35:26,865 [8-34] DEBUG ISCKeyedObjectPool - Borrowing object for 'employees' === 2016-03-30 13:35:26,865 [8-34] DEBUG PoolableDataSourceFactory - Activated DataSource 46 of type 'employees' === 2016-03-30 13:35:26,865 [8-34] DEBUG DSRequest - Caching instance 46 of DS 'employees' from DSRequest.getDataSource() === 2016-03-30 13:35:26,865 [8-34] DEBUG DSRequest - Caching instance 46 of DS employees === 2016-03-30 13:35:26,866 [8-34] DEBUG RPCManager - Request #1 (DSRequest) payload: { values:null, operationConfig:{ dataSource:"employees", repo:null, operationType:"custom", textMatchStyle:"exact" }, appID:"builtinApplication", operation:"doRenameEmployeeTo2", oldValues:null, criteria:{ } } === 2016-03-30 13:35:26,866 [8-34] INFO IDACall - Performing 1 operation(s) === 2016-03-30 13:35:26,866 [8-34] DEBUG DeclarativeSecurity - Processing security checks for DataSource null, field null === 2016-03-30 13:35:26,866 [8-34] DEBUG DeclarativeSecurity - DataSource employees is not in the pre-checked list, processing... === 2016-03-30 13:35:26,866 [8-34] DEBUG DeclarativeSecurity - Processing security checks for DataSource null, field null === 2016-03-30 13:35:26,866 [8-34] DEBUG DeclarativeSecurity - Request is not a client request, ignoring security checks. === 2016-03-30 13:35:26,866 [8-34] DEBUG ISCKeyedObjectPool - Borrowing object for 'employees' === 2016-03-30 13:35:26,866 [8-34] DEBUG PoolableDataSourceFactory - Activated DataSource 49 of type 'employees' === 2016-03-30 13:35:26,867 [8-34] DEBUG DSRequest - Caching instance 49 of DS 'employees' from DSRequest.getDataSource() === 2016-03-30 13:35:26,867 [8-34] DEBUG DSRequest - Caching instance 49 of DS employees === 2016-03-30 13:35:26,867 [8-34] DEBUG DeclarativeSecurity - Processing security checks for DataSource null, field null === 2016-03-30 13:35:26,867 [8-34] DEBUG DeclarativeSecurity - Request is not a client request, ignoring security checks. === 2016-03-30 13:35:26,867 [8-34] DEBUG ServerObject - Couldn't find a public method named: fetch on class: com.smartgwt.sample.server.listener.Employees === 2016-03-30 13:35:26,867 [8-34] DEBUG DataSourceDMI - DataSourceDMI: no public method name: fetch available on class: com.smartgwt.sample.server.listener.Employees - defaulting to builtin operations. === 2016-03-30 13:35:26,867 [8-34] DEBUG AppBase - [builtinApplication.null] No userTypes defined, allowing anyone access to all operations for this application === 2016-03-30 13:35:26,867 [8-34] DEBUG AppBase - [builtinApplication.null] No public zero-argument method named '_null' found, performing generic datasource operation === 2016-03-30 13:35:26,867 [8-34] INFO SQLDataSource - [builtinApplication.null] Performing fetch operation with criteria: {} values: {} === 2016-03-30 13:35:26,867 [8-34] INFO SQLWhereClause - [builtinApplication.null] empty condition === 2016-03-30 13:35:26,867 [8-34] INFO SQLDataSource - [builtinApplication.null] derived query: SELECT $defaultSelectClause FROM $defaultTableClause WHERE $defaultWhereClause === 2016-03-30 13:35:26,868 [8-34] INFO SQLDataSource - [builtinApplication.null] 49: Executing SQL query on 'HSQLDB': SELECT employeeTable.userOrder, employeeTable.Name, employeeTable.EmployeeId, employeeTable.ReportsTo, employeeTable.Job, employeeTable.Email, employeeTable.EmployeeType, employeeTable.EmployeeStatus, employeeTable.Salary, employeeTable.OrgUnit, employeeTable.Gender, employeeTable.MaritalStatus FROM employeeTable WHERE ('1'='1') === 2016-03-30 13:35:26,868 [8-34] DEBUG PoolableSQLConnectionFactory - [builtinApplication.null] DriverManager fetching connection for HSQLDB via jdbc url jdbc:hsqldb:hsql://localhost/isomorphic === 2016-03-30 13:35:26,868 [8-34] DEBUG PoolableSQLConnectionFactory - [builtinApplication.null] Passing JDBC URL only to getConnection === 2016-03-30 13:35:26,970 [8-34] DEBUG PoolableSQLConnectionFactory - [builtinApplication.null] makeObject() created an unpooled Connection '1611779883' === 2016-03-30 13:35:26,970 [8-34] DEBUG SQLConnectionManager - [builtinApplication.null] Borrowed connection '1611779883' [B]=== 2016-03-30 13:35:26,970 [8-34] INFO SQLDriver - [builtinApplication.null] Executing SQL query on 'HSQLDB' using connection '1611779883': SELECT employeeTable.userOrder, employeeTable.Name, employeeTable.EmployeeId, employeeTable.ReportsTo, employeeTable.Job, employeeTable.Email, employeeTable.EmployeeType, employeeTable.EmployeeStatus, employeeTable.Salary, employeeTable.OrgUnit, employeeTable.Gender, employeeTable.MaritalStatus FROM employeeTable WHERE ('1'='1') === 2016-03-30 13:35:26,971 [8-34] INFO DSResponse - DSResponse: List with 115 items[/B] === 2016-03-30 13:35:26,971 [8-34] DEBUG DSRequest - About to free up resources for request of type fetch on DataSource employees === 2016-03-30 13:35:26,971 [8-34] DEBUG SQLDataSource - About to clear SQLDriver state for DS instance 49 === 2016-03-30 13:35:26,971 [8-34] DEBUG SQLDriver - Freeing SQLDriver dbConnection 1611779883 for SQLDriver instance 1864992977 === 2016-03-30 13:35:26,972 [8-34] DEBUG SQLConnectionManager - About to close JDBCConnection with hashcode "1611779883" === 2016-03-30 13:35:26,972 [8-34] DEBUG SQLDataSource - About to clear SQLDriver state for DS instance 49 === 2016-03-30 13:35:26,972 [8-34] DEBUG SQLDataSource - About to clear SQLDriver state for DS instance 49 === 2016-03-30 13:35:26,972 [8-34] DEBUG PoolableDataSourceFactory - Cleared and passivated DataSource 49 of type 'employees' === 2016-03-30 13:35:26,972 [8-34] DEBUG ISCKeyedObjectPool - Borrowing object for 'integer' === 2016-03-30 13:35:26,972 [8-34] DEBUG PoolableDataSourceFactory - Tried to create DataSource of type 'integer' but null was returned === 2016-03-30 13:35:26,972 [8-34] DEBUG PoolableDataSourceFactory - Created DataSource null of type 'integer' in the pooling flow === 2016-03-30 13:35:26,972 [8-34] DEBUG ISCKeyedObjectPool - Borrowing object for 'text' === 2016-03-30 13:35:26,972 [8-34] DEBUG PoolableDataSourceFactory - Tried to create DataSource of type 'text' but null was returned === 2016-03-30 13:35:26,972 [8-34] DEBUG PoolableDataSourceFactory - Created DataSource null of type 'text' in the pooling flow === 2016-03-30 13:35:26,972 [8-34] DEBUG ISCKeyedObjectPool - Borrowing object for 'integer' === 2016-03-30 13:35:26,973 [8-34] DEBUG PoolableDataSourceFactory - Tried to create DataSource of type 'integer' but null was returned === 2016-03-30 13:35:26,973 [8-34] DEBUG PoolableDataSourceFactory - Created DataSource null of type 'integer' in the pooling flow === 2016-03-30 13:35:26,973 [8-34] DEBUG ISCKeyedObjectPool - Borrowing object for 'integer' === 2016-03-30 13:35:26,973 [8-34] DEBUG PoolableDataSourceFactory - Tried to create DataSource of type 'integer' but null was returned === 2016-03-30 13:35:26,973 [8-34] DEBUG PoolableDataSourceFactory - Created DataSource null of type 'integer' in the pooling flow === 2016-03-30 13:35:26,973 [8-34] DEBUG ISCKeyedObjectPool - Borrowing object for 'text' === 2016-03-30 13:35:26,973 [8-34] DEBUG PoolableDataSourceFactory - Tried to create DataSource of type 'text' but null was returned === 2016-03-30 13:35:26,973 [8-34] DEBUG PoolableDataSourceFactory - Created DataSource null of type 'text' in the pooling flow === 2016-03-30 13:35:26,973 [8-34] DEBUG ISCKeyedObjectPool - Borrowing object for 'text' === 2016-03-30 13:35:26,973 [8-34] DEBUG PoolableDataSourceFactory - Tried to create DataSource of type 'text' but null was returned === 2016-03-30 13:35:26,973 [8-34] DEBUG PoolableDataSourceFactory - Created DataSource null of type 'text' in the pooling flow === 2016-03-30 13:35:26,973 [8-34] DEBUG ISCKeyedObjectPool - Borrowing object for 'text' === 2016-03-30 13:35:26,973 [8-34] DEBUG PoolableDataSourceFactory - Tried to create DataSource of type 'text' but null was returned === 2016-03-30 13:35:26,973 [8-34] DEBUG PoolableDataSourceFactory - Created DataSource null of type 'text' in the pooling flow === 2016-03-30 13:35:26,973 [8-34] DEBUG ISCKeyedObjectPool - Borrowing object for 'text' === 2016-03-30 13:35:26,973 [8-34] DEBUG PoolableDataSourceFactory - Tried to create DataSource of type 'text' but null was returned === 2016-03-30 13:35:26,973 [8-34] DEBUG PoolableDataSourceFactory - Created DataSource null of type 'text' in the pooling flow === 2016-03-30 13:35:26,973 [8-34] DEBUG ISCKeyedObjectPool - Borrowing object for 'float' === 2016-03-30 13:35:26,973 [8-34] DEBUG PoolableDataSourceFactory - Tried to create DataSource of type 'float' but null was returned === 2016-03-30 13:35:26,973 [8-34] DEBUG PoolableDataSourceFactory - Created DataSource null of type 'float' in the pooling flow === 2016-03-30 13:35:26,973 [8-34] DEBUG ISCKeyedObjectPool - Borrowing object for 'text' === 2016-03-30 13:35:26,973 [8-34] DEBUG PoolableDataSourceFactory - Tried to create DataSource of type 'text' but null was returned === 2016-03-30 13:35:26,973 [8-34] DEBUG PoolableDataSourceFactory - Created DataSource null of type 'text' in the pooling flow === 2016-03-30 13:35:26,973 [8-34] DEBUG ISCKeyedObjectPool - Borrowing object for 'text' === 2016-03-30 13:35:26,973 [8-34] DEBUG PoolableDataSourceFactory - Tried to create DataSource of type 'text' but null was returned === 2016-03-30 13:35:26,973 [8-34] DEBUG PoolableDataSourceFactory - Created DataSource null of type 'text' in the pooling flow === 2016-03-30 13:35:26,973 [8-34] DEBUG ISCKeyedObjectPool - Borrowing object for 'text' === 2016-03-30 13:35:26,973 [8-34] DEBUG PoolableDataSourceFactory - Tried to create DataSource of type 'text' but null was returned === 2016-03-30 13:35:26,973 [8-34] DEBUG PoolableDataSourceFactory - Created DataSource null of type 'text' in the pooling flow === 2016-03-30 13:35:26,973 [8-34] DEBUG ISCKeyedObjectPool - Borrowing object for 'employees' === 2016-03-30 13:35:26,974 [8-34] DEBUG PoolableDataSourceFactory - Activated DataSource 49 of type 'employees' === 2016-03-30 13:35:26,974 [8-34] DEBUG DSRequest - Caching instance 49 of DS 'employees' from DSRequest.getDataSource() === 2016-03-30 13:35:26,974 [8-34] DEBUG DSRequest - Caching instance 49 of DS employees === 2016-03-30 13:35:26,974 [8-34] DEBUG DeclarativeSecurity - Processing security checks for DataSource null, field null === 2016-03-30 13:35:26,974 [8-34] DEBUG DeclarativeSecurity - Request is not a client request, ignoring security checks. === 2016-03-30 13:35:26,974 [8-34] DEBUG ServerObject - Couldn't find a public method named: fetch on class: com.smartgwt.sample.server.listener.Employees === 2016-03-30 13:35:26,974 [8-34] DEBUG DataSourceDMI - DataSourceDMI: no public method name: fetch available on class: com.smartgwt.sample.server.listener.Employees - defaulting to builtin operations. === 2016-03-30 13:35:26,974 [8-34] DEBUG AppBase - [builtinApplication.null] No userTypes defined, allowing anyone access to all operations for this application === 2016-03-30 13:35:26,974 [8-34] DEBUG AppBase - [builtinApplication.null] No public zero-argument method named '_null' found, performing generic datasource operation === 2016-03-30 13:35:26,974 [8-34] INFO SQLDataSource - [builtinApplication.null] Performing fetch operation with outputs: [EmployeeId, Name] criteria: {_constructor:"AdvancedCriteria",criteria:[{fieldName:"Email",value:"cmadigan@server.com",operator:"iEquals"},{fieldName:"Name",value:"Ralph Brogan",operator:"iEquals"},{criteria:[{fieldName:"EmployeeId",value:null,operator:"notEqual"}],operator:"or"}],operator:"and"} values: {_constructor:"AdvancedCriteria",criteria:[{fieldName:"Email",value:"cmadigan@server.com",operator:"iEquals"},{fieldName:"Name",value:"Ralph Brogan",operator:"iEquals"},{criteria:[{fieldName:"EmployeeId",value:null,operator:"notEqual"}],operator:"or"}],operator:"and"} === 2016-03-30 13:35:26,975 [8-34] INFO SQLDataSource - [builtinApplication.null] derived query: SELECT $defaultSelectClause FROM $defaultTableClause WHERE $defaultWhereClause === 2016-03-30 13:35:26,976 [8-34] INFO SQLDataSource - [builtinApplication.null] 49: Executing SQL query on 'HSQLDB': SELECT employeeTable.Name, employeeTable.EmployeeId FROM employeeTable WHERE ((LOWER(employeeTable.Email) = LOWER('cmadigan@server.com') AND employeeTable.Email IS NOT NULL) AND (LOWER(employeeTable.Name) = LOWER('ralph brogan') AND employeeTable.Name IS NOT NULL) AND ((employeeTable.EmployeeId IS NOT NULL))) === 2016-03-30 13:35:26,976 [8-34] DEBUG PoolableSQLConnectionFactory - [builtinApplication.null] DriverManager fetching connection for HSQLDB via jdbc url jdbc:hsqldb:hsql://localhost/isomorphic === 2016-03-30 13:35:26,976 [8-34] DEBUG PoolableSQLConnectionFactory - [builtinApplication.null] Passing JDBC URL only to getConnection === 2016-03-30 13:35:27,077 [8-34] DEBUG PoolableSQLConnectionFactory - [builtinApplication.null] makeObject() created an unpooled Connection '1098929166' === 2016-03-30 13:35:27,077 [8-34] DEBUG SQLConnectionManager - [builtinApplication.null] Borrowed connection '1098929166' [B]=== 2016-03-30 13:35:27,077 [8-34] INFO SQLDriver - [builtinApplication.null] Executing SQL query on 'HSQLDB' using connection '1098929166': SELECT employeeTable.Name, employeeTable.EmployeeId FROM employeeTable WHERE ((LOWER(employeeTable.Email) = LOWER('cmadigan@server.com') AND employeeTable.Email IS NOT NULL) AND (LOWER(employeeTable.Name) = LOWER('ralph brogan') AND employeeTable.Name IS NOT NULL) AND ((employeeTable.EmployeeId IS NOT NULL))) === 2016-03-30 13:35:27,078 [8-34] INFO DSResponse - DSResponse: List with 0 items[/B] === 2016-03-30 13:35:27,078 [8-34] DEBUG DSRequest - About to free up resources for request of type fetch on DataSource employees === 2016-03-30 13:35:27,078 [8-34] DEBUG ServerObject - Couldn't find a public method named: update on class: com.smartgwt.sample.server.listener.Employees === 2016-03-30 13:35:27,078 [8-34] DEBUG DataSourceDMI - DataSourceDMI: no public method name: update available on class: com.smartgwt.sample.server.listener.Employees - defaulting to builtin operations. === 2016-03-30 13:35:27,078 [8-34] DEBUG AppBase - [builtinApplication.null] No userTypes defined, allowing anyone access to all operations for this application === 2016-03-30 13:35:27,078 [8-34] DEBUG AppBase - [builtinApplication.null] No public zero-argument method named '_null' found, performing generic datasource operation === 2016-03-30 13:35:27,078 [8-34] INFO SQLDataSource - [builtinApplication.null] Performing update operation with criteria: {_constructor:"AdvancedCriteria",criteria:[{fieldName:"Name",value:"Ralph Brogan2",operator:"equals"},{fieldName:"Email",value:"rbrogan@server.com",operator:"equals"}],operator:"and"} values: {Name:"Ralph Brogan"} === 2016-03-30 13:35:27,079 [8-34] DEBUG PoolableSQLConnectionFactory - [builtinApplication.null] DriverManager fetching connection for HSQLDB via jdbc url jdbc:hsqldb:hsql://localhost/isomorphic === 2016-03-30 13:35:27,079 [8-34] DEBUG PoolableSQLConnectionFactory - [builtinApplication.null] Passing JDBC URL only to getConnection === 2016-03-30 13:35:27,180 [8-34] DEBUG PoolableSQLConnectionFactory - [builtinApplication.null] makeObject() created an unpooled Connection '1762451911' === 2016-03-30 13:35:27,180 [8-34] DEBUG SQLConnectionManager - [builtinApplication.null] Borrowed connection '1762451911' === 2016-03-30 13:35:27,180 [8-34] DEBUG SQLTransaction - [builtinApplication.null] Started new HSQLDB transaction "1762451911" === 2016-03-30 13:35:27,181 [8-34] DEBUG SQLDataSource - [builtinApplication.null] Setting DSRequest as being part of a transaction === 2016-03-30 13:35:27,181 [8-34] INFO SQLDriver - [builtinApplication.null] Executing SQL query on 'HSQLDB' using connection '1762451911': UPDATE employeeTable SET Name='Ralph Brogan' WHERE ((employeeTable.Name = 'Ralph Brogan2' AND employeeTable.Name IS NOT NULL) AND (employeeTable.Email = 'rbrogan@server.com' AND employeeTable.Email IS NOT NULL)) === 2016-03-30 13:35:27,181 [8-34] WARN SQLDataSource - [builtinApplication.null] update operation affected no rows === 2016-03-30 13:35:27,182 [8-34] DEBUG DSRequest - freeOnExecute is false for request of type update on DataSource employees - not freeing resources! === 2016-03-30 13:35:27,182 [8-34] DEBUG DSRequest - About to free up resources for request of type custom on DataSource employees === 2016-03-30 13:35:27,182 [8-34] DEBUG DSRequest - Ignoring freeResources call because this is not a primary request! === 2016-03-30 13:35:27,182 [8-34] DEBUG RPCManager - Content type for RPC transaction: text/plain; charset=UTF-8 === 2016-03-30 13:35:27,182 [8-34] DEBUG SQLTransaction - Committing HSQLDB transaction "1762451911" === 2016-03-30 13:35:27,182 [8-34] DEBUG SQLTransaction - Committing HSQLDB transaction "1762451911" === 2016-03-30 13:35:27,182 [8-34] DEBUG SQLTransaction - Committing HSQLDB transaction "1762451911" === 2016-03-30 13:35:27,182 [8-34] DEBUG RPCManager - DMI response, dropExtraFields: false === 2016-03-30 13:35:27,182 [8-34] DEBUG DSRequest - Ignoring freeQueueResources call because this is not a primary request! === 2016-03-30 13:35:27,182 [8-34] DEBUG SQLTransaction - getConnection() looked for transactional connection for HSQLDB: hashcode "1762451911" === 2016-03-30 13:35:27,182 [8-34] DEBUG SQLTransaction - Ending HSQLDB transaction "1762451911" === 2016-03-30 13:35:27,182 [8-34] DEBUG SQLConnectionManager - About to close JDBCConnection with hashcode "1762451911" === 2016-03-30 13:35:27,182 [8-34] DEBUG SQLDataSource - About to clear SQLDriver state for DS instance 46 === 2016-03-30 13:35:27,182 [8-34] DEBUG SQLDataSource - About to clear SQLDriver state for DS instance 46 === 2016-03-30 13:35:27,183 [8-34] DEBUG SQLDataSource - About to clear SQLDriver state for DS instance 46 === 2016-03-30 13:35:27,183 [8-34] DEBUG PoolableDataSourceFactory - Cleared and passivated DataSource 46 of type 'employees' === 2016-03-30 13:35:27,183 [8-34] DEBUG SQLDataSource - About to clear SQLDriver state for DS instance 49 === 2016-03-30 13:35:27,183 [8-34] DEBUG SQLDriver - Freeing SQLDriver dbConnection 1098929166 for SQLDriver instance 1864992977 === 2016-03-30 13:35:27,183 [8-34] DEBUG SQLConnectionManager - About to close JDBCConnection with hashcode "1098929166" === 2016-03-30 13:35:27,183 [8-34] DEBUG SQLDataSource - About to clear SQLDriver state for DS instance 49 === 2016-03-30 13:35:27,183 [8-34] DEBUG SQLDataSource - About to clear SQLDriver state for DS instance 49 === 2016-03-30 13:35:27,183 [8-34] DEBUG PoolableDataSourceFactory - Cleared and passivated DataSource 49 of type 'employees' === 2016-03-30 13:35:27,183 [8-34] DEBUG DSRequest - Ignoring freeResources call because they have already been freed === 2016-03-30 13:35:27,183 [8-34] DEBUG DSRequest - Ignoring freeQueueResources call because they have already been freed === 2016-03-30 13:35:27,183 [8-34] DEBUG DSRequest - Ignoring freeResources call because they have already been freed === 2016-03-30 13:35:27,183 [8-34] DEBUG DSRequest - Ignoring freeQueueResources call because they have already been freed === 2016-03-30 13:35:27,183 [8-34] INFO Compression - /builtinds/sc/IDACall: 159 -> 142 bytes
This is the testcase:
BuiltInDS.java:
Code:
package com.smartgwt.sample.client; import com.google.gwt.core.client.EntryPoint; import com.smartgwt.client.core.KeyIdentifier; import com.smartgwt.client.data.DataSource; import com.smartgwt.client.data.Record; import com.smartgwt.client.util.Page; import com.smartgwt.client.util.PageKeyHandler; import com.smartgwt.client.util.SC; 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.layout.VLayout; public class BuiltInDS implements EntryPoint { public void onModuleLoad() { KeyIdentifier debugKey = new KeyIdentifier(); debugKey.setCtrlKey(true); debugKey.setKeyName("D"); Page.registerKey(debugKey, new PageKeyHandler() { public void execute(String keyName) { SC.showConsole(); } }); VLayout mainLayout = new VLayout(20); mainLayout.setWidth100(); mainLayout.setHeight100(); { IButton renameEmployeeTo2 = new IButton("Ralph Brogan -> Ralph Brogan2"); renameEmployeeTo2.setWidth(200); renameEmployeeTo2.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { doRenameEmployeeTo2(); } }); mainLayout.addMember(renameEmployeeTo2); } { IButton renameEmployeeToNormal = new IButton("Ralph Brogan2 -> Ralph Brogan"); renameEmployeeToNormal.setWidth(200); renameEmployeeToNormal.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { doRenameEmployeeToNormal(); } }); mainLayout.addMember(renameEmployeeToNormal); } { IButton resetViaPK = new IButton("Reset Ralph Brogan via PK"); resetViaPK.setWidth(200); resetViaPK.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { resetViaPK(); } }); mainLayout.addMember(resetViaPK); } mainLayout.draw(); } private void doRenameEmployeeTo2() { DataSource.get("employees").performCustomOperation("doRenameEmployeeTo2"); } private void doRenameEmployeeToNormal() { DataSource.get("employees").performCustomOperation("doRenameEmployeeToNormal"); } private void resetViaPK() { DataSource.get("employees").updateData(new Record() { { setAttribute("EmployeeId", 192); setAttribute("Name", "Ralph Brogan"); } }); } }
Code:
<DataSource ID="employees" serverType="sql" tableName="employeeTable" recordName="employee" testFileName="/examples/shared/ds/test_data/employees.data.xml" titleField="Name" > <fields> <field name="userOrder" title="userOrder" type="integer" canEdit="false" hidden="true"/> <field name="Name" title="Name" type="text" length="128"> <validators> <validator serverOnly="true" type="isUnique" criteriaFields="Email"></validator> </validators> </field> <field name="EmployeeId" title="Employee ID" type="integer" primaryKey="true" required="true"/> <field name="ReportsTo" title="Manager" type="integer" required="true" foreignKey="employees.EmployeeId" rootValue="1" detail="true"/> <field name="Job" title="Title" type="text" length="128"/> <field name="Email" title="Email" type="text" length="128"/> <field name="EmployeeType" title="Employee Type" type="text" length="40"/> <field name="EmployeeStatus" title="Status" type="text" length="40"/> <field name="Salary" title="Salary" type="float"/> <field name="OrgUnit" title="Org Unit" type="text" length="128"/> <field name="Gender" title="Gender" type="text" length="7"> <valueMap> <value>male</value> <value>female</value> </valueMap> </field> <field name="MaritalStatus" title="Marital Status" type="text" length="10"> <valueMap> <value>married</value> <value>single</value> </valueMap> </field> </fields> <serverObject lookupStyle="new" className="com.smartgwt.sample.server.listener.Employees" /> <operationBindings> <operationBinding operationType="custom" operationId="doRenameEmployeeTo2" serverMethod="doRenameEmployeeTo2" /> <operationBinding operationType="custom" operationId="doRenameEmployeeToNormal" serverMethod="doRenameEmployeeToNormal" /> </operationBindings> </DataSource>
Code:
package com.smartgwt.sample.server.listener; import java.util.LinkedHashMap; import java.util.Map; import javax.servlet.http.HttpServletRequest; import com.isomorphic.criteria.DefaultOperators; import com.isomorphic.criteria.criterion.SimpleCriterion; import com.isomorphic.datasource.DSRequest; import com.isomorphic.datasource.DSResponse; import com.isomorphic.datasource.DataSource; public class Employees { public DSResponse doRenameEmployeeTo2(DSRequest request, HttpServletRequest servletRequest) throws Exception { DSRequest updateEmployeeRequest = new DSRequest("employees", DataSource.OP_UPDATE, request.getRPCManager()); updateEmployeeRequest.setAllowMultiUpdate(true); updateEmployeeRequest.addToCriteria(new SimpleCriterion("Name", DefaultOperators.Equals, "Ralph Brogan2")); updateEmployeeRequest.addToCriteria(new SimpleCriterion("Email", DefaultOperators.Equals, "rbrogan@server.com")); Map<String, String> values = new LinkedHashMap<String, String>(); values.put("Name", "Ralph Brogan"); updateEmployeeRequest.setValues(values); DSResponse updateEmployeeResponse = updateEmployeeRequest.execute(); if (updateEmployeeResponse.statusIsError()) return updateEmployeeResponse; return new DSResponse().setSuccess(); } public DSResponse doRenameEmployeeToNormal(DSRequest request, HttpServletRequest servletRequest) throws Exception { DSRequest updateEmployeeRequest = new DSRequest("employees", DataSource.OP_UPDATE, request.getRPCManager()); updateEmployeeRequest.setAllowMultiUpdate(true); updateEmployeeRequest.addToCriteria(new SimpleCriterion("Name", DefaultOperators.Equals, "Ralph Brogan2")); updateEmployeeRequest.addToCriteria(new SimpleCriterion("Email", DefaultOperators.Equals, "rbrogan@server.com")); Map<String, String> values = new LinkedHashMap<String, String>(); values.put("Name", "Ralph Brogan"); updateEmployeeRequest.setValues(values); DSResponse updateEmployeeResponse = updateEmployeeRequest.execute(); if (updateEmployeeResponse.statusIsError()) return updateEmployeeResponse; return new DSResponse().setSuccess(); } }
Use the 3rd button to reset the entry if needed.
For me, if you change the criteriaFields="Email" in the .ds.xml to criteriaFields="EmployeeType", I even get validation errors (these are not guaranteed as they whole behavior is non-deterministic and depends on the data for the 1st row of the 1st SELECT).
In my opinion the process should be like this (for doRenameEmployeeToNormal), 1st without criteriaFields:
Code:
SELECT COUNT(*) cnt FROM employeeTable WHERE Name = 'Ralph Brogan' --new value AND NOT --inverted requestCriteria (Name = 'Ralph Brogan2' --from criteria AND Email = rbrogan@server.com) --from criteria If this returns 1 (or more, but this should not happen as the value was not unique then, before), the update will violate the constraint iff it targets any rows -> validation error. Next step: SELECT COUNT(*) cnt FROM employeeTable WHERE Name = 'Ralph Brogan2' --from criteria AND Email = rbrogan@server.com --from criteria If this returns 2 or more, the update would set those rows to "Ralph Brogan" --> not allowed, validation error
Code:
SELECT Email, COUNT(*) cnt FROM employeeTable WHERE Name = 'Ralph Brogan' --new value AND NOT --inverted requestCriteria (Name = 'Ralph Brogan2' --from criteria AND Email = rbrogan@server.com) --from criteria GROUP BY Email --ds.xml criteriaField Keep DSResponse in memory. SELECT Email, COUNT(*) cnt FROM employeeTable WHERE Name = 'Ralph Brogan2' --from criteria AND Email = rbrogan@server.com --from criteria GROUP BY Email --ds.xml criteriaField Walk though the 1st result and check that the COUNT(*) plus the COUNT(*) for the same key in the other result are <=1.
As it is unclear how many rows will be SELECTED and processed in the criteriaFields-case (one row per returned criteriaField) and this could potentially be much data, an improvement would be to do in the DB, where it belongs:
Code:
SELECT Email, SUM(cnt) cntsum FROM ( --1st statement SELECT Email, COUNT(*) cnt FROM employeeTable WHERE Name = 'Ralph Brogan' --new value AND NOT --inverted requestCriteria (Name = 'Ralph Brogan2' --from criteria AND Email = rbrogan@server.com) --from criteria GROUP BY Email --ds.xml criteriaField UNION ALL --2nd statement SELECT Email, COUNT(*) cnt FROM employeeTable WHERE Name = 'Ralph Brogan2' --from criteria AND Email = rbrogan@server.com --from criteria GROUP BY Email --ds.xml criteriaField ) GROUP BY Email --ds.xml criteriaField HAVING SUM(cnt) > 1
I'm pretty sure that this change will require some time which is OK for me as I have a workaround for my code in mind.
But the way the unique validator behaves currently for setAllowMultiUpdate-Requests is definitely wrong. Perhaps it should be disabled until this gets reworked.
Best regards
Blama
Comment