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