From decffc8d89faecd8f00b7201fc8f88b8c8b5da05 Mon Sep 17 00:00:00 2001 From: Oliver Grande Date: Tue, 6 Aug 2024 15:44:59 +0200 Subject: [PATCH] Feature/version 2.2.0 (#353) * Feature/transient fields (#142) * Fix null pointer if no enumerations are part of servcie * Enable HAS and enumeration as return type of operations * Allow Enumerations as operation parameter * Provide java based operations converted enumeration * Add SourceClear addon * Delete SourceClean addon * Enumerations at UDF functions - Works for bound functions - Works for functions with import * Correct unit tests * Enable multiple values for flags enumerations * Switch detection default between Embedded and Entity type * Allow collection attributes for metadata * Enable query without collection attributes * Rework query result conversion to reduce memory consumtion * Missing Metadata pom * First step entity with collections as return of function/action * Bugfix: Expand on Parent with $filter on navigation path * Enable the use of Join Tables - Filter on navigation via Join Table generally not supported * Create new query for inlinecount * Enable Join Tables@Navigation Filter - Not working mapped associations - Not working $count * Enable Join Tables@Navigation Filter - With mapped associations - With $count, know issue EclipsLink two sub types * Increase Version --> 0.2.6 * Remove deprecated JPAExecutableQuery * Bugfix: unidirectional join table based association * Access collection properties via navigation - New version * Access collection properties w and w/o $select * Access collection properties which are part of complex property * CUD operations on collection properties * Filter on collection properties pre optimization * Filter on collection properties any/all * Filter on collection w/o filter on $count and orderby $count * Correct issue #29 - Correct /$count implementation in JPAJoinQuery - Clean-up Hibernate differences -- Subquery from clause -- Id Class determination * Increase Version to 0.2.7 * Enable ..@odata.navigationLink in case of odata.metadata=full - links@complex types not yet working * Preparation for solution of issue OLINGO-1143 * Correct NullPointer and some clean-ups * Support order by $count - Increase version to 0.2.8 * Intermediate merge clean-up * Solve merge problem navigate complex collections * Resolve merge conflict converter * Correct error collection property and expand=* * Enable filter $count on collection property * Update version and url * Create and use page provider * Recreate change for solution of issue OLINGO-1143 * Finalize top level server driven paging * Correct string handling error * Correct link problem * Enable skiptoken to have other type than String * Move paging so next request injects old uriInfo correctly * Enable deep insert via complex properties * Ignore test for next commit * Enable create of a new entity linked by a to one using PATCH * Update to Olingo 4.5.0 - use new Olingo version - adopt one test to changed @odata.context content * Collection attributes rework. Issue #60 - Change processor pom -> javax.servlet became provided * Enable annotation for properties to mark them authorization relevant * Enable multiple protected properties at complex attributes * Rework EdmProtectedBy annotation to handle multiple claims at complex * Process protection * Rework handling of ignored protected * Made inner class static * Fixing issue #60 * $count query not working correctly on complex collection attributes * Various small bugs - Reorg. pom.xml - Correct http status on empty result - Correct empty check for collection properties * Correct typo in interface JPAODataPagingProvider * Prepare JPAODataDatabaseTableFunction for paging * Change to HSQLDB * Remove function from sql-file to work around problems with Derby * Correct NullPointerException EdmEntitySetResult * Determine result of create dependent based on before image * EntityType getAttribute did not return value for embedded id * Provide CUD example and correct query status codes * Correct query response * Maven Archetype for Spring based service * Upgrade to Junit5 to get support for Java > 1.8 * Extract interface for better unit test support - Cleanup tests * Clean-up interface implementation * Add SQL function handling * Complete DB function changes * Clean-up test class * Clean-up test class * Update Version number * Update Version number * Additional test for API classes * Enable suppress of wildcard in metadata * Enable wildcards during query processing * Increase information on error during filter * Support of PUT on collection properties and primitive properties * Fix Null Pointer on PATCH with return != minimal * Claims where not respected by queries issue #69 part one - /$count - ?$count=true - paging queries * Claims where not respected by queries issue #69 part two - $filter - Update on Olingo 4.6.0 * Restrict the DISTINCT to protections only * Increase test coverage - New tests - Rework vocabulary handling * Cleanup Vocabularies II - Add Action and Function - Remove NavigationProperty and dependent * Additional test and solution of issue #78 * Enable new annotation EdmVisibleFor at properties * Introduction of Request Context - Deprecation of two variants of JPAODataGetHandler.process - Increase test coverage * Restrict EdmVisiableFor to nullable non key attributes * Skip properties from selection that do not belong to provided group * Handle collection properties and navigation path * Enable groups at complex collections * Handle OrderBy clause * Correct unit test * Rework test containing images * Rework context handling - Move of cud handler to request context - Move of DebugSupport to request context (is request specific because of isUserAuthorized) - Introduction of builder for Service Context to get a leaner creation of handler * Providing groups during modifying requests * Re-enable old shortcut with late metadata provisioning * Update pom for missing jackson dependency * Update Archetype to version 0.3.4 * Update Version * Extension of session context, so an emf can be provided * Ignore annotation qualified if empty * Go back to Olingo JSON deserializer * Add qualifier to annotation * Increase release * Clean-up JPAEdmNameBuilder * Preparation of custom name builder - Create public interface - replace class by new interface * Revoke test adoption * Providing custome name builder * Update annotation EdmFunction * Increase release * Adopt archetype - Simplify service - Add integration test * Modify example did not persist on create * Deep Insert did not responded deep * Always select ETag * Complete ETag handling * Missing Context Changes * Reset unit test to current pushed state * Increase test coverage * Adopt test to real number of complex types * Enable V4.01 JSON format as request and response * Additional Integration tests support OData V4.01 JSON * Allow usage of different types of transaction * Increase Spring support * Bugfix/issue83 (#120) * Update Version * Extension of session context, so an emf can be provided * Preparation of custom name builder - Create public interface - replace class by new interface * Revoke test adoption * Providing custome name builder * Update annotation EdmFunction * Increase release * Adopt archetype - Simplify service - Add integration test * Modify example did not persist on create * Deep Insert did not responded deep * Always select ETag * Complete ETag handling * Missing Context Changes * Reset unit test to current pushed state * Increase test coverage * Adopt test to real number of complex types * Enable V4.01 JSON format as request and response * Additional Integration tests support OData V4.01 JSON * Allow usage of different types of transaction * Increase Spring support * Restrict $expand select by key range * Support root with navigation like AdministrativeDivision(...)/Children * Protect dilution of test coverage by test package (#133) * Increase release * Increase release (#136) * Protect dilution of test coverage by test package (#135) * Remove deprecated artifacts (#134) * Remove deprecated artefacts * Missing test adoptions * Upgrade Olingo version and processor version * Feature/transient fields (#137) * Increase release * Upgrade Olingo version and processor version * Prevent NPE on expand empty result (#138) * Correct SonarQube and SpotBug hints * Feature/transient fields (#139) * Increase release * Upgrade Olingo version and processor version * Correct SonarQube and SpotBug hints * SpotBugs and clean-ups * Build metadata I * First draft of criteria builder and query implementation (#140) * First draft of criteria builder and query implementation * Add Transient * First join version * Correct structured type * Test adoption was missing * Update criteria builder * Solve enum error * Support aggregation function * Correct buildInverseJoinColumns() error * Add Test buildInverseJoinColumns() error * Enable table join without entity type * Correct Error table name * Own test for JPAEdmProvider and correct name builder error * Additional test * Support parallel processing of batch requests * Complete synchronized creation of metadata * Enable transient field calculator * Suppress transient fields at $filter and $orderby * Enable transient collections and collections with transient attributes * Additional tests for changing operations * Enable absolute path in url * Add sonar plugin (#144) * Add sonar plugin * Update pom with sonar plugin * Feature/logging (#145) * Add sonar plugin * Update pom with sonar plugin * Remove old xmake version * Feature/logging (#147) * Add sonar plugin * Update pom with sonar plugin * Remove old xmake version * Logging as part of standard debugger * Generic authorization check for JPAExampleCUDRequestHandler * Feature/logging (#148) * Add sonar plugin * Update pom with sonar plugin * Remove old xmake version * Logging as part of standard debugger * Generic authorization check for JPAExampleCUDRequestHandler * Update xmake version * Feature/logging (#150) * Add sonar plugin * Update pom with sonar plugin * Remove old xmake version * Logging as part of standard debugger * Generic authorization check for JPAExampleCUDRequestHandler * Update xmake version * Clean-up sonar issues * Feature/logging (#151) * Add sonar plugin * Update pom with sonar plugin * Remove old xmake version * Logging as part of standard debugger * Generic authorization check for JPAExampleCUDRequestHandler * Update xmake version * Clean-up sonar issues * Update build status on read.me * New badges * Update README.md * Update README.md * Feature/logging (#152) * Add sonar plugin * Update pom with sonar plugin * Remove old xmake version * Logging as part of standard debugger * Generic authorization check for JPAExampleCUDRequestHandler * Update xmake version * Clean-up sonar issues * Update build status on read.me * New badges * Clean-up sonar issues * Create additional test for debugger * Add tests for debugger * Update README.md * Replace database derby -> hsqldb * Update README.md (#153) * Feature/logging (#154) * Add sonar plugin * Update pom with sonar plugin * Remove old xmake version * Logging as part of standard debugger * Generic authorization check for JPAExampleCUDRequestHandler * Update xmake version * Clean-up sonar issues * Update build status on read.me * New badges * Clean-up sonar issues * Create additional test for debugger * Add tests for debugger * Update README.md * Replace database derby -> hsqldb * Cleanup sonar issue * Increase test coverage for criteria builder (#155) * Feature/criteria builder (#156) * Increase test coverage for criteria builder * Replace double implementation by mocks * Update README.md (#157) * Feature/criteria builder (#158) * Increase test coverage for criteria builder * Replace double implementation by mocks * Rework vocabulary read * Feature/criteria builder (#159) * Increase test coverage for criteria builder * Replace double implementation by mocks * Rework vocabulary read * Add method to retrieve all claims * Update README.md (#160) * Update README.md * Update README.md * Remove potential null pointer (#161) * Bugfix/sonar issues (#162) * Remove potential null pointer * Remove potential Null Pointer * Bugfix/sonar issues (#163) * Remove potential null pointer * Remove potential Null Pointer * Remove potential Null Pointer * Bugfix/sonar issues (#164) * Remove potential null pointer * Remove potential Null Pointer * Remove potential Null Pointer * Additional clean-ups * Work on soanr issues * Bugfix/sonar issues (#165) * Remove potential null pointer * Remove potential Null Pointer * Remove potential Null Pointer * Additional clean-ups * Work on soanr issues * Additional cleanups * Feature/release 1.0.0 (#170) * Bugfix/typo package (#168) * Update version to 1.0.0-RC * Rename package to ...metadata.core.edm.mapper.extension * Rename message constants * Correct typos in method names * Rollback change to Optional * Support Include Annotation (#169) * Feature/include annotation (#171) * Support Include Annotation * Increase test coverage transient properties * Bugfix/issue98 (#173) * Support java.time.Instant and BigInteger * Correct issue #98 - Correct typo - Make DatabaseProcessor public * Enhance documentation of transient property calculator * Bugfix/issue98 (#174) * Support java.time.Instant and BigInteger * Correct issue #98 - Correct typo - Make DatabaseProcessor public * Enhance documentation of transient property calculator * Clean-up sonar issue * Clean-up sonar * Clean-up author * Meaningfull error message on $apply * Service Context creates Emf Wrapper if present in class path * Suppoprt of MappedSuperclass * $top and $skip use order by primary key * sonar issues (#175) * Support java.time.Instant and BigInteger * Correct issue #98 - Correct typo - Make DatabaseProcessor public * Enhance documentation of transient property calculator * Clean-up sonar issue * Clean-up sonar * Clean-up author * Meaningfull error message on $apply * Service Context creates Emf Wrapper if present in class path * Suppoprt of MappedSuperclass * $top and $skip use order by primary key * Clean-up sonar issues * Bugfix/issue98 (#176) * Support java.time.Instant and BigInteger * Correct issue #98 - Correct typo - Make DatabaseProcessor public * Enhance documentation of transient property calculator * Clean-up sonar issue * Clean-up sonar * Clean-up author * Meaningfull error message on $apply * Service Context creates Emf Wrapper if present in class path * Suppoprt of MappedSuperclass * $top and $skip use order by primary key * Clean-up sonar issues * Clean-up sonar issues * Bugfix/issue98 (#177) * Support java.time.Instant and BigInteger * Correct issue #98 - Correct typo - Make DatabaseProcessor public * Enhance documentation of transient property calculator * Clean-up sonar issue * Clean-up sonar * Clean-up author * Meaningfull error message on $apply * Service Context creates Emf Wrapper if present in class path * Suppoprt of MappedSuperclass * $top and $skip use order by primary key * Clean-up sonar issues * Clean-up sonar issues * Delete intermediate code * Feature/criteria builder (#178) * Increase test coverage for criteria builder * Replace double implementation by mocks * Rework vocabulary read * Add method to retrieve all claims * Add README.md and correct spelling errors * Add README.md and correct spelling errors (#179) * Feature/release 1.0.0 (#192) * Bugfix/typo package (#168) * Update version to 1.0.0-RC * Rename package to ...metadata.core.edm.mapper.extension * Rename message constants * Correct typos in method names * Rollback change to Optional * Support Include Annotation (#169) * Feature/include annotation (#171) * Support Include Annotation * Increase test coverage transient properties * Bugfix/syntax errors (#181) * Feature/release 1.0.0 (#170) * Bugfix/typo package (#168) * Update version to 1.0.0-RC * Rename package to ...metadata.core.edm.mapper.extension * Rename message constants * Correct typos in method names * Rollback change to Optional * Support Include Annotation (#169) * Feature/include annotation (#171) * Support Include Annotation * Increase test coverage transient properties * Bugfix/issue98 (#173) * Support java.time.Instant and BigInteger * Correct issue #98 - Correct typo - Make DatabaseProcessor public * Enhance documentation of transient property calculator * Bugfix/issue98 (#174) * Support java.time.Instant and BigInteger * Correct issue #98 - Correct typo - Make DatabaseProcessor public * Enhance documentation of transient property calculator * Clean-up sonar issue * Clean-up sonar * Clean-up author * Meaningfull error message on $apply * Service Context creates Emf Wrapper if present in class path * Suppoprt of MappedSuperclass * $top and $skip use order by primary key * sonar issues (#175) * Support java.time.Instant and BigInteger * Correct issue #98 - Correct typo - Make DatabaseProcessor public * Enhance documentation of transient property calculator * Clean-up sonar issue * Clean-up sonar * Clean-up author * Meaningfull error message on $apply * Service Context creates Emf Wrapper if present in class path * Suppoprt of MappedSuperclass * $top and $skip use order by primary key * Clean-up sonar issues * Bugfix/issue98 (#176) * Support java.time.Instant and BigInteger * Correct issue #98 - Correct typo - Make DatabaseProcessor public * Enhance documentation of transient property calculator * Clean-up sonar issue * Clean-up sonar * Clean-up author * Meaningfull error message on $apply * Service Context creates Emf Wrapper if present in class path * Suppoprt of MappedSuperclass * $top and $skip use order by primary key * Clean-up sonar issues * Clean-up sonar issues * Bugfix/issue98 (#177) * Support java.time.Instant and BigInteger * Correct issue #98 - Correct typo - Make DatabaseProcessor public * Enhance documentation of transient property calculator * Clean-up sonar issue * Clean-up sonar * Clean-up author * Meaningfull error message on $apply * Service Context creates Emf Wrapper if present in class path * Suppoprt of MappedSuperclass * $top and $skip use order by primary key * Clean-up sonar issues * Clean-up sonar issues * Delete intermediate code * Feature/criteria builder (#178) * Increase test coverage for criteria builder * Replace double implementation by mocks * Rework vocabulary read * Add method to retrieve all claims * Add README.md and correct spelling errors * Add README.md and correct spelling errors * Correct syntax errors metadata * Clean-up typos * Rework build of OrderBy creation (#183) * Bugfix/syntax errors (#185) * Feature/release 1.0.0 (#170) * Bugfix/typo package (#168) * Update version to 1.0.0-RC * Rename package to ...metadata.core.edm.mapper.extension * Rename message constants * Correct typos in method names * Rollback change to Optional * Support Include Annotation (#169) * Feature/include annotation (#171) * Support Include Annotation * Increase test coverage transient properties * Bugfix/issue98 (#173) * Support java.time.Instant and BigInteger * Correct issue #98 - Correct typo - Make DatabaseProcessor public * Enhance documentation of transient property calculator * Bugfix/issue98 (#174) * Support java.time.Instant and BigInteger * Correct issue #98 - Correct typo - Make DatabaseProcessor public * Enhance documentation of transient property calculator * Clean-up sonar issue * Clean-up sonar * Clean-up author * Meaningfull error message on $apply * Service Context creates Emf Wrapper if present in class path * Suppoprt of MappedSuperclass * $top and $skip use order by primary key * sonar issues (#175) * Support java.time.Instant and BigInteger * Correct issue #98 - Correct typo - Make DatabaseProcessor public * Enhance documentation of transient property calculator * Clean-up sonar issue * Clean-up sonar * Clean-up author * Meaningfull error message on $apply * Service Context creates Emf Wrapper if present in class path * Suppoprt of MappedSuperclass * $top and $skip use order by primary key * Clean-up sonar issues * Bugfix/issue98 (#176) * Support java.time.Instant and BigInteger * Correct issue #98 - Correct typo - Make DatabaseProcessor public * Enhance documentation of transient property calculator * Clean-up sonar issue * Clean-up sonar * Clean-up author * Meaningfull error message on $apply * Service Context creates Emf Wrapper if present in class path * Suppoprt of MappedSuperclass * $top and $skip use order by primary key * Clean-up sonar issues * Clean-up sonar issues * Bugfix/issue98 (#177) * Support java.time.Instant and BigInteger * Correct issue #98 - Correct typo - Make DatabaseProcessor public * Enhance documentation of transient property calculator * Clean-up sonar issue * Clean-up sonar * Clean-up author * Meaningfull error message on $apply * Service Context creates Emf Wrapper if present in class path * Suppoprt of MappedSuperclass * $top and $skip use order by primary key * Clean-up sonar issues * Clean-up sonar issues * Delete intermediate code * Feature/criteria builder (#178) * Increase test coverage for criteria builder * Replace double implementation by mocks * Rework vocabulary read * Add method to retrieve all claims * Add README.md and correct spelling errors * Add README.md and correct spelling errors * Add README.md and correct spelling errors (#179) * Correct syntax errors metadata * Clean-up typos * Update Version -> 1.0.0 * Usage of builder to create external request context (#187) * Bugfix/continue on error (#189) * Move batch processor from API to PROCESSOR package * Correct continue-on-error handling * Support generated Id in example CUD handler (#190) * Support generated Id in example CUD handler * Correct failing test * Adoption of archetype to 1.0.0 (#191) * Adoption of archetype to 1.0.0 * Correct sql error * Clean-up sonar issues * Correct criteria builder implementation (#193) * Feature/criteria builder (#194) * Correct criteria builder implementation * Enable sub query as FROM * Update tests * Update sonar issues * Update sonar issue * Add mutation testing to processor and metadata * Feature/criteria builder (#195) * Correct criteria builder implementation * Enable sub query as FROM * Update tests * Update sonar issues * Update sonar issue * Add mutation testing to processor and metadata * Eliminate duplicate code * Feature/criteria builder (#196) * Correct criteria builder implementation * Enable sub query as FROM * Update tests * Update sonar issues * Update sonar issue * Add mutation testing to processor and metadata * Eliminate duplicate code * Add Locale to request context, so it can be set from user data * Correct request context copy * Feature/criteria builder (#197) * Correct criteria builder implementation * Enable sub query as FROM * Update tests * Update sonar issues * Update sonar issue * Add mutation testing to processor and metadata * Eliminate duplicate code * Add Locale to request context, so it can be set from user data * Correct request context copy * Missing transient fields at $expand * Add tests * Feature/criteria builder (#198) * Correct criteria builder implementation * Enable sub query as FROM * Update tests * Update sonar issues * Update sonar issue * Add mutation testing to processor and metadata * Eliminate duplicate code * Add Locale to request context, so it can be set from user data * Correct request context copy * Missing transient fields at $expand * Add tests * Fix GeneratedKey problem * Feature/criteria builder (#199) * Correct criteria builder implementation * Enable sub query as FROM * Update tests * Update sonar issues * Update sonar issue * Add mutation testing to processor and metadata * Eliminate duplicate code * Add Locale to request context, so it can be set from user data * Correct request context copy * Missing transient fields at $expand * Add tests * Fix GeneratedKey problem * Intermediate state * Enable next block of queries * Expand with Join Tables * Last unit test corrections * Eliminate typos * Finalize changes * missing variable usage (#200) * missing variable usage * Increase processor version * Clean-up Sonar issues (#201) * Bugfix/no mapper for operations (#202) * Clean-up Sonar issues * Add test for type mapping for actions and java function + Cleanup test classes * Feature/1.0.1 (#203) * Correct criteria builder implementation * Enable sub query as FROM * Update tests * Update sonar issues * Update sonar issue * Add mutation testing to processor and metadata * Eliminate duplicate code * Add Locale to request context, so it can be set from user data * Correct request context copy * Missing transient fields at $expand * Add tests * Fix GeneratedKey problem * Change version to 1.0.1 * New version (#204) * Bugfix/filter eq withdate (#205) * New version * Support filter on Date and DateTime * Correct precision * Change another test * Us generated alias for column selection (#206) * Upgrade Version (#207) * Us generated alias for column selection * Upgrade version * Reduce complexity (#209) * ALL did not work with functions like startswith (#210) * Update version (#208) * Entity Set Path @ DB Functions (#211) * Feature/new archetype (#212) * Clean-up folder name * Create archetype repo * Use copy of spring archetype as start * Enable named queries * Feature/new archetype (#213) * Clean-up folder name * Create archetype repo * Use copy of spring archetype as start * Enable named queries * Allow Star for non string claim properties * Add Equals method to DeepProtectedExample * Feature/new archetype (#214) * Clean-up folder name * Create archetype repo * Use copy of spring archetype as start * Enable named queries * Allow Star for non string claim properties * Add Equals method to DeepProtectedExample * Enable grant access to all for non string fields read * Replace Reflections API by Reflections8 (#215) * Support entitytypes singeltons (#216) * New annotation to make singletons and entity types - Update to olingo 4.8.0 - Deprecate EdmAsEntitySet - Read not ready * Update README.md (#218) * Update README.md * Update README.md * Update README.md * Solve issue 136 (#217) * Solve issue 136 * Eliminate hyphen * Eliminate WhiteSource detected vulnerabilities (#219) * Eliminate WhiteSource detected vulnerabilities * Add now required dependency * Remove setExternalName from Metadata Post Processor (#220) * Support entitytypes singeltons (#221) * New annotation to make singletons and entity types - Update to olingo 4.8.0 - Deprecate EdmAsEntitySet * Wrong entity type on singleton only * Add java doc to TopLevelElementRepresentation * Finalize singleton metadata handling * Clean-up test classes * Add option to retrieve Singleton from SD * Rename test class * Support entitytypes singeltons (#222) * New annotation to make singletons and entity types - Update to olingo 4.8.0 - Deprecate EdmAsEntitySet * Wrong entity type on singleton only * Add java doc to TopLevelElementRepresentation * Finalize singleton metadata handling * Clean-up test classes * Add option to retrieve Singleton from SD * Rename test class * Add and clean-up unit tests * Query extension declaration (#223) * New annotation to make singletons and entity types - Update to olingo 4.8.0 - Deprecate EdmAsEntitySet * Wrong entity type on singleton only * Add java doc to TopLevelElementRepresentation * Finalize singleton metadata handling * Clean-up test classes * Add option to retrieve Singleton from SD * Rename test class * Add and clean-up unit tests * Introduce query extension metadata * Enable Singleton and Cast on subtypes - clean-up sonar issues * Support entity types and singletons (#224) * New annotation to make singletons and entity types - Update to olingo 4.8.0 - Deprecate EdmAsEntitySet * Wrong entity type on singleton only * Add java doc to TopLevelElementRepresentation * Finalize singleton metadata handling * Clean-up test classes * Add option to retrieve Singleton from SD * Rename test class * Add and clean-up unit tests * Introduce query extension metadata * Enable Singleton and Cast on subtypes - clean-up sonar issues * Clean-up sonar issues * Support cast on navigations (#225) * New annotation to make singletons and entity types - Update to olingo 4.8.0 - Deprecate EdmAsEntitySet * Wrong entity type on singleton only * Add java doc to TopLevelElementRepresentation * Finalize singleton metadata handling * Clean-up test classes * Add option to retrieve Singleton from SD * Rename test class * Add and clean-up unit tests * Introduce query extension metadata * Enable Singleton and Cast on subtypes - clean-up sonar issues * Clean-up sonar issues * Cast within navigation path * Add cast collections * Add tests for EdmBoundCast * Support Cast on $expand * Update README.md (#226) * Update README.md * Update README.md * Update README.md * Update README.md * Bugfix/no constranis with ignored properties (#227) * Suppress referential constraint for properties that shall be ignored * No Referential Constraint in case one property shall be ignored * Bugfix/no constranis with ignored properties (#228) * Suppress referential constraint for properties that shall be ignored * No Referential Constraint in case one property shall be ignored * Bring back error on ignored * Enable subtyping for complex types (#229) * Support transient properties that require ignored properties (#231) * Bugfix/multi level inheritance (#232) * Adopt process-cb * Correct type converter problem * Use dbType for tuple result mapping * Bugfix/multi level inheritance (#233) * Adopt process-cb * Correct type converter problem * Use dbType for tuple result mapping * Correct constructor test of extension * Feature/release 1.0.3 (#234) * Correct typos and replace deprecated method * Set final Version * Feature/release 1.0.4 (#235) * Error corrections - Debugger did not work - Transaction Factory not forwarded * Update release * Make debugger runtime a List * Feature/release 1.0.4 (#236) * Error corrections - Debugger did not work - Transaction Factory not forwarded * Update release * Make debugger runtime a List * Small adoptions to test model * Reset change * Error correction (#237) * Feature/release 1.0.5 (#238) * Error corrections - Debugger did not work - Transaction Factory not forwarded * Update release * Make debugger runtime a List * Small adoptions to test model * Reset change * Add test for criteria builder * Error correction * Update H2 version (#239) * Update H2 version * Update version and rework DataSourceHelper * Feature/release 1.0.6 (#240) * Update H2 version * Update version and rework DataSourceHelper * Let H2 run in Postges mode * Upgrade version and additional tests (#241) * Upgrade version and additional tests * Remove session context from queries * Remove session context from processors * New version an sonar clean-up (#242) * Release 1.1.0 (#243) * New version an sonar clean-up * New path property fro sonar * Release 1.1.0 (#244) * New version an sonar clean-up * New path property fro sonar * Test usage of SNAPSHOT version * Update version -> 1.0.8 (#245) * Update version -> 1.0.8 * Count queries support Integer as result * Update archetype to support PATCH * Feature/release 1.0.0 (#247) * New version an sonar clean-up * New path property fro sonar * Test usage of SNAPSHOT version * Adopt archetype * Clean-up sonar issues * Resolve more sonar issues * Introducing virtual property * Clean-up associations and enable default columns when using ...-cb * Generate metadata for overloaded action (#248) * Generate metadata for overloaded action * Process action * Feature/action overload (#249) * Generate metadata for overloaded action * Process action * Use constructor of entity type an action is called for not of binding * Fix claims in collections where (#250) * Fix claims in collections where * Missed interface definition * Missed test renaming * Use dbtype to build key pair in case a conversion exists (#251) * Enable more constructors for binding parameter (#252) * Feature/more flexable constructor determination operations (#253) * Enable more constructors for binding parameter * Clean-up sonar errors * New type cast added - they have possible data lost (#254) * Collection not longer retrieves transient (#255) * Collection not longer retrieves transient * Skip unit test * Enhance partner determination (#256) * Bugfix/reuse navigation as partner (#257) * Enhance partner determination * Remove one sonar issue * Bugfix/reuse navigation as partner (#258) * Enhance partner determination * Remove one sonar issue * Correct partner determination * No fallback to server locale in case bundle requested local not found (#259) * Replace file reader (#260) * Replace fixed values by variables (#261) * Bugfix/error in controller test (#262) * Replace fixed values by variables * Update pom * Update archetype pom (#265) * Defect/archetype pom (#266) * Update archetype pom * Check older version of sonar plugin * Update test constants (#267) * Correct count behavior (#268) * Change to Java 11 (#269) * Change to Java 11 * Revert Java 11 * Update README.md (#270) * Defect/count not working on hana (#271) * Correct count behavior * Ignore sonar check. Equal methods are generated * Defect/java function parameter name (#272) * Raise meaningful exception on empty function parameter name * Meaningful exception if action parameter name empty * Raise message in case of Void as return type for functions (#273) * Raise message in case of Void as return type for functions * Add extenstion * Error in if clause * Extension of visitor for java functions (#274) * Update version to 1.1.1 (#275) * Feature/predefined annotations (#276) * First list of predefined OData annotations * Update capabilities annotations * Sonar issues * Update dependencies * Adopt layer test * Add page to order by builder (#277) * Feature/predefined annotations (#278) * First list of predefined OData annotations * Update capabilities annotations * Sonar issues * Update dependencies * Adopt layer test * Intermediate state * Clean-up basic mapping and introduce annotation * Finalize annotation mapper * Feature/predefined annotations (#279) * First list of predefined OData annotations * Update capabilities annotations * Sonar issues * Update dependencies * Adopt layer test * Intermediate state * Clean-up basic mapping and introduce annotation * Finalize annotation mapper * Update dependency * Feature/predefined annotations (#280) * First list of predefined OData annotations * Update capabilities annotations * Sonar issues * Update dependencies * Adopt layer test * Intermediate state * Clean-up basic mapping and introduce annotation * Finalize annotation mapper * Update dependency * Adopt HSQLDB * Update .xmake.cfg (#283) * Update .xmake.cfg * Update .xmake.cfg * Correct spelling (#281) * Feature/predefined annotations (#282) * First list of predefined OData annotations * Update capabilities annotations * Sonar issues * Update dependencies * Adopt layer test * Intermediate state * Clean-up basic mapping and introduce annotation * Finalize annotation mapper * Update dependency * Adopt HSQLDB * Rename package for vocabulary parser * Move OData vocabularies to new module * Merge tests * Annotations in metadata * Finalize annotations within metadata * Update .xmake.cfg (#284) * Update .xmake.cfg * Update .xmake.cfg * Feature/predefined annotations (#286) * First list of predefined OData annotations * Update capabilities annotations * Sonar issues * Update dependencies * Adopt layer test * Intermediate state * Clean-up basic mapping and introduce annotation * Finalize annotation mapper * Update dependency * Adopt HSQLDB * Rename package for vocabulary parser * Move OData vocabularies to new module * Merge tests * Annotations in metadata * Finalize annotations within metadata * Support sorting restrictions by annotation * Build annotation based expand check * Expand star via path reads only requested from database * Check countability * Support collection property count * Annotations@Singeltons * Provide AnnotationProvider to all model elements * Enable annotations at properties * Feature/predefined annotations (#287) * First list of predefined OData annotations * Update capabilities annotations * Sonar issues * Update dependencies * Adopt layer test * Intermediate state * Clean-up basic mapping and introduce annotation * Finalize annotation mapper * Update dependency * Adopt HSQLDB * Rename package for vocabulary parser * Move OData vocabularies to new module * Merge tests * Annotations in metadata * Finalize annotations within metadata * Support sorting restrictions by annotation * Build annotation based expand check * Expand star via path reads only requested from database * Check countability * Support collection property count * Annotations@Singeltons * Provide AnnotationProvider to all model elements * Enable annotations at properties * Enable access to annotations via JPARequestEntity * Watch filtering * Clean-up pom and typo correction (#288) * Solve issue 212 and fix permission check issue with join tables (#289) * Solve issue 212 and fix permission check issue with join tables * $count for collection join as well * Clean-up code * Fix issue, converting type (#290) * Fix issue, converting type * Fix unit test * Correct type determination * Multi step Mapped Superclass (#291) * Upgrade H2 to 2.2.220 (#293) * Correct navigation to one is null (#294) * Fix join column determination with cyclic dependency (#295) * Fix join column determination with cyclic dependency * Additional test for navigation with mapped join table * Make OneToOne required and handle non JPA Processor errors in metadata (#296) * Defect/issue214 (#297) * Make OneToOne required and handle non JPA Processor errors in metadata * Clean-up warnings * Defect/issue214 (#298) * Make OneToOne required and handle non JPA Processor errors in metadata * Clean-up warnings * Add check for OneToMany * Clean-up sonar issues * Update .xmake.cfg (#300) * Update version to 2.0.0 (#299) * Update version to 2.0.0 * Unify junit version * Make use of Jakarta * Clean-up sonar issues created by switching to Java 17 * Update dependencies * Update dependencies and clean-up typos * Add unit tests * Increase test coverage * Defect/issue226 (#301) * Update version * Adopt correction done for release * Clean-up archetype * Clean-up error * Add test to check Olingo not supporting $expand...($level=; $expand...) * Note thrown runtime exception * First level of COUNT query as IN (#302) * Defect/issues239 (#303) * First level of COUNT query as IN * Deletion of JPACollectionFilterQuery * Defect/issues239 (#304) * First level of COUNT query as IN * Deletion of JPACollectionFilterQuery * Handle null where conditions * Rename properties file to prevent name clashes (#305) * Rename properties file to prevent name clashes * Update dependency, resolve conflict with Olingo * Clean-up pom and some code (#306) * Defect/pom cleanup (#307) * Clean-up pom and some code * Update dependencies * Archetype and SOnar clean-up (#308) * Add protection where to collection query (#309) * Check if cast is necessary (#310) * Set version to 2.0.2 (#311) * Update to Olingo 5.0.0 and remove wrapper (#314) * Extend interface for server driven paging (#313) * Extend interface for server driven paging * Correct failing tests * Clean-ups * Sonar clean-up * Change default implementation * Enhance paging provider interface * Finalize paging provider * Feature/enhancement of paging (#315) * Extend interface for server driven paging * Correct failing tests * Clean-ups * Sonar clean-up * Change default implementation * Enhance paging provider interface * Finalize paging provider * Use synchronized instead of thread save collections * First version and some clean-ups (#312) * First version and some clean-ups * Update filter restriction and new query directives * New query implementation for count (#316) * Feature/annotation api extension (#317) * First step * Enhancement with path expressions * Re-factor annotation search * Get annotation value from property * Prepare test of EntityType and NavigationProperty * Finalize annotation API * Update project files (#318) * Add null check for NOT IN clauses (#319) * Fix $count problem with collection properties (#320) * Fix $count problem with collection properties * Handle navigation filter query for collection properties * Defect/use raw uri for vocabularies (#321) * Update urls * update pom * Defect/use raw uri for vocabularies (#322) * Update urls * update pom * Defect/missing paging expand with cp (#323) * Introduction of default paging provider * Skip token shall be null * Handle LIMIT and OFFSET with TypedQuery (#324) * Defect/eclipse link hana problem (#325) * Handle LIMIT and OFFSET with TypedQuery * Update default for LIMIT * Check restrictions on IN clause (#326) * Correct NPE if enum property is nullable (#327) * Update README.md (#328) * Update README.md * Update README.md * Update version to 2.2.0-SNAPSHOT * Fix duplicates result multi expands issue#292 (#329) * Defect/enumeration convertion error (#330) * Update Version * Make use of @Enumerated and fix order issue * Suppress generation of defaults for LIMIT and OFFSET (#331) * Enable order by to one associations (#333) * Enhanced ETag handling for GET entity set requests (#332) * Enhanced ETag handling for GET entity set requests * Prepare ETag handling for functions and actions * Cleanup test for filter using db functions * Fix unit tests * ETag at Functions and Actions * Defect/orderby navigation to on (#334) * Enable order by to one associations * Group By at sub queries * Correct collection property select clause issues (#335) * Enable nested lambda expressions (#336) * Fix problem with expand and lambda to collection (#337) * Set version to 2.1.3 (#338) * Feature/release 2.1.3 (#339) * Set version to 2.1.3 * Update readme * Enable weak etags * Add word * Generalize unit test * Feature/release 2.1.3 (#340) * Set version to 2.1.3 * Update readme * Enable weak etags * Add word * Generalize unit test * Adopt validation header check to potential lower case conversion * Update to version 2.1.4 (#341) * Update to version 2.2.0 (#342) * Defect/issue 330 (#343) * Add derby as db option and allow integer as count value * Merge remote-tracking branch 'origin/master' into defect/issue-330 * Defect/issue 330 (#344) * Add derby as db option and allow integer as count value * Merge remote-tracking branch 'origin/master' into defect/issue-330 * Enable database specific conversion of cb * Update test * Fix issue with orderby and description properties (#345) - re-think orderby creation * Add test * Update gitignore * Handle missed changes * clean-up * Update artifact pom * Clean-up duplicate code --- additionalWords.directory | 4 + jpa-archetype/pom.xml | 2 +- jpa/.gitignore | 4 +- jpa/odata-jpa-annotation/pom.xml | 1 - .../core/edm/annotation/EdmTransient.java | 1 - .../edm/mapper/api/JPAStructuredType.java | 2 + .../exception/ODataJPAModelException.java | 1 + .../impl/IntermediateDescriptionProperty.java | 1 + .../metadata-exceptions-i18n.properties | 1 + .../impl/IntermediateSimplePropertyTest.java | 6 +- jpa/odata-jpa-odata-vocabularies/pom.xml | 2 +- .../odata/v4/core/terms/GeneralProperty.java | 19 + .../cb/api/EntityManagerFactoryWrapper.java | 16 +- .../cb/exceptions/SqlParameterException.java | 14 + .../processor/cb/impl/CollectionJoinImpl.java | 7 +- .../cb/impl/CriteriaBuilderImpl.java | 74 +- .../processor/cb/impl/CriteriaQueryImpl.java | 39 +- .../cb/impl/EntityManagerWrapper.java | 24 +- .../jpa/processor/cb/impl/ExpressionImpl.java | 163 ++- .../jpa/processor/cb/impl/FromImpl.java | 22 +- .../jpa/processor/cb/impl/PathImpl.java | 40 +- .../processor/cb/impl/SqlDefaultPattern.java | 7 + .../processor/cb/impl/SqlPagingFunctions.java | 20 +- .../processor/cb/impl/SqlStringFunctions.java | 4 +- .../jpa/processor/cb/impl/SubqueryImpl.java | 6 +- .../api/EntityManagerFactoryWrapperTest.java | 66 +- .../cb/impl/CriteriaBuilderDerbyTest.java | 84 +- .../cb/impl/CriteriaBuilderH2Test.java | 2 +- .../cb/impl/CriteriaBuilderHSQLDBTest.java | 2 +- .../cb/impl/CriteriaBuilderImplTest.java | 14 +- .../cb/impl/CriteriaBuilderOverallTest.java | 44 +- .../cb/impl/CriteriaQueryImplTest.java | 9 +- .../cb/impl/EntityManagerWrapperTest.java | 2 +- .../processor/cb/impl/JoinTableJoinTest.java | 13 +- .../processor/cb/impl/PredicateImplTest.java | 2 +- .../jpa/processor/cb/impl/SimpleJoinTest.java | 2 +- .../processor/cb/impl/SubqueryImplTest.java | 2 +- .../processor/cb/ProcessorSqlFunction.java | 8 + .../processor/cb/ProcessorSqlOperator.java | 8 + .../processor/cb/ProcessorSqlParameter.java | 8 + .../jpa/processor/cb/ProcessorSqlPattern.java | 5 + .../cb/ProcessorSqlPatternProvider.java | 111 ++ jpa/odata-jpa-processor/.project | 6 - ....eclipse.wst.common.project.facet.core.xml | 2 +- .../core/api/JPAODataServiceContext.java | 18 +- .../api/JPAODataServiceContextBuilder.java | 8 + .../api/JPAODataSessionContextAccess.java | 3 + .../JPAAbstractDatabaseProcessor.java | 40 + .../database/JPADefaultDatabaseProcessor.java | 33 +- .../database/JPADerbySqlPatternProvider.java | 56 + .../database/JPAHanaDatabaseProcessor.java | 44 + .../JPAODataDatabaseProcessorFactory.java | 12 +- .../JPAPostgresqlDatabaseProcessor.java | 28 + .../JPAPostgresqlSqlPatternProvider.java | 27 + .../database/JPA_DERBY_DatabaseProcessor.java | 61 +- .../JPA_HSQLDB_DatabaseProcessor.java | 32 +- .../JPA_POSTSQL_DatabaseProcessor.java | 35 +- .../ODataJPAIllegalArgumentException.java | 22 + .../core/modify/JPAConversionHelper.java | 109 +- .../processor/JPACUDRequestProcessor.java | 22 +- .../core/processor/JPACoreDebugger.java | 2 +- .../JPAAbstractProcessorAttributeImpl.java | 79 ++ .../properties/JPACollectionProperty.java | 5 + .../properties/JPAOrderByPropertyFactory.java | 142 +++ .../properties/JPAProcessorAttribute.java | 78 ++ .../JPAProcessorCountAttribute.java | 5 + .../JPAProcessorCountAttributeImpl.java | 99 ++ .../JPAProcessorDescriptionAttribute.java | 5 + .../JPAProcessorDescriptionAttributeImpl.java | 132 +++ .../JPAProcessorNavigationAttribute.java | 5 + .../JPAProcessorSimpleAttribute.java | 5 + .../JPAProcessorSimpleAttributeImpl.java | 84 ++ .../core/query/ExpressionUtility.java | 40 +- .../core/query/JPAAbstractJoinQuery.java | 83 +- .../core/query/JPAAbstractQuery.java | 124 +- .../core/query/JPACollectionJoinQuery.java | 2 +- .../core/query/JPAExpandFilterQuery.java | 22 +- .../core/query/JPAExpandItemInfoFactory.java | 18 +- .../core/query/JPAExpandJoinCountQuery.java | 3 +- .../core/query/JPAExpandJoinQuery.java | 10 +- .../core/query/JPAExpandSubCountQuery.java | 3 +- .../core/query/JPAExpandSubQuery.java | 40 +- .../core/query/JPAJoinCountQuery.java | 2 +- .../processor/core/query/JPAJoinQuery.java | 19 +- .../core/query/JPAOrderByBuilder.java | 281 ++--- .../core/query/JPARowNumberFilterQuery.java | 8 +- .../jpa/processor/core/query/Utility.java | 8 - .../processor-exceptions-i18n.properties | 1 + .../core/api/JPAODataContextAccessDouble.java | 18 +- .../JPAODataServiceContextBuilderTest.java | 26 +- .../api/JPAODataSessionContextAccessTest.java | 5 + .../JPAHanaDatabaseProcessorTest.java | 27 + .../JPAODataDatabaseProcessorFactoryTest.java | 2 +- .../JPAPostgresqlDatabaseProcessorTest.java | 43 + .../JPAPostgresqlSqlPatternProviderTest.java | 30 + .../filter/TestJPACustomScalarFunctions.java | 142 --- .../core/filter/TestScalarDbFunctions.java | 8 - .../AbstractJPAProcessorAttributeTest.java | 55 + .../JPAOrderByPropertyFactoryTest.java | 295 +++++ .../JPAProcessorCountAttributeImplTest.java | 157 +++ ...ProcessorDescriptionAttributeImplTest.java | 289 +++++ .../JPAProcessorSimpleAttributeImplTest.java | 253 ++++ .../core/query/JPAAbstractQueryTest.java | 4 +- .../core/query/JPAOrderByBuilderTest.java | 298 ++--- .../query/TestJPAExpandQueryCreateResult.java | 2 +- .../core/query/TestJPAProcessorExpand.java | 22 +- .../TestJPAQueryBuildSelectionPathList.java | 2 +- .../core/query/TestJPAQueryFromClause.java | 79 +- .../core/query/TestJPAQueryOrderByClause.java | 45 +- .../core/query/TestJPAQuerySelectClause.java | 14 +- .../core/query/TestJPAQueryTopSkip.java | 1 + .../core/query/TestJPAServerDrivenPaging.java | 1 + .../core/query/TestTopSkipCountOnDerby.java | 86 ++ .../TestFunctionActionConstructor.java | 1 - .../core/util/IntegrationTestHelper.java | 35 +- .../jpa/processor/core/util/TestBase.java | 24 +- .../processor/core/util/TestGroupBase.java | 17 +- .../processor/core/util/TestQueryBase.java | 19 +- jpa/odata-jpa-test/pom.xml | 12 + .../core/testmodel/DataSourceHelper.java | 2 +- .../testmodel/TimestampLongConverter.java | 2 +- .../db/migration/V1_1__SchemaMigration.java | 6 + .../main/resources/META-INF/persistence.xml | 1 - .../resources/db/migration/P_V1_0__olingo.sql | 1020 +++++++++++++++++ .../resources/db/migration/V1_0__olingo.sql | 1 + .../test/TimestampLongConverterTest.java | 18 + jpa/pom.xml | 18 +- 127 files changed, 4496 insertions(+), 1309 deletions(-) create mode 100644 jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/core/terms/GeneralProperty.java create mode 100644 jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/exceptions/SqlParameterException.java create mode 100644 jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/SqlDefaultPattern.java create mode 100644 jpa/odata-jpa-processor-ext/src/main/java/com/sap/olingo/jpa/processor/cb/ProcessorSqlFunction.java create mode 100644 jpa/odata-jpa-processor-ext/src/main/java/com/sap/olingo/jpa/processor/cb/ProcessorSqlOperator.java create mode 100644 jpa/odata-jpa-processor-ext/src/main/java/com/sap/olingo/jpa/processor/cb/ProcessorSqlParameter.java create mode 100644 jpa/odata-jpa-processor-ext/src/main/java/com/sap/olingo/jpa/processor/cb/ProcessorSqlPattern.java create mode 100644 jpa/odata-jpa-processor-ext/src/main/java/com/sap/olingo/jpa/processor/cb/ProcessorSqlPatternProvider.java create mode 100644 jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPADerbySqlPatternProvider.java create mode 100644 jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPAHanaDatabaseProcessor.java create mode 100644 jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPAPostgresqlDatabaseProcessor.java create mode 100644 jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPAPostgresqlSqlPatternProvider.java create mode 100644 jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/exception/ODataJPAIllegalArgumentException.java create mode 100644 jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAAbstractProcessorAttributeImpl.java create mode 100644 jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPACollectionProperty.java create mode 100644 jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAOrderByPropertyFactory.java create mode 100644 jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorAttribute.java create mode 100644 jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorCountAttribute.java create mode 100644 jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorCountAttributeImpl.java create mode 100644 jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorDescriptionAttribute.java create mode 100644 jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorDescriptionAttributeImpl.java create mode 100644 jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorNavigationAttribute.java create mode 100644 jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorSimpleAttribute.java create mode 100644 jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorSimpleAttributeImpl.java create mode 100644 jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/database/JPAHanaDatabaseProcessorTest.java create mode 100644 jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/database/JPAPostgresqlDatabaseProcessorTest.java create mode 100644 jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/database/JPAPostgresqlSqlPatternProviderTest.java delete mode 100644 jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/TestJPACustomScalarFunctions.java create mode 100644 jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/properties/AbstractJPAProcessorAttributeTest.java create mode 100644 jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/properties/JPAOrderByPropertyFactoryTest.java create mode 100644 jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorCountAttributeImplTest.java create mode 100644 jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorDescriptionAttributeImplTest.java create mode 100644 jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorSimpleAttributeImplTest.java create mode 100644 jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestTopSkipCountOnDerby.java create mode 100644 jpa/odata-jpa-test/src/main/resources/db/migration/P_V1_0__olingo.sql create mode 100644 jpa/odata-jpa-test/src/test/java/com/sap/olingo/jpa/processor/test/TimestampLongConverterTest.java diff --git a/additionalWords.directory b/additionalWords.directory index e58801bab..b1b660860 100644 --- a/additionalWords.directory +++ b/additionalWords.directory @@ -65,3 +65,7 @@ MULTI Asc Validator olingo +Concat +Postgresql +Hana +Desc diff --git a/jpa-archetype/pom.xml b/jpa-archetype/pom.xml index 89e24aeee..84c024b5e 100644 --- a/jpa-archetype/pom.xml +++ b/jpa-archetype/pom.xml @@ -15,6 +15,6 @@ - odata-jpa-archetype-spring + odata-jpa-archetype-spring \ No newline at end of file diff --git a/jpa/.gitignore b/jpa/.gitignore index 9ecd51296..f702fd365 100644 --- a/jpa/.gitignore +++ b/jpa/.gitignore @@ -8,4 +8,6 @@ target/ # --- EclipseIDE stuff START .settings/ .metadata -*.log \ No newline at end of file +*.log +/.dbeaver/ +/old.project diff --git a/jpa/odata-jpa-annotation/pom.xml b/jpa/odata-jpa-annotation/pom.xml index c37037e57..4034593b9 100644 --- a/jpa/odata-jpa-annotation/pom.xml +++ b/jpa/odata-jpa-annotation/pom.xml @@ -1,4 +1,3 @@ - getAttribute(@Nonnull final UriResourceProperty ur */ public List getDeclaredCollectionAttributes() throws ODataJPAModelException; + @CheckForNull public JPAPath getPath(final String externalName) throws ODataJPAModelException; + @CheckForNull public JPAPath getPath(final String externalName, final boolean respectIgnore) throws ODataJPAModelException; /** diff --git a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/exception/ODataJPAModelException.java b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/exception/ODataJPAModelException.java index 62e27d70c..801841998 100644 --- a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/exception/ODataJPAModelException.java +++ b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/exception/ODataJPAModelException.java @@ -86,6 +86,7 @@ public enum MessageKeys implements ODataJPAMessageKey { NO_JOIN_TABLE_TYPE, PATH_ELEMENT_NOT_FOUND, PATH_ELEMENT_NOT_EMBEDDABLE, + PATH_NOT_FOUND, DB_TYPE_NOT_DETERMINED, FILE_NOT_FOUND, MISSING_ONE_TO_ONE_ANNOTATION, diff --git a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateDescriptionProperty.java b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateDescriptionProperty.java index 65ebc3c30..a13d4c6a0 100644 --- a/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateDescriptionProperty.java +++ b/jpa/odata-jpa-metadata/src/main/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateDescriptionProperty.java @@ -195,6 +195,7 @@ public JPAAssociationPath getPath() throws ODataJPAModelException { } private class AssociationPath implements JPAAssociationPath { + private List joinColumns = null; @Override diff --git a/jpa/odata-jpa-metadata/src/main/resources/metadata-exceptions-i18n.properties b/jpa/odata-jpa-metadata/src/main/resources/metadata-exceptions-i18n.properties index c96ff251b..8186a6b8d 100644 --- a/jpa/odata-jpa-metadata/src/main/resources/metadata-exceptions-i18n.properties +++ b/jpa/odata-jpa-metadata/src/main/resources/metadata-exceptions-i18n.properties @@ -84,6 +84,7 @@ ODataJPAModelException.EXTENSION_PROVIDER_WRONG_PARAMETER = The constructor of q ODataJPAModelException.NAVI_PROPERTY_NOT_FOUND = Could not determine the target for navigation property '%1$s' of type '%2$s'. ODataJPAModelException.PATH_ELEMENT_NOT_FOUND = Element '%1$s' of path '%2$s' not found. ODataJPAModelException.PATH_ELEMENT_NOT_EMBEDDABLE = Element '%1$s' of path '%2$s' should be an embeddable type. +ODataJPAModelException.PATH_NOT_FOUND = Path for '%1$s' not found. ODataJPAModelException.REFERENCED_PROPERTY_NOT_FOUND = Error when creating Referential Constraints for '%1$s': Property for '%2$s' not found at '%3$s'. ODataJPAModelException.INHERITANCE_NOT_ALLOWED = Abstract entity type '%1$s' must not inherit from a non-abstract entity type '%2$s'. diff --git a/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateSimplePropertyTest.java b/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateSimplePropertyTest.java index 1f5640747..9e9078161 100644 --- a/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateSimplePropertyTest.java +++ b/jpa/odata-jpa-metadata/src/test/java/com/sap/olingo/jpa/metadata/core/edm/mapper/impl/IntermediateSimplePropertyTest.java @@ -59,7 +59,7 @@ import com.sap.olingo.jpa.metadata.core.edm.mapper.extension.IntermediateReferenceList; import com.sap.olingo.jpa.metadata.core.edm.mapper.util.AnnotationTestHelper; import com.sap.olingo.jpa.metadata.core.edm.mapper.util.MemberDouble; -import com.sap.olingo.jpa.metadata.odata.v4.core.terms.GenaralProperty; +import com.sap.olingo.jpa.metadata.odata.v4.core.terms.GeneralProperty; import com.sap.olingo.jpa.metadata.odata.v4.core.terms.Terms; import com.sap.olingo.jpa.metadata.odata.v4.general.Aliases; import com.sap.olingo.jpa.metadata.odata.v4.provider.JavaBasedCoreAnnotationsProvider; @@ -971,7 +971,7 @@ void checkGetAnnotationValueSimpleProperty() throws ODataJPAModelException { final Attribute jpaAttribute = helper.getAttribute(helper.getEntityType( AnnotationsParent.class), "area"); final IntermediateProperty property = new IntermediateSimpleProperty(nameBuilder, jpaAttribute, helper.schema); - final var act = property.getAnnotationValue(Aliases.CORE, Terms.COMPUTED_DEFAULT_VALUE, GenaralProperty.VALUE, + final var act = property.getAnnotationValue(Aliases.CORE, Terms.COMPUTED_DEFAULT_VALUE, GeneralProperty.VALUE, Boolean.class); assertNotNull(act); assertTrue(act); @@ -984,7 +984,7 @@ void checkGetAnnotationVaueReturnsNullAliasUnknown() throws ODataJPAModelExcepti AnnotationsParent.class), "area"); final IntermediateProperty property = new IntermediateSimpleProperty(nameBuilder, jpaAttribute, helper.schema); assertNull(property.getAnnotationValue(Aliases.CAPABILITIES.alias(), Terms.COMPUTED_DEFAULT_VALUE.term(), - GenaralProperty.VALUE.property())); + GeneralProperty.VALUE.property())); } @Test diff --git a/jpa/odata-jpa-odata-vocabularies/pom.xml b/jpa/odata-jpa-odata-vocabularies/pom.xml index 5874fee54..9d701a73a 100644 --- a/jpa/odata-jpa-odata-vocabularies/pom.xml +++ b/jpa/odata-jpa-odata-vocabularies/pom.xml @@ -1,4 +1,4 @@ - + 4.0.0 com.sap.olingo diff --git a/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/core/terms/GeneralProperty.java b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/core/terms/GeneralProperty.java new file mode 100644 index 000000000..dbb2d0fad --- /dev/null +++ b/jpa/odata-jpa-odata-vocabularies/src/main/java/com/sap/olingo/jpa/metadata/odata/v4/core/terms/GeneralProperty.java @@ -0,0 +1,19 @@ +package com.sap.olingo.jpa.metadata.odata.v4.core.terms; + +import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.PropertyAccess; + +public enum GeneralProperty implements PropertyAccess { + VALUE("Value"); + + private final String property; + + GeneralProperty(final String property) { + this.property = property; + } + + @Override + public String property() { + return property; + } + +} diff --git a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/api/EntityManagerFactoryWrapper.java b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/api/EntityManagerFactoryWrapper.java index b136bf793..7f2f0cc67 100644 --- a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/api/EntityManagerFactoryWrapper.java +++ b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/api/EntityManagerFactoryWrapper.java @@ -13,42 +13,46 @@ import jakarta.persistence.metamodel.Metamodel; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAServiceDocument; +import com.sap.olingo.jpa.processor.cb.ProcessorSqlPatternProvider; import com.sap.olingo.jpa.processor.cb.impl.EntityManagerWrapper; public final class EntityManagerFactoryWrapper implements EntityManagerFactory { private final EntityManagerFactory emf; private final JPAServiceDocument sd; + private final ProcessorSqlPatternProvider pattern; - public EntityManagerFactoryWrapper(final EntityManagerFactory emf, final JPAServiceDocument sd) { + public EntityManagerFactoryWrapper(final EntityManagerFactory emf, final JPAServiceDocument sd, + final ProcessorSqlPatternProvider pattern) { super(); this.emf = emf; this.sd = sd; + this.pattern = pattern; } @Override public EntityManager createEntityManager() { - return new EntityManagerWrapper(emf.createEntityManager(), sd); + return new EntityManagerWrapper(emf.createEntityManager(), sd, pattern); } @Override public EntityManager createEntityManager(@SuppressWarnings("rawtypes") final Map map) { - return new EntityManagerWrapper(emf.createEntityManager(map), sd); + return new EntityManagerWrapper(emf.createEntityManager(map), sd, pattern); } @Override public EntityManager createEntityManager(final SynchronizationType synchronizationType) { - return new EntityManagerWrapper(emf.createEntityManager(synchronizationType), sd); + return new EntityManagerWrapper(emf.createEntityManager(synchronizationType), sd, pattern); } @Override public EntityManager createEntityManager(final SynchronizationType synchronizationType, @SuppressWarnings("rawtypes") final Map map) { - return new EntityManagerWrapper(emf.createEntityManager(synchronizationType, map), sd); + return new EntityManagerWrapper(emf.createEntityManager(synchronizationType, map), sd, pattern); } @Override public CriteriaBuilder getCriteriaBuilder() { - try (EntityManager em = new EntityManagerWrapper(emf.createEntityManager(), sd)) { + try (EntityManager em = new EntityManagerWrapper(emf.createEntityManager(), sd, pattern)) { return em.getCriteriaBuilder(); } } diff --git a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/exceptions/SqlParameterException.java b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/exceptions/SqlParameterException.java new file mode 100644 index 000000000..f95480921 --- /dev/null +++ b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/exceptions/SqlParameterException.java @@ -0,0 +1,14 @@ +package com.sap.olingo.jpa.processor.cb.exceptions; + +public class SqlParameterException extends RuntimeException { + + /** + * + */ + private static final long serialVersionUID = 3404311627671683055L; + + public SqlParameterException(final String message) { + super(message); + } + +} diff --git a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/CollectionJoinImpl.java b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/CollectionJoinImpl.java index 384b76fe2..030cc5c87 100644 --- a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/CollectionJoinImpl.java +++ b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/CollectionJoinImpl.java @@ -82,8 +82,11 @@ List> resolvePathElements() { try { if (!attribute.isComplex()) { final JPAStructuredType source = attribute.asAssociation().getSourceType(); - pathList.add(new PathImpl<>(source.getPath(attribute.asAssociation().getAlias()), - parent, st, tableAlias)); + final var associationPath = source.getPath(attribute.asAssociation().getAlias()); + if (associationPath != null) + pathList.add(new PathImpl<>(associationPath, parent, st, tableAlias)); + else + throw new IllegalStateException(); } else { final JPAStructuredType source = attribute.getStructuredType(); for (final JPAPath p : source.getPathList()) { diff --git a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaBuilderImpl.java b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaBuilderImpl.java index 30504eb67..ba8018db4 100644 --- a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaBuilderImpl.java +++ b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaBuilderImpl.java @@ -39,6 +39,7 @@ import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAServiceDocument; import com.sap.olingo.jpa.processor.cb.ProcessorCriteriaBuilder; import com.sap.olingo.jpa.processor.cb.ProcessorCriteriaQuery; +import com.sap.olingo.jpa.processor.cb.ProcessorSqlPatternProvider; import com.sap.olingo.jpa.processor.cb.exceptions.NotImplementedException; import com.sap.olingo.jpa.processor.cb.impl.ExpressionImpl.ParameterExpression; import com.sap.olingo.jpa.processor.cb.impl.PredicateImpl.BinaryExpressionPredicate.Operation; @@ -48,10 +49,13 @@ class CriteriaBuilderImpl implements ProcessorCriteriaBuilder { // NOSONAR private final JPAServiceDocument sd; private final ParameterBuffer parameter; + private final ProcessorSqlPatternProvider sqlPattern; - CriteriaBuilderImpl(final JPAServiceDocument sd, final ParameterBuffer parameterBuffer) { + CriteriaBuilderImpl(final JPAServiceDocument sd, final ParameterBuffer parameterBuffer, + final ProcessorSqlPatternProvider sqlPattern) { this.sd = sd; this.parameter = parameterBuffer; + this.sqlPattern = sqlPattern; } /** @@ -188,41 +192,41 @@ public Expression coalesce(@Nonnull final Expression x, * @return coalesce expression */ @Override - public Expression coalesce(@Nonnull final Expression x, @Nonnull final Y y) { - return new ExpressionImpl.CoalesceExpression().value(x).value(literal(y)); + public Expression coalesce(@Nonnull final Expression expression, @Nonnull final Y y) { + return new ExpressionImpl.CoalesceExpression().value(expression).value(literal(y)); } /** * Create an expression for string concatenation. * @param x string expression - * @param y string expression + * @param second string expression * @return expression corresponding to concatenation */ @Override - public Expression concat(@Nonnull final Expression x, @Nonnull final Expression y) { - return new ExpressionImpl.ConcatExpression(x, y); + public Expression concat(@Nonnull final Expression first, @Nonnull final Expression second) { + return new ExpressionImpl.ConcatExpression(first, second, sqlPattern); } /** * Create an expression for string concatenation. * @param x string expression - * @param y string + * @param value string * @return expression corresponding to concatenation */ @Override - public Expression concat(@Nonnull final Expression x, @Nonnull final String y) { - return new ExpressionImpl.ConcatExpression(x, literal(y)); + public Expression concat(@Nonnull final Expression expression, @Nonnull final String value) { + return new ExpressionImpl.ConcatExpression(expression, literal(value), sqlPattern); } /** * Create an expression for string concatenation. * @param x string - * @param y string expression + * @param expression string expression * @return expression corresponding to concatenation */ @Override - public Expression concat(@Nonnull final String x, @Nonnull final Expression y) { - return new ExpressionImpl.ConcatExpression(literal(x), y); + public Expression concat(@Nonnull final String value, @Nonnull final Expression expression) { + return new ExpressionImpl.ConcatExpression(literal(value), expression, sqlPattern); } /** @@ -287,17 +291,17 @@ public CriteriaUpdate createCriteriaUpdate(final Class targetEntity) { @Override public ProcessorCriteriaQuery createQuery() { - return new CriteriaQueryImpl<>(Object.class, sd, this); + return new CriteriaQueryImpl<>(Object.class, sd, this, sqlPattern); } @Override public ProcessorCriteriaQuery createQuery(final Class resultClass) { - return new CriteriaQueryImpl<>(resultClass, sd, this); + return new CriteriaQueryImpl<>(resultClass, sd, this, sqlPattern); } @Override public ProcessorCriteriaQuery createTupleQuery() { - return new CriteriaQueryImpl<>(Tuple.class, sd, this); + return new CriteriaQueryImpl<>(Tuple.class, sd, this, sqlPattern); } @Override @@ -724,7 +728,7 @@ public Predicate le(@Nonnull final Expression x, @Nonnull fina * @return least expression */ @Override - public > Expression least(@Nonnull final Expression x) { + public > Expression least(@Nonnull final Expression expression) { throw new NotImplementedException(); } @@ -734,8 +738,8 @@ public > Expression least(@Nonnull final Expr * @return length expression */ @Override - public Expression length(@Nonnull final Expression x) { - return new ExpressionImpl.UnaryFunctionalExpression<>(x, SqlStringFunctions.LENGTH); + public Expression length(@Nonnull final Expression expression) { + return new ExpressionImpl.LengthExpression(expression, sqlPattern); } /** @@ -874,13 +878,14 @@ private Expression literal(@Nonnull final T value, @Nonnull final Express * if found. * The first position in a string is denoted by 1. If the * string to be located is not found, 0 is returned. - * @param x expression for string to be searched + * @param expression expression for string to be searched * @param pattern expression for string to be located * @return expression corresponding to position */ @Override - public Expression locate(@Nonnull final Expression x, @Nonnull final Expression pattern) { - return new ExpressionImpl.LocateExpression(x, pattern, null); + public Expression locate(@Nonnull final Expression expression, + @Nonnull final Expression pattern) { + return new ExpressionImpl.LocateExpression(expression, pattern, null, sqlPattern); } /** @@ -889,15 +894,16 @@ public Expression locate(@Nonnull final Expression x, @Nonnull * if found. * The first position in a string is denoted by 1. If the * string to be located is not found, 0 is returned. - * @param x expression for string to be searched + * @param expression expression for string to be searched * @param pattern expression for string to be located * @param from expression for position at which to start search * @return expression corresponding to position */ @Override - public Expression locate(@Nonnull final Expression x, @Nonnull final Expression pattern, + public Expression locate(@Nonnull final Expression expression, + @Nonnull final Expression pattern, @Nonnull final Expression from) { - return new ExpressionImpl.LocateExpression(x, pattern, from); + return new ExpressionImpl.LocateExpression(expression, pattern, from, sqlPattern); } /** @@ -906,13 +912,13 @@ public Expression locate(@Nonnull final Expression x, @Nonnull * if found. * The first position in a string is denoted by 1. If the * string to be located is not found, 0 is returned. - * @param x expression for string to be searched + * @param expression expression for string to be searched * @param pattern string to be located * @return expression corresponding to position */ @Override - public Expression locate(@Nonnull final Expression x, @Nonnull final String pattern) { - return new ExpressionImpl.LocateExpression(x, literal(pattern, x), null); + public Expression locate(@Nonnull final Expression expression, @Nonnull final String pattern) { + return new ExpressionImpl.LocateExpression(expression, literal(pattern, expression), null, sqlPattern); } /** @@ -921,15 +927,15 @@ public Expression locate(@Nonnull final Expression x, @Nonnull * if found. * The first position in a string is denoted by 1. If the * string to be located is not found, 0 is returned. - * @param x expression for string to be searched + * @param expression expression for string to be searched * @param pattern string to be located * @param from position at which to start search * @return expression corresponding to position */ @Override - public Expression locate(@Nonnull final Expression x, @Nonnull final String pattern, + public Expression locate(@Nonnull final Expression expression, @Nonnull final String pattern, final int from) { - return new ExpressionImpl.LocateExpression(x, literal(pattern), literal(from)); + return new ExpressionImpl.LocateExpression(expression, literal(pattern), literal(from), sqlPattern); } /** @@ -1351,7 +1357,7 @@ public Expression sqrt(final Expression x) { */ @Override public Expression substring(@Nonnull final Expression x, @Nonnull final Expression from) { - return new ExpressionImpl.SubstringExpression(x, from, null); + return new ExpressionImpl.SubstringExpression(x, from, null, sqlPattern); } /** @@ -1367,7 +1373,7 @@ public Expression substring(@Nonnull final Expression x, @Nonnul @Override public Expression substring(@Nonnull final Expression x, @Nonnull final Expression from, @Nonnull final Expression len) { - return new ExpressionImpl.SubstringExpression(x, from, len); + return new ExpressionImpl.SubstringExpression(x, from, len, sqlPattern); } /** @@ -1381,7 +1387,7 @@ public Expression substring(@Nonnull final Expression x, @Nonnul */ @Override public Expression substring(@Nonnull final Expression x, @Nonnull final int from) { - return new ExpressionImpl.SubstringExpression(x, literal(from), null); + return new ExpressionImpl.SubstringExpression(x, literal(from), null, sqlPattern); } /** @@ -1396,7 +1402,7 @@ public Expression substring(@Nonnull final Expression x, @Nonnul */ @Override public Expression substring(final Expression x, final int from, final int len) { - return new ExpressionImpl.SubstringExpression(x, literal(from), literal(len)); + return new ExpressionImpl.SubstringExpression(x, literal(from), literal(len), sqlPattern); } /** diff --git a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaQueryImpl.java b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaQueryImpl.java index e73d72271..84212b94f 100644 --- a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaQueryImpl.java +++ b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaQueryImpl.java @@ -31,6 +31,7 @@ import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAServiceDocument; import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException; import com.sap.olingo.jpa.processor.cb.ProcessorCriteriaQuery; +import com.sap.olingo.jpa.processor.cb.ProcessorSqlPatternProvider; import com.sap.olingo.jpa.processor.cb.ProcessorSubquery; import com.sap.olingo.jpa.processor.cb.exceptions.InternalServerError; import com.sap.olingo.jpa.processor.cb.exceptions.NotImplementedException; @@ -53,12 +54,14 @@ class CriteriaQueryImpl implements ProcessorCriteriaQuery, SqlConvertible private Optional maxResults; private Optional firstResult; private final CriteriaBuilder cb; + private final ProcessorSqlPatternProvider sqlPattern; CriteriaQueryImpl(final Class clazz, final JPAServiceDocument sd, final AliasBuilder ab, - final CriteriaBuilder cb) { + final CriteriaBuilder cb, final ProcessorSqlPatternProvider sqlPattern) { super(); this.resultType = clazz; this.sd = sd; + this.sqlPattern = sqlPattern; this.where = Optional.empty(); this.orderList = Optional.empty(); this.groupBy = Optional.empty(); @@ -70,8 +73,9 @@ class CriteriaQueryImpl implements ProcessorCriteriaQuery, SqlConvertible this.selectAliasBuilder = new AliasBuilder("S"); } - CriteriaQueryImpl(final Class clazz, final JPAServiceDocument sd, final CriteriaBuilder cb) { - this(clazz, sd, new AliasBuilder(), cb); + CriteriaQueryImpl(final Class clazz, final JPAServiceDocument sd, final CriteriaBuilder cb, + final ProcessorSqlPatternProvider sqlPattern) { + this(clazz, sd, new AliasBuilder(), cb, sqlPattern); } @Override @@ -111,14 +115,17 @@ public StringBuilder asSQL(final StringBuilder statement) { .append(" "); ((SqlConvertible) e).asSQL(statement); }); - maxResults.ifPresent(limit -> statement.append(" ") - .append(SqlPagingFunctions.LIMIT) - .append(" ") - .append(limit)); - firstResult.ifPresent(offset -> statement.append(" ") - .append(SqlPagingFunctions.OFFSET) - .append(" ") - .append(offset)); + if (sqlPattern.maxResultsFirst()) { + maxResults.ifPresent(limit -> statement.append(" ") + .append(SqlPagingFunctions.LIMIT.toString(sqlPattern, limit))); + firstResult.ifPresent(offset -> statement.append(" ") + .append(SqlPagingFunctions.OFFSET.toString(sqlPattern, offset))); + } else { + firstResult.ifPresent(offset -> statement.append(" ") + .append(SqlPagingFunctions.OFFSET.toString(sqlPattern, offset))); + maxResults.ifPresent(limit -> statement.append(" ") + .append(SqlPagingFunctions.LIMIT.toString(sqlPattern, limit))); + } return statement; } @@ -261,8 +268,8 @@ public CriteriaQuery groupBy(final List> grouping) { */ @Override public CriteriaQuery having(final Expression restriction) { - final Predicate[] p = { (Predicate) restriction }; - return having(p); // NOSONAR + final Predicate[] predicate = { (Predicate) restriction }; + return having(predicate); // NOSONAR } /** @@ -279,12 +286,12 @@ public CriteriaQuery having(final Expression restriction) { */ @Override public CriteriaQuery having(final Predicate... restrictions) { - final Predicate p = restrictions.length > 1 + final Predicate predicate = restrictions.length > 1 ? cb.and(restrictions) : restrictions.length == 1 // NOSONAR ? restrictions[0] : null; - having = Optional.ofNullable(p); + having = Optional.ofNullable(predicate); return this; } @@ -405,7 +412,7 @@ public CriteriaQuery select(final Selection selection) { @Override public ProcessorSubquery subquery(@Nonnull final Class type) { - return new SubqueryImpl<>(type, this, aliasBuilder, cb); + return new SubqueryImpl<>(type, this, aliasBuilder, cb, sqlPattern); } /** diff --git a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/EntityManagerWrapper.java b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/EntityManagerWrapper.java index a0e4b6983..8d0aa0180 100644 --- a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/EntityManagerWrapper.java +++ b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/EntityManagerWrapper.java @@ -26,6 +26,7 @@ import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAServiceDocument; import com.sap.olingo.jpa.processor.cb.ProcessorCriteriaBuilder; +import com.sap.olingo.jpa.processor.cb.ProcessorSqlPatternProvider; import com.sap.olingo.jpa.processor.cb.exceptions.NotImplementedException; public class EntityManagerWrapper implements EntityManager { // NOSONAR @@ -34,11 +35,14 @@ public class EntityManagerWrapper implements EntityManager { // NOSONAR private final EntityManager em; private final JPAServiceDocument sd; private final ParameterBuffer parameterBuffer; + private final ProcessorSqlPatternProvider sqlPattern; - public EntityManagerWrapper(final EntityManager em, final JPAServiceDocument sd) { + public EntityManagerWrapper(final EntityManager em, final JPAServiceDocument sd, + final ProcessorSqlPatternProvider sqlPattern) { super(); this.em = em; this.sd = sd; + this.sqlPattern = sqlPattern != null ? sqlPattern : new SqlDefaultPattern(); this.cb = Optional.empty(); this.parameterBuffer = new ParameterBuffer(); } @@ -283,14 +287,14 @@ public Map getProperties() { /** * Create an instance of Query for executing a * Java Persistence query language statement. - * @param qlString a Java Persistence query string + * @param queryString a Java Persistence query string * @return the new query instance * @throws IllegalArgumentException if the query string is * found to be invalid */ @Override - public Query createQuery(final String qlString) { - return em.createQuery(qlString); + public Query createQuery(final String queryString) { + return em.createQuery(queryString); } /** @@ -341,7 +345,7 @@ public Query createQuery(@SuppressWarnings("rawtypes") final CriteriaDelete dele * The select list of the query must contain only a single * item, which must be assignable to the type specified by * the resultClass argument. - * @param qlString a Java Persistence query string + * @param queryString a Java Persistence query string * @param resultClass the type of the query result * @return the new query instance * @throws IllegalArgumentException if the query string is found @@ -350,7 +354,7 @@ public Query createQuery(@SuppressWarnings("rawtypes") final CriteriaDelete dele * @since Java Persistence 2.0 */ @Override - public TypedQuery createQuery(final String qlString, final Class resultClass) { + public TypedQuery createQuery(final String queryString, final Class resultClass) { throw new NotImplementedException(); } @@ -596,7 +600,7 @@ public Object getDelegate() { * provider-specific API. If the provider's EntityManager * implementation does not support the specified class, the * PersistenceException is thrown. - * @param cls the class of the object to be returned. This is + * @param clazz the class of the object to be returned. This is * normally either the underlying EntityManager implementation * class or an interface that it implements. * @return an instance of the specified class @@ -605,8 +609,8 @@ public Object getDelegate() { * @since Java Persistence 2.0 */ @Override - public T unwrap(final Class cls) { - return em.unwrap(cls); + public T unwrap(final Class clazz) { + return em.unwrap(clazz); } /** @@ -676,7 +680,7 @@ public ProcessorCriteriaBuilder getCriteriaBuilder() { if (!em.isOpen()) throw new IllegalStateException("Entity Manager had been closed"); return cb.orElseGet(() -> { - cb = Optional.of(new CriteriaBuilderImpl(sd, parameterBuffer)); + cb = Optional.of(new CriteriaBuilderImpl(sd, parameterBuffer, sqlPattern)); return cb.get(); }); } diff --git a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/ExpressionImpl.java b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/ExpressionImpl.java index 01fd2e907..5206338a1 100644 --- a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/ExpressionImpl.java +++ b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/ExpressionImpl.java @@ -7,6 +7,7 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.stream.Collectors; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -29,7 +30,12 @@ import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAPath; import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException; import com.sap.olingo.jpa.processor.cb.ProcessorCriteriaBuilder.WindowFunction; +import com.sap.olingo.jpa.processor.cb.ProcessorSqlFunction; +import com.sap.olingo.jpa.processor.cb.ProcessorSqlOperator; +import com.sap.olingo.jpa.processor.cb.ProcessorSqlParameter; +import com.sap.olingo.jpa.processor.cb.ProcessorSqlPatternProvider; import com.sap.olingo.jpa.processor.cb.exceptions.NotImplementedException; +import com.sap.olingo.jpa.processor.cb.exceptions.SqlParameterException; import com.sap.olingo.jpa.processor.cb.joiner.SqlConvertible; import com.sap.olingo.jpa.processor.cb.joiner.StringBuilderCollector; @@ -129,6 +135,17 @@ public Predicate isNull() { throw new NotImplementedException(); } + void checkParameterCount(final int act, final int max, final String function) { + if (act < max) + throw new SqlParameterException("Not all parameter supported for: " + function); + } + + void addParameter(final ProcessorSqlParameter parameter, final List parameterValues, + final SqlConvertible value) { + parameterValues.add(parameter.keyword()); + parameterValues.add(value.asSQL(new StringBuilder())); + } + static class AggregationExpression extends ExpressionImpl { private final SqlAggregation function; @@ -155,7 +172,7 @@ private StringBuilder expression(final StringBuilder statement) { statement .append(tableAlias) .append(DOT) - .append(from.st.getPath(keys.get(0).getExternalName()).getDBFieldName()); + .append(getJPAPath(from, keys).getDBFieldName()); } catch (final ODataJPAModelException e) { throw new IllegalArgumentException(e); } @@ -165,6 +182,15 @@ private StringBuilder expression(final StringBuilder statement) { } } + @Nonnull + private JPAPath getJPAPath(final FromImpl from, final List keys) throws ODataJPAModelException { + final var jpaPath = from.st.getPath(keys.get(0).getExternalName()); + if (jpaPath != null) + return jpaPath; + else + throw new IllegalStateException(); + } + SqlConvertible getExpression() { return expression; } @@ -176,10 +202,10 @@ static class ArithmeticExpression extends ExpressionImpl { private final SqlConvertible right; private final SqlArithmetic operation; - ArithmeticExpression(@Nonnull final Expression x, @Nonnull final Expression y, + ArithmeticExpression(@Nonnull final Expression expression, @Nonnull final Expression y, @Nonnull final SqlArithmetic operation) { - this.left = (SqlConvertible) Objects.requireNonNull(x); + this.left = (SqlConvertible) Objects.requireNonNull(expression); this.right = (SqlConvertible) Objects.requireNonNull(y); this.operation = Objects.requireNonNull(operation); } @@ -229,28 +255,42 @@ public Coalesce value(@Nonnull final T value) { static class ConcatExpression extends ExpressionImpl { private final SqlConvertible first; private final SqlConvertible second; + private final ProcessorSqlPatternProvider sqlPattern; - ConcatExpression(@Nonnull final Expression first, @Nonnull final Expression second) { + ConcatExpression(@Nonnull final Expression first, @Nonnull final Expression second, + final ProcessorSqlPatternProvider sqlPattern) { this.first = (SqlConvertible) Objects.requireNonNull(first); this.second = (SqlConvertible) Objects.requireNonNull(second); + this.sqlPattern = sqlPattern; } @Override public StringBuilder asSQL(final StringBuilder statement) { - statement.append(SqlStringFunctions.CONCAT) - .append(OPENING_BRACKET); - first.asSQL(statement) - .append(", "); - second.asSQL(statement); - return statement.append(CLOSING_BRACKET); + + final var concatenateClause = sqlPattern.getConcatenatePattern(); + if (concatenateClause instanceof final ProcessorSqlFunction function) { + statement.append(function.function()) + .append(OPENING_BRACKET); + first.asSQL(statement) + .append(function.parameters().get(1).keyword()); + second.asSQL(statement); + return statement.append(CLOSING_BRACKET); + } else { + final var operation = (ProcessorSqlOperator) concatenateClause; + statement.append(OPENING_BRACKET); + first.asSQL(statement) + .append(operation.parameters().get(1).keyword()); + second.asSQL(statement); + return statement.append(CLOSING_BRACKET); + } } } static class DistinctExpression extends ExpressionImpl { private final SqlConvertible left; - DistinctExpression(@Nonnull final Expression x) { - this.left = (SqlConvertible) Objects.requireNonNull(x); + DistinctExpression(@Nonnull final Expression expression) { + this.left = (SqlConvertible) Objects.requireNonNull(expression); } @Override @@ -304,27 +344,62 @@ static class LocateExpression extends ExpressionImpl { private final SqlConvertible expression; private final SqlConvertible pattern; private final Optional from; + private final ProcessorSqlPatternProvider sqlPattern; - LocateExpression(@Nonnull final Expression x, @Nonnull final Expression pattern, - final Expression from) { - this.expression = (SqlConvertible) Objects.requireNonNull(x); + LocateExpression(@Nonnull final Expression expression, @Nonnull final Expression pattern, + final Expression from, @Nonnull final ProcessorSqlPatternProvider sqlPattern) { + this.expression = (SqlConvertible) Objects.requireNonNull(expression); this.pattern = (SqlConvertible) Objects.requireNonNull(pattern); this.from = Optional.ofNullable((SqlConvertible) from); + this.sqlPattern = sqlPattern; } @Override public StringBuilder asSQL(final StringBuilder statement) { // LOCATE(, , ) - statement.append(SqlStringFunctions.LOCATE) + final var function = sqlPattern.getLocatePattern(); + checkParameterCount(function.parameters().size(), from.isPresent() ? 3 : 2, + SqlStringFunctions.LOCATE.toString()); + final List parameterValues = new ArrayList<>(5); + statement.append(function.function()) .append(OPENING_BRACKET); - pattern.asSQL(statement) - .append(", "); - expression.asSQL(statement); - from.ifPresent(l -> l.asSQL(statement.append(", "))); + + for (final var parameter : function.parameters()) { + switch (parameter.parameter()) { + case ProcessorSqlPatternProvider.SEARCH_STRING_PLACEHOLDER -> addParameter(parameter, parameterValues, + pattern); + case ProcessorSqlPatternProvider.VALUE_PLACEHOLDER -> addParameter(parameter, parameterValues, + expression); + case ProcessorSqlPatternProvider.START_PLACEHOLDER -> from.ifPresent(f -> addParameter(parameter, + parameterValues, f)); + default -> throw new IllegalArgumentException(parameter.parameter() + + " not supported for function LOCATE only supports function as pattern"); + } + } + statement.append(parameterValues.stream().collect(Collectors.joining())); return statement.append(CLOSING_BRACKET); } } + static class LengthExpression extends ExpressionImpl { + private final SqlConvertible left; + private final ProcessorSqlPatternProvider sqlPattern; + + LengthExpression(@Nonnull final Expression expression, @Nonnull final ProcessorSqlPatternProvider sqlPattern) { + this.left = (SqlConvertible) Objects.requireNonNull(expression); + this.sqlPattern = Objects.requireNonNull(sqlPattern); + } + + @Override + public StringBuilder asSQL(final StringBuilder statement) { + + final var function = sqlPattern.getLengthPattern(); + statement.append(function.function()).append(OPENING_BRACKET); + return left.asSQL(statement).append(CLOSING_BRACKET); + } + + } + static final class ParameterExpression extends ExpressionImpl implements Parameter { private final Integer index; @@ -339,10 +414,10 @@ static final class ParameterExpression extends ExpressionImpl implement this.jpaPath = Optional.empty(); } - ParameterExpression(final Integer i, final S value, @Nullable final Expression x) { + ParameterExpression(final Integer i, final S value, @Nullable final Expression expression) { this.index = i; this.value = value; - setPath(x); + setPath(expression); } @SuppressWarnings("unchecked") @@ -354,9 +429,9 @@ T getValue() { return (T) value; } - void setPath(@Nullable final Expression x) { - if (x instanceof PathImpl && ((PathImpl) x).path.isPresent()) { - jpaPath = Optional.of(((PathImpl) x).path.get()); // NOSONAR + void setPath(@Nullable final Expression expression) { + if (expression instanceof PathImpl && ((PathImpl) expression).path.isPresent()) { + jpaPath = Optional.of(((PathImpl) expression).path.get()); // NOSONAR converter = Optional.ofNullable(jpaPath.get().getLeaf().getConverter()); } else { this.converter = Optional.empty(); @@ -432,23 +507,37 @@ public StringBuilder asSQL(final StringBuilder statement) { static class SubstringExpression extends ExpressionImpl { private final SqlConvertible expression; private final SqlConvertible from; - private final Optional len; + private final Optional length; + private final ProcessorSqlPatternProvider sqlPattern; - SubstringExpression(@Nonnull final Expression x, @Nonnull final Expression from, - final Expression len) { - this.expression = (SqlConvertible) Objects.requireNonNull(x); + SubstringExpression(@Nonnull final Expression expression, @Nonnull final Expression from, + final Expression length, @Nonnull final ProcessorSqlPatternProvider sqlPattern) { + this.expression = (SqlConvertible) Objects.requireNonNull(expression); this.from = (SqlConvertible) Objects.requireNonNull(from); - this.len = Optional.ofNullable((SqlConvertible) len); + this.length = Optional.ofNullable((SqlConvertible) length); + this.sqlPattern = Objects.requireNonNull(sqlPattern); } @Override public StringBuilder asSQL(final StringBuilder statement) { - statement.append(SqlStringFunctions.SUBSTRING) + final var function = sqlPattern.getSubStringPattern(); + checkParameterCount(function.parameters().size(), length.isPresent() ? 3 : 2, + SqlStringFunctions.SUBSTRING.toString()); + final List parameterValues = new ArrayList<>(3); + statement.append(function.function()) .append(OPENING_BRACKET); - expression.asSQL(statement) - .append(", "); - from.asSQL(statement); - len.ifPresent(l -> l.asSQL(statement.append(", "))); + + for (final var parameter : function.parameters()) { + switch (parameter.parameter()) { + case ProcessorSqlPatternProvider.VALUE_PLACEHOLDER -> addParameter(parameter, parameterValues, expression); + case ProcessorSqlPatternProvider.START_PLACEHOLDER -> addParameter(parameter, parameterValues, from); + case ProcessorSqlPatternProvider.LENGTH_PLACEHOLDER -> length.ifPresent(f -> addParameter(parameter, + parameterValues, f)); + default -> throw new IllegalArgumentException(parameter.parameter() + + " not supported for function SUBSTRING"); + } + } + statement.append(parameterValues.stream().collect(Collectors.joining())); return statement.append(CLOSING_BRACKET); } } @@ -472,8 +561,8 @@ static class UnaryFunctionalExpression extends ExpressionImpl { private final SqlConvertible left; private final SqlStringFunctions function; - UnaryFunctionalExpression(@Nonnull final Expression x, @Nonnull final SqlStringFunctions function) { - this.left = (SqlConvertible) Objects.requireNonNull(x); + UnaryFunctionalExpression(@Nonnull final Expression expression, @Nonnull final SqlStringFunctions function) { + this.left = (SqlConvertible) Objects.requireNonNull(expression); this.function = Objects.requireNonNull(function); } diff --git a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/FromImpl.java b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/FromImpl.java index 22a4509d3..6d6feff42 100644 --- a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/FromImpl.java +++ b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/FromImpl.java @@ -383,18 +383,18 @@ public Join join(@Nonnull final String attributeName, final JoinTyp if (joinAttribute == null) throw new IllegalArgumentException(buildExceptionText(attributeName)); - final JPAPath joinPath = determinePath(joinAttribute); @SuppressWarnings("rawtypes") Join join; if (joinAttribute instanceof final JPADescriptionAttribute attribute) { final JoinType joinType = jt == null ? JoinType.LEFT : jt; final Optional path = Optional.ofNullable(attribute.asAssociationAttribute().getPath()); - join = new SimpleJoin<>(path.orElseThrow(() -> new IllegalArgumentException(buildExceptionText(attributeName))), + join = new SimpleJoin<>(path.orElseThrow(() -> new IllegalArgumentException(buildExceptionText( + attributeName))), joinType, determineParent(), aliasBuilder, cb); } else if (joinAttribute instanceof JPACollectionAttribute) { - join = new CollectionJoinImpl<>(joinPath, determineParent(), aliasBuilder, cb, jt); + join = new CollectionJoinImpl<>(getJPAPath(joinAttribute), determineParent(), aliasBuilder, cb, jt); } else if (joinAttribute.isComplex()) { - join = new PathJoin<>((FromImpl) determineParent(), joinPath, aliasBuilder, cb); + join = new PathJoin<>((FromImpl) determineParent(), getJPAPath(joinAttribute), aliasBuilder, cb); } else { final JoinType joinType = jt == null ? JoinType.INNER : jt; Optional associationPath; @@ -405,8 +405,9 @@ public Join join(@Nonnull final String attributeName, final JoinTyp associationPath = Optional.ofNullable(source.getAssociationPath(joinAttribute.getExternalName())); if (associationPath.orElseThrow(() -> new IllegalArgumentException(buildExceptionText(attributeName))) .hasJoinTable()) - join = new JoinTableJoin<>(associationPath.orElseThrow(() -> new IllegalArgumentException(buildExceptionText( - attributeName))), joinType, determineParent(), aliasBuilder, cb); + join = new JoinTableJoin<>(associationPath.orElseThrow(() -> new IllegalArgumentException( + buildExceptionText( + attributeName))), joinType, determineParent(), aliasBuilder, cb); else join = new SimpleJoin<>(associationPath.orElseThrow(() -> new IllegalArgumentException(buildExceptionText( attributeName))), joinType, determineParent(), aliasBuilder, cb); @@ -583,6 +584,15 @@ private JPAPath determinePath(final JPAAttribute joinAttribute) throws ODataJPAM return st.getPath(joinAttribute.getExternalName()); } + @Nonnull + private JPAPath getJPAPath(final JPAAttribute joinAttribute) throws ODataJPAModelException { + final var jpaPath = determinePath(joinAttribute); + if (jpaPath != null) + return jpaPath; + else + throw new IllegalStateException(); + } + private JPAAssociationAttribute getAssociation(final JPAStructuredType source, final String attributeName) { try { return source.getAssociation(attributeName); diff --git a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/PathImpl.java b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/PathImpl.java index 1c1510c85..875662033 100644 --- a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/PathImpl.java +++ b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/PathImpl.java @@ -59,8 +59,8 @@ class PathImpl extends ExpressionImpl implements Path { @Override public StringBuilder asSQL(final StringBuilder statement) { - tableAlias.ifPresent(p -> { - statement.append(p); + tableAlias.ifPresent(alias -> { + statement.append(alias); statement.append(DOT); }); path.ifPresent(p -> statement.append(p.getDBFieldName())); @@ -147,24 +147,48 @@ public Path get(final String attributeName) { } else { source = st; } - final JPAAttribute a = source.getDeclaredAttribute(attributeName) + final JPAAttribute attribute = source.getDeclaredAttribute(attributeName) .orElseThrow(() -> new IllegalArgumentException("'" + attributeName + "' not found at " + st .getInternalName())); if (this.path.isPresent()) { if (isKeyPath(path.get())) { - return new PathImpl<>(st.getPath(a.getExternalName()), Optional.of(this), st, tableAlias); + return createPathForKeyAttribute(attribute); } - final StringBuilder pathDescription = new StringBuilder(path.get().getAlias()).append(JPAPath.PATH_SEPARATOR) - .append(a.getExternalName()); - return new PathImpl<>(st.getPath(pathDescription.toString(), false), Optional.of(this), st, tableAlias); + return createPathForDescriptionAttribute(attribute); } else { - return new PathImpl<>(st.getPath(a.getExternalName(), false), Optional.of(this), st, tableAlias); + return createPathForAttribute(attribute); } } catch (final ODataJPAModelException e) { throw new IllegalArgumentException("'" + attributeName + "' not found", e); } } + private Path createPathForDescriptionAttribute(final JPAAttribute attribute) throws ODataJPAModelException { + final StringBuilder pathDescription = new StringBuilder(path.get().getAlias()).append(JPAPath.PATH_SEPARATOR) + .append(attribute.getExternalName()); + final var jpaPath = st.getPath(pathDescription.toString(), false); + if (jpaPath != null) + return new PathImpl<>(jpaPath, Optional.of(this), st, tableAlias); + else + throw new IllegalStateException(); + } + + private Path createPathForKeyAttribute(final JPAAttribute attribute) throws ODataJPAModelException { + final var jpaPath = st.getPath(attribute.getExternalName()); + if (jpaPath != null) + return new PathImpl<>(jpaPath, Optional.of(this), st, tableAlias); + else + throw new IllegalStateException(); + } + + private Path createPathForAttribute(final JPAAttribute attribute) throws ODataJPAModelException { + final var jpaPath = st.getPath(attribute.getExternalName(), false); + if (jpaPath != null) + return new PathImpl<>(jpaPath, Optional.of(this), st, tableAlias); + else + throw new IllegalStateException(); + } + private boolean isKeyPath(final JPAPath jpaPath) throws ODataJPAModelException { return st.getKeyPath() .stream() diff --git a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/SqlDefaultPattern.java b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/SqlDefaultPattern.java new file mode 100644 index 000000000..548a7652b --- /dev/null +++ b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/SqlDefaultPattern.java @@ -0,0 +1,7 @@ +package com.sap.olingo.jpa.processor.cb.impl; + +import com.sap.olingo.jpa.processor.cb.ProcessorSqlPatternProvider; + +class SqlDefaultPattern implements ProcessorSqlPatternProvider { + +} diff --git a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/SqlPagingFunctions.java b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/SqlPagingFunctions.java index 4348a5627..931babc12 100644 --- a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/SqlPagingFunctions.java +++ b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/SqlPagingFunctions.java @@ -1,20 +1,24 @@ package com.sap.olingo.jpa.processor.cb.impl; +import static com.sap.olingo.jpa.processor.cb.ProcessorSqlPatternProvider.VALUE_PLACEHOLDER; + +import com.sap.olingo.jpa.processor.cb.ProcessorSqlPatternProvider; + enum SqlPagingFunctions { - LIMIT("LIMIT", Integer.MAX_VALUE), - OFFSET("OFFSET", 0); + LIMIT(Integer.MAX_VALUE), + OFFSET(0); - private final String keyWord; private final int defaultValue; - private SqlPagingFunctions(final String keyWord, final int defaultValue) { - this.keyWord = keyWord; + private SqlPagingFunctions(final int defaultValue) { this.defaultValue = defaultValue; } - @Override - public String toString() { - return keyWord; + public String toString(final ProcessorSqlPatternProvider sqlPattern, final Integer value) { + if (this == LIMIT) { + return sqlPattern.getMaxResultsPattern().replace(VALUE_PLACEHOLDER, value.toString()); + } + return sqlPattern.getFirstResultPattern().replace(VALUE_PLACEHOLDER, value.toString()); } int defaultValue() { diff --git a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/SqlStringFunctions.java b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/SqlStringFunctions.java index 47755ba7c..93bd2092e 100644 --- a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/SqlStringFunctions.java +++ b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/SqlStringFunctions.java @@ -4,13 +4,13 @@ enum SqlStringFunctions { LOWER("LOWER"), UPPER("UPPER"), - LENGTH("LENGTH"), TRIM("TRIM"), + LENGTH("LENGTH"), SUBSTRING("SUBSTRING"), CONCAT("CONCAT"), LOCATE("LOCATE"); - private String keyWord; + private final String keyWord; private SqlStringFunctions(final String keyWord) { this.keyWord = keyWord; diff --git a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/SubqueryImpl.java b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/SubqueryImpl.java index 4962ada1b..47d095dba 100644 --- a/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/SubqueryImpl.java +++ b/jpa/odata-jpa-processor-cb/src/main/java/com/sap/olingo/jpa/processor/cb/impl/SubqueryImpl.java @@ -31,6 +31,7 @@ import jakarta.persistence.metamodel.EntityType; import com.sap.olingo.jpa.processor.cb.ProcessorCriteriaQuery; +import com.sap.olingo.jpa.processor.cb.ProcessorSqlPatternProvider; import com.sap.olingo.jpa.processor.cb.ProcessorSubquery; import com.sap.olingo.jpa.processor.cb.exceptions.NotImplementedException; import com.sap.olingo.jpa.processor.cb.joiner.SqlConvertible; @@ -53,11 +54,12 @@ class SubqueryImpl implements ProcessorSubquery, SqlConvertible { private Optional firstResult; SubqueryImpl(@Nonnull final Class type, @Nonnull final CriteriaQuery parent, final AliasBuilder ab, - final CriteriaBuilder cb) { + final CriteriaBuilder cb, final ProcessorSqlPatternProvider sqlPattern) { super(); this.type = Objects.requireNonNull(type); this.parent = Objects.requireNonNull(parent); - this.inner = new CriteriaQueryImpl<>(type, ((CriteriaQueryImpl) parent).getServiceDocument(), ab, cb); + this.inner = new CriteriaQueryImpl<>(type, ((CriteriaQueryImpl) parent).getServiceDocument(), ab, cb, + sqlPattern); maxResult = Optional.empty(); firstResult = Optional.empty(); } diff --git a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/api/EntityManagerFactoryWrapperTest.java b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/api/EntityManagerFactoryWrapperTest.java index a91f45c02..711d63144 100644 --- a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/api/EntityManagerFactoryWrapperTest.java +++ b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/api/EntityManagerFactoryWrapperTest.java @@ -67,58 +67,58 @@ static void classSetup() { void setup() { sd = mock(JPAServiceDocument.class); - cut = new EntityManagerFactoryWrapper(emf, sd); + cut = new EntityManagerFactoryWrapper(emf, sd, null); } static Stream method() throws NoSuchMethodException, SecurityException { - final Class c = EntityManagerFactory.class; + final Class clazz = EntityManagerFactory.class; final EntityGraph entityGraph = mock(EntityGraph.class); final Query query = mock(Query.class); final String dummy = "Test"; return Stream.of( - arguments(c.getMethod("getMetamodel"), dummy, dummy, metamodel), - arguments(c.getMethod("isOpen"), dummy, dummy, Boolean.TRUE), - arguments(c.getMethod("close"), dummy, dummy, null), - arguments(c.getMethod("getProperties"), dummy, dummy, properties), - arguments(c.getMethod("getCache"), dummy, dummy, cache), - arguments(c.getMethod("unwrap", Class.class), c, dummy, emf), - arguments(c.getMethod("getPersistenceUnitUtil"), dummy, dummy, punitUtil), - arguments(c.getMethod("addNamedQuery", String.class, Query.class), dummy, query, null), - arguments(c.getMethod("addNamedEntityGraph", String.class, EntityGraph.class), dummy, entityGraph, null)); + arguments(clazz.getMethod("getMetamodel"), dummy, dummy, metamodel), + arguments(clazz.getMethod("isOpen"), dummy, dummy, Boolean.TRUE), + arguments(clazz.getMethod("close"), dummy, dummy, null), + arguments(clazz.getMethod("getProperties"), dummy, dummy, properties), + arguments(clazz.getMethod("getCache"), dummy, dummy, cache), + arguments(clazz.getMethod("unwrap", Class.class), clazz, dummy, emf), + arguments(clazz.getMethod("getPersistenceUnitUtil"), dummy, dummy, punitUtil), + arguments(clazz.getMethod("addNamedQuery", String.class, Query.class), dummy, query, null), + arguments(clazz.getMethod("addNamedEntityGraph", String.class, EntityGraph.class), dummy, entityGraph, null)); } static Stream emWrapperMethod() throws NoSuchMethodException, SecurityException { - final Class c = EntityManagerFactory.class; + final Class clazz = EntityManagerFactory.class; final String dummy = "Test"; return Stream.of( - arguments(c.getMethod("createEntityManager"), dummy, dummy), - arguments(c.getMethod("createEntityManager", Map.class), new HashMap<>(), dummy), - arguments(c.getMethod("createEntityManager", SynchronizationType.class), SynchronizationType.SYNCHRONIZED, + arguments(clazz.getMethod("createEntityManager"), dummy, dummy), + arguments(clazz.getMethod("createEntityManager", Map.class), new HashMap<>(), dummy), + arguments(clazz.getMethod("createEntityManager", SynchronizationType.class), SynchronizationType.SYNCHRONIZED, dummy), - arguments(c.getMethod("createEntityManager", SynchronizationType.class, Map.class), + arguments(clazz.getMethod("createEntityManager", SynchronizationType.class, Map.class), SynchronizationType.SYNCHRONIZED, new HashMap<>())); } @ParameterizedTest @MethodSource("method") - void testOriginalCalled(final Method m, final Object p1, final Object p2, final Object ret) + void testOriginalCalled(final Method method, final Object p1, final Object p2, final Object ret) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { Object result = null; - if (m.getParameterCount() == 0) { - result = m.invoke(cut); + if (method.getParameterCount() == 0) { + result = method.invoke(cut); final EntityManagerFactory v = verify(emf); - m.invoke(v); - } else if (m.getParameterCount() == 1) { - result = m.invoke(cut, p1); + method.invoke(v); + } else if (method.getParameterCount() == 1) { + result = method.invoke(cut, p1); final EntityManagerFactory v = verify(emf); - m.invoke(v, p1); - } else if (m.getParameterCount() == 2) { - result = m.invoke(cut, p1, p2); + method.invoke(v, p1); + } else if (method.getParameterCount() == 2) { + result = method.invoke(cut, p1, p2); final EntityManagerFactory v = verify(emf); - m.invoke(v, p1, p2); + method.invoke(v, p1, p2); } if (ret != null) { assertEquals(ret, result); @@ -127,16 +127,16 @@ void testOriginalCalled(final Method m, final Object p1, final Object p2, final @ParameterizedTest @MethodSource("emWrapperMethod") - void testEmWrapperCreated(final Method m, final Object p1, final Object p2) + void testEmWrapperCreated(final Method method, final Object p1, final Object p2) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { Object result = null; - if (m.getParameterCount() == 0) { - result = m.invoke(cut); - } else if (m.getParameterCount() == 1) { - result = m.invoke(cut, p1); - } else if (m.getParameterCount() == 2) { - result = m.invoke(cut, p1, p2); + if (method.getParameterCount() == 0) { + result = method.invoke(cut); + } else if (method.getParameterCount() == 1) { + result = method.invoke(cut, p1); + } else if (method.getParameterCount() == 2) { + result = method.invoke(cut, p1, p2); } assertTrue(result instanceof EntityManagerWrapper); } diff --git a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaBuilderDerbyTest.java b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaBuilderDerbyTest.java index 00de83997..cc4e018d1 100644 --- a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaBuilderDerbyTest.java +++ b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaBuilderDerbyTest.java @@ -1,20 +1,32 @@ package com.sap.olingo.jpa.processor.cb.impl; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; + import javax.sql.DataSource; import jakarta.persistence.EntityManagerFactory; +import jakarta.persistence.criteria.Expression; +import jakarta.persistence.criteria.Root; import org.apache.olingo.commons.api.ex.ODataException; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import com.sap.olingo.jpa.metadata.api.JPAEdmProvider; import com.sap.olingo.jpa.metadata.api.JPAEntityManagerFactory; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAServiceDocument; +import com.sap.olingo.jpa.processor.cb.ProcessorSqlFunction; +import com.sap.olingo.jpa.processor.cb.ProcessorSqlOperator; +import com.sap.olingo.jpa.processor.cb.ProcessorSqlParameter; +import com.sap.olingo.jpa.processor.cb.ProcessorSqlPattern; +import com.sap.olingo.jpa.processor.cb.ProcessorSqlPatternProvider; +import com.sap.olingo.jpa.processor.cb.joiner.SqlConvertible; import com.sap.olingo.jpa.processor.core.testmodel.DataSourceHelper; +import com.sap.olingo.jpa.processor.core.testmodel.Person; -@Disabled class CriteriaBuilderDerbyTest extends CriteriaBuilderOverallTest { private static final String PUNIT_NAME = "com.sap.olingo.jpa"; private static final String[] enumPackages = { "com.sap.olingo.jpa.processor.core.testmodel" }; @@ -34,6 +46,72 @@ static void classSetup() throws ODataException { @BeforeEach void setup() { - super.setup(emf, sd); + super.setup(emf, sd, new SqlPattern()); + } + + @Override + @Test + void testSimpleConcatQuery() { + final Root person = q.from(Person.class); + final Expression concat = cb.concat(cb.concat(person.get("lastName"), ","), person.get("firstName")); + + q.multiselect(person.get("iD")); + q.where(cb.equal(concat, "Mustermann,Max")); + ((SqlConvertible) q).asSQL(stmt); + assertEquals( + expectedQueryConcat(), + stmt.toString().trim()); + + // Test need to be skipped, as the following error occurs: + // Internal Exception: java.sql.SQLSyntaxErrorException: Comparisons between 'LONG VARCHAR (UCS_BASIC)' and 'LONG + // VARCHAR (UCS_BASIC)' are not supported. + } + + @Override + protected String expectedQueryLimitOffset() { + return "SELECT E0.\"ID\" S0 FROM \"OLINGO\".\"BusinessPartner\" E0 WHERE (E0.\"Type\" = ?1) OFFSET 1 ROWS FETCH NEXT 1 ROWS ONLY"; + } + + @Override + protected String expectedQuerySubstring() { + return "SELECT E0.\"CodeID\" S0 FROM \"OLINGO\".\"AdministrativeDivisionDescription\" E0 WHERE ((E0.\"LanguageISO\" = ?1) AND (LOWER(SUBSTR(E0.\"Name\", ?2, ?3)) = ?4))"; + } + + @Override + protected String expectedQueryConcat() { + return "SELECT E0.\"ID\" S0 FROM \"OLINGO\".\"BusinessPartner\" E0 WHERE ((((E0.\"NameLine2\" || ?1) || E0.\"NameLine1\") = ?2) AND (E0.\"Type\" = ?3))"; + } + + private static class SqlPattern implements ProcessorSqlPatternProvider { + + @Override + public ProcessorSqlPattern getConcatenatePattern() { + return new ProcessorSqlOperator(Arrays.asList( + new ProcessorSqlParameter(VALUE_PLACEHOLDER, false), + new ProcessorSqlParameter(" || ", VALUE_PLACEHOLDER, false))); + } + + @Override + public ProcessorSqlFunction getSubStringPattern() { + return new ProcessorSqlFunction("SUBSTR", Arrays.asList( + new ProcessorSqlParameter(VALUE_PLACEHOLDER, false), + new ProcessorSqlParameter(COMMA_SEPARATOR, START_PLACEHOLDER, false), + new ProcessorSqlParameter(COMMA_SEPARATOR, LENGTH_PLACEHOLDER, true))); + } + + @Override + public String getMaxResultsPattern() { + return "FETCH NEXT #VALUE# ROWS ONLY"; + } + + @Override + public String getFirstResultPattern() { + return "OFFSET #VALUE# ROWS"; + } + + @Override + public boolean maxResultsFirst() { + return false; + } } } diff --git a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaBuilderH2Test.java b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaBuilderH2Test.java index 2889ee8f4..07ab2cd33 100644 --- a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaBuilderH2Test.java +++ b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaBuilderH2Test.java @@ -47,7 +47,7 @@ public static void classSetup() throws ODataException { @BeforeEach public void setup() { - super.setup(emf, sd); + super.setup(emf, sd, null); } @Test diff --git a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaBuilderHSQLDBTest.java b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaBuilderHSQLDBTest.java index 24b967f34..84e34c56e 100644 --- a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaBuilderHSQLDBTest.java +++ b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaBuilderHSQLDBTest.java @@ -32,6 +32,6 @@ static void classSetup() throws ODataException { @BeforeEach void setup() { - super.setup(emf, sd); + super.setup(emf, sd, null); } } diff --git a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaBuilderImplTest.java b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaBuilderImplTest.java index ebee4f9ff..d377e126a 100644 --- a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaBuilderImplTest.java +++ b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaBuilderImplTest.java @@ -224,7 +224,6 @@ static Stream unaryFunctionsImplemented() throws NoSuchMethodExceptio return Stream.of( arguments(c.getMethod("lower", Expression.class), "LOWER(E0.\"Name\")"), arguments(c.getMethod("upper", Expression.class), "UPPER(E0.\"Name\")"), - arguments(c.getMethod("length", Expression.class), "LENGTH(E0.\"Name\")"), arguments(c.getMethod("trim", Expression.class), "TRIM(E0.\"Name\")")); } @@ -243,7 +242,7 @@ static Stream subQueryExpressionsImplemented() throws NoSuchMethodExc @BeforeEach void setup() { - cut = new CriteriaBuilderImpl(sd, new ParameterBuffer()); + cut = new CriteriaBuilderImpl(sd, new ParameterBuffer(), new SqlDefaultPattern()); statement = new StringBuilder(); query = cut.createTupleQuery(); } @@ -502,6 +501,15 @@ void testLiteralThrowsExceptionOnNullValue() { assertThrows(IllegalArgumentException.class, () -> cut.literal(null)); } + @Test + void testCreateLengthFunction() { + final String exp = "LENGTH(E0.\"Name\")"; + final Root administrativeDivision = query.from(AdministrativeDivisionDescription.class); + final Expression act = cut.length(administrativeDivision.get("name")); + assertEquals(exp, ((SqlConvertible) act).asSQL(statement).toString()); + + } + @Test void testCreateLikeExpressionWithString() { final String exp = "(E0.\"CodeID\" LIKE ?1)"; @@ -851,6 +859,7 @@ void testCreateRowNumberWithOrderByPrimaryKey() { void testCreateRowNumberWithPartitionBy() { final String exp = "ROW_NUMBER() OVER( PARTITION BY E0.\"CodeID\")"; final Root administrativeDivision = query.from(AdministrativeDivision.class); + @SuppressWarnings("unchecked") final Selection act = cut.rowNumber().partitionBy(administrativeDivision.get("codeID")); assertEquals(exp, ((SqlConvertible) act).asSQL(statement).toString()); } @@ -859,6 +868,7 @@ void testCreateRowNumberWithPartitionBy() { void testCreateRowNumberWithPartitionAndOrder() { final String exp = "ROW_NUMBER() OVER( PARTITION BY E0.\"CodeID\" ORDER BY E0.\"CodeID\" ASC)"; final Root administrativeDivision = query.from(AdministrativeDivision.class); + @SuppressWarnings("unchecked") final Selection act = cut.rowNumber() .partitionBy(administrativeDivision.get("codeID")) .orderBy(cut.asc(administrativeDivision.get("codeID"))); diff --git a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaBuilderOverallTest.java b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaBuilderOverallTest.java index 352f79ef8..60dc5bdc3 100644 --- a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaBuilderOverallTest.java +++ b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaBuilderOverallTest.java @@ -24,6 +24,7 @@ import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAServiceDocument; import com.sap.olingo.jpa.processor.cb.ProcessorCriteriaBuilder; +import com.sap.olingo.jpa.processor.cb.ProcessorSqlPatternProvider; import com.sap.olingo.jpa.processor.cb.joiner.SqlConvertible; import com.sap.olingo.jpa.processor.core.testmodel.AdministrativeDivision; import com.sap.olingo.jpa.processor.core.testmodel.AdministrativeDivisionDescription; @@ -39,8 +40,8 @@ abstract class CriteriaBuilderOverallTest { protected StringBuilder stmt; protected CriteriaQuery q; - void setup(final EntityManagerFactory emf, final JPAServiceDocument sd) { - em = new EntityManagerWrapper(emf.createEntityManager(), sd); + void setup(final EntityManagerFactory emf, final JPAServiceDocument sd, final ProcessorSqlPatternProvider sqlPattern) { + em = new EntityManagerWrapper(emf.createEntityManager(), sd, sqlPattern); cb = (ProcessorCriteriaBuilder) em.getCriteriaBuilder(); assertNotNull(cb); stmt = new StringBuilder(); @@ -194,7 +195,7 @@ void testSimpleSubstringQuery() { q.where(cb.and(equal, lower)); ((SqlConvertible) q).asSQL(stmt); assertEquals( - "SELECT E0.\"CodeID\" S0 FROM \"OLINGO\".\"AdministrativeDivisionDescription\" E0 WHERE ((E0.\"LanguageISO\" = ?1) AND (LOWER(SUBSTRING(E0.\"Name\", ?2, ?3)) = ?4))", + expectedQuerySubstring(), stmt.toString().trim()); final TypedQuery tq = em.createQuery(q); final List act = tq.getResultList(); @@ -225,13 +226,13 @@ void testSimpleLocateQuery() { @Test void testSimpleConcatQuery() { final Root person = q.from(Person.class); - final Expression locate = cb.concat(cb.concat(person.get("lastName"), ","), person.get("firstName")); + final Expression concat = cb.concat(cb.concat(person.get("lastName"), ","), person.get("firstName")); q.multiselect(person.get("iD")); - q.where(cb.equal(locate, "Mustermann,Max")); + q.where(cb.equal(concat, "Mustermann,Max")); ((SqlConvertible) q).asSQL(stmt); assertEquals( - "SELECT E0.\"ID\" S0 FROM \"OLINGO\".\"BusinessPartner\" E0 WHERE ((CONCAT(CONCAT(E0.\"NameLine2\", ?1), E0.\"NameLine1\") = ?2) AND (E0.\"Type\" = ?3))", + expectedQueryConcat(), stmt.toString().trim()); final TypedQuery tq = em.createQuery(q); final List act = tq.getResultList(); @@ -265,7 +266,6 @@ void testSelectPrimitiveCollectionProperty() { comment.alias("Comment"); q.multiselect(id, comment); q.where(cb.equal(id, '1')); - // ((SqlConvertible) q).asSQL(stmt); final TypedQuery tq = em.createQuery(q); final List act = tq.getResultList(); assertEquals(2, act.size()); @@ -280,7 +280,6 @@ void testSelectComplexCollectionProperty() { addr.alias("inhouseAddress"); q.multiselect(id, addr); q.where(cb.equal(id, "99")); - // ((SqlConvertible) q).asSQL(stmt); final TypedQuery tq = em.createQuery(q); final List act = tq.getResultList(); assertEquals(2, act.size()); @@ -295,7 +294,7 @@ void testSelectCountOneKey() { qc.multiselect(cb.countDistinct(org)); qc.where(cb.equal(org.get("userName"), "Willi")); final TypedQuery tq = em.createQuery(qc); - final Long act = tq.getSingleResult(); + final Long act = ((Number) tq.getSingleResult()).longValue(); assertEquals(3L, act); } @@ -311,4 +310,31 @@ void testSelectDateTime() { assertEquals(LocalDate.parse("1999-04-01"), act.get(0).get("S2")); assertEquals(LocalDateTime.parse("2016-01-20T09:21:23"), act.get(0).get("S1")); } + + @Test + void testSelectLimitOffset() { + final Root person = q.from(Person.class); + + q.multiselect(person.get("iD")); + final TypedQuery tq = em.createQuery(q); + tq.setFirstResult(1); + tq.setMaxResults(1); + ((SqlConvertible) q).asSQL(stmt); + assertEquals(expectedQueryLimitOffset(), stmt.toString().trim()); + final List act = tq.getResultList(); + assertEquals(1, act.size()); + assertNotNull(act.get(0)); + } + + protected String expectedQueryLimitOffset() { + return "SELECT E0.\"ID\" S0 FROM \"OLINGO\".\"BusinessPartner\" E0 WHERE (E0.\"Type\" = ?1) LIMIT 1 OFFSET 1"; + } + + protected String expectedQueryConcat() { + return "SELECT E0.\"ID\" S0 FROM \"OLINGO\".\"BusinessPartner\" E0 WHERE ((CONCAT(CONCAT(E0.\"NameLine2\", ?1), E0.\"NameLine1\") = ?2) AND (E0.\"Type\" = ?3))"; + } + + protected String expectedQuerySubstring() { + return "SELECT E0.\"CodeID\" S0 FROM \"OLINGO\".\"AdministrativeDivisionDescription\" E0 WHERE ((E0.\"LanguageISO\" = ?1) AND (LOWER(SUBSTRING(E0.\"Name\", ?2, ?3)) = ?4))"; + } } \ No newline at end of file diff --git a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaQueryImplTest.java b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaQueryImplTest.java index 790ca5885..30739f1f3 100644 --- a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaQueryImplTest.java +++ b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/CriteriaQueryImplTest.java @@ -27,6 +27,7 @@ import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAServiceDocument; import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException; import com.sap.olingo.jpa.processor.cb.ProcessorSelection; +import com.sap.olingo.jpa.processor.cb.ProcessorSqlPatternProvider; import com.sap.olingo.jpa.processor.cb.exceptions.InternalServerError; import com.sap.olingo.jpa.processor.core.testmodel.AdministrativeDivision; import com.sap.olingo.jpa.processor.core.testmodel.Organization; @@ -34,11 +35,13 @@ class CriteriaQueryImplTest extends BuilderBaseTest { private CriteriaQueryImpl cut; private CriteriaBuilder cb; + private ProcessorSqlPatternProvider sqlPattern; @BeforeEach void setup() throws ODataJPAModelException { - cb = new CriteriaBuilderImpl(sd, new ParameterBuffer()); - cut = new CriteriaQueryImpl<>(Object.class, sd, cb); + sqlPattern = new SqlDefaultPattern(); + cb = new CriteriaBuilderImpl(sd, new ParameterBuffer(), sqlPattern); + cut = new CriteriaQueryImpl<>(Object.class, sd, cb, sqlPattern); } @Test @@ -163,7 +166,7 @@ void testDefaultImplementationOnPathWrapper() { void testFromRethrowsException() throws ODataJPAModelException { final JPAServiceDocument serviceDocument = mock(JPAServiceDocument.class); when(serviceDocument.getEntity(any(Class.class))).thenThrow(ODataJPAModelException.class); - cut = new CriteriaQueryImpl<>(Object.class, serviceDocument, cb); + cut = new CriteriaQueryImpl<>(Object.class, serviceDocument, cb, sqlPattern); assertThrows(InternalServerError.class, () -> cut.from(AdministrativeDivision.class)); } diff --git a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/EntityManagerWrapperTest.java b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/EntityManagerWrapperTest.java index 026437997..2ed96c52a 100644 --- a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/EntityManagerWrapperTest.java +++ b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/EntityManagerWrapperTest.java @@ -113,7 +113,7 @@ static void classSetup() { @BeforeEach void setup() { sd = mock(JPAServiceDocument.class); - cut = new EntityManagerWrapper(em, sd); + cut = new EntityManagerWrapper(em, sd, null); } static Stream parameterFreeMethod() throws NoSuchMethodException, SecurityException { diff --git a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/JoinTableJoinTest.java b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/JoinTableJoinTest.java index 349d97c5a..09ed74e76 100644 --- a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/JoinTableJoinTest.java +++ b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/JoinTableJoinTest.java @@ -16,6 +16,7 @@ import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAssociationPath; import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException; import com.sap.olingo.jpa.processor.cb.ProcessorCriteriaQuery; +import com.sap.olingo.jpa.processor.cb.ProcessorSqlPatternProvider; import com.sap.olingo.jpa.processor.cb.exceptions.NotImplementedException; import com.sap.olingo.jpa.processor.core.testmodel.Person; import com.sap.olingo.jpa.processor.core.testmodel.Team; @@ -24,16 +25,18 @@ class JoinTableJoinTest extends BuilderBaseTest { private JoinTableJoin cut; private AliasBuilder ab; private CriteriaBuilderImpl cb; - private ProcessorCriteriaQuery q; + private ProcessorCriteriaQuery query; private JPAAssociationPath path; + private ProcessorSqlPatternProvider sqlPattern; @BeforeEach void setup() throws ODataJPAModelException { + sqlPattern = new SqlDefaultPattern(); ab = new AliasBuilder(); - cb = new CriteriaBuilderImpl(sd, new ParameterBuffer()); - q = cb.createTupleQuery(); + cb = new CriteriaBuilderImpl(sd, new ParameterBuffer(), sqlPattern); + query = cb.createTupleQuery(); path = sd.getEntity(Person.class).getAssociationPath("Teams"); - cut = new JoinTableJoin<>(path, JoinType.LEFT, q.from(Person.class), ab, cb); + cut = new JoinTableJoin<>(path, JoinType.LEFT, query.from(Person.class), ab, cb); } @Test @@ -64,7 +67,7 @@ void testJoinTypeInner() { @Test void testGetParentReturnsInnerJoin() throws ODataJPAModelException { - final JoinTableJoin other = new JoinTableJoin<>(path, JoinType.RIGHT, q.from(Person.class), ab, cb); + final JoinTableJoin other = new JoinTableJoin<>(path, JoinType.RIGHT, query.from(Person.class), ab, cb); final AbstractJoinImp act = (AbstractJoinImp) cut.getParent(); assertNotNull(act); assertFalse(act.equals(cut)); // NOSONAR diff --git a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/PredicateImplTest.java b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/PredicateImplTest.java index 75e60c11a..1afdd1cab 100644 --- a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/PredicateImplTest.java +++ b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/PredicateImplTest.java @@ -62,7 +62,7 @@ static Stream notImplemented() throws NoSuchMethodException, Security void setup() { cut = new PredicateImpl.NotPredicate(mock(SqlConvertible.class)); - final CriteriaBuilder cb = new CriteriaBuilderImpl(sd, new ParameterBuffer()); + final CriteriaBuilder cb = new CriteriaBuilderImpl(sd, new ParameterBuffer(), new SqlDefaultPattern()); statement = new StringBuilder(); query = cb.createTupleQuery(); } diff --git a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/SimpleJoinTest.java b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/SimpleJoinTest.java index 6bfb86b50..5f04545c3 100644 --- a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/SimpleJoinTest.java +++ b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/SimpleJoinTest.java @@ -33,7 +33,7 @@ class SimpleJoinTest extends BuilderBaseTest { @BeforeEach void setup() throws ODataJPAModelException { ab = new AliasBuilder(); - cb = new CriteriaBuilderImpl(sd, new ParameterBuffer()); + cb = new CriteriaBuilderImpl(sd, new ParameterBuffer(), new SqlDefaultPattern()); final ProcessorCriteriaQuery q = cb.createTupleQuery(); final JPAAssociationPath path = sd.getEntity(BusinessPartner.class).getAssociationPath("Roles"); cut = new SimpleJoin<>(path, JoinType.INNER, q.from(BusinessPartner.class), ab, cb); diff --git a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/SubqueryImplTest.java b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/SubqueryImplTest.java index f3ac6bb8f..f8f3982b8 100644 --- a/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/SubqueryImplTest.java +++ b/jpa/odata-jpa-processor-cb/src/test/java/com/sap/olingo/jpa/processor/cb/impl/SubqueryImplTest.java @@ -96,7 +96,7 @@ void setup() { ab = mock(AliasBuilder.class); parent = mock(CriteriaQueryImpl.class); when(parent.getServiceDocument()).thenReturn(sd); - cut = new SubqueryImpl<>(Long.class, parent, ab, cb); + cut = new SubqueryImpl<>(Long.class, parent, ab, cb, new SqlDefaultPattern()); } @ParameterizedTest diff --git a/jpa/odata-jpa-processor-ext/src/main/java/com/sap/olingo/jpa/processor/cb/ProcessorSqlFunction.java b/jpa/odata-jpa-processor-ext/src/main/java/com/sap/olingo/jpa/processor/cb/ProcessorSqlFunction.java new file mode 100644 index 000000000..ffdc55a33 --- /dev/null +++ b/jpa/odata-jpa-processor-ext/src/main/java/com/sap/olingo/jpa/processor/cb/ProcessorSqlFunction.java @@ -0,0 +1,8 @@ +package com.sap.olingo.jpa.processor.cb; + +import java.util.List; + +public record ProcessorSqlFunction(String function, List parameters) + implements ProcessorSqlPattern { + +} diff --git a/jpa/odata-jpa-processor-ext/src/main/java/com/sap/olingo/jpa/processor/cb/ProcessorSqlOperator.java b/jpa/odata-jpa-processor-ext/src/main/java/com/sap/olingo/jpa/processor/cb/ProcessorSqlOperator.java new file mode 100644 index 000000000..59368ebb5 --- /dev/null +++ b/jpa/odata-jpa-processor-ext/src/main/java/com/sap/olingo/jpa/processor/cb/ProcessorSqlOperator.java @@ -0,0 +1,8 @@ +package com.sap.olingo.jpa.processor.cb; + +import java.util.List; + +public record ProcessorSqlOperator(List parameters) + implements ProcessorSqlPattern { + +} diff --git a/jpa/odata-jpa-processor-ext/src/main/java/com/sap/olingo/jpa/processor/cb/ProcessorSqlParameter.java b/jpa/odata-jpa-processor-ext/src/main/java/com/sap/olingo/jpa/processor/cb/ProcessorSqlParameter.java new file mode 100644 index 000000000..e2d37a8d9 --- /dev/null +++ b/jpa/odata-jpa-processor-ext/src/main/java/com/sap/olingo/jpa/processor/cb/ProcessorSqlParameter.java @@ -0,0 +1,8 @@ +package com.sap.olingo.jpa.processor.cb; + +public record ProcessorSqlParameter(String keyword, String parameter, boolean isOptional) { + + public ProcessorSqlParameter(final String parameter, final boolean isOptional) { + this("", parameter, isOptional); + } +} diff --git a/jpa/odata-jpa-processor-ext/src/main/java/com/sap/olingo/jpa/processor/cb/ProcessorSqlPattern.java b/jpa/odata-jpa-processor-ext/src/main/java/com/sap/olingo/jpa/processor/cb/ProcessorSqlPattern.java new file mode 100644 index 000000000..a3e866d1e --- /dev/null +++ b/jpa/odata-jpa-processor-ext/src/main/java/com/sap/olingo/jpa/processor/cb/ProcessorSqlPattern.java @@ -0,0 +1,5 @@ +package com.sap.olingo.jpa.processor.cb; + +public interface ProcessorSqlPattern { + +} diff --git a/jpa/odata-jpa-processor-ext/src/main/java/com/sap/olingo/jpa/processor/cb/ProcessorSqlPatternProvider.java b/jpa/odata-jpa-processor-ext/src/main/java/com/sap/olingo/jpa/processor/cb/ProcessorSqlPatternProvider.java new file mode 100644 index 000000000..5b67fed2e --- /dev/null +++ b/jpa/odata-jpa-processor-ext/src/main/java/com/sap/olingo/jpa/processor/cb/ProcessorSqlPatternProvider.java @@ -0,0 +1,111 @@ +package com.sap.olingo.jpa.processor.cb; + +import java.util.Arrays; +import java.util.Collections; + +import javax.annotation.Nonnull; + +/** + * + * + * @author Oliver Grande + * 2024-07-03 + * 2.2.0 + */ +public interface ProcessorSqlPatternProvider { + static final String VALUE_PLACEHOLDER = "#VALUE#"; + static final String START_PLACEHOLDER = "#START#"; + static final String LENGTH_PLACEHOLDER = "#LENGTH#"; + static final String SEARCH_STRING_PLACEHOLDER = "#SEARCH_STRING#"; + static final String COMMA_SEPARATOR = ", "; + // Pattern + static final String LIMIT_PATTERN = "LIMIT " + VALUE_PLACEHOLDER; + static final String OFFSET_PATTERN = "OFFSET " + VALUE_PLACEHOLDER; + + /** + * Default pattern: LIMIT #VALUE# + * @return Pattern for the clause limiting the number of rows returned + */ + @Nonnull + default String getMaxResultsPattern() { + return LIMIT_PATTERN; + } + + /** + * Default pattern: OFFSET #VALUE# + * @return Pattern for the clause defining the first row to be returned + */ + @Nonnull + default String getFirstResultPattern() { + return OFFSET_PATTERN; + } + + /** + * Default: true + * @return true if the database requires that the max result clause need to be before the first result clause + */ + default boolean maxResultsFirst() { + return true; + } + + /** + * Default pattern: SUBSTRING(#VALUE#, #START#, #LENGTH#) + *

+ * The sub string of value from start with the length length. + *

+ * @return Pattern for the sub string function + */ + @Nonnull + default ProcessorSqlFunction getSubStringPattern() { + return new ProcessorSqlFunction("SUBSTRING", Arrays.asList( + new ProcessorSqlParameter(VALUE_PLACEHOLDER, false), + new ProcessorSqlParameter(COMMA_SEPARATOR, START_PLACEHOLDER, false), + new ProcessorSqlParameter(COMMA_SEPARATOR, LENGTH_PLACEHOLDER, true))); + } + + /** + * Default pattern: CONCAT(#VALUE#, #VALUE#) + *

+ * Concatenates two string. Some database, e.g. Postgresql, not having a named function to concatenate two strings, + * but use an operator, + * e.g. ||. + * @return Pattern for a function that concatenates two strings + */ + @Nonnull + default ProcessorSqlPattern getConcatenatePattern() { + return new ProcessorSqlFunction("CONCAT", Arrays.asList( + new ProcessorSqlParameter(VALUE_PLACEHOLDER, false), + new ProcessorSqlParameter(COMMA_SEPARATOR, VALUE_PLACEHOLDER, false))); + } + + /** + * Default pattern: LOCATE(#SEARCH_STRING#, #VALUE#, #START#) + *

+ * Returns the position of the first occurrence of search_string in value. The search shall start at + * start + *

+ * The start position is seen as optional. In case the database supports an optional occurrences, to express that e.g. + * the 3rd occurrence shall be found, it must not be mentioned here, as this is not supported by JPA. + * @return Pattern for a function that searches for the position of a search string in another string/value. + */ + @Nonnull + default ProcessorSqlFunction getLocatePattern() { + return new ProcessorSqlFunction("LOCATE", Arrays.asList( + new ProcessorSqlParameter(SEARCH_STRING_PLACEHOLDER, false), + new ProcessorSqlParameter(COMMA_SEPARATOR, VALUE_PLACEHOLDER, false), + new ProcessorSqlParameter(COMMA_SEPARATOR, START_PLACEHOLDER, true))); + } + + /** + * Default pattern: LENGTH(#VALUE#) + *

+ * The character length of value. + *

+ * @return Pattern to determine the character length of a string. + */ + @Nonnull + default ProcessorSqlFunction getLengthPattern() { + return new ProcessorSqlFunction("LENGTH", Collections.singletonList( + new ProcessorSqlParameter(VALUE_PLACEHOLDER, false))); + } +} diff --git a/jpa/odata-jpa-processor/.project b/jpa/odata-jpa-processor/.project index 0dcfc02bf..644577220 100644 --- a/jpa/odata-jpa-processor/.project +++ b/jpa/odata-jpa-processor/.project @@ -15,14 +15,8 @@ - - org.eclipse.wst.validation.validationbuilder - - - - org.eclipse.jem.workbench.JavaEMFNature org.eclipse.wst.common.modulecore.ModuleCoreNature org.eclipse.jdt.core.javanature org.eclipse.m2e.core.maven2Nature diff --git a/jpa/odata-jpa-processor/.settings/org.eclipse.wst.common.project.facet.core.xml b/jpa/odata-jpa-processor/.settings/org.eclipse.wst.common.project.facet.core.xml index d55740c7d..babef879f 100644 --- a/jpa/odata-jpa-processor/.settings/org.eclipse.wst.common.project.facet.core.xml +++ b/jpa/odata-jpa-processor/.settings/org.eclipse.wst.common.project.facet.core.xml @@ -1,5 +1,5 @@ - + diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataServiceContext.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataServiceContext.java index 01f0ef8e2..4ec752291 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataServiceContext.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataServiceContext.java @@ -29,6 +29,7 @@ import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEdmNameBuilder; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAServiceDocument; import com.sap.olingo.jpa.metadata.core.edm.mapper.impl.JPADefaultEdmNameBuilder; +import com.sap.olingo.jpa.processor.cb.ProcessorSqlPatternProvider; import com.sap.olingo.jpa.processor.core.api.JPAODataQueryDirectives.JPAODataQueryDirectivesImpl; import com.sap.olingo.jpa.processor.core.database.JPADefaultDatabaseProcessor; import com.sap.olingo.jpa.processor.core.database.JPAODataDatabaseOperations; @@ -55,6 +56,7 @@ public final class JPAODataServiceContext implements JPAODataSessionContextAcces private final boolean useAbsoluteContextURL; private final List annotationProvider; private final JPAODataQueryDirectives queryDirectives; + private final ProcessorSqlPatternProvider sqlPattern; public static JPAODataServiceContextBuilder with() { return new Builder(); @@ -78,6 +80,7 @@ private JPAODataServiceContext(final Builder builder) { useAbsoluteContextURL = builder.useAbsoluteContextURL; annotationProvider = Arrays.asList(builder.annotationProvider); queryDirectives = builder.queryDirectives; + sqlPattern = builder.sqlPattern; } @Override @@ -153,6 +156,11 @@ public JPAODataQueryDirectives getQueryDirectives() { return queryDirectives; } + @Override + public ProcessorSqlPatternProvider getSqlPatternProvider() { + return sqlPattern; + } + static class Builder implements JPAODataServiceContextBuilder { private String namespace; @@ -172,6 +180,7 @@ static class Builder implements JPAODataServiceContextBuilder { private boolean useAbsoluteContextURL = false; private AnnotationProvider[] annotationProvider; private JPAODataQueryDirectivesImpl queryDirectives; + private ProcessorSqlPatternProvider sqlPattern; private Builder() { super(); @@ -398,7 +407,8 @@ private void createEmfWrapper() { jpaEdm = new JPAEdmProvider(emf.get().getMetamodel(), postProcessor, packageName, nameBuilder, Arrays .asList(annotationProvider)); emf = Optional.of(wrapperClass.getConstructor(EntityManagerFactory.class, - JPAServiceDocument.class).newInstance(emf.get(), jpaEdm.getServiceDocument())); + JPAServiceDocument.class, ProcessorSqlPatternProvider.class) + .newInstance(emf.get(), jpaEdm.getServiceDocument(), sqlPattern)); LOGGER.trace("Criteria Builder Extension found. It will be used"); } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { @@ -421,5 +431,11 @@ public JPAODataServiceContextBuilder setQueryDirectives(final JPAODataQueryDirec this.queryDirectives = queryDirectives; return this; } + + @Override + public JPAODataServiceContextBuilder setSqlPatternProvider(final ProcessorSqlPatternProvider sqlPattern) { + this.sqlPattern = sqlPattern; + return this; + } } } \ No newline at end of file diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataServiceContextBuilder.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataServiceContextBuilder.java index b4c0f5de9..ad3f33d3d 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataServiceContextBuilder.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataServiceContextBuilder.java @@ -14,6 +14,7 @@ import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.AnnotationProvider; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEdmNameBuilder; import com.sap.olingo.jpa.metadata.core.edm.mapper.impl.JPADefaultEdmNameBuilder; +import com.sap.olingo.jpa.processor.cb.ProcessorSqlPatternProvider; import com.sap.olingo.jpa.processor.core.api.JPAODataServiceContext.Builder; import com.sap.olingo.jpa.processor.core.database.JPADefaultDatabaseProcessor; import com.sap.olingo.jpa.processor.core.database.JPAODataDatabaseOperations; @@ -140,4 +141,11 @@ JPAODataServiceContextBuilder setBatchProcess */ JPAODataQueryDirectivesBuilder useQueryDirectives(); + /** + * Some database use different clauses for a certain function. E.g., to limit the number of rows returned. + * Some databases use LIMIT and OFFSET, other OFFSET ... ROWS and FETCH NEXT ... ROWS.
+ * This is relevant when module odata-jpa-processor-cb is used. + * @since 2.2.0 + */ + JPAODataServiceContextBuilder setSqlPatternProvider(ProcessorSqlPatternProvider sqlPattern); } \ No newline at end of file diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataSessionContextAccess.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataSessionContextAccess.java index e7b31d7c9..24477c6b6 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataSessionContextAccess.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/api/JPAODataSessionContextAccess.java @@ -11,6 +11,7 @@ import com.sap.olingo.jpa.metadata.api.JPAEdmProvider; import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.AnnotationProvider; +import com.sap.olingo.jpa.processor.cb.ProcessorSqlPatternProvider; import com.sap.olingo.jpa.processor.core.database.JPAODataDatabaseOperations; /** @@ -68,4 +69,6 @@ public default boolean useAbsoluteContextURL() { public JPAODataQueryDirectives getQueryDirectives(); + public ProcessorSqlPatternProvider getSqlPatternProvider(); + } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPAAbstractDatabaseProcessor.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPAAbstractDatabaseProcessor.java index f540246bd..a4c420811 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPAAbstractDatabaseProcessor.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPAAbstractDatabaseProcessor.java @@ -2,26 +2,37 @@ import static com.sap.olingo.jpa.processor.core.exception.ODataJPADBAdaptorException.MessageKeys.PARAMETER_CONVERSION_ERROR; import static com.sap.olingo.jpa.processor.core.exception.ODataJPADBAdaptorException.MessageKeys.PARAMETER_MISSING; +import static com.sap.olingo.jpa.processor.core.exception.ODataJPAProcessorException.MessageKeys.NOT_SUPPORTED_FUNC_WITH_NAVI; import static org.apache.olingo.commons.api.http.HttpStatusCode.BAD_REQUEST; import static org.apache.olingo.commons.api.http.HttpStatusCode.INTERNAL_SERVER_ERROR; +import java.util.ArrayList; import java.util.List; import jakarta.persistence.EntityManager; import jakarta.persistence.Query; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Expression; +import jakarta.persistence.criteria.From; import org.apache.olingo.commons.api.edm.EdmElement; import org.apache.olingo.commons.api.edm.EdmFunction; import org.apache.olingo.commons.api.edm.EdmPrimitiveType; import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeException; +import org.apache.olingo.commons.api.http.HttpStatusCode; import org.apache.olingo.server.api.ODataApplicationException; import org.apache.olingo.server.api.uri.UriParameter; import org.apache.olingo.server.api.uri.UriResource; import org.apache.olingo.server.api.uri.UriResourceEntitySet; import org.apache.olingo.server.api.uri.UriResourceFunction; import org.apache.olingo.server.api.uri.UriResourceKind; +import org.apache.olingo.server.api.uri.queryoption.SearchOption; +import com.sap.olingo.jpa.metadata.api.JPAHttpHeaderMap; +import com.sap.olingo.jpa.metadata.api.JPARequestParameterMap; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPADataBaseFunction; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAParameter; import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException; import com.sap.olingo.jpa.processor.core.api.JPAODataDatabaseProcessor; @@ -32,6 +43,35 @@ public abstract class JPAAbstractDatabaseProcessor implements JPAODataDatabasePr static final String FUNC_NAME_PLACEHOLDER = "$FUNCTIONNAME$"; static final String PARAMETER_PLACEHOLDER = "$PARAMETER$"; + @Override + public Expression createSearchWhereClause(final CriteriaBuilder cb, final CriteriaQuery criteriaQuery, + final From root, final JPAEntityType entityType, final SearchOption searchOption) + throws ODataApplicationException { + + throw new ODataJPADBAdaptorException(ODataJPADBAdaptorException.MessageKeys.NOT_SUPPORTED_SEARCH, + HttpStatusCode.NOT_IMPLEMENTED); + } + + @Override + public Object executeFunctionQuery(final List uriResourceParts, + final JPADataBaseFunction jpaFunction, final EntityManager em, final JPAHttpHeaderMap headers, + final JPARequestParameterMap parameters) throws ODataApplicationException { + final UriResource last = uriResourceParts.get(uriResourceParts.size() - 1); + + if (last.getKind() == UriResourceKind.count) { + final List countResult = new ArrayList<>(); + countResult.add(executeCountQuery(uriResourceParts, jpaFunction, em, functionCountPattern())); + return countResult; + } + if (last.getKind() == UriResourceKind.function) + return executeQuery(uriResourceParts, jpaFunction, em,functionSelectPattern()); + throw new ODataJPAProcessorException(NOT_SUPPORTED_FUNC_WITH_NAVI, HttpStatusCode.NOT_IMPLEMENTED); + } + + protected abstract String functionSelectPattern(); + + protected abstract String functionCountPattern(); + protected UriResourceEntitySet determineTargetEntitySet(final List uriParts) { for (int i = uriParts.size() - 1; i >= 0; i--) { if (uriParts.get(i).getKind() == UriResourceKind.entitySet) diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPADefaultDatabaseProcessor.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPADefaultDatabaseProcessor.java index 62ffe3163..58eec2c4c 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPADefaultDatabaseProcessor.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPADefaultDatabaseProcessor.java @@ -1,11 +1,5 @@ package com.sap.olingo.jpa.processor.core.database; -import static com.sap.olingo.jpa.processor.core.exception.ODataJPAProcessorException.MessageKeys.NOT_SUPPORTED_FUNC_WITH_NAVI; - -import java.util.ArrayList; -import java.util.List; - -import jakarta.persistence.EntityManager; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.Expression; @@ -13,18 +7,12 @@ import org.apache.olingo.commons.api.http.HttpStatusCode; import org.apache.olingo.server.api.ODataApplicationException; -import org.apache.olingo.server.api.uri.UriResource; -import org.apache.olingo.server.api.uri.UriResourceKind; import org.apache.olingo.server.api.uri.queryoption.SearchOption; import org.apache.olingo.server.api.uri.queryoption.expression.BinaryOperatorKind; -import com.sap.olingo.jpa.metadata.api.JPAHttpHeaderMap; -import com.sap.olingo.jpa.metadata.api.JPARequestParameterMap; -import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPADataBaseFunction; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType; import com.sap.olingo.jpa.processor.core.exception.ODataJPADBAdaptorException; import com.sap.olingo.jpa.processor.core.exception.ODataJPAFilterException; -import com.sap.olingo.jpa.processor.core.exception.ODataJPAProcessorException; import com.sap.olingo.jpa.processor.core.filter.JPAAggregationOperation; import com.sap.olingo.jpa.processor.core.filter.JPAArithmeticOperator; import com.sap.olingo.jpa.processor.core.filter.JPABooleanOperator; @@ -101,21 +89,14 @@ public Expression createSearchWhereClause(final CriteriaBuilder cb, fin } - @SuppressWarnings("unchecked") @Override - public Object executeFunctionQuery(final List uriResourceParts, - final JPADataBaseFunction jpaFunction, final EntityManager em, final JPAHttpHeaderMap headers, - final JPARequestParameterMap parameters) throws ODataApplicationException { - final UriResource last = uriResourceParts.get(uriResourceParts.size() - 1); - - if (last.getKind() == UriResourceKind.count) { - final List countResult = new ArrayList<>(); - countResult.add(executeCountQuery(uriResourceParts, jpaFunction, em, SELECT_COUNT_PATTERN)); - return countResult; - } - if (last.getKind() == UriResourceKind.function) - return executeQuery(uriResourceParts, jpaFunction, em, SELECT_BASE_PATTERN); - throw new ODataJPAProcessorException(NOT_SUPPORTED_FUNC_WITH_NAVI, HttpStatusCode.NOT_IMPLEMENTED); + protected String functionSelectPattern() { + return SELECT_BASE_PATTERN; + } + + @Override + protected String functionCountPattern() { + return SELECT_COUNT_PATTERN; } @Override diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPADerbySqlPatternProvider.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPADerbySqlPatternProvider.java new file mode 100644 index 000000000..55ff1a24c --- /dev/null +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPADerbySqlPatternProvider.java @@ -0,0 +1,56 @@ +package com.sap.olingo.jpa.processor.core.database; + +import java.util.Arrays; + +import com.sap.olingo.jpa.processor.cb.ProcessorSqlFunction; +import com.sap.olingo.jpa.processor.cb.ProcessorSqlOperator; +import com.sap.olingo.jpa.processor.cb.ProcessorSqlParameter; +import com.sap.olingo.jpa.processor.cb.ProcessorSqlPattern; +import com.sap.olingo.jpa.processor.cb.ProcessorSqlPatternProvider; + +public class JPADerbySqlPatternProvider implements ProcessorSqlPatternProvider { + static final String MAX_RESULTS_PATTERN = "FETCH NEXT " + VALUE_PLACEHOLDER + " ROWS ONLY"; + static final String FIRST_RESULT_PATTERN = "OFFSET " + VALUE_PLACEHOLDER + " ROWS"; + + /** + * Concatenation operator + */ + @Override + public ProcessorSqlPattern getConcatenatePattern() { + return new ProcessorSqlOperator(Arrays.asList( + new ProcessorSqlParameter(VALUE_PLACEHOLDER, false), + new ProcessorSqlParameter(" || ", VALUE_PLACEHOLDER, false))); + } + + /** + * SUBSTR function + */ + @Override + public ProcessorSqlFunction getSubStringPattern() { + return new ProcessorSqlFunction("SUBSTR", Arrays.asList( + new ProcessorSqlParameter(VALUE_PLACEHOLDER, false), + new ProcessorSqlParameter(START_PLACEHOLDER, false), + new ProcessorSqlParameter(LENGTH_PLACEHOLDER, true))); + } + + /** + * Offset and fetch + */ + @Override + public String getMaxResultsPattern() { + return MAX_RESULTS_PATTERN; + } + + /** + * Offset and fetch + */ + @Override + public String getFirstResultPattern() { + return FIRST_RESULT_PATTERN; + } + + @Override + public boolean maxResultsFirst() { + return false; + } +} diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPAHanaDatabaseProcessor.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPAHanaDatabaseProcessor.java new file mode 100644 index 000000000..47f67938e --- /dev/null +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPAHanaDatabaseProcessor.java @@ -0,0 +1,44 @@ +package com.sap.olingo.jpa.processor.core.database; + +import java.util.Arrays; + +import com.sap.olingo.jpa.processor.cb.ProcessorSqlFunction; +import com.sap.olingo.jpa.processor.cb.ProcessorSqlParameter; +import com.sap.olingo.jpa.processor.cb.ProcessorSqlPatternProvider; + +/** + * Sample implementation a database processor for SAP HANA + * + * @author Oliver Grande + * Created: 04.07.2024 + * @since + */ +public class JPAHanaDatabaseProcessor extends JPAAbstractDatabaseProcessor implements + ProcessorSqlPatternProvider { // NOSONAR + private static final String SELECT_BASE_PATTERN = "SELECT * FROM $FUNCTIONNAME$($PARAMETER$)"; + private static final String SELECT_COUNT_PATTERN = "SELECT COUNT(*) FROM $FUNCTIONNAME$($PARAMETER$)"; + + public JPAHanaDatabaseProcessor() { + super(); + } + + @Override + protected String functionSelectPattern() { + return SELECT_BASE_PATTERN; + } + + @Override + protected String functionCountPattern() { + return SELECT_COUNT_PATTERN; + } + + @Override + public ProcessorSqlFunction getLocatePattern() { + // INSTR: Returns the position of the first occurrence of the second string within the first string (>= 1) or 0, if + // the second string is not contained in the first. + return new ProcessorSqlFunction("INSTR", Arrays.asList( + new ProcessorSqlParameter(VALUE_PLACEHOLDER, false), + new ProcessorSqlParameter(COMMA_SEPARATOR, SEARCH_STRING_PLACEHOLDER, false))); + } + +} diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPAODataDatabaseProcessorFactory.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPAODataDatabaseProcessorFactory.java index 1be7364ba..b18c7fc90 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPAODataDatabaseProcessorFactory.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPAODataDatabaseProcessorFactory.java @@ -15,15 +15,15 @@ public class JPAODataDatabaseProcessorFactory { private static final Log LOGGER = LogFactory.getLog(JPAODataDatabaseProcessorFactory.class); private static final String PRODUCT_NAME_H2 = "H2"; private static final String PRODUCT_NAME_HSQLDB = "HSQL Database Engine"; - private static final String PRODUCT_NAME_POSTSQL = "PostgreSQL"; + private static final String PRODUCT_NAME_POSTGRESQL = "PostgreSQL"; - public JPAODataDatabaseProcessor create(final DataSource ds) throws SQLException { - if (ds != null) { - try (Connection connection = ds.getConnection()) { + public JPAODataDatabaseProcessor create(final DataSource dataSource) throws SQLException { + if (dataSource != null) { + try (Connection connection = dataSource.getConnection()) { final DatabaseMetaData dbMetadata = connection.getMetaData(); - if (dbMetadata.getDatabaseProductName().equals(PRODUCT_NAME_POSTSQL)) { + if (dbMetadata.getDatabaseProductName().equals(PRODUCT_NAME_POSTGRESQL)) { LOGGER.trace("Create database-processor for PostgreSQL"); - return new JPA_POSTSQL_DatabaseProcessor(); + return new JPAPostgresqlDatabaseProcessor(); } else if (dbMetadata.getDatabaseProductName().equals(PRODUCT_NAME_HSQLDB)) { LOGGER.trace("Create database-processor for HSQLDB"); return new JPA_HSQLDB_DatabaseProcessor(); diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPAPostgresqlDatabaseProcessor.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPAPostgresqlDatabaseProcessor.java new file mode 100644 index 000000000..81a2800ea --- /dev/null +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPAPostgresqlDatabaseProcessor.java @@ -0,0 +1,28 @@ +package com.sap.olingo.jpa.processor.core.database; + +/** + * Sample implementation a database processor for PostgreSQL + * + * @author Oliver Grande + * Created: 04.07.2019 + * + */ +public class JPAPostgresqlDatabaseProcessor extends JPAAbstractDatabaseProcessor { // NOSONAR + private static final String SELECT_BASE_PATTERN = "SELECT * FROM $FUNCTIONNAME$($PARAMETER$)"; + private static final String SELECT_COUNT_PATTERN = "SELECT COUNT(*) FROM $FUNCTIONNAME$($PARAMETER$)"; + + public JPAPostgresqlDatabaseProcessor() { + super(); + } + + @Override + protected String functionSelectPattern() { + return SELECT_BASE_PATTERN; + } + + @Override + protected String functionCountPattern() { + return SELECT_COUNT_PATTERN; + } + +} diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPAPostgresqlSqlPatternProvider.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPAPostgresqlSqlPatternProvider.java new file mode 100644 index 000000000..c48876061 --- /dev/null +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPAPostgresqlSqlPatternProvider.java @@ -0,0 +1,27 @@ +package com.sap.olingo.jpa.processor.core.database; + +import java.util.Arrays; + +import com.sap.olingo.jpa.processor.cb.ProcessorSqlFunction; +import com.sap.olingo.jpa.processor.cb.ProcessorSqlParameter; +import com.sap.olingo.jpa.processor.cb.ProcessorSqlPatternProvider; + +public class JPAPostgresqlSqlPatternProvider implements ProcessorSqlPatternProvider { + @Override + public ProcessorSqlFunction getSubStringPattern() { + // substring ( string text [ FROM start integer ] [ FOR count integer ] ) + return new ProcessorSqlFunction("SUBSTRING", Arrays.asList( + new ProcessorSqlParameter(VALUE_PLACEHOLDER, false), + new ProcessorSqlParameter(" FROM ", START_PLACEHOLDER, false), + new ProcessorSqlParameter(" FOR ", LENGTH_PLACEHOLDER, true))); + } + + @Override + public ProcessorSqlFunction getLocatePattern() { + // position ( substring text IN string text ) + return new ProcessorSqlFunction("POSITION", Arrays.asList( + new ProcessorSqlParameter(SEARCH_STRING_PLACEHOLDER, false), + new ProcessorSqlParameter(" IN ", VALUE_PLACEHOLDER, false))); + } + +} diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_DERBY_DatabaseProcessor.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_DERBY_DatabaseProcessor.java index 4111fed76..4002b4a88 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_DERBY_DatabaseProcessor.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_DERBY_DatabaseProcessor.java @@ -1,60 +1,25 @@ package com.sap.olingo.jpa.processor.core.database; -import static com.sap.olingo.jpa.processor.core.exception.ODataJPAProcessorException.MessageKeys.NOT_SUPPORTED_FUNC_WITH_NAVI; - -import java.util.ArrayList; -import java.util.List; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.Expression; -import jakarta.persistence.criteria.From; - -import org.apache.olingo.commons.api.http.HttpStatusCode; -import org.apache.olingo.server.api.ODataApplicationException; -import org.apache.olingo.server.api.uri.UriResource; -import org.apache.olingo.server.api.uri.UriResourceKind; -import org.apache.olingo.server.api.uri.queryoption.SearchOption; - -import com.sap.olingo.jpa.metadata.api.JPAHttpHeaderMap; -import com.sap.olingo.jpa.metadata.api.JPARequestParameterMap; -import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPADataBaseFunction; -import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType; -import com.sap.olingo.jpa.processor.core.exception.ODataJPADBAdaptorException; -import com.sap.olingo.jpa.processor.core.exception.ODataJPAProcessorException; - public class JPA_DERBY_DatabaseProcessor extends JPAAbstractDatabaseProcessor { // NOSONAR - private static final String SELECT_BASE_PATTERN = "SELECT * FROM TABLE ($FUNCTIONNAME$($PARAMETER$))"; - private static final String SELECT_COUNT_PATTERN = "SELECT COUNT(*) FROM TABLE ($FUNCTIONNAME$($PARAMETER$))"; - - @Override - public Expression createSearchWhereClause(final CriteriaBuilder cb, final CriteriaQuery cq, - final From root, final JPAEntityType entityType, final SearchOption searchOption) - throws ODataApplicationException { - throw new ODataJPADBAdaptorException(ODataJPADBAdaptorException.MessageKeys.NOT_SUPPORTED_SEARCH, - HttpStatusCode.NOT_IMPLEMENTED); - } /** * See: Derby: Function Invocation */ - @Override - public Object executeFunctionQuery(final List uriResourceParts, - final JPADataBaseFunction jpaFunction, final EntityManager em, final JPAHttpHeaderMap headers, - final JPARequestParameterMap parameters) throws ODataApplicationException { - - final UriResource last = uriResourceParts.get(uriResourceParts.size() - 1); + private static final String SELECT_BASE_PATTERN = "SELECT * FROM TABLE ($FUNCTIONNAME$($PARAMETER$))"; + private static final String SELECT_COUNT_PATTERN = "SELECT COUNT(*) FROM TABLE ($FUNCTIONNAME$($PARAMETER$))"; - if (last.getKind() == UriResourceKind.count) { - final List countResult = new ArrayList<>(); - countResult.add(executeCountQuery(uriResourceParts, jpaFunction, em, SELECT_COUNT_PATTERN)); - return countResult; - } - if (last.getKind() == UriResourceKind.function) - return executeQuery(uriResourceParts, jpaFunction, em, SELECT_BASE_PATTERN); - throw new ODataJPAProcessorException(NOT_SUPPORTED_FUNC_WITH_NAVI, HttpStatusCode.NOT_IMPLEMENTED); + public JPA_DERBY_DatabaseProcessor() { + super(); + } + @Override + protected String functionSelectPattern() { + return SELECT_BASE_PATTERN; + } + + @Override + protected String functionCountPattern() { + return SELECT_COUNT_PATTERN; } } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_HSQLDB_DatabaseProcessor.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_HSQLDB_DatabaseProcessor.java index ef2d5e39e..3a81d6676 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_HSQLDB_DatabaseProcessor.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_HSQLDB_DatabaseProcessor.java @@ -1,11 +1,5 @@ package com.sap.olingo.jpa.processor.core.database; -import static com.sap.olingo.jpa.processor.core.exception.ODataJPAProcessorException.MessageKeys.NOT_SUPPORTED_FUNC_WITH_NAVI; - -import java.util.ArrayList; -import java.util.List; - -import jakarta.persistence.EntityManager; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.Expression; @@ -13,16 +7,10 @@ import org.apache.olingo.commons.api.http.HttpStatusCode; import org.apache.olingo.server.api.ODataApplicationException; -import org.apache.olingo.server.api.uri.UriResource; -import org.apache.olingo.server.api.uri.UriResourceKind; import org.apache.olingo.server.api.uri.queryoption.SearchOption; -import com.sap.olingo.jpa.metadata.api.JPAHttpHeaderMap; -import com.sap.olingo.jpa.metadata.api.JPARequestParameterMap; -import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPADataBaseFunction; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType; import com.sap.olingo.jpa.processor.core.exception.ODataJPADBAdaptorException; -import com.sap.olingo.jpa.processor.core.exception.ODataJPAProcessorException; public class JPA_HSQLDB_DatabaseProcessor extends JPAAbstractDatabaseProcessor { // NOSONAR private static final String SELECT_BASE_PATTERN = "SELECT * FROM TABLE ($FUNCTIONNAME$($PARAMETER$))"; @@ -38,19 +26,13 @@ public Expression createSearchWhereClause(final CriteriaBuilder cb, fin } @Override - public Object executeFunctionQuery(final List uriResourceParts, - final JPADataBaseFunction jpaFunction, final EntityManager em, final JPAHttpHeaderMap headers, - final JPARequestParameterMap parameters) throws ODataApplicationException { - - final UriResource last = uriResourceParts.get(uriResourceParts.size() - 1); - if (last.getKind() == UriResourceKind.count) { - final List countResult = new ArrayList<>(); - countResult.add(executeCountQuery(uriResourceParts, jpaFunction, em, SELECT_COUNT_PATTERN)); - return countResult; - } - if (last.getKind() == UriResourceKind.function) - return executeQuery(uriResourceParts, jpaFunction, em, SELECT_BASE_PATTERN); - throw new ODataJPAProcessorException(NOT_SUPPORTED_FUNC_WITH_NAVI, HttpStatusCode.NOT_IMPLEMENTED); + protected String functionSelectPattern() { + return SELECT_BASE_PATTERN; + } + + @Override + protected String functionCountPattern() { + return SELECT_COUNT_PATTERN; } } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_POSTSQL_DatabaseProcessor.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_POSTSQL_DatabaseProcessor.java index 5dadc6cbb..c5da8c910 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_POSTSQL_DatabaseProcessor.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/database/JPA_POSTSQL_DatabaseProcessor.java @@ -1,11 +1,5 @@ package com.sap.olingo.jpa.processor.core.database; -import static com.sap.olingo.jpa.processor.core.exception.ODataJPAProcessorException.MessageKeys.NOT_SUPPORTED_FUNC_WITH_NAVI; - -import java.util.ArrayList; -import java.util.List; - -import jakarta.persistence.EntityManager; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.Expression; @@ -13,16 +7,10 @@ import org.apache.olingo.commons.api.http.HttpStatusCode; import org.apache.olingo.server.api.ODataApplicationException; -import org.apache.olingo.server.api.uri.UriResource; -import org.apache.olingo.server.api.uri.UriResourceKind; import org.apache.olingo.server.api.uri.queryoption.SearchOption; -import com.sap.olingo.jpa.metadata.api.JPAHttpHeaderMap; -import com.sap.olingo.jpa.metadata.api.JPARequestParameterMap; -import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPADataBaseFunction; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType; import com.sap.olingo.jpa.processor.core.exception.ODataJPADBAdaptorException; -import com.sap.olingo.jpa.processor.core.exception.ODataJPAProcessorException; /** * Sample implementation a database processor for PostgreSQL @@ -30,7 +18,9 @@ * @author Oliver Grande * Created: 04.07.2019 * + * @deprecated Use JPAPostgresqlDatabaseProcessor instead */ +@Deprecated(since = "2.2.0", forRemoval = true) public class JPA_POSTSQL_DatabaseProcessor extends JPAAbstractDatabaseProcessor { // NOSONAR private static final String SELECT_BASE_PATTERN = "SELECT * FROM $FUNCTIONNAME$($PARAMETER$)"; private static final String SELECT_COUNT_PATTERN = "SELECT COUNT(*) FROM $FUNCTIONNAME$($PARAMETER$)"; @@ -48,19 +38,12 @@ public Expression createSearchWhereClause(final CriteriaBuilder cb, fin } @Override - public Object executeFunctionQuery(final List uriResourceParts, - final JPADataBaseFunction jpaFunction, final EntityManager em, final JPAHttpHeaderMap headers, - final JPARequestParameterMap parameters) throws ODataApplicationException { - - final UriResource last = uriResourceParts.get(uriResourceParts.size() - 1); - - if (last.getKind() == UriResourceKind.count) { - final List countResult = new ArrayList<>(); - countResult.add(executeCountQuery(uriResourceParts, jpaFunction, em, SELECT_COUNT_PATTERN)); - return countResult; - } - if (last.getKind() == UriResourceKind.function) - return executeQuery(uriResourceParts, jpaFunction, em, SELECT_BASE_PATTERN); - throw new ODataJPAProcessorException(NOT_SUPPORTED_FUNC_WITH_NAVI, HttpStatusCode.NOT_IMPLEMENTED); + protected String functionSelectPattern() { + return SELECT_BASE_PATTERN; + } + + @Override + protected String functionCountPattern() { + return SELECT_COUNT_PATTERN; } } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/exception/ODataJPAIllegalArgumentException.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/exception/ODataJPAIllegalArgumentException.java new file mode 100644 index 000000000..df911cced --- /dev/null +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/exception/ODataJPAIllegalArgumentException.java @@ -0,0 +1,22 @@ +package com.sap.olingo.jpa.processor.core.exception; + +public class ODataJPAIllegalArgumentException extends RuntimeException { + + private static final long serialVersionUID = 8012137500100028346L; + + private final String[] params; + + public ODataJPAIllegalArgumentException(final Throwable cause) { + super(cause); + params = new String[0]; + } + + public ODataJPAIllegalArgumentException(final String... params) { + super(); + this.params = params; + } + + public String[] getParams() { + return params; + } +} diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/modify/JPAConversionHelper.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/modify/JPAConversionHelper.java index 191fca4e8..3a9a9bf8f 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/modify/JPAConversionHelper.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/modify/JPAConversionHelper.java @@ -14,6 +14,8 @@ import java.util.List; import java.util.Map; +import javax.annotation.Nonnull; + import jakarta.persistence.AttributeConverter; import org.apache.olingo.commons.api.data.ComplexValue; @@ -210,53 +212,58 @@ public Map convertProperties(final OData odata, final JPAStructu } catch (final ODataJPAModelException e) { throw new ODataJPAProcessorException(e, HttpStatusCode.INTERNAL_SERVER_ERROR); } - switch (odataProperty.getValueType()) { - case COMPLEX: - try { + if (path != null) { + switch (odataProperty.getValueType()) { + case COMPLEX: + try { + final String name = path.getPath().get(0).getInternalName(); + final JPAStructuredType a = st.getAttribute(name) + .orElseThrow(() -> new ODataJPAProcessorException(ATTRIBUTE_NOT_FOUND, + HttpStatusCode.INTERNAL_SERVER_ERROR, name)) + .getStructuredType(); + internalName = name; + jpaAttribute = convertProperties(odata, a, ((ComplexValue) odataProperty.getValue()).getValue()); + } catch (final ODataJPAModelException e) { + throw new ODataJPAProcessorException(e, HttpStatusCode.INTERNAL_SERVER_ERROR); + } + break; + case PRIMITIVE, ENUM: + final JPAAttribute attribute = path.getLeaf(); + internalName = attribute.getInternalName(); + jpaAttribute = processAttributeConverter(odataProperty.getValue(), attribute); + break; + case COLLECTION_PRIMITIVE, COLLECTION_ENUM: + final JPAAttribute attribute2 = path.getLeaf(); + internalName = attribute2.getInternalName(); + jpaAttribute = new ArrayList<>(); + for (final Object property : (List) odataProperty.getValue()) + ((List) jpaAttribute).add(processAttributeConverter(property, attribute2)); + + break; + case COLLECTION_COMPLEX: final String name = path.getPath().get(0).getInternalName(); - final JPAStructuredType a = st.getAttribute(name) - .orElseThrow(() -> new ODataJPAProcessorException(ATTRIBUTE_NOT_FOUND, - HttpStatusCode.INTERNAL_SERVER_ERROR, name)) - .getStructuredType(); - internalName = name; - jpaAttribute = convertProperties(odata, a, ((ComplexValue) odataProperty.getValue()).getValue()); - } catch (final ODataJPAModelException e) { - throw new ODataJPAProcessorException(e, HttpStatusCode.INTERNAL_SERVER_ERROR); - } - break; - case PRIMITIVE, ENUM: - final JPAAttribute attribute = path.getLeaf(); - internalName = attribute.getInternalName(); - jpaAttribute = processAttributeConverter(odataProperty.getValue(), attribute); - break; - case COLLECTION_PRIMITIVE, COLLECTION_ENUM: - final JPAAttribute attribute2 = path.getLeaf(); - internalName = attribute2.getInternalName(); - jpaAttribute = new ArrayList<>(); - for (final Object property : (List) odataProperty.getValue()) - ((List) jpaAttribute).add(processAttributeConverter(property, attribute2)); - - break; - case COLLECTION_COMPLEX: - final String name = path.getPath().get(0).getInternalName(); - jpaAttribute = new ArrayList<>(); - try { - final JPAStructuredType a = st.getAttribute(name) - .orElseThrow(() -> new ODataJPAProcessorException(ATTRIBUTE_NOT_FOUND, - HttpStatusCode.INTERNAL_SERVER_ERROR, name)) - .getStructuredType(); - internalName = name; - for (final ComplexValue property : (List) odataProperty.getValue()) - ((List) jpaAttribute).add(convertProperties(odata, a, property.getValue())); - } catch (final ODataJPAModelException e) { - throw new ODataJPAProcessorException(e, HttpStatusCode.INTERNAL_SERVER_ERROR); - } - break; - default: - throw new ODataJPAProcessorException(MessageKeys.NOT_SUPPORTED_PROP_TYPE, HttpStatusCode.NOT_IMPLEMENTED, - odataProperty.getValueType().name()); + jpaAttribute = new ArrayList<>(); + try { + final JPAStructuredType a = st.getAttribute(name) + .orElseThrow(() -> new ODataJPAProcessorException(ATTRIBUTE_NOT_FOUND, + HttpStatusCode.INTERNAL_SERVER_ERROR, name)) + .getStructuredType(); + internalName = name; + for (final ComplexValue property : (List) odataProperty.getValue()) + ((List) jpaAttribute).add(convertProperties(odata, a, property.getValue())); + } catch (final ODataJPAModelException e) { + throw new ODataJPAProcessorException(e, HttpStatusCode.INTERNAL_SERVER_ERROR); + } + break; + default: + throw new ODataJPAProcessorException(MessageKeys.NOT_SUPPORTED_PROP_TYPE, HttpStatusCode.NOT_IMPLEMENTED, + odataProperty.getValueType().name()); + } + jpaAttributes.put(internalName, jpaAttribute); + } else { + throw new ODataJPAProcessorException(MessageKeys.NOT_SUPPORTED_PROP_TYPE, HttpStatusCode.NOT_IMPLEMENTED, + odataProperty.getValueType().name()); } - jpaAttributes.put(internalName, jpaAttribute); } return jpaAttributes; } @@ -275,7 +282,7 @@ public Map convertUriKeys(final OData odata, final JPAStructured String internalName; for (final UriParameter key : keyPredicates) { try { - final JPAAttribute attribute = st.getPath(key.getName()).getLeaf(); + final JPAAttribute attribute = getJPAPath(st, key.getName()).getLeaf(); internalName = attribute.getInternalName(); final Object jpaAttribute = ExpressionUtility.convertValueOnAttribute(odata, attribute, key.getText(), true); result.put(internalName, jpaAttribute); @@ -286,6 +293,16 @@ public Map convertUriKeys(final OData odata, final JPAStructured return result; } + @Nonnull + private JPAPath getJPAPath(final JPAStructuredType st, final String name) throws ODataJPAModelException, + ODataJPAProcessorException { + final var jpaPath = st.getPath(name); + if (jpaPath != null) + return jpaPath; + else + throw new ODataJPAProcessorException(ATTRIBUTE_NOT_FOUND, HttpStatusCode.INTERNAL_SERVER_ERROR); + } + /** * Like {@link #buildGetterMap}, but without buffer * @param instance diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPACUDRequestProcessor.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPACUDRequestProcessor.java index e6b94a67d..1c404b29a 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPACUDRequestProcessor.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPACUDRequestProcessor.java @@ -19,6 +19,8 @@ import java.util.Map; import java.util.Optional; +import javax.annotation.Nonnull; + import jakarta.persistence.EntityManager; import org.apache.olingo.commons.api.data.Entity; @@ -189,7 +191,7 @@ public void deleteEntity(final ODataRequest request, final ODataResponse respons throw new ODataJPAProcessorException(ENTITY_TYPE_UNKNOWN, BAD_REQUEST, edmEntitySet.getName()); final List uriKeyPredicates = uriResourceEntitySet.getKeyPredicates(); for (final UriParameter uriParam : uriKeyPredicates) { - final JPAAttribute attribute = et.getPath(uriParam.getName()).getLeaf(); + final JPAAttribute attribute = getJPAPath(et, uriParam.getName()).getLeaf(); jpaKeyPredicates.put(attribute.getInternalName(), ExpressionUtility.convertValueOnAttribute(odata, attribute, uriParam.getText(), true)); } @@ -416,9 +418,9 @@ private Map convertUriPath(final JPAEntityType et, final List jpaEmbedded = new HashMap<>(); - final JPAPath path = st.getPath(uriResourceProperty.getProperty().getName()); final String internalName = path.getPath().get(0).getInternalName(); currentMap.put(internalName, jpaEmbedded); @@ -427,12 +429,26 @@ private Map convertUriPath(final JPAEntityType et, final List new ODataJPAProcessorException(ATTRIBUTE_NOT_FOUND, INTERNAL_SERVER_ERROR, internalName)).getStructuredType(); } else { - currentMap.put(st.getPath(uriResourceProperty.getProperty().getName()).getLeaf().getInternalName(), null); + currentMap.put(path.getLeaf().getInternalName(), null); } } return jpaAttributes; } + @Nonnull + private JPAPath getJPAPath(final JPAStructuredType st, final String name) + throws ODataJPAProcessorException { + try { + final var jpaPath = st.getPath(name); + if (jpaPath != null) + return jpaPath; + else + throw new ODataJPAProcessorException(ATTRIBUTE_NOT_FOUND, INTERNAL_SERVER_ERROR, name); + } catch (final ODataJPAModelException e) { + throw new ODataJPAProcessorException(e, BAD_REQUEST); + } + } + private Optional createBeforeImage(final JPARequestEntity requestEntity, final EntityManager em) throws ODataJPAProcessorException, ODataJPAInvocationTargetException { diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPACoreDebugger.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPACoreDebugger.java index d0e7138f8..a3d2bf0da 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPACoreDebugger.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/processor/JPACoreDebugger.java @@ -140,7 +140,7 @@ private long getMemoryConsumption() { final ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); if (threadMXBean instanceof final com.sun.management.ThreadMXBean sunMXBean) { - return sunMXBean.getThreadAllocatedBytes(Thread.currentThread().getId()); + return sunMXBean.getCurrentThreadAllocatedBytes(); } return 0L; } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAAbstractProcessorAttributeImpl.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAAbstractProcessorAttributeImpl.java new file mode 100644 index 000000000..2bdbfe90f --- /dev/null +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAAbstractProcessorAttributeImpl.java @@ -0,0 +1,79 @@ +package com.sap.olingo.jpa.processor.core.properties; + +import static com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException.MessageKeys.QUERY_PREPARATION_NOT_ALLOWED_MEMBER; +import static com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException.MessageKeys.QUERY_PREPARATION_ORDER_BY_TRANSIENT; +import static org.apache.olingo.commons.api.http.HttpStatusCode.BAD_REQUEST; +import static org.apache.olingo.commons.api.http.HttpStatusCode.FORBIDDEN; + +import java.util.List; +import java.util.Map; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.Expression; +import jakarta.persistence.criteria.From; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.JoinType; +import jakarta.persistence.criteria.Order; + +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAssociationPath; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAElement; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAPath; +import com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException; + +abstract class JPAAbstractProcessorAttributeImpl implements JPAProcessorAttribute { + + final JPAPath path; + final List hops; + + JPAAbstractProcessorAttributeImpl(final JPAPath path, final List hops) { + super(); + this.path = path; + this.hops = hops; + } + + Join createJoinFromPath(final String alias, final List pathList, + final From root, final JoinType finalJoinType) { + + Join join = null; + JoinType joinType; + for (int i = 0; i < pathList.size(); i++) { + if (i == pathList.size() - 1) { + joinType = finalJoinType; + } else { + joinType = JoinType.INNER; + } + if (i == 0) { + join = root.join(pathList.get(i).getInternalName(), joinType); + join.alias(alias); + } else if (i < pathList.size()) { + join = join.join(pathList.get(i).getInternalName(), joinType); + join.alias(pathList.get(i).getExternalName()); + } + } + return join; + } + + Order addOrderByExpression(final CriteriaBuilder cb, final boolean isDescending, + final Expression expression) { + return isDescending ? cb.desc(expression) : cb.asc(expression); + } + + @SuppressWarnings("unchecked") + From asJoin(final From target, final Map> joinTables) { + return joinTables.containsKey(getAlias()) + ? (From) joinTables.get(getAlias()) + : (From) createJoinFromPath(getAlias(), hops.get(0).getPath(), target, JoinType.LEFT); + } + + @Override + public Order createOrderBy(final CriteriaBuilder cb, final List groups) throws ODataJPAQueryException { + if (path.isTransient()) + throw new ODataJPAQueryException(QUERY_PREPARATION_ORDER_BY_TRANSIENT, BAD_REQUEST, path + .getLeaf().toString()); + if (!path.isPartOfGroups(groups)) { + throw new ODataJPAQueryException(QUERY_PREPARATION_NOT_ALLOWED_MEMBER, FORBIDDEN, path.getAlias()); + } + return addOrderByExpression(cb, sortDescending(), getPath()); + } + +} diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPACollectionProperty.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPACollectionProperty.java new file mode 100644 index 000000000..218ef36c6 --- /dev/null +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPACollectionProperty.java @@ -0,0 +1,5 @@ +package com.sap.olingo.jpa.processor.core.properties; + +public interface JPACollectionProperty extends JPAProcessorAttribute { + +} diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAOrderByPropertyFactory.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAOrderByPropertyFactory.java new file mode 100644 index 000000000..6320c4e26 --- /dev/null +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAOrderByPropertyFactory.java @@ -0,0 +1,142 @@ +package com.sap.olingo.jpa.processor.core.properties; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Optional; + +import javax.annotation.Nonnull; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.From; + +import org.apache.olingo.server.api.uri.UriResourceKind; +import org.apache.olingo.server.api.uri.UriResourceNavigation; +import org.apache.olingo.server.api.uri.UriResourceProperty; +import org.apache.olingo.server.api.uri.queryoption.OrderByItem; +import org.apache.olingo.server.api.uri.queryoption.expression.Member; + +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAssociationPath; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPACollectionAttribute; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPADescriptionAttribute; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAPath; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAStructuredType; +import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException; +import com.sap.olingo.jpa.processor.core.exception.ODataJPAIllegalArgumentException; + +public class JPAOrderByPropertyFactory { + /** + * + * @param orderByItem + * @param et + * @return + * @throws ODataJPAIllegalArgumentException + */ + public JPAProcessorAttribute createProperty(final OrderByItem orderByItem, final JPAEntityType et, + final Locale locale) { + try { + final var orderInfo = extractOrderInfo(et, orderByItem); + return createProperty(orderByItem, locale, orderInfo); + } catch (final ODataJPAModelException e) { + throw new ODataJPAIllegalArgumentException(orderByItem.getExpression().toString()); + } + } + + public JPAProcessorAttribute createProperty(final From target, final JPAPath path, final CriteriaBuilder cb) { + final var orderByAttribute = new JPAProcessorSimpleAttributeImpl(path, Collections.emptyList(), false); + return orderByAttribute.setTarget(target, Collections.emptyMap(), cb); + } + + private OrderInfo extractOrderInfo(final JPAEntityType et, final OrderByItem orderByItem) + throws ODataJPAModelException { + + final List hops = new ArrayList<>(); + var path = new StringBuilder(); + JPAStructuredType type = et; + if (orderByItem.getExpression() instanceof final Member member) { + for (final var part : member.getResourcePath().getUriResourceParts()) { + if (part instanceof final UriResourceProperty property) { + if (property.isCollection()) { + path.append(property.getProperty().getName()); + final var associationPath = ((JPACollectionAttribute) getJPAPath(type, path.toString()).getLeaf()) + .asAssociation(); + type = associationPath.getTargetType(); + hops.add(associationPath); + path = new StringBuilder(); + } else { + path.append(property.getProperty().getName()).append(JPAPath.PATH_SEPARATOR); + } + } else if (part instanceof final UriResourceNavigation navigation) { + path.append(navigation.getProperty().getName()); + final var associationPath = type.getAssociationPath(path.toString()); + type = associationPath.getTargetType(); + hops.add(associationPath); + path = new StringBuilder(); + } + } + } else { + // TODO Support methods like tolower for order by as well + throw new ODataJPAIllegalArgumentException(orderByItem.getExpression().toString()); + } + return new OrderInfo(hops, path, type); + } + + private JPAProcessorAttribute createProperty(final OrderByItem orderByItem, final Locale locale, + final OrderInfo orderInfo) throws ODataJPAModelException { + switch (getKindOfLastPart(orderByItem)) { + case primitiveProperty: + final var pathString = orderInfo.path.deleteCharAt(orderInfo.path.length() - 1).toString(); + final var lastType = orderInfo.type; + return Optional.ofNullable(lastType.getPath(pathString)) + .filter(attribute -> attribute.getLeaf() instanceof JPADescriptionAttribute) + .map(attribute -> createDescriptionProperty(orderByItem, orderInfo.hops, attribute, locale)) + .or(() -> this.createPrimitiveProperty(orderByItem, orderInfo.hops, pathString, lastType)) + .orElseThrow(() -> new ODataJPAIllegalArgumentException(orderByItem.getExpression().toString())); + case count: + return new JPAProcessorCountAttributeImpl(orderInfo.hops, orderByItem.isDescending()); + default: + throw new ODataJPAIllegalArgumentException(orderByItem.getExpression().toString()); + } + } + + private JPAProcessorAttribute createDescriptionProperty(final OrderByItem orderByItem, + final List hops, final JPAPath attribute, final Locale locale) { + return new JPAProcessorDescriptionAttributeImpl(attribute, hops, orderByItem.isDescending(), locale); + } + + private Optional createPrimitiveProperty(final OrderByItem orderByItem, + final List hops, final String pathString, final JPAStructuredType type) { + + try { + return Optional.ofNullable(type.getPath(pathString)) + .map(attribute -> new JPAProcessorSimpleAttributeImpl(attribute, hops, orderByItem.isDescending())); + } catch (final ODataJPAModelException e) { + return Optional.empty(); + } + } + + private UriResourceKind getKindOfLastPart(final OrderByItem orderByItem) { + final var parts = ((Member) orderByItem.getExpression()).getResourcePath().getUriResourceParts(); + return parts.get(parts.size() - 1).getKind(); + } + + @Nonnull + private JPAPath getJPAPath(final JPAStructuredType st, final String name) { + + try { + final var jpaPath = st.getPath(name); + if (jpaPath != null) + return jpaPath; + else + throw new ODataJPAIllegalArgumentException(name); + } catch (final ODataJPAModelException e) { + throw new ODataJPAIllegalArgumentException(e); + } + } + + private static record OrderInfo(List hops, StringBuilder path, JPAStructuredType type) { + + } +} diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorAttribute.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorAttribute.java new file mode 100644 index 000000000..7948ea141 --- /dev/null +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorAttribute.java @@ -0,0 +1,78 @@ +package com.sap.olingo.jpa.processor.core.properties; + +import java.util.List; +import java.util.Map; + +import javax.annotation.Nonnull; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.From; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.Order; +import jakarta.persistence.criteria.Path; + +import com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException; + +public interface JPAProcessorAttribute { + /** + * + * @return + */ + @Nonnull + String getAlias(); + + /** + * As of now transient properties are not sortable + * @return + */ + boolean isSortable(); + + /** + * @return True if sorting descending is required + */ + boolean sortDescending(); + + /** + * Indicates that a join is required to fulfill the request + * @return + */ + boolean requiresJoin(); + + /** + * + * @param target + * @param joinTables + * @param cb + * @return + */ + JPAProcessorAttribute setTarget(@Nonnull final From target, @Nonnull final Map> joinTables, + @Nonnull final CriteriaBuilder cb); + + /** + * If required, a join with 'from' is generated. If a join is required can be checked with + * {@link #requiresJoin()}.
+ * Requires that the target was already set via + * @param + * @param + * @return Null if no join is required + */ + Join createJoin(); + + /** + * Generates an order statement for this attribute.
+ * Requires that {@link #createJoin(From, CriteriaBuilder)} was called before. + * @param cb + * @param groups + * @return + * @throws ODataJPAQueryException + */ + @Nonnull + Order createOrderBy(final CriteriaBuilder cb, final List groups) throws ODataJPAQueryException; + + /** + * Requires that {@link #setTarget(From, CriteriaBuilder)} was called before. + * @return + */ + Path getPath(); + +} diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorCountAttribute.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorCountAttribute.java new file mode 100644 index 000000000..985cdc694 --- /dev/null +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorCountAttribute.java @@ -0,0 +1,5 @@ +package com.sap.olingo.jpa.processor.core.properties; + +public interface JPAProcessorCountAttribute extends JPAProcessorAttribute { + +} diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorCountAttributeImpl.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorCountAttributeImpl.java new file mode 100644 index 000000000..8f03d6420 --- /dev/null +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorCountAttributeImpl.java @@ -0,0 +1,99 @@ +package com.sap.olingo.jpa.processor.core.properties; + +import static com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException.MessageKeys.QUERY_PREPARATION_ORDER_BY_TRANSIENT; +import static org.apache.olingo.commons.api.http.HttpStatusCode.NOT_IMPLEMENTED; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.From; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.Order; +import jakarta.persistence.criteria.Path; + +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAssociationPath; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAttribute; +import com.sap.olingo.jpa.processor.core.exception.ODataJPAIllegalArgumentException; +import com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException; + +class JPAProcessorCountAttributeImpl extends JPAAbstractProcessorAttributeImpl implements JPAProcessorCountAttribute { + + private final boolean descending; + private Optional> from; + + JPAProcessorCountAttributeImpl(final List hops, final boolean descending) { + super(null, hops); + this.descending = descending; + this.from = Optional.empty(); + if (hops.size() > 1) + throw new ODataJPAIllegalArgumentException(hops.get(1).getAlias()); + } + + @Override + public String getAlias() { + return hops.isEmpty() ? "Count" : hops.get(0).getAlias(); + } + + @Override + public boolean isSortable() { + return !hops.isEmpty() && !hops.get(0).getLeaf().isTransient(); + } + + @Override + public boolean sortDescending() { + return descending; + } + + @Override + public boolean requiresJoin() { + return !hops.isEmpty(); + } + + @Override + public JPAProcessorAttribute setTarget(final From target, final Map> joinTables, + final CriteriaBuilder cb) { + determineFrom(target, joinTables); + return this; + } + + @SuppressWarnings("unchecked") + @Override + public Join createJoin() { + return requiresJoin() + ? (Join) from.orElseThrow(IllegalAccessError::new) + : null; + } + + @Override + public Path getPath() { + if (from.isEmpty()) + throw new IllegalAccessError(); + return null; + } + + @Override + public Order createOrderBy(final CriteriaBuilder cb, final List groups) throws ODataJPAQueryException { + if (isTransient()) + throw new ODataJPAQueryException(QUERY_PREPARATION_ORDER_BY_TRANSIENT, NOT_IMPLEMENTED, hops.get(0).getAlias()); + return addOrderByExpression(cb, sortDescending(), cb.count(from.get())); + } + + private boolean isTransient() { + for (final var hop : hops) { + for (final var part : hop.getPath()) { + if (part instanceof final JPAAttribute attribute + && attribute.isTransient()) + return true; + } + } + return false; + } + + void determineFrom(final From target, final Map> joinTables) { + from = Optional.of(requiresJoin() + ? asJoin(target, joinTables) + : target); + } +} diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorDescriptionAttribute.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorDescriptionAttribute.java new file mode 100644 index 000000000..f498eb1bb --- /dev/null +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorDescriptionAttribute.java @@ -0,0 +1,5 @@ +package com.sap.olingo.jpa.processor.core.properties; + +public interface JPAProcessorDescriptionAttribute extends JPAProcessorAttribute { + +} diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorDescriptionAttributeImpl.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorDescriptionAttributeImpl.java new file mode 100644 index 000000000..12a61c793 --- /dev/null +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorDescriptionAttributeImpl.java @@ -0,0 +1,132 @@ +package com.sap.olingo.jpa.processor.core.properties; + +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.Expression; +import jakarta.persistence.criteria.From; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.JoinType; +import jakarta.persistence.criteria.Path; +import jakarta.persistence.criteria.Predicate; + +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAssociationPath; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPADescriptionAttribute; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAElement; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAPath; +import com.sap.olingo.jpa.processor.core.exception.ODataJPAIllegalArgumentException; + +public class JPAProcessorDescriptionAttributeImpl extends JPAAbstractProcessorAttributeImpl implements + JPAProcessorDescriptionAttribute { + + private final boolean sortDescending; + private final Locale locale; + private Optional> criteriaPath; + private Optional> from; + + public JPAProcessorDescriptionAttributeImpl(final JPAPath path, final List hops, + final boolean descending, final Locale locale) { + super(path, hops); + this.sortDescending = descending; + this.locale = locale; + this.criteriaPath = Optional.empty(); + this.from = Optional.empty(); + if (hops.size() > 1) + throw new ODataJPAIllegalArgumentException(path.getAlias()); + } + + @Override + public String getAlias() { + return path.getAlias(); + } + + @Override + public boolean isSortable() { + return true; + } + + @Override + public boolean sortDescending() { + return sortDescending; + } + + @Override + public boolean requiresJoin() { + return true; + } + + @Override + public Path getPath() { + if (criteriaPath.isEmpty()) { + criteriaPath = Optional.of( + from.orElseThrow(IllegalAccessError::new) + .get(((JPADescriptionAttribute) path.getLeaf()).getDescriptionAttribute().getInternalName())); + } + return criteriaPath.get(); + } + + @Override + public JPAProcessorAttribute setTarget(final From target, final Map> joinTables, + final CriteriaBuilder cb) { + determineFrom(target, cb, joinTables); + return this; + } + + @SuppressWarnings("unchecked") + @Override + public Join createJoin() { + return (Join) from.orElseThrow(IllegalAccessError::new); + } + + @SuppressWarnings("unchecked") + void determineFrom(final From target, final CriteriaBuilder cb, + final Map> joinTables) throws IllegalAccessError { + + from = Optional.of( + joinTables.containsKey(getAlias()) + ? (From) joinTables.get(getAlias()) + : (From) createJoinFromPath(target, cb)); + } + + @SuppressWarnings("unchecked") + From createJoinFromPath(final From target, final CriteriaBuilder cb) { + final JPADescriptionAttribute descriptionField = ((JPADescriptionAttribute) path.getLeaf()); + + final var parentFrom = (!hops.isEmpty()) + ? createJoinFromPath(hops.get(0).getAlias(), hops.get(0).getPath(), target, JoinType.LEFT) + : target; + + final Join join = createJoinFromPath(getAlias(), path.getPath(), parentFrom, JoinType.LEFT); + if (descriptionField.isLocationJoin()) { + join.on(createOnCondition(join, descriptionField, locale.toString(), cb)); + } else { + join.on(createOnCondition(join, descriptionField, locale.getLanguage(), cb)); + } + return (From) join; + } + + private Expression createOnCondition(final Join join, final JPADescriptionAttribute descriptionField, + final String localValue, final CriteriaBuilder cb) { + final Predicate existingOn = join.getOn(); + Expression result = cb.equal(determinePath(join, descriptionField.getLocaleFieldName()), localValue); + if (existingOn != null) { + result = cb.and(existingOn, result); + } + for (final var value : descriptionField.getFixedValueAssignment().entrySet()) { + result = cb.and(result, + cb.equal(determinePath(join, value.getKey()), value.getValue())); + } + return result; + } + + private Expression determinePath(final Join join, final JPAPath jpaPath) { + Path attributePath = join; + for (final JPAElement pathElement : jpaPath.getPath()) { + attributePath = attributePath.get(pathElement.getInternalName()); + } + return attributePath; + } +} diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorNavigationAttribute.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorNavigationAttribute.java new file mode 100644 index 000000000..4eb70c968 --- /dev/null +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorNavigationAttribute.java @@ -0,0 +1,5 @@ +package com.sap.olingo.jpa.processor.core.properties; + +public interface JPAProcessorNavigationAttribute extends JPAProcessorAttribute { + +} diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorSimpleAttribute.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorSimpleAttribute.java new file mode 100644 index 000000000..f6ced9624 --- /dev/null +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorSimpleAttribute.java @@ -0,0 +1,5 @@ +package com.sap.olingo.jpa.processor.core.properties; + +public interface JPAProcessorSimpleAttribute extends JPAProcessorAttribute { + +} diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorSimpleAttributeImpl.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorSimpleAttributeImpl.java new file mode 100644 index 000000000..d61768e2a --- /dev/null +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorSimpleAttributeImpl.java @@ -0,0 +1,84 @@ +package com.sap.olingo.jpa.processor.core.properties; + +import static com.sap.olingo.jpa.processor.core.query.ExpressionUtility.convertToCriteriaPath; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.From; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.Path; + +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAssociationPath; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAPath; +import com.sap.olingo.jpa.processor.core.exception.ODataJPAIllegalArgumentException; + +class JPAProcessorSimpleAttributeImpl extends JPAAbstractProcessorAttributeImpl implements JPAProcessorSimpleAttribute { + + private final boolean sortDescending; + private Optional> criteriaPath; + private Optional> from; + + JPAProcessorSimpleAttributeImpl(final JPAPath path, final List hops, final boolean descending) { + super(path, hops); + this.sortDescending = descending; + this.criteriaPath = Optional.empty(); + this.from = Optional.empty(); + if (hops.size() > 1) + throw new ODataJPAIllegalArgumentException(path.getAlias()); + } + + @Override + public String getAlias() { + return hops.isEmpty() ? path.getAlias() : hops.get(0).getAlias(); + } + + @Override + public boolean isSortable() { + return !path.isTransient(); + } + + @Override + public boolean sortDescending() { + return sortDescending; + } + + @Override + public boolean requiresJoin() { + return !hops.isEmpty(); + } + + @Override + public JPAProcessorAttribute setTarget(final From target, final Map> joinTables, + final CriteriaBuilder cb) { + determineFrom(target, joinTables); + return this; + } + + @SuppressWarnings("unchecked") + @Override + public Join createJoin() { + return requiresJoin() + ? (Join) from.orElseThrow(IllegalAccessError::new) + : null; + } + + @Override + public Path getPath() { + if (criteriaPath.isEmpty()) { + criteriaPath = Optional.of(convertToCriteriaPath( + from.orElseThrow(IllegalAccessError::new), + path.getPath())); + criteriaPath.get().alias(path.getAlias()); + } + return criteriaPath.get(); + } + + void determineFrom(final From target, final Map> joinTables) { + from = Optional.of(requiresJoin() + ? asJoin(target, joinTables) + : target); + } +} diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/ExpressionUtility.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/ExpressionUtility.java index 557d9825e..f997157f2 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/ExpressionUtility.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/ExpressionUtility.java @@ -1,10 +1,14 @@ package com.sap.olingo.jpa.processor.core.query; +import static com.sap.olingo.jpa.processor.core.exception.ODataJPAProcessorException.MessageKeys.ATTRIBUTE_NOT_FOUND; + import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Map; +import javax.annotation.Nonnull; + import jakarta.persistence.AttributeConverter; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.Expression; @@ -29,9 +33,11 @@ import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAParameterFacet; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAPath; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAStructuredType; import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException; import com.sap.olingo.jpa.processor.cb.ProcessorCriteriaBuilder; import com.sap.olingo.jpa.processor.core.exception.ODataJPAFilterException; +import com.sap.olingo.jpa.processor.core.exception.ODataJPAProcessorException; import com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException; import com.sap.olingo.jpa.processor.core.filter.JPAInvertibleVisitableExpression; @@ -45,12 +51,16 @@ private ExpressionUtility() {} public static Expression createEQExpression(final OData odata, final CriteriaBuilder cb, final From root, final JPAEntityType jpaEntity, final UriParameter keyPredicate) throws ODataJPAFilterException, ODataJPAModelException { + try { + final JPAPath path = getJPAPath(jpaEntity, keyPredicate.getName()); + final JPAAttribute attribute = path.getLeaf(); - final JPAPath path = jpaEntity.getPath(keyPredicate.getName()); - final JPAAttribute attribute = path.getLeaf(); - - return cb.equal(convertToCriteriaPath(root, path.getPath()), convertValueOnAttribute(odata, attribute, keyPredicate - .getText())); + return cb.equal(convertToCriteriaPath(root, path.getPath()), convertValueOnAttribute(odata, attribute, + keyPredicate + .getText())); + } catch (final ODataJPAProcessorException e) { + throw new ODataJPAFilterException(e, HttpStatusCode.INTERNAL_SERVER_ERROR); + } } @@ -63,11 +73,11 @@ public static Expression createEQExpression(final OData odata, final Cr */ @SuppressWarnings("unchecked") public static Path convertToCriteriaPath(final Map> joinTables, final From root, - final List jpaPath) { + final JPAPath jpaPath) { Path path = root; - for (final JPAElement jpaPathElement : jpaPath) + for (final JPAElement jpaPathElement : jpaPath.getPath()) if (jpaPathElement instanceof final JPADescriptionAttribute descriptionAttribute) { - final Join join = (Join) joinTables.get(jpaPathElement.getInternalName()); + final Join join = (Join) joinTables.get(jpaPath.getAlias()); path = join.get(descriptionAttribute.getDescriptionAttribute().getInternalName()); } else if (jpaPathElement instanceof JPACollectionAttribute) { path = joinTables.get(jpaPathElement.getExternalName()); @@ -99,13 +109,13 @@ public static List> convertToCriteriaPathList(final From root try { final List> result = new ArrayList<>(jpaAttributes.size()); for (final JPAAttribute attribute : jpaAttributes) { - final JPAPath path = et.getPath(attribute.getExternalName()); + final JPAPath path = getJPAPath(et, attribute.getExternalName()); final Path p = convertToCriteriaPath(root, path.getPath()); p.alias(path.getAlias()); result.add(p); } return result; - } catch (final ODataJPAModelException e) { + } catch (final ODataJPAModelException | ODataJPAProcessorException e) { throw new ODataJPAQueryException(e, HttpStatusCode.INTERNAL_SERVER_ERROR); } } @@ -207,4 +217,14 @@ public static Expression createSubQueryBasedExpression(final Subquery> jakarta.persistence.criteria.Express return null; } - /** - * @param orderByTarget - * @param selectionPath - * @param query - * @param lastInfo - * @return - * @throws ODataApplicationException - * @throws JPANoSelectionException - */ - protected Map> createFromClause(final List orderByTarget, + protected Map> createFromClause(final List orderByTarget, final Collection selectionPath, final CriteriaQuery query, final JPANavigationPropertyInfo lastInfo) throws ODataApplicationException, JPANoSelectionException { @@ -271,7 +265,7 @@ protected > jakarta.persistence.criteria.Express createFromClauseNavigationJoins(joinTables); createFromClauseCollectionsJoins(joinTables); // 2. OrderBy navigation property - createFromClauseOrderBy(orderByTarget, joinTables, target); + createFromClauseOrderBy2(orderByTarget, joinTables, target); // 3. Description Join determine createFromClauseDescriptionFields(selectionPath, joinTables, target, navigationInfo); // 4. Collection Attribute Joins @@ -577,7 +571,10 @@ private boolean checkCollectionIsPartOfGroup(final String collectionPath) throws try { final JPAPath path = jpaEntity.getPath(collectionPath); - return path.isPartOfGroups(groups); + if (path != null) + return path.isPartOfGroups(groups); + else + throw new ODataJPAProcessorException(MessageKeys.QUERY_PREPARATION_ERROR, HttpStatusCode.INTERNAL_SERVER_ERROR); } catch (final ODataJPAModelException e) { throw new ODataJPAProcessorException(e, HttpStatusCode.INTERNAL_SERVER_ERROR); } @@ -629,29 +626,34 @@ private void copySelectableProperties(final SelectionPathInfo selectabl @SuppressWarnings("unchecked") private > jakarta.persistence.criteria.Expression createBoundaryEquals( - final JPAEntityType et, final From from, final JPAKeyPair jpaKeyPair) throws ODataJPAModelException { + final JPAEntityType et, final From from, final JPAKeyPair jpaKeyPair) throws ODataJPAModelException, + ODataJPAQueryException { jakarta.persistence.criteria.Expression whereCondition = null; final List keyElements = new ArrayList<>(et.getKey()); Collections.reverse(keyElements); for (final JPAAttribute keyElement : keyElements) { - final Path keyPath = (Path) ExpressionUtility.> convertToCriteriaPath(from, et.getPath( - keyElement - .getExternalName()) - .getPath()); - final jakarta.persistence.criteria.Expression equalFragment = cb.equal(keyPath, jpaKeyPair.getMin().get( - keyElement)); - if (whereCondition == null) - whereCondition = equalFragment; - else - whereCondition = cb.and(whereCondition, equalFragment); + final var jpaPath = et.getPath(keyElement.getExternalName()); + if (jpaPath != null) { + final Path keyPath = (Path) ExpressionUtility.> convertToCriteriaPath(from, jpaPath + .getPath()); + final jakarta.persistence.criteria.Expression equalFragment = cb.equal(keyPath, jpaKeyPair.getMin() + .get(keyElement)); + if (whereCondition == null) + whereCondition = equalFragment; + else + whereCondition = cb.and(whereCondition, equalFragment); + } else { + throw new ODataJPAQueryException(QUERY_RESULT_KEY_PROPERTY_ERROR, INTERNAL_SERVER_ERROR, et.getExternalName()); + } } return whereCondition; } @SuppressWarnings("unchecked") - private > jakarta.persistence.criteria.Expression createBoundaryWithUpper( - final JPAEntityType et, final From from, final JPAKeyPair jpaKeyPair) throws ODataJPAModelException { + private > jakarta.persistence.criteria.Expression createBoundaryWithUpper( // NOSONAR + final JPAEntityType et, final From from, final JPAKeyPair jpaKeyPair) throws ODataJPAModelException, + ODataJPAQueryException { final List keyElements = new ArrayList<>(et.getKey()); Collections.reverse(keyElements); @@ -660,22 +662,29 @@ private > jakarta.persistence.criteria.Expressio for (int primaryIndex = 0; primaryIndex < keyElements.size(); primaryIndex++) { for (int secondaryIndex = primaryIndex; secondaryIndex < keyElements.size(); secondaryIndex++) { final JPAAttribute keyElement = keyElements.get(secondaryIndex); - final Path keyPath = (Path) ExpressionUtility.> convertToCriteriaPath(from, - et.getPath(keyElement.getExternalName()).getPath()); - final Y lowerBoundary = jpaKeyPair.getMinElement(keyElement); - final Y upperBoundary = jpaKeyPair.getMaxElement(keyElement); - if (secondaryIndex == primaryIndex) { - if (primaryIndex == 0) { - lowerExpression = cb.greaterThanOrEqualTo(keyPath, lowerBoundary); - upperExpression = cb.lessThanOrEqualTo(keyPath, upperBoundary); + final var jpaPath = et.getPath(keyElement.getExternalName()); + if (jpaPath != null) { + final Path keyPath = (Path) ExpressionUtility.> convertToCriteriaPath(from, + jpaPath.getPath()); + final Y lowerBoundary = jpaKeyPair.getMinElement(keyElement); + final Y upperBoundary = jpaKeyPair.getMaxElement(keyElement); + if (secondaryIndex == primaryIndex) { + if (primaryIndex == 0) { + lowerExpression = cb.greaterThanOrEqualTo(keyPath, lowerBoundary); + upperExpression = cb.lessThanOrEqualTo(keyPath, upperBoundary); + } else { + lowerExpression = cb.or(lowerExpression, cb.greaterThan(keyPath, lowerBoundary)); + upperExpression = cb.or(upperExpression, cb.lessThan(keyPath, upperBoundary)); + } } else { - lowerExpression = cb.or(lowerExpression, cb.greaterThan(keyPath, lowerBoundary)); - upperExpression = cb.or(upperExpression, cb.lessThan(keyPath, upperBoundary)); + lowerExpression = cb.and(lowerExpression, cb.equal(keyPath, lowerBoundary)); + upperExpression = cb.and(upperExpression, cb.equal(keyPath, upperBoundary)); } } else { - lowerExpression = cb.and(lowerExpression, cb.equal(keyPath, lowerBoundary)); - upperExpression = cb.and(upperExpression, cb.equal(keyPath, upperBoundary)); + throw new ODataJPAQueryException(QUERY_RESULT_KEY_PROPERTY_ERROR, INTERNAL_SERVER_ERROR, et + .getExternalName()); } + } } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAAbstractQuery.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAAbstractQuery.java index e368e9daf..b7180d2e2 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAAbstractQuery.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAAbstractQuery.java @@ -3,14 +3,11 @@ import static com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException.MessageKeys.MISSING_CLAIM; import static com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException.MessageKeys.MISSING_CLAIMS_PROVIDER; import static com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException.MessageKeys.QUERY_PREPARATION_ORDER_BY_NOT_SUPPORTED; -import static com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException.MessageKeys.QUERY_PREPARATION_ORDER_BY_TRANSIENT; -import static com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException.MessageKeys.QUERY_RESULT_CONV_ERROR; import static com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException.MessageKeys.QUERY_RESULT_ENTITY_TYPE_ERROR; import static com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException.MessageKeys.WILDCARD_UPPER_NOT_SUPPORTED; import static java.util.stream.Collectors.toList; import static org.apache.olingo.commons.api.http.HttpStatusCode.BAD_REQUEST; import static org.apache.olingo.commons.api.http.HttpStatusCode.INTERNAL_SERVER_ERROR; -import static org.apache.olingo.commons.api.http.HttpStatusCode.NOT_IMPLEMENTED; import java.util.ArrayList; import java.util.Collection; @@ -23,6 +20,7 @@ import java.util.NoSuchElementException; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import javax.annotation.Nonnull; @@ -39,24 +37,16 @@ import jakarta.persistence.criteria.Subquery; import org.apache.olingo.commons.api.edm.EdmEntityType; -import org.apache.olingo.commons.api.edm.EdmNavigationProperty; import org.apache.olingo.commons.api.ex.ODataException; import org.apache.olingo.commons.api.http.HttpStatusCode; import org.apache.olingo.server.api.OData; import org.apache.olingo.server.api.ODataApplicationException; -import org.apache.olingo.server.api.uri.UriInfoResource; import org.apache.olingo.server.api.uri.UriParameter; -import org.apache.olingo.server.api.uri.UriResource; -import org.apache.olingo.server.api.uri.UriResourceNavigation; -import org.apache.olingo.server.api.uri.UriResourceProperty; -import org.apache.olingo.server.api.uri.queryoption.OrderByItem; import org.apache.olingo.server.api.uri.queryoption.OrderByOption; import org.apache.olingo.server.api.uri.queryoption.SkipOption; import org.apache.olingo.server.api.uri.queryoption.TopOption; -import org.apache.olingo.server.api.uri.queryoption.expression.Member; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAssociationPath; -import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPACollectionAttribute; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPADescriptionAttribute; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAElement; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType; @@ -73,9 +63,12 @@ import com.sap.olingo.jpa.processor.core.api.JPAODataRequestContextAccess; import com.sap.olingo.jpa.processor.core.api.JPAServiceDebugger; import com.sap.olingo.jpa.processor.core.api.JPAServiceDebugger.JPARuntimeMeasurement; +import com.sap.olingo.jpa.processor.core.exception.ODataJPAIllegalArgumentException; import com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException; import com.sap.olingo.jpa.processor.core.filter.JPAFilterComplier; import com.sap.olingo.jpa.processor.core.processor.JPAEmptyDebugger; +import com.sap.olingo.jpa.processor.core.properties.JPAOrderByPropertyFactory; +import com.sap.olingo.jpa.processor.core.properties.JPAProcessorAttribute; public abstract class JPAAbstractQuery { protected static final String PERMISSIONS_REGEX = ".*[\\*\\%\\+\\_].*"; @@ -183,36 +176,54 @@ protected final void createFromClauseDescriptionFields(final Collection * @param orderByTarget * @param joinTables */ - protected void createFromClauseOrderBy(final List orderByTarget, + protected void createFromClauseOrderBy2(final List orderByTarget, final Map> joinTables, final From from) { - for (final JPAAssociationPath orderBy : orderByTarget) { - From join = from; - for (final JPAElement o : orderBy.getPath()) { - join = join.join(o.getInternalName(), JoinType.LEFT); - } - // Take on condition from JPA metadata; no explicit on - joinTables.put(orderBy.getAlias(), join); - } + orderByTarget.stream() + .map(property -> property.setTarget(from, joinTables, cb)) + .filter(JPAProcessorAttribute::requiresJoin) + .forEach(property -> joinTables.put(property.getAlias(), property.createJoin())); } protected List> createGroupBy(final Map> joinTables, // NOSONAR - final From from, final Collection selectionPathList, @Nonnull Set> orderByPaths) { + final From from, final Collection selectionPathList, @Nonnull final Set> orderByPaths) { try (JPARuntimeMeasurement serializerMeasurement = debugger.newMeasurement(this, "createGroupBy")) { final List> groupBy = new ArrayList<>(); for (final JPAPath jpaPath : selectionPathList) { - var path = ExpressionUtility.convertToCriteriaPath(joinTables, from, jpaPath.getPath()); + final var path = ExpressionUtility.convertToCriteriaPath(joinTables, from, jpaPath); orderByPaths.remove(path); groupBy.add(path); } - for (var path : orderByPaths) { + for (final var path : orderByPaths) { groupBy.add(path); } return groupBy; } } + protected List> createGroupBy(final Map> joinTables, + final From from, final Collection selectionPathList, + @Nonnull final List orderByAttributes) { + + try (JPARuntimeMeasurement serializerMeasurement = debugger.newMeasurement(this, "createGroupBy")) { + final Set> orderByPaths = new HashSet<>(); + final List> groupBy = new ArrayList<>(); + for (final JPAPath jpaPath : selectionPathList) { + final var path = ExpressionUtility.convertToCriteriaPath(joinTables, from, jpaPath); + orderByPaths.add(path); + groupBy.add(path); + } + + for (final var attribute : orderByAttributes) { + final var path = attribute.getPath(); + if (path != null && !orderByPaths.contains(path)) + groupBy.add(path); + } + return groupBy; + } + } + protected Join createJoinFromPath(final String alias, final List pathList, final From root, final JoinType finalJoinType) { @@ -266,7 +277,7 @@ protected List> createSelectClause(final Map> jo // Build select clause for (final JPAPath jpaPath : requestedProperties) { if (jpaPath.isPartOfGroups(groups)) { - final Path path = ExpressionUtility.convertToCriteriaPath(joinTables, target, jpaPath.getPath()); + final Path path = ExpressionUtility.convertToCriteriaPath(joinTables, target, jpaPath); path.alias(jpaPath.getAlias()); selections.add(path); } @@ -287,8 +298,7 @@ protected jakarta.persistence.criteria.Expression createProtectionWhere throw new ODataJPAQueryException(MISSING_CLAIM, HttpStatusCode.FORBIDDEN); } if (!(containsAll(values))) { - final Path path = ExpressionUtility.convertToCriteriaPath(dummyJoinTables, from, protection.getPath() - .getPath()); + final Path path = ExpressionUtility.convertToCriteriaPath(dummyJoinTables, from, protection.getPath()); restriction = addWhereClause(restriction, createProtectionWhereForAttribute(values, path, protection .supportsWildcards())); } @@ -378,56 +388,26 @@ protected final List extractDescriptionAttributes(final Collection extractOrderByNavigationAttributes(final OrderByOption orderBy) + protected List getOrderByAttributes(final OrderByOption orderBy) throws ODataApplicationException { - - final List navigationAttributes = new ArrayList<>(); if (orderBy != null) { - for (final OrderByItem orderByItem : orderBy.getOrders()) { - final org.apache.olingo.server.api.uri.queryoption.expression.Expression expression = - orderByItem.getExpression(); - if (expression instanceof final Member member) { - final UriInfoResource resourcePath = member.getResourcePath(); - final StringBuilder pathString = new StringBuilder(); - for (final UriResource uriResource : resourcePath.getUriResourceParts()) { - try { - if (uriResource instanceof final UriResourceNavigation resourceNavigation) { - final EdmNavigationProperty edmNavigationProperty = resourceNavigation.getProperty(); - navigationAttributes.add(jpaEntity.getAssociationPath(edmNavigationProperty.getName())); - } else if (uriResource instanceof final UriResourceProperty resourceProperty && resourceProperty - .isCollection()) { - pathString.append(((UriResourceProperty) uriResource).getProperty().getName()); - final JPAPath jpaPath = jpaEntity.getPath(pathString.toString()); - if (jpaPath.isTransient()) { - throw new ODataJPAQueryException(QUERY_PREPARATION_ORDER_BY_TRANSIENT, NOT_IMPLEMENTED, jpaPath - .getLeaf().toString()); - } - navigationAttributes.add(((JPACollectionAttribute) jpaPath.getLeaf()).asAssociation()); - - } else if (uriResource instanceof final UriResourceProperty resourceProperty) { - pathString.append(resourceProperty.getProperty().getName()); - pathString.append(JPAPath.PATH_SEPARATOR); - } - } catch (final ODataJPAModelException e) { - throw new ODataJPAQueryException(QUERY_RESULT_CONV_ERROR, INTERNAL_SERVER_ERROR, e); - } - } - } else { - // TODO Support methods like tolower for order by as well - throw new ODataJPAQueryException(QUERY_PREPARATION_ORDER_BY_NOT_SUPPORTED, BAD_REQUEST, - expression.toString()); + try { + final var factory = new JPAOrderByPropertyFactory(); + final var orderByAttributes = orderBy.getOrders().stream() + .map(o -> factory.createProperty(o, jpaEntity, getLocale())) + .collect(Collectors.toList()); // NOSONAR + for (final var orderByAttribute : orderByAttributes) { + if (!orderByAttribute.isSortable()) + throw new ODataJPAQueryException(QUERY_PREPARATION_ORDER_BY_NOT_SUPPORTED, BAD_REQUEST, orderByAttribute + .getAlias()); } + return orderByAttributes; + + } catch (final ODataJPAIllegalArgumentException e) { + throw new ODataJPAQueryException(QUERY_PREPARATION_ORDER_BY_NOT_SUPPORTED, BAD_REQUEST, e.getParams()); } } - debugger.trace(this, "The following navigation attributes in order by were found: %s", navigationAttributes - .toString()); - return navigationAttributes; + return new ArrayList<>(); } protected void generateDescriptionJoin(final Map> joinTables, final Set pathSet, @@ -442,7 +422,7 @@ protected void generateDescriptionJoin(final Map> joinTables, } else { join.on(createOnCondition(join, descriptionField, getLocale().getLanguage())); } - joinTables.put(descriptionField.getInternalName(), join); + joinTables.put(descriptionFieldPath.getAlias(), join); } } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPACollectionJoinQuery.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPACollectionJoinQuery.java index 6d926e484..a5407d15d 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPACollectionJoinQuery.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPACollectionJoinQuery.java @@ -145,7 +145,7 @@ protected List> createSelectClause(final Map> jo // Build select clause for (final JPAPath jpaPath : jpaPathList) { if (jpaPath.isPartOfGroups(groups)) { - final Path path = ExpressionUtility.convertToCriteriaPath(joinTables, target, jpaPath.getPath()); + final Path path = ExpressionUtility.convertToCriteriaPath(joinTables, target, jpaPath); path.alias(jpaPath.getAlias()); selections.add(path); } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandFilterQuery.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandFilterQuery.java index 2a2f3a94b..b3d0a7e8f 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandFilterQuery.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandFilterQuery.java @@ -11,11 +11,9 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -52,6 +50,7 @@ import com.sap.olingo.jpa.processor.core.filter.JPAFilterRestrictionsWatchDog; import com.sap.olingo.jpa.processor.core.filter.JPAOperationConverter; import com.sap.olingo.jpa.processor.core.processor.JPAODataInternalRequestContext; +import com.sap.olingo.jpa.processor.core.properties.JPAProcessorAttribute; class JPAExpandFilterQuery extends JPAAbstractSubQuery { final List keyPredicates; @@ -125,18 +124,17 @@ public Subquery getSubQuery(@Nullable final Subquery childQuery, try (JPARuntimeMeasurement measurement = debugger.newMeasurement(this, "createSubQuery")) { final ProcessorSubquery nextQuery = (ProcessorSubquery) this.subQuery; final JPAQueryPair queries = createQueries(childQuery); - final List orderByAttributes = extractOrderByNavigationAttributes(navigationInfo.getUriInfo() - .getOrderByOption()); + final var orderByAttributes = getOrderByAttributes(navigationInfo.getUriInfo().getOrderByOption()); createRoots(childQuery, queries, nextQuery); buildJoinTable(orderByAttributes, emptyList(), childQuery); - final Set> orderByPaths = new HashSet<>(); + final List selections = selectionPathIn(); nextQuery.where(createWhere(childQuery)); nextQuery.multiselect(selectIn(childQuery, selections)); - nextQuery.orderBy(createOrderBy(childQuery, orderByPaths)); + nextQuery.orderBy(createOrderBy(childQuery, orderByAttributes)); nextQuery.setFirstResult(getSkipValue(childQuery)); nextQuery.setMaxResults(getTopValue(childQuery)); - nextQuery.groupBy(createGroupBy(joinTables, queryRoot, selections, orderByPaths)); + nextQuery.groupBy(createGroupBy(joinTables, queryRoot, selections, orderByAttributes)); return nextQuery; } } @@ -165,15 +163,14 @@ protected Expression applyAdditionalFilter(final Expression wh } catch (final ExpressionVisitException e) { throw new ODataJPAQueryException(e, HttpStatusCode.INTERNAL_SERVER_ERROR); } - } return where; } - void buildJoinTable(final List orderByAttributes, final Collection selectionPath, + void buildJoinTable(final List orderByAttributes, final Collection selectionPath, final Subquery childQuery) throws ODataApplicationException { createFromClauseJoinTable(joinTables, childQuery); - createFromClauseOrderBy(orderByAttributes, joinTables, queryRoot); + createFromClauseOrderBy2(orderByAttributes, joinTables, queryRoot); createFromClauseDescriptionFields(selectionPath, joinTables, queryRoot, singletonList(navigationInfo)); } @@ -199,12 +196,11 @@ void setFilter(final JPANavigationPropertyInfo navigationInfo) throws ODataJPAMo } } - private List createOrderBy(final Subquery childQuery, final Set> orderByPaths) + private List createOrderBy(final Subquery childQuery, final List orderByAttributes) throws ODataApplicationException { if (!hasRowLimit(childQuery)) { final JPAOrderByBuilder orderByBuilder = new JPAOrderByBuilder(jpaEntity, queryRoot, cb, groups); - return orderByBuilder.createOrderByList(joinTables, navigationInfo.getUriInfo(), navigationInfo.getPage(), - orderByPaths); + return orderByBuilder.createOrderByList(joinTables, orderByAttributes, navigationInfo.getPage()); } return emptyList(); } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandItemInfoFactory.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandItemInfoFactory.java index 07e90ac41..798a69b59 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandItemInfoFactory.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandItemInfoFactory.java @@ -1,5 +1,7 @@ package com.sap.olingo.jpa.processor.core.query; +import static com.sap.olingo.jpa.processor.core.exception.ODataJPAProcessorException.MessageKeys.ATTRIBUTE_NOT_FOUND; + import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -32,6 +34,7 @@ import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAStructuredType; import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException; import com.sap.olingo.jpa.processor.core.api.JPAODataGroupProvider; +import com.sap.olingo.jpa.processor.core.exception.ODataJPAProcessorException; import com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException; public final class JPAExpandItemInfoFactory { @@ -108,9 +111,7 @@ public List buildCollectionItemInfo(final JPAServiceDocum if (pathElement instanceof final JPAAttribute attribute && attribute.isCollection()) { if (path.isPartOfGroups(groups.isPresent() ? groups.get().getGroups() : new ArrayList<>(0)) && !attribute.isTransient()) { - final JPAPath collectionPath = ((JPAEntityType) pathInfo[ET_INDEX]) - .getPath(pathName.deleteCharAt(pathName.length() - 1).toString()); - collectionProperties.add((JPACollectionAttribute) collectionPath.getLeaf()); + collectionProperties.add(getCollectionPath(pathInfo, pathName)); } break; } @@ -144,6 +145,17 @@ public List buildCollectionItemInfo(final JPAServiceDocum return itemList; } + private JPACollectionAttribute getCollectionPath(final Object[] pathInfo, final StringBuilder pathName) + throws ODataJPAModelException, ODataJPAProcessorException { + + final var name = pathName.deleteCharAt(pathName.length() - 1).toString(); + final JPAPath collectionPath = ((JPAEntityType) pathInfo[ET_INDEX]).getPath(name); + if (collectionPath != null) + return (JPACollectionAttribute) collectionPath.getLeaf(); + else + throw new ODataJPAProcessorException(ATTRIBUTE_NOT_FOUND, HttpStatusCode.INTERNAL_SERVER_ERROR, name); + } + private Object[] determineNavigationElements(final JPAServiceDocument sd, final List startResourceList, final JPAEntityType et) throws ODataJPAQueryException { diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandJoinCountQuery.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandJoinCountQuery.java index 38989eef0..623a9b0ab 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandJoinCountQuery.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandJoinCountQuery.java @@ -33,6 +33,7 @@ import com.sap.olingo.jpa.processor.core.api.JPAODataRequestContextAccess; import com.sap.olingo.jpa.processor.core.api.JPAServiceDebugger.JPARuntimeMeasurement; import com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException; +import com.sap.olingo.jpa.processor.core.properties.JPAProcessorAttribute; /** * Requires Processor Query @@ -69,7 +70,7 @@ public JPAExpandQueryResult execute() throws ODataApplicationException { } @Override - protected Map> createFromClause(final List orderByTarget, + protected Map> createFromClause(final List orderByTarget, final Collection selectionPath, final CriteriaQuery query, final JPANavigationPropertyInfo lastInfo) throws ODataApplicationException, JPANoSelectionException { diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandJoinQuery.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandJoinQuery.java index 58e1de561..d7b635647 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandJoinQuery.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandJoinQuery.java @@ -35,6 +35,7 @@ import com.sap.olingo.jpa.processor.core.api.JPAODataRequestContextAccess; import com.sap.olingo.jpa.processor.core.api.JPAServiceDebugger.JPARuntimeMeasurement; import com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException; +import com.sap.olingo.jpa.processor.core.properties.JPAProcessorAttribute; /** * A query to retrieve the expand entities. @@ -229,8 +230,7 @@ final Map count() throws ODataApplicationException { private JPAQueryCreationResult createTupleQuery() throws ODataApplicationException, JPANoSelectionException { try (JPARuntimeMeasurement measurement = debugger.newMeasurement(this, "createTupleQuery")) { - final List orderByAttributes = extractOrderByNavigationAttributes(uriResource - .getOrderByOption()); + final var orderByAttributes = getOrderByAttributes(uriResource.getOrderByOption()); final SelectionPathInfo selectionPath = buildSelectionPathList(this.uriResource); final Map> joinTables = createFromClause(orderByAttributes, selectionPath.joinedPersistent(), cq, lastInfo); @@ -243,11 +243,11 @@ private JPAQueryCreationResult createTupleQuery() throws ODataApplicationExcepti final Set> orderByPaths = new HashSet<>(); final List orderBy = createOrderByJoinCondition(association); - orderBy.addAll(new JPAOrderByBuilder(jpaEntity, target, cb, groups).createOrderByList(joinTables, uriResource, - page, orderByPaths)); + orderBy.addAll(new JPAOrderByBuilder(jpaEntity, target, cb, groups).createOrderByList(joinTables, + orderByAttributes, page)); cq.orderBy(orderBy); - if (!orderByAttributes.isEmpty()) { + if (orderByAttributes.stream().anyMatch(JPAProcessorAttribute::requiresJoin)) { cq.groupBy(createGroupBy(joinTables, target, selectionPath.joinedPersistent(), orderByPaths)); } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandSubCountQuery.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandSubCountQuery.java index dadf3dbe7..ded106b72 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandSubCountQuery.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandSubCountQuery.java @@ -34,6 +34,7 @@ import com.sap.olingo.jpa.processor.core.api.JPAODataRequestContextAccess; import com.sap.olingo.jpa.processor.core.api.JPAServiceDebugger.JPARuntimeMeasurement; import com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException; +import com.sap.olingo.jpa.processor.core.properties.JPAProcessorAttribute; /** * Requires Processor Query @@ -66,7 +67,7 @@ public JPAExpandQueryResult execute() throws ODataApplicationException { } @Override - protected Map> createFromClause(final List orderByTarget, + protected Map> createFromClause(final List orderByTarget, final Collection selectionPath, final CriteriaQuery query, final JPANavigationPropertyInfo lastInfo) throws ODataApplicationException, JPANoSelectionException { diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandSubQuery.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandSubQuery.java index db76cc838..6092f0880 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandSubQuery.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAExpandSubQuery.java @@ -12,12 +12,10 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; import javax.annotation.Nonnull; @@ -48,6 +46,7 @@ import com.sap.olingo.jpa.processor.core.api.JPAODataRequestContextAccess; import com.sap.olingo.jpa.processor.core.api.JPAServiceDebugger.JPARuntimeMeasurement; import com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException; +import com.sap.olingo.jpa.processor.core.properties.JPAProcessorAttribute; /** * Requires Processor Query @@ -83,7 +82,7 @@ public JPAExpandQueryResult execute() throws ODataApplicationException { } @Override - protected Map> createFromClause(final List orderByTarget, + protected Map> createFromClause(final List orderByTarget, final Collection selectionPath, final CriteriaQuery query, final JPANavigationPropertyInfo lastInfo) throws ODataApplicationException, JPANoSelectionException { @@ -235,25 +234,16 @@ private void createFromClauseRoot(final CriteriaQuery query, final HashMap createOrderBy(final Map> joinTables, Set> orderByPaths) throws ODataApplicationException { - if (association.hasJoinTable() && hasRowLimit(lastInfo)) { - try { - final List orders = new ArrayList<>(); + private List createOrderBy(final Map> joinTables, + final List orderByAttributes) throws ODataApplicationException { - for (final JPAOnConditionItem c : association.getJoinTable().getJoinColumns()) { - final Path path = root.get(c.getLeftPath().getAlias()); - orders.add(cb.asc(path)); - } - return orders; - } catch (final ODataJPAModelException e) { - throw new ODataJPAQueryException(e, INTERNAL_SERVER_ERROR); - } + final JPAOrderByBuilder orderByBuilder = new JPAOrderByBuilder(jpaEntity, root, cb, groups); + if (association.hasJoinTable() && hasRowLimit(lastInfo)) { + return orderByBuilder.createOrderByList(association); } else if (hasRowLimit(lastInfo)) { - final JPAOrderByBuilder orderByBuilder = new JPAOrderByBuilder(jpaEntity, root, cb, groups); - return orderByBuilder.createOrderByListAlias(joinTables, uriResource.getOrderByOption(), association); + return orderByBuilder.createOrderByListAlias(joinTables, orderByAttributes, association); } else { - final JPAOrderByBuilder orderByBuilder = new JPAOrderByBuilder(jpaEntity, root, cb, groups); - return orderByBuilder.createOrderByList(joinTables, uriResource.getOrderByOption(), association, orderByPaths); + return orderByBuilder.createOrderByList(joinTables, orderByAttributes, association); } } @@ -282,8 +272,7 @@ private JPAQueryPair createQueries(final SelectionPathInfo selectionPat try (JPARuntimeMeasurement measurement = debugger.newMeasurement(this, "createTupleQuery")) { final ProcessorCriteriaQuery tupleQuery = (ProcessorCriteriaQuery) cq; - final List orderByAttributes = extractOrderByNavigationAttributes(uriResource - .getOrderByOption()); + final var orderByAttributes = getOrderByAttributes(uriResource.getOrderByOption()); final SelectionPathInfo selectionPath = buildSelectionPathList(this.uriResource); final JPAQueryPair queries = createQueries(selectionPath); addFilterCompiler(lastInfo); @@ -293,11 +282,10 @@ private JPAQueryPair createQueries(final SelectionPathInfo selectionPat subQuery); tupleQuery.where(createWhere(subQuery, lastInfo)); tupleQuery.multiselect(createSelectClause(joinTables, selectionPath.joinedPersistent(), groups)); - final Set> orderByPaths = new HashSet<>(); - tupleQuery.orderBy(createOrderBy(joinTables, orderByPaths)); + tupleQuery.orderBy(createOrderBy(joinTables, orderByAttributes)); tupleQuery.distinct(orderByAttributes.isEmpty()); if (!orderByAttributes.isEmpty()) { - cq.groupBy(createGroupBy(joinTables, target, selectionPath.joinedPersistent(),orderByPaths)); + cq.groupBy(createGroupBy(joinTables, target, selectionPath.joinedPersistent(), orderByAttributes)); } final TypedQuery query = em.createQuery(tupleQuery); return new JPAQueryCreationResult(query, selectionPath); @@ -305,7 +293,7 @@ private JPAQueryPair createQueries(final SelectionPathInfo selectionPat } Map> createJoinTables(final ProcessorCriteriaQuery tupleQuery, - final SelectionPathInfo selectionPath, final List orderByAttributes, + final SelectionPathInfo selectionPath, final List orderByAttributes, final Subquery subQuery) throws ODataApplicationException, JPANoSelectionException { Map> joinTables = new HashMap<>(); @@ -315,7 +303,7 @@ private JPAQueryPair createQueries(final SelectionPathInfo selectionPat } else { joinTables = createFromClause(emptyList(), selectionPath.joinedPersistent(), cq, lastInfo); } - createFromClauseOrderBy(orderByAttributes, joinTables, root); + createFromClauseOrderBy2(orderByAttributes, joinTables, root); return joinTables; } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAJoinCountQuery.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAJoinCountQuery.java index b36f07762..ac1fde690 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAJoinCountQuery.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAJoinCountQuery.java @@ -44,7 +44,7 @@ public Long countResults() throws ODataApplicationException { cq.where(whereClause); cq.multiselect(cb.count(target)); final var result = em.createQuery(cq).getSingleResult(); - return (Long) result.get(0); + return ((Number) result.get(0)).longValue(); } catch (final JPANoSelectionException e) { return 0L; } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAJoinQuery.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAJoinQuery.java index 20c213707..52ab4aac9 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAJoinQuery.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAJoinQuery.java @@ -7,17 +7,14 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Optional; -import java.util.Set; import javax.annotation.Nonnull; import jakarta.persistence.Tuple; import jakarta.persistence.TypedQuery; import jakarta.persistence.criteria.AbstractQuery; -import jakarta.persistence.criteria.Path; import org.apache.olingo.commons.api.ex.ODataException; import org.apache.olingo.server.api.OData; @@ -30,6 +27,7 @@ import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAServiceDocument; import com.sap.olingo.jpa.processor.core.api.JPAODataRequestContextAccess; import com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException; +import com.sap.olingo.jpa.processor.core.properties.JPAProcessorAttribute; public class JPAJoinQuery extends JPAAbstractJoinQuery { @@ -82,10 +80,10 @@ public JPAConvertibleResult execute() throws ODataApplicationException { // Pre-process URI parameter, so they can be used at different places final var selectionPath = buildSelectionPathList(this.uriResource); try (var measurement = debugger.newMeasurement(this, "execute")) { - final var orderByNavigationAttributes = extractOrderByNavigationAttributes(uriResource - .getOrderByOption()); - final var joinTables = createFromClause(orderByNavigationAttributes, - selectionPath.joinedPersistent(), cq, lastInfo); + final var orderByAttributes = getOrderByAttributes(uriResource.getOrderByOption()); + + final var joinTables = createFromClause(orderByAttributes, selectionPath.joinedPersistent(), cq, + lastInfo); cq.multiselect(createSelectClause(joinTables, selectionPath.joinedPersistent(), target, groups)) .distinct(determineDistinct()); @@ -95,11 +93,10 @@ public JPAConvertibleResult execute() throws ODataApplicationException { cq.where(whereClause); } - final Set> orderByPaths = new HashSet<>(); - cq.orderBy(createOrderByBuilder().createOrderByList(joinTables, uriResource, page, orderByPaths)); + cq.orderBy(createOrderByBuilder().createOrderByList(joinTables, orderByAttributes, page)); - if (!orderByNavigationAttributes.isEmpty()) { - cq.groupBy(createGroupBy(joinTables, root, selectionPath.joinedPersistent(), orderByPaths)); + if (orderByAttributes.stream().anyMatch(JPAProcessorAttribute::requiresJoin)) { + cq.groupBy(createGroupBy(joinTables, root, selectionPath.joinedPersistent(), orderByAttributes)); } final TypedQuery typedQuery = em.createQuery(cq); diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAOrderByBuilder.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAOrderByBuilder.java index b988ae3cb..7794f8b97 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAOrderByBuilder.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPAOrderByBuilder.java @@ -1,22 +1,13 @@ package com.sap.olingo.jpa.processor.core.query; -import static com.sap.olingo.jpa.processor.core.exception.ODataJPAProcessorException.MessageKeys.ATTRIBUTE_NOT_FOUND; -import static com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException.MessageKeys.QUERY_PREPARATION_NOT_ALLOWED_MEMBER; -import static com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException.MessageKeys.QUERY_PREPARATION_ORDER_BY_TRANSIENT; import static org.apache.olingo.commons.api.http.HttpStatusCode.BAD_REQUEST; -import static org.apache.olingo.commons.api.http.HttpStatusCode.FORBIDDEN; -import static org.apache.olingo.commons.api.http.HttpStatusCode.INTERNAL_SERVER_ERROR; -import static org.apache.olingo.commons.api.http.HttpStatusCode.NOT_IMPLEMENTED; import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; import javax.annotation.Nonnull; -import javax.annotation.Nullable; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.From; @@ -27,32 +18,18 @@ import org.apache.commons.logging.LogFactory; import org.apache.olingo.commons.api.http.HttpStatusCode; import org.apache.olingo.server.api.ODataApplicationException; -import org.apache.olingo.server.api.uri.UriInfoResource; -import org.apache.olingo.server.api.uri.UriResource; -import org.apache.olingo.server.api.uri.UriResourceComplexProperty; -import org.apache.olingo.server.api.uri.UriResourceCount; -import org.apache.olingo.server.api.uri.UriResourceNavigation; -import org.apache.olingo.server.api.uri.UriResourcePrimitiveProperty; -import org.apache.olingo.server.api.uri.UriResourceProperty; -import org.apache.olingo.server.api.uri.queryoption.OrderByItem; -import org.apache.olingo.server.api.uri.queryoption.OrderByOption; -import org.apache.olingo.server.api.uri.queryoption.expression.Expression; -import org.apache.olingo.server.api.uri.queryoption.expression.Member; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAnnotatable; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAssociationPath; -import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAttribute; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAElement; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAOnConditionItem; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAPath; -import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAStructuredType; import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException; import com.sap.olingo.jpa.processor.core.api.JPAODataPage; -import com.sap.olingo.jpa.processor.core.exception.ODataJPANotImplementedException; -import com.sap.olingo.jpa.processor.core.exception.ODataJPAProcessException; -import com.sap.olingo.jpa.processor.core.exception.ODataJPAProcessorException; import com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException; +import com.sap.olingo.jpa.processor.core.properties.JPAOrderByPropertyFactory; +import com.sap.olingo.jpa.processor.core.properties.JPAProcessorAttribute; /** * Constructor of Order By clause. @@ -127,21 +104,27 @@ final class JPAOrderByBuilder { */ @Nonnull List createOrderByList(@Nonnull final Map> joinTables, - @Nonnull final UriInfoResource uriResource, @Nullable final JPAODataPage page, final Set> orderByPaths) - throws ODataApplicationException { + final List orderByAttributes, final JPAODataPage page) + throws ODataJPAQueryException { final List result = new ArrayList<>(); try { - if (uriResource.getOrderByOption() != null) { + if (page != null + && (page.top() != Integer.MAX_VALUE + || page.skip() != 0)) { + LOGGER.trace("Determined $top/$skip or page: add primary key to Order By"); + final var factory = new JPAOrderByPropertyFactory(); + for (final var key : jpaEntity.getKey()) { + orderByAttributes.add(factory.createProperty(target, jpaEntity.getPath(key.getExternalName()), cb)); + } + } + + if (!orderByAttributes.isEmpty()) { LOGGER.trace(LOG_ORDER_BY); - addOrderByFromUriResource(joinTables, result, orderByPaths, uriResource.getOrderByOption()); + addOrderByProperties(orderByAttributes, result); watchDog.watch(result); } - if (uriResource.getTopOption() != null || uriResource.getSkipOption() != null - || (page != null && page.top() != Integer.MAX_VALUE)) { - LOGGER.trace("Determined $top/$skip or page: add primary key to Order By"); - addOrderByPrimaryKey(result, orderByPaths); - } + } catch (final ODataJPAModelException e) { throw new ODataJPAQueryException(e, BAD_REQUEST); } @@ -153,70 +136,68 @@ List createOrderByList(final Map> joinTables) { return Collections.emptyList(); } + /** + * For row number query with join table + * @param association + * @return + * @throws ODataApplicationException + */ + List createOrderByList(final JPAAssociationPath association) + throws ODataApplicationException { + final List result = new ArrayList<>(); + addOrderByInvertJoinCondition(association, result); + return result; + } + /** * Create a list of order by for $expand query part. It does not take top and skip into account, but the * association. *

+ * @throws ODataApplicationException */ @Nonnull - List createOrderByList(@Nonnull final Map> joinTables, - @Nullable final OrderByOption orderBy, @Nonnull final JPAAssociationPath association, @Nonnull final Set> orderByPaths) + List createOrderByList(final Map> joinTables, + final List orderByAttributes, @Nonnull final JPAAssociationPath association) throws ODataApplicationException { - final List result = new ArrayList<>(); - try { - LOGGER.trace("Determined relationship and add corresponding to OrderBy"); - addOrderByJoinCondition(association, result); - if (orderBy != null) { - LOGGER.trace(LOG_ORDER_BY); - addOrderByFromUriResource(joinTables, result, orderByPaths, orderBy); - } - } catch (final ODataJPAModelException e) { - throw new ODataJPAQueryException(e, BAD_REQUEST); - } + addOrderByJoinCondition(orderByAttributes, association); + addOrderByProperties(orderByAttributes, result); return result; } @Nonnull List createOrderByListAlias(@Nonnull final Map> joinTables, - @Nullable final OrderByOption orderBy, @Nonnull final JPAAssociationPath association) + final List orderByAttributes, @Nonnull final JPAAssociationPath association) throws ODataApplicationException { final List result = new ArrayList<>(); - final Set> orderByPaths = new HashSet<>(); - try { - LOGGER.trace("Determined relationship and add corresponding to OrderBy"); - addOrderByJoinConditionAlias(association, result); - if (orderBy != null) { - LOGGER.trace(LOG_ORDER_BY); - addOrderByFromUriResource(joinTables, result, orderByPaths, orderBy); - } - } catch (final ODataJPAModelException e) { - throw new ODataJPAQueryException(e, BAD_REQUEST); - } + + addOrderByJoinConditionAlias(association, result); + addOrderByProperties(orderByAttributes, result); + return result; } @Nonnull List createOrderByList(@Nonnull final Map> joinTables, - @Nullable final OrderByOption orderBy) throws ODataApplicationException { + final List orderByAttributes) throws ODataApplicationException { final List result = new ArrayList<>(); - final Set> orderByPaths = new HashSet<>(); - try { - if (orderBy != null) { - LOGGER.trace(LOG_ORDER_BY); - addOrderByFromUriResource(joinTables, result, orderByPaths, orderBy); - } - } catch (final ODataJPAModelException e) { - throw new ODataJPAQueryException(e, BAD_REQUEST); - } + addOrderByProperties(orderByAttributes, result); return result; } + void addOrderByProperties(final List orderByAttributes, final List result) + throws ODataJPAQueryException { + for (final var attribute : orderByAttributes) { + result.add(attribute.createOrderBy(cb, groups)); + } + } + void addOrderByJoinConditionAlias(final JPAAssociationPath association, final List orders) throws ODataApplicationException { + LOGGER.trace("Determined relationship and add corresponding to OrderBy"); try { final List joinColumns = association.hasJoinTable() ? asPathList(association) : association.getRightColumnsList(); @@ -230,13 +211,32 @@ void addOrderByJoinConditionAlias(final JPAAssociationPath association, final Li } } - void addOrderByJoinCondition(final JPAAssociationPath association, final List orders) - throws ODataApplicationException { + void addOrderByJoinCondition(final List orderByAttributes, + final JPAAssociationPath association) throws ODataApplicationException { + LOGGER.trace("Determined relationship and add corresponding to OrderBy"); try { + final var factory = new JPAOrderByPropertyFactory(); final List joinColumns = association.hasJoinTable() ? asPathList(association) : association.getRightColumnsList(); + for (var i = joinColumns.size() - 1; i >= 0; i--) { + final JPAPath joinPath = joinColumns.get(i); + orderByAttributes.add(0, factory.createProperty(target, joinPath, cb)); + } + } catch (final ODataJPAModelException e) { + throw new ODataJPAQueryException(e, HttpStatusCode.BAD_REQUEST); + } + } + + void addOrderByInvertJoinCondition(final JPAAssociationPath association, final List orders) + throws ODataApplicationException { + + LOGGER.trace("Determined relationship and add corresponding to OrderBy"); + try { + final List joinColumns = association.hasJoinTable() + ? asInvertPathList(association) : association.getLeftColumnsList(); + for (final JPAPath j : joinColumns) { Path jpaProperty = target; for (final JPAElement pathElement : j.getPath()) { @@ -256,6 +256,13 @@ private List asPathList(final JPAAssociationPath association) throws OD .toList(); } + private List asInvertPathList(final JPAAssociationPath association) throws ODataJPAModelException { + final List joinColumns = association.getJoinTable().getJoinColumns(); + return joinColumns.stream() + .map(JPAOnConditionItem::getLeftPath) + .toList(); + } + @SuppressWarnings("unchecked") From determineParentFrom(final JPAAssociationPath association, final List navigationInfo) throws ODataJPAQueryException { @@ -269,136 +276,4 @@ From determineParentFrom(final JPAAss HttpStatusCode.BAD_REQUEST); } - private void addOrderByExpression(final List orders, final OrderByItem orderByItem, - final jakarta.persistence.criteria.Expression expression) { - - if (orderByItem.isDescending()) { - orders.add(cb.desc(expression)); - } else { - orders.add(cb.asc(expression)); - } - } - - private void addOrderByExpression(final List orders, final OrderByItem orderByItem, - final Set> orderByPaths, final Path path) { - - if (!orderByPaths.contains(path)) { - orderByPaths.add(path); - addOrderByExpression(orders, orderByItem, path); - } - } - - private void addOrderByFromUriResource(final Map> joinTables, final List orders, - final Set> orderByPaths, final OrderByOption orderByOption) throws ODataJPAProcessException, - ODataJPAModelException { - - for (final OrderByItem orderByItem : orderByOption.getOrders()) { - final Expression expression = orderByItem.getExpression(); - if (expression instanceof final Member member) { - final UriInfoResource resourcePath = member.getResourcePath(); - JPAStructuredType type = jpaEntity; - Path path = target; - StringBuilder externalPath = new StringBuilder(); - for (final UriResource uriResourceItem : resourcePath.getUriResourceParts()) { - if (isPrimitiveSimpleProperty(uriResourceItem)) { - path = convertPropertyPath(type, uriResourceItem, path); - addOrderByExpression(orders, orderByItem, orderByPaths, path); - } else if (isComplexSimpleProperty(uriResourceItem)) { - final JPAAttribute attribute = getAttribute(type, uriResourceItem); - addPathByAttribute(externalPath, attribute); - path = path.get(attribute.getInternalName()); - type = attribute.getStructuredType(); - } else if ((uriResourceItem instanceof final UriResourceNavigation navigation - && navigation.isCollection()) - || (uriResourceItem instanceof final UriResourceProperty property - && property.isCollection())) { - // In case the orderby contains a navigation or collection a $count has to follow. This is ensured by Olingo - appendPathByCollection(externalPath, uriResourceItem); - final From join = joinTables.get(externalPath.toString()); - addOrderByExpression(orders, orderByItem, cb.count(join)); - } else if (uriResourceItem instanceof UriResourceNavigation) { - appendPathByCollection(externalPath, uriResourceItem); - type = type.getAssociationPath(externalPath.toString()).getTargetType(); - path = joinTables.get(externalPath.toString()); - externalPath = new StringBuilder(); - } else if (!(uriResourceItem instanceof UriResourceCount)) { - throw new ODataJPANotImplementedException("orderby using " + uriResourceItem.getKind().name()); - } - } - } - } - } - - private Path convertPropertyPath(final JPAStructuredType type, - final UriResource uriResourceItem, final Path startPath) - throws ODataJPAQueryException, ODataJPAProcessorException, ODataJPAModelException { - - final JPAPath attributePath = type.getPath(((UriResourceProperty) uriResourceItem).getProperty().getName()); - if (attributePath == null) { - throw new ODataJPAProcessorException(ATTRIBUTE_NOT_FOUND, INTERNAL_SERVER_ERROR, - uriResourceItem.getSegmentValue()); - } - if (!attributePath.isPartOfGroups(groups)) { - throw new ODataJPAQueryException(QUERY_PREPARATION_NOT_ALLOWED_MEMBER, FORBIDDEN, attributePath.getAlias()); - } - Path path = startPath; - for (final JPAElement pathElement : attributePath.getPath()) { - if (pathElement instanceof final JPAAttribute attribute && attribute.isTransient()) { - throw new ODataJPAQueryException(QUERY_PREPARATION_ORDER_BY_TRANSIENT, NOT_IMPLEMENTED, - pathElement.getExternalName()); - } - path = path.get(pathElement.getInternalName()); - } - path.alias(attributePath.getAlias()); - return path; - } - - private void addOrderByPrimaryKey(final List orders, final Set> existing) - throws ODataJPAQueryException, ODataJPAModelException { - - final List> paths = ExpressionUtility.convertToCriteriaPathList(target, jpaEntity, jpaEntity.getKey()); - for (final Path p : paths) { - if (!existing.contains(p)) { - orders.add(cb.asc(p)); - existing.add(p); - } - } - } - - private void addPathByAttribute(final StringBuilder externalPath, final JPAAttribute attribute) { - externalPath.append(attribute.getExternalName()); - externalPath.append(JPAPath.PATH_SEPARATOR); - } - - private void appendPathByCollection(final StringBuilder externalPath, final UriResource uriResourceItem) { - if (uriResourceItem instanceof final UriResourceNavigation navigation) { - externalPath.append(navigation.getProperty().getName()); - } else { - externalPath.append(((UriResourceProperty) uriResourceItem).getProperty().getName()); - } - } - - private JPAAttribute getAttribute(final JPAStructuredType type, final UriResource uriResourceItem) - throws ODataJPAProcessorException, ODataJPAModelException, ODataJPAQueryException { - - final JPAAttribute attribute = type.getAttribute((UriResourceProperty) uriResourceItem).orElseThrow( - () -> new ODataJPAProcessorException(ATTRIBUTE_NOT_FOUND, INTERNAL_SERVER_ERROR, - uriResourceItem.getSegmentValue())); - if (attribute.isTransient()) { - throw new ODataJPAQueryException(QUERY_PREPARATION_ORDER_BY_TRANSIENT, NOT_IMPLEMENTED, - attribute.getExternalName()); - } - return attribute; - - } - - private boolean isComplexSimpleProperty(final UriResource uriResourceItem) { - return uriResourceItem instanceof UriResourceComplexProperty - && !((UriResourceProperty) uriResourceItem).isCollection(); - } - - private boolean isPrimitiveSimpleProperty(final UriResource uriResourceItem) { - return uriResourceItem instanceof UriResourcePrimitiveProperty - && !((UriResourceProperty) uriResourceItem).isCollection(); - } } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPARowNumberFilterQuery.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPARowNumberFilterQuery.java index 4e865e908..ef6aab5c8 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPARowNumberFilterQuery.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/JPARowNumberFilterQuery.java @@ -13,6 +13,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -157,7 +158,7 @@ private List> createSelectForParent() { // Build select clause for (final JPAPath jpaPath : this.outerSelections) { if (jpaPath.isPartOfGroups(groups)) { - final Path path = ExpressionUtility.convertToCriteriaPath(joinTables, queryRoot, jpaPath.getPath()); + final Path path = ExpressionUtility.convertToCriteriaPath(joinTables, queryRoot, jpaPath); path.alias(jpaPath.getAlias()); selections.add(path); } @@ -199,6 +200,9 @@ protected Expression applyAdditionalFilter(final Expression wh private List createOrderBy() throws ODataApplicationException { final JPAOrderByBuilder orderBy = new JPAOrderByBuilder(jpaEntity, queryRoot, cb, groups); - return orderBy.createOrderByList(emptyMap(), navigationInfo.getUriInfo().getOrderByOption()); + final var orderByAttributes = getOrderByAttributes(navigationInfo.getUriInfo().getOrderByOption()).stream() + .map(attribute -> attribute.setTarget(queryRoot, joinTables, cb)) + .collect(Collectors.toList()); // NOSONAR get mutable list + return orderBy.createOrderByList(emptyMap(), orderByAttributes); } } diff --git a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/Utility.java b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/Utility.java index 76410e368..1b49dafdc 100644 --- a/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/Utility.java +++ b/jpa/odata-jpa-processor/src/main/java/com/sap/olingo/jpa/processor/core/query/Utility.java @@ -463,14 +463,6 @@ public static EdmEntityType determineTargetEntityType(final List re return targetEdmEntity; } - public static JPAStructuredType determineTargetStructuredType(final JPAStructuredType st, - final List resources) throws ODataJPAModelException { - final var path = determinePropertyNavigationPrefix(resources); - if (path != null && !path.isEmpty()) - return st.getPath(path).getLeaf().getStructuredType(); - return st; - } - public static boolean hasNavigation(final List uriResourceParts) { if (uriResourceParts != null) { for (int i = uriResourceParts.size() - 1; i >= 0; i--) { diff --git a/jpa/odata-jpa-processor/src/main/resources/processor-exceptions-i18n.properties b/jpa/odata-jpa-processor/src/main/resources/processor-exceptions-i18n.properties index c4272a12f..c6dfc627c 100644 --- a/jpa/odata-jpa-processor/src/main/resources/processor-exceptions-i18n.properties +++ b/jpa/odata-jpa-processor/src/main/resources/processor-exceptions-i18n.properties @@ -79,6 +79,7 @@ ODataJPAQueryException.QUERY_PREPARATION_NOT_ALLOWED_MEMBER = Not authorized to ODataJPAQueryException.QUERY_PREPARATION_ORDER_BY_TRANSIENT= Usage of '%1$s' within OrderBy clauses not supported ODataJPAQueryException.QUERY_PREPARATION_JOIN_TABLE_TYPE_MISSING=The expand implementation requires that a join table ('%1$s') has an entity. ODataJPAQueryException.QUERY_PREPARATION_COLLECTION_PROPERTY_NOT_SUPPORTED=Filter with collection property not supported +ODataJPAQueryException.QUERY_PREPARATION_ORDER_BY_NOT_SUPPORTED=Order by not supported for '%1$s' ODataJPAQueryException.QUERY_PREPARATION_NO_PAGE_FOUND=No page found while converting '%1$s' ODataJPAQueryException.NOT_SUPPORTED_RESOURCE_TYPE = Resource type '%1$s' not supported ODataJPAQueryException.MISSING_CLAIMS_PROVIDER = Authorization information missing diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/JPAODataContextAccessDouble.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/JPAODataContextAccessDouble.java index e17a169d6..695533645 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/JPAODataContextAccessDouble.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/JPAODataContextAccessDouble.java @@ -14,6 +14,8 @@ import com.sap.olingo.jpa.metadata.api.JPAEdmProvider; import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.AnnotationProvider; +import com.sap.olingo.jpa.processor.cb.ProcessorSqlPatternProvider; +import com.sap.olingo.jpa.processor.core.database.JPAAbstractDatabaseProcessor; import com.sap.olingo.jpa.processor.core.database.JPADefaultDatabaseProcessor; import com.sap.olingo.jpa.processor.core.database.JPAODataDatabaseOperations; import com.sap.olingo.jpa.processor.core.database.JPAODataDatabaseProcessorFactory; @@ -21,14 +23,16 @@ public class JPAODataContextAccessDouble implements JPAODataSessionContextAccess { private final JPAEdmProvider edmProvider; private final DataSource dataSource; - private final JPADefaultDatabaseProcessor processor; + private final JPAAbstractDatabaseProcessor processor; private final String[] packageNames; private final JPAODataPagingProvider pagingProvider; private final AnnotationProvider annotationProvider; private JPAODataQueryDirectives directives; + private final ProcessorSqlPatternProvider sqlPatternProvider; public JPAODataContextAccessDouble(final JPAEdmProvider edmProvider, final DataSource dataSource, - final JPAODataPagingProvider provider, final AnnotationProvider annotationProvider, final String... packages) { + final JPAODataPagingProvider provider, final AnnotationProvider annotationProvider, + final ProcessorSqlPatternProvider sqlPatternProvider, final String... packages) { super(); this.edmProvider = edmProvider; this.dataSource = dataSource; @@ -36,6 +40,7 @@ public JPAODataContextAccessDouble(final JPAEdmProvider edmProvider, final DataS this.packageNames = packages; this.pagingProvider = provider != null ? provider : new JPADefaultPagingProvider(); this.annotationProvider = annotationProvider; + this.sqlPatternProvider = sqlPatternProvider; try { this.directives = JPAODataServiceContext.with().useQueryDirectives().maxValuesInInClause(3).build().build() .getQueryDirectives(); @@ -52,7 +57,9 @@ public List getReferences() { @Override public JPAODataDatabaseOperations getOperationConverter() { - return processor; + return processor instanceof JPAODataDatabaseOperations + ? (JPAODataDatabaseOperations) processor + : new JPADefaultDatabaseProcessor(); } @Override @@ -89,4 +96,9 @@ public List getAnnotationProvider() { public JPAODataQueryDirectives getQueryDirectives() { return directives; } + + @Override + public ProcessorSqlPatternProvider getSqlPatternProvider() { + return sqlPatternProvider; + } } diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/JPAODataServiceContextBuilderTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/JPAODataServiceContextBuilderTest.java index 99f5d8fdb..66f8b881e 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/JPAODataServiceContextBuilderTest.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/JPAODataServiceContextBuilderTest.java @@ -39,9 +39,11 @@ import com.sap.olingo.jpa.metadata.core.edm.mapper.extension.IntermediatePropertyAccess; import com.sap.olingo.jpa.metadata.core.edm.mapper.extension.IntermediateReferenceList; import com.sap.olingo.jpa.metadata.core.edm.mapper.impl.JPADefaultEdmNameBuilder; +import com.sap.olingo.jpa.processor.cb.ProcessorSqlPatternProvider; import com.sap.olingo.jpa.processor.core.api.example.JPAExamplePagingProvider; import com.sap.olingo.jpa.processor.core.database.JPADefaultDatabaseProcessor; import com.sap.olingo.jpa.processor.core.database.JPAODataDatabaseOperations; +import com.sap.olingo.jpa.processor.core.database.JPAPostgresqlSqlPatternProvider; import com.sap.olingo.jpa.processor.core.testmodel.DataSourceHelper; class JPAODataServiceContextBuilderTest { @@ -171,6 +173,20 @@ void checkReturnsDatabaseProcessor() throws ODataException { assertEquals(processor, cut.getDatabaseProcessor()); } + @Test + void checkReturnsSqlPatternProvider() throws ODataException { + + final ProcessorSqlPatternProvider provider = new JPAPostgresqlSqlPatternProvider(); + cut = JPAODataServiceContext.with() + .setDataSource(dataSource) + .setPUnit(PUNIT_NAME) + .setSqlPatternProvider(provider) + .build(); + + assertNotNull(cut.getSqlPatternProvider()); + assertEquals(provider, cut.getSqlPatternProvider()); + } + @Test void checkJPAEdmContainsPostProcessor() throws ODataException { @@ -201,7 +217,7 @@ void checkReturnsErrorProcessor() throws ODataException { } @Test - void checkThrowsExceptionOnDBConnectionProblem() throws ODataException, SQLException { + void checkThrowsExceptionOnDBConnectionProblem() throws SQLException { final DataSource dataSourceSpy = spy(dataSource); when(dataSourceSpy.getConnection()).thenThrow(SQLException.class); assertThrows(ODataException.class, () -> JPAODataServiceContext.with() @@ -212,7 +228,7 @@ void checkThrowsExceptionOnDBConnectionProblem() throws ODataException, SQLExcep } @Test - void checkJPAEdmContainsDefaultNameBuilder() throws ODataException, SQLException { + void checkJPAEdmContainsDefaultNameBuilder() throws ODataException { cut = JPAODataServiceContext.with() .setDataSource(dataSource) @@ -226,7 +242,7 @@ void checkJPAEdmContainsDefaultNameBuilder() throws ODataException, SQLException } @Test - void checkJPAEdmContainsCustomNameBuilder() throws ODataException, SQLException { + void checkJPAEdmContainsCustomNameBuilder() throws ODataException { final JPAEdmNameBuilder nameBuilder = mock(JPAEdmNameBuilder.class); when(nameBuilder.getNamespace()).thenReturn("unit.test"); @@ -344,7 +360,7 @@ void checkReturnAnnotationProviderList() throws ODataException { assertTrue(cut.getAnnotationProvider().contains(provider2)); } - private class TestEdmPostProcessor implements JPAEdmMetadataPostProcessor { + private static class TestEdmPostProcessor implements JPAEdmMetadataPostProcessor { @Override public void processNavigationProperty(final IntermediateNavigationPropertyAccess property, @@ -373,7 +389,7 @@ public void processEntityType(final IntermediateEntityTypeAccess entity) { } } - private class TestBatchProcessorFactory implements JPAODataBatchProcessorFactory { + private static class TestBatchProcessorFactory implements JPAODataBatchProcessorFactory { @Override public JPAODataBatchProcessor getBatchProcessor(@Nonnull final JPAODataSessionContextAccess serviceContext, diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/JPAODataSessionContextAccessTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/JPAODataSessionContextAccessTest.java index 940f25e53..a5b661b11 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/JPAODataSessionContextAccessTest.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/api/JPAODataSessionContextAccessTest.java @@ -17,6 +17,7 @@ import com.sap.olingo.jpa.metadata.api.JPAEdmProvider; import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.AnnotationProvider; +import com.sap.olingo.jpa.processor.cb.ProcessorSqlPatternProvider; import com.sap.olingo.jpa.processor.core.database.JPAODataDatabaseOperations; class JPAODataSessionContextAccessTest { @@ -97,5 +98,9 @@ public JPAODataQueryDirectives getQueryDirectives() { return null; } + @Override + public ProcessorSqlPatternProvider getSqlPatternProvider() { + return null; + } } } diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/database/JPAHanaDatabaseProcessorTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/database/JPAHanaDatabaseProcessorTest.java new file mode 100644 index 000000000..117c81a30 --- /dev/null +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/database/JPAHanaDatabaseProcessorTest.java @@ -0,0 +1,27 @@ +package com.sap.olingo.jpa.processor.core.database; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.sap.olingo.jpa.processor.cb.ProcessorSqlPatternProvider; + +class JPAHanaDatabaseProcessorTest extends JPA_XXX_DatabaseProcessorTest { + + @BeforeEach + void setup() { + initEach(); + oneParameterResult = "SELECT * FROM Example(?1)"; + twoParameterResult = "SELECT * FROM Example(?1,?2)"; + countResult = "SELECT COUNT(*) FROM Example(?1)"; + cut = new JPAHanaDatabaseProcessor(); + } + + @Test + void testGetLocatePattern() { + assertTrue(cut instanceof ProcessorSqlPatternProvider); + assertEquals("INSTR", ((ProcessorSqlPatternProvider) cut).getLocatePattern().function()); + } +} diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/database/JPAODataDatabaseProcessorFactoryTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/database/JPAODataDatabaseProcessorFactoryTest.java index 0a67fd249..e0ee2fe59 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/database/JPAODataDatabaseProcessorFactoryTest.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/database/JPAODataDatabaseProcessorFactoryTest.java @@ -32,7 +32,7 @@ static Stream processorProvider() { return Stream.of( arguments("H2", JPA_HSQLDB_DatabaseProcessor.class), arguments("HSQL Database Engine", JPA_HSQLDB_DatabaseProcessor.class), - arguments("PostgreSQL", JPA_POSTSQL_DatabaseProcessor.class), + arguments("PostgreSQL", JPAPostgresqlDatabaseProcessor.class), arguments("HANA", JPADefaultDatabaseProcessor.class)); } diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/database/JPAPostgresqlDatabaseProcessorTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/database/JPAPostgresqlDatabaseProcessorTest.java new file mode 100644 index 000000000..32d3b604e --- /dev/null +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/database/JPAPostgresqlDatabaseProcessorTest.java @@ -0,0 +1,43 @@ +package com.sap.olingo.jpa.processor.core.database; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Root; + +import org.apache.olingo.commons.api.http.HttpStatusCode; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.uri.queryoption.SearchOption; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType; + +class JPAPostgresqlDatabaseProcessorTest extends JPA_XXX_DatabaseProcessorTest { + @BeforeEach + public void setup() { + initEach(); + oneParameterResult = "SELECT * FROM Example(?1)"; + twoParameterResult = "SELECT * FROM Example(?1,?2)"; + countResult = "SELECT COUNT(*) FROM Example(?1)"; + cut = new JPAPostgresqlDatabaseProcessor(); + } + + @SuppressWarnings("unchecked") + @Test + void testAbortsOnSearchRequest() { + final CriteriaBuilder cb = mock(CriteriaBuilder.class); + final CriteriaQuery cq = mock(CriteriaQuery.class); + final Root root = mock(Root.class); + final JPAEntityType entityType = mock(JPAEntityType.class); + final SearchOption searchOption = mock(SearchOption.class); + + final ODataApplicationException act = assertThrows(ODataApplicationException.class, + () -> cut.createSearchWhereClause(cb, cq, root, entityType, searchOption)); + assertEquals(HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), act.getStatusCode()); + + } +} diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/database/JPAPostgresqlSqlPatternProviderTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/database/JPAPostgresqlSqlPatternProviderTest.java new file mode 100644 index 000000000..65559e5d4 --- /dev/null +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/database/JPAPostgresqlSqlPatternProviderTest.java @@ -0,0 +1,30 @@ +package com.sap.olingo.jpa.processor.core.database; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.sap.olingo.jpa.processor.cb.ProcessorSqlPatternProvider; + +class JPAPostgresqlSqlPatternProviderTest { + private ProcessorSqlPatternProvider cut; + + @BeforeEach + void setup() { + cut = new JPAPostgresqlSqlPatternProvider(); + } + + @Test + void testGetLocatePattern() { + assertEquals("POSITION", cut.getLocatePattern().function()); + assertEquals(" IN ", cut.getLocatePattern().parameters().get(1).keyword()); + } + + @Test + void testGetSubStringPattern() { + assertEquals("SUBSTRING", cut.getSubStringPattern().function()); + assertEquals(2, cut.getLocatePattern().parameters().size()); + } + +} diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/TestJPACustomScalarFunctions.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/TestJPACustomScalarFunctions.java deleted file mode 100644 index 7f0a0ee44..000000000 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/TestJPACustomScalarFunctions.java +++ /dev/null @@ -1,142 +0,0 @@ -package com.sap.olingo.jpa.processor.core.filter; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.params.provider.Arguments.arguments; - -import java.io.IOException; -import java.util.List; -import java.util.Map; -import java.util.stream.Stream; - -import javax.sql.DataSource; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.EntityManagerFactory; -import jakarta.persistence.EntityTransaction; -import jakarta.persistence.Query; - -import org.apache.olingo.commons.api.ex.ODataException; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.sap.olingo.jpa.metadata.api.JPAEntityManagerFactory; -import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException; -import com.sap.olingo.jpa.metadata.core.edm.mapper.impl.JPADefaultEdmNameBuilder; -import com.sap.olingo.jpa.processor.core.testmodel.DataSourceHelper; -import com.sap.olingo.jpa.processor.core.util.IntegrationTestHelper; -import com.sap.olingo.jpa.processor.core.util.TestHelper; - -class TestJPACustomScalarFunctions { - - protected static final String PUNIT_NAME = "com.sap.olingo.jpa"; - protected static EntityManagerFactory emf; - protected TestHelper helper; - protected Map> headers; - protected static JPADefaultEdmNameBuilder nameBuilder; - protected static DataSource ds; - - @BeforeAll - public static void setupClass() throws ODataJPAModelException { - ds = DataSourceHelper.createDataSource(DataSourceHelper.DB_HSQLDB); - emf = JPAEntityManagerFactory.getEntityManagerFactory(PUNIT_NAME, ds); - nameBuilder = new JPADefaultEdmNameBuilder(PUNIT_NAME); - CreateDensityFunction(); - } - - @AfterAll - public static void tearDownClass() throws ODataJPAModelException { - DropDensityFunction(); - } - - @Test - void testFilterOnFunctionAndProperty() throws IOException, ODataException { - - final IntegrationTestHelper helper = new IntegrationTestHelper(emf, - "AdministrativeDivisions?$filter=com.sap.olingo.jpa.PopulationDensity(Area=$it/Area,Population=$it/Population) mul 1000000 gt 1000 and ParentDivisionCode eq 'BE255'&orderBy=DivisionCode)"); - helper.assertStatus(200); - - final ArrayNode orgs = helper.getValues(); - assertEquals(2, orgs.size()); - assertEquals("35002", orgs.get(0).get("DivisionCode").asText()); - } - - @Test - void testFilterOnFunction() throws IOException, ODataException { - - final IntegrationTestHelper helper = new IntegrationTestHelper(emf, - "AdministrativeDivisions?$filter=com.sap.olingo.jpa.PopulationDensity(Area=$it/Area,Population=$it/Population) gt 1"); - helper.assertStatus(200); - } - - private static Stream provideFunctionQueries() { - return Stream.of( - arguments("FunctionAndMultiply", - "AdministrativeDivisions?$filter=com.sap.olingo.jpa.PopulationDensity(Area=Area,Population=Population) mul 1000000 gt 100", - 59), - arguments("FunctionWithFixedValue", - "AdministrativeDivisions?$filter=com.sap.olingo.jpa.PopulationDensity(Area=13079087,Population=$it/Population) mul 1000000 gt 1000", - 29), - arguments("FunctionComputedValue", - "AdministrativeDivisions?$filter=com.sap.olingo.jpa.PopulationDensity(Area=Area div 1000000,Population=Population) gt 1000", - 7), - arguments("FunctionMixParamOrder", - "AdministrativeDivisions?$filter=com.sap.olingo.jpa.PopulationDensity(Population=Population,Area=Area) mul 1000000 gt 1000", - 7)); - } - - @ParameterizedTest - @MethodSource("provideFunctionQueries") - void testFilterOnFunctionAndMultiply(final String text, final String queryString, final int numberOfResults) - throws IOException, ODataException { - - final IntegrationTestHelper helper = new IntegrationTestHelper(emf, queryString); - helper.assertStatus(200); - - final ArrayNode divisions = helper.getValues(); - assertEquals(numberOfResults, divisions.size(), text); - } - - private static void CreateDensityFunction() { - final EntityManager em = emf.createEntityManager(); - final EntityTransaction t = em.getTransaction(); - - final StringBuffer sqlString = new StringBuffer(); - - sqlString.append( - "CREATE FUNCTION \"OLINGO\".\"PopulationDensity\" (UnitArea INT, Population BIGINT ) "); - sqlString.append("RETURNS DOUBLE "); - sqlString.append("BEGIN ATOMIC "); // - sqlString.append(" DECLARE aDouble DOUBLE; "); // - sqlString.append(" DECLARE pDouble DOUBLE; "); - sqlString.append(" SET aDouble = UnitArea; "); - sqlString.append(" SET pDouble = Population; "); - sqlString.append(" IF UnitArea <= 0 THEN RETURN 0; "); - sqlString.append(" ELSE RETURN pDouble / aDouble; "); // * 1000000 - sqlString.append(" END IF; "); // - sqlString.append("END"); - - t.begin(); - final Query q = em.createNativeQuery(sqlString.toString()); - q.executeUpdate(); - t.commit(); - } - - private static void DropDensityFunction() { - final EntityManager em = emf.createEntityManager(); - final EntityTransaction t = em.getTransaction(); - - final StringBuffer sqlString = new StringBuffer(); - - sqlString.append("DROP FUNCTION \"OLINGO\".\"PopulationDensity\""); - - t.begin(); - final Query q = em.createNativeQuery(sqlString.toString()); - q.executeUpdate(); - t.commit(); - } -} diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/TestScalarDbFunctions.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/TestScalarDbFunctions.java index 91f096705..ca789e774 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/TestScalarDbFunctions.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/filter/TestScalarDbFunctions.java @@ -4,7 +4,6 @@ import static org.junit.jupiter.params.provider.Arguments.arguments; import java.io.IOException; -import java.sql.SQLException; import java.util.List; import java.util.Map; import java.util.stream.Stream; @@ -14,7 +13,6 @@ import jakarta.persistence.EntityManagerFactory; import org.apache.olingo.commons.api.ex.ODataException; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -42,12 +40,6 @@ public static void setupClass() { nameBuilder = new JPADefaultEdmNameBuilder(PUNIT_NAME); } - @AfterAll - public static void teardownClass() throws SQLException { - emf.close(); - ds.getConnection().close(); - } - @Test void testFilterOnFunctionAndProperty() throws IOException, ODataException { diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/properties/AbstractJPAProcessorAttributeTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/properties/AbstractJPAProcessorAttributeTest.java new file mode 100644 index 000000000..2f6361450 --- /dev/null +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/properties/AbstractJPAProcessorAttributeTest.java @@ -0,0 +1,55 @@ +package com.sap.olingo.jpa.processor.core.properties; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAssociationPath; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAPath; + +abstract class AbstractJPAProcessorAttributeTest { + protected JPAPath path; + protected JPAAssociationPath hop; + protected JPAProcessorAttribute cut; + + @BeforeEach + void setup() { + path = mock(JPAPath.class); + hop = mock(JPAAssociationPath.class); + + when(hop.getAlias()).thenReturn("hophop"); + when(path.getAlias()).thenReturn("path"); + } + + @Test + void testReturnsDescending() { + createCutSortOrder(true); + assertTrue(cut.sortDescending()); + } + + @Test + void testReturnsAscending() { + createCutSortOrder(false); + assertFalse(cut.sortDescending()); + } + + @Test + void testGetPathThrowsExceptionIfSetTargetNotCalled() { + + createCutSortOrder(true); + assertThrows(IllegalAccessError.class, () -> cut.getPath()); + } + + protected abstract void createCutSortOrder(boolean descending); + + abstract void testJoinRequired(); + + abstract void testJoinNotRequired(); + + abstract void testCreateJoinThrowsExceptionIfSetTargetNotCalled(); +} diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/properties/JPAOrderByPropertyFactoryTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/properties/JPAOrderByPropertyFactoryTest.java new file mode 100644 index 000000000..918b03a76 --- /dev/null +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/properties/JPAOrderByPropertyFactoryTest.java @@ -0,0 +1,295 @@ +package com.sap.olingo.jpa.processor.core.properties; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Locale; + +import org.apache.olingo.commons.api.edm.EdmFunction; +import org.apache.olingo.commons.api.edm.EdmNavigationProperty; +import org.apache.olingo.commons.api.edm.EdmProperty; +import org.apache.olingo.commons.api.ex.ODataException; +import org.apache.olingo.server.api.uri.UriInfoResource; +import org.apache.olingo.server.api.uri.UriResourceComplexProperty; +import org.apache.olingo.server.api.uri.UriResourceCount; +import org.apache.olingo.server.api.uri.UriResourceFunction; +import org.apache.olingo.server.api.uri.UriResourceKind; +import org.apache.olingo.server.api.uri.UriResourceNavigation; +import org.apache.olingo.server.api.uri.UriResourcePrimitiveProperty; +import org.apache.olingo.server.api.uri.queryoption.OrderByItem; +import org.apache.olingo.server.api.uri.queryoption.expression.Member; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType; +import com.sap.olingo.jpa.metadata.core.edm.mapper.exception.ODataJPAModelException; +import com.sap.olingo.jpa.processor.core.exception.ODataJPAIllegalArgumentException; +import com.sap.olingo.jpa.processor.core.testmodel.AssociationOneToOneSource; +import com.sap.olingo.jpa.processor.core.testmodel.CollectionDeep; +import com.sap.olingo.jpa.processor.core.testmodel.Organization; +import com.sap.olingo.jpa.processor.core.testmodel.Person; +import com.sap.olingo.jpa.processor.core.util.TestBase; +import com.sap.olingo.jpa.processor.core.util.TestHelper; + +class JPAOrderByPropertyFactoryTest extends TestBase { + private JPAOrderByPropertyFactory cut; + private TestHelper testHelper; + private OrderByItem orderByItem; + private JPAEntityType et; + private Member expression; + private UriInfoResource uriInfo; + + @BeforeEach + void setup() throws ODataException { + orderByItem = mock(OrderByItem.class); + et = mock(JPAEntityType.class); + expression = mock(Member.class); + uriInfo = mock(UriInfoResource.class); + when(orderByItem.getExpression()).thenReturn(expression); + when(expression.getResourcePath()).thenReturn(uriInfo); + cut = new JPAOrderByPropertyFactory(); + testHelper = getHelper(); + } + + @Test + void testCreatePrimitiveSimpleProperty() throws ODataJPAModelException { + final var property = mock(UriResourcePrimitiveProperty.class); + final var edmType = mock(EdmProperty.class); + when(uriInfo.getUriResourceParts()).thenReturn(Collections.singletonList(property)); + when(property.getProperty()).thenReturn(edmType); + when(property.getKind()).thenReturn(UriResourceKind.primitiveProperty); + when(edmType.getName()).thenReturn("Name1"); + + et = testHelper.getJPAEntityType(Organization.class); + + final var act = cut.createProperty(orderByItem, et, Locale.ENGLISH); + assertTrue(act instanceof JPAProcessorSimpleAttribute); + assertTrue(((JPAProcessorSimpleAttribute) act).isSortable()); + assertFalse(((JPAProcessorSimpleAttribute) act).requiresJoin()); + assertEquals("Name1", act.getAlias()); + } + + @Test + void testCreateComplexSimpleProperty() throws ODataJPAModelException { + final var complex = mock(UriResourceComplexProperty.class); + final var property = mock(UriResourcePrimitiveProperty.class); + final var edmProperty = mock(EdmProperty.class); + final var edmComplex = mock(EdmProperty.class); + when(uriInfo.getUriResourceParts()).thenReturn(Arrays.asList(complex, property)); + when(property.getProperty()).thenReturn(edmProperty); + when(edmProperty.getName()).thenReturn("Region"); + when(property.getKind()).thenReturn(UriResourceKind.primitiveProperty); + when(complex.getProperty()).thenReturn(edmComplex); + when(edmComplex.getName()).thenReturn("Address"); + + et = testHelper.getJPAEntityType(Organization.class); + + final var act = cut.createProperty(orderByItem, et, Locale.ENGLISH); + assertTrue(act instanceof JPAProcessorSimpleAttribute); + assertTrue(((JPAProcessorSimpleAttribute) act).isSortable()); + assertFalse(((JPAProcessorSimpleAttribute) act).requiresJoin()); + assertEquals("Address/Region", act.getAlias()); + } + + @Test + void testCreateNavigationSimpleProperty() throws ODataJPAModelException { + // "AssociationOneToOneSources?$orderby=ColumnTarget/Source asc" + final var navigation = mock(UriResourceNavigation.class); + final var property = mock(UriResourcePrimitiveProperty.class); + final var edmProperty = mock(EdmProperty.class); + final var edmNavigation = mock(EdmNavigationProperty.class); + when(uriInfo.getUriResourceParts()).thenReturn(Arrays.asList(navigation, property)); + when(property.getProperty()).thenReturn(edmProperty); + when(edmProperty.getName()).thenReturn("Source"); + when(property.getKind()).thenReturn(UriResourceKind.primitiveProperty); + when(navigation.getProperty()).thenReturn(edmNavigation); + when(edmNavigation.getName()).thenReturn("ColumnTarget"); + when(orderByItem.isDescending()).thenReturn(false); + + et = testHelper.getJPAEntityType(AssociationOneToOneSource.class); + + final var act = cut.createProperty(orderByItem, et, Locale.ENGLISH); + assertTrue(act instanceof JPAProcessorSimpleAttribute); + assertTrue(((JPAProcessorSimpleAttribute) act).isSortable()); + assertTrue(((JPAProcessorSimpleAttribute) act).requiresJoin()); + assertEquals("ColumnTarget", act.getAlias()); + } + + @Test + void testCreateNavigationComplexPathProperty() throws ODataJPAModelException { + // "Persons?$orderby=AdministrativeInformation/Created/User/LastName" + final var firstComplex = mock(UriResourceComplexProperty.class); + final var secondComplex = mock(UriResourceComplexProperty.class); + final var edmFirstProperty = mock(EdmProperty.class); + final var edmSecondProperty = mock(EdmProperty.class); + + final var navigation = mock(UriResourceNavigation.class); + final var property = mock(UriResourcePrimitiveProperty.class); + final var edmProperty = mock(EdmProperty.class); + final var edmNavigation = mock(EdmNavigationProperty.class); + + when(uriInfo.getUriResourceParts()).thenReturn(Arrays.asList(firstComplex, secondComplex, navigation, property)); + + when(firstComplex.getProperty()).thenReturn(edmFirstProperty); + when(edmFirstProperty.getName()).thenReturn("AdministrativeInformation"); + when(secondComplex.getProperty()).thenReturn(edmSecondProperty); + when(edmSecondProperty.getName()).thenReturn("Created"); + + when(navigation.getProperty()).thenReturn(edmNavigation); + when(edmNavigation.getName()).thenReturn("User"); + when(property.getProperty()).thenReturn(edmProperty); + when(edmProperty.getName()).thenReturn("LastName"); + when(property.getKind()).thenReturn(UriResourceKind.primitiveProperty); + when(orderByItem.isDescending()).thenReturn(false); + + et = testHelper.getJPAEntityType(Person.class); + + final var act = cut.createProperty(orderByItem, et, Locale.ENGLISH); + assertTrue(act instanceof JPAProcessorSimpleAttribute); + assertTrue(((JPAProcessorSimpleAttribute) act).isSortable()); + assertTrue(((JPAProcessorSimpleAttribute) act).requiresJoin()); + assertEquals("AdministrativeInformation/Created/User", act.getAlias()); + } + + @Test + void testCreateNavigationCount() throws ODataJPAModelException { + // "Organizations?$orderby=Roles/$count" + final var navigation = mock(UriResourceNavigation.class); + final var count = mock(UriResourceCount.class); + final var edmNavigation = mock(EdmNavigationProperty.class); + when(uriInfo.getUriResourceParts()).thenReturn(Arrays.asList(navigation, count)); + when(navigation.getProperty()).thenReturn(edmNavigation); + when(edmNavigation.getName()).thenReturn("Roles"); + when(count.getKind()).thenReturn(UriResourceKind.count); + when(orderByItem.isDescending()).thenReturn(false); + + et = testHelper.getJPAEntityType(Organization.class); + + final var act = cut.createProperty(orderByItem, et, Locale.ENGLISH); + assertTrue(act instanceof JPAProcessorCountAttribute); + assertTrue(((JPAProcessorCountAttribute) act).isSortable()); + assertTrue(((JPAProcessorCountAttribute) act).requiresJoin()); + assertEquals("Roles", act.getAlias()); + } + + @Test + void testCreatePrimitiveCollectionCountViaComplex() throws ODataJPAModelException { + // "CollectionDeeps?$orderby=FirstLevel/SecondLevel/Comment/$count asc" + final var firstComplex = mock(UriResourceComplexProperty.class); + final var secondComplex = mock(UriResourceComplexProperty.class); + final var edmFirstProperty = mock(EdmProperty.class); + final var edmSecondProperty = mock(EdmProperty.class); + + when(firstComplex.getProperty()).thenReturn(edmFirstProperty); + when(edmFirstProperty.getName()).thenReturn("FirstLevel"); + when(secondComplex.getProperty()).thenReturn(edmSecondProperty); + when(edmSecondProperty.getName()).thenReturn("SecondLevel"); + + final var collection = mock(UriResourcePrimitiveProperty.class); + final var edmCollection = mock(EdmProperty.class); + + final var count = mock(UriResourceCount.class); + when(uriInfo.getUriResourceParts()).thenReturn(Arrays.asList(firstComplex, secondComplex, collection, count)); + + when(collection.getProperty()).thenReturn(edmCollection); + when(collection.isCollection()).thenReturn(true); + when(edmCollection.getName()).thenReturn("Comment"); + when(count.getKind()).thenReturn(UriResourceKind.count); + when(orderByItem.isDescending()).thenReturn(false); + + et = testHelper.getJPAEntityType(CollectionDeep.class); + + final var act = cut.createProperty(orderByItem, et, Locale.ENGLISH); + assertTrue(act instanceof JPAProcessorCountAttribute); + assertTrue(((JPAProcessorCountAttribute) act).isSortable()); + assertTrue(((JPAProcessorCountAttribute) act).requiresJoin()); + assertEquals("FirstLevel/SecondLevel/Comment", act.getAlias()); + } + + @Test + void testCreateComplexCollectionCountViaComplex() throws ODataJPAModelException { + // "CollectionDeeps?$orderby=FirstLevel/SecondLevel/Address/$count asc" + final var firstComplex = mock(UriResourceComplexProperty.class); + final var secondComplex = mock(UriResourceComplexProperty.class); + final var edmFirstProperty = mock(EdmProperty.class); + final var edmSecondProperty = mock(EdmProperty.class); + + when(firstComplex.getProperty()).thenReturn(edmFirstProperty); + when(edmFirstProperty.getName()).thenReturn("FirstLevel"); + when(secondComplex.getProperty()).thenReturn(edmSecondProperty); + when(edmSecondProperty.getName()).thenReturn("SecondLevel"); + + final var collection = mock(UriResourceComplexProperty.class); + final var edmCollection = mock(EdmProperty.class); + + final var count = mock(UriResourceCount.class); + when(uriInfo.getUriResourceParts()).thenReturn(Arrays.asList(firstComplex, secondComplex, collection, count)); + + when(collection.getProperty()).thenReturn(edmCollection); + when(collection.isCollection()).thenReturn(true); + when(edmCollection.getName()).thenReturn("Address"); + when(count.getKind()).thenReturn(UriResourceKind.count); + when(orderByItem.isDescending()).thenReturn(false); + + et = testHelper.getJPAEntityType(CollectionDeep.class); + + final var act = cut.createProperty(orderByItem, et, Locale.ENGLISH); + assertTrue(act instanceof JPAProcessorCountAttribute); + assertTrue(((JPAProcessorCountAttribute) act).isSortable()); + assertTrue(((JPAProcessorCountAttribute) act).requiresJoin()); + assertEquals("FirstLevel/SecondLevel/Address", act.getAlias()); + } + + @Test + void testCreateDescriptionSimpleProperty() throws ODataJPAModelException { + final var property = mock(UriResourcePrimitiveProperty.class); + final var edmType = mock(EdmProperty.class); + when(uriInfo.getUriResourceParts()).thenReturn(Collections.singletonList(property)); + when(property.getProperty()).thenReturn(edmType); + when(property.getKind()).thenReturn(UriResourceKind.primitiveProperty); + when(edmType.getName()).thenReturn("LocationName"); + + et = testHelper.getJPAEntityType(Organization.class); + + final var act = cut.createProperty(orderByItem, et, Locale.ENGLISH); + assertTrue(act instanceof JPAProcessorDescriptionAttribute); + assertTrue(((JPAProcessorDescriptionAttribute) act).isSortable()); + assertTrue(((JPAProcessorDescriptionAttribute) act).requiresJoin()); + assertEquals("LocationName", act.getAlias()); + } + + @Test + void testThrowsBadRequestExceptionOnUnknownProperty() throws ODataJPAModelException { + final var property = mock(UriResourcePrimitiveProperty.class); + final var edmType = mock(EdmProperty.class); + when(uriInfo.getUriResourceParts()).thenReturn(Collections.singletonList(property)); + when(property.getProperty()).thenReturn(edmType); + when(property.getKind()).thenReturn(UriResourceKind.primitiveProperty); + when(edmType.getName()).thenReturn("Name"); + + et = testHelper.getJPAEntityType(Organization.class); + + assertThrows(ODataJPAIllegalArgumentException.class, + () -> cut.createProperty(orderByItem, et, Locale.ENGLISH)); + } + + @Test + void testThrowsNotImplementedOnOrderByFunction() { + final var function = mock(UriResourceFunction.class); + final var edmType = mock(EdmFunction.class); + when(uriInfo.getUriResourceParts()).thenReturn(Collections.singletonList(function)); + when(function.getFunction()).thenReturn(edmType); + when(function.getKind()).thenReturn(UriResourceKind.function); + when(edmType.getName()).thenReturn("Name"); + + assertThrows(ODataJPAIllegalArgumentException.class, + () -> cut.createProperty(orderByItem, et, Locale.ENGLISH)); + } + +} diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorCountAttributeImplTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorCountAttributeImplTest.java new file mode 100644 index 000000000..44d8b97f1 --- /dev/null +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorCountAttributeImplTest.java @@ -0,0 +1,157 @@ +package com.sap.olingo.jpa.processor.core.properties; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.Collections; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.From; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.JoinType; + +import org.junit.jupiter.api.Test; + +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAssociationAttribute; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAssociationPath; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAttribute; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAElement; +import com.sap.olingo.jpa.processor.core.exception.ODataJPAIllegalArgumentException; +import com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException; + +class JPAProcessorCountAttributeImplTest extends AbstractJPAProcessorAttributeTest { + + @Override + protected void createCutSortOrder(final boolean descending) { + cut = new JPAProcessorCountAttributeImpl(Collections.emptyList(), descending); + } + + @Test + @Override + void testJoinRequired() { + cut = new JPAProcessorCountAttributeImpl(Collections.singletonList(hop), true); + assertTrue(cut.requiresJoin()); + } + + @Test + @Override + void testJoinNotRequired() { + cut = new JPAProcessorCountAttributeImpl(Collections.emptyList(), true); + assertFalse(cut.requiresJoin()); + } + + @Test + void testIsSortable() { + final var association = mock(JPAAssociationAttribute.class); + when(association.isTransient()).thenReturn(false); + when(hop.getLeaf()).thenReturn(association); + cut = new JPAProcessorCountAttributeImpl(Collections.singletonList(hop), true); + assertTrue(cut.isSortable()); + } + + @Test + void testNotIsSortableNoHop() { + cut = new JPAProcessorCountAttributeImpl(Collections.emptyList(), true); + assertFalse(cut.isSortable()); + } + + @Test + void testNotIsSortableTransient() { + final var association = mock(JPAAssociationAttribute.class); + when(association.isTransient()).thenReturn(true); + when(hop.getLeaf()).thenReturn(association); + cut = new JPAProcessorCountAttributeImpl(Collections.singletonList(hop), true); + assertFalse(cut.isSortable()); + } + + @Test + void testAliasFromHop() { + cut = new JPAProcessorCountAttributeImpl(Collections.singletonList(hop), true); + assertEquals("hophop", cut.getAlias()); + } + + @Test + void testAliasNoHop() { + cut = new JPAProcessorCountAttributeImpl(Collections.emptyList(), true); + assertEquals("Count", cut.getAlias()); + } + + @Test + void testExceptionOnMoreThanOneHop() { + final var hop2 = mock(JPAAssociationPath.class); + final ODataJPAIllegalArgumentException act = assertThrows(ODataJPAIllegalArgumentException.class, // NOSONAR + () -> new JPAProcessorCountAttributeImpl(Arrays.asList(hop, hop2), true)); + assertEquals(1, act.getParams().length); + } + + @Test + void testCreateJoinReturnsNullIfNotNeeded() { + final var cb = mock(CriteriaBuilder.class); + final var from = mock(From.class); + cut = new JPAProcessorCountAttributeImpl(Collections.emptyList(), true); + cut.setTarget(from, Collections.emptyMap(), cb); + assertNull(cut.createJoin()); + } + + @Test + void testCreateJoin() { + final var cb = mock(CriteriaBuilder.class); + final var from = mock(From.class); + final var join = mock(Join.class); + final var target = mock(JPAElement.class); + when(target.getInternalName()).thenReturn("target"); + when(from.join("target", JoinType.LEFT)).thenReturn(join); + when(hop.getPath()).thenReturn(Collections.singletonList(target)); + + cut = new JPAProcessorCountAttributeImpl(Collections.singletonList(hop), true); + cut.setTarget(from, Collections.emptyMap(), cb); + final var act = cut.createJoin(); + assertEquals(join, act); + } + + @Test + void testCreateJoinTakenFromCache() { + final var cb = mock(CriteriaBuilder.class); + final var from = mock(From.class); + final var join = mock(Join.class); + final var target = mock(JPAElement.class); + when(target.getInternalName()).thenReturn("target"); + when(hop.getPath()).thenReturn(Collections.singletonList(target)); + + cut = new JPAProcessorCountAttributeImpl(Collections.singletonList(hop), true); + cut.setTarget(from, Collections.singletonMap("hophop", join), cb); + final var act = cut.createJoin(); + assertEquals(join, act); + verify(from, times(0)).join(anyString(), any()); + } + + @Test + @Override + void testCreateJoinThrowsExceptionIfSetTargetNotCalled() { + cut = new JPAProcessorCountAttributeImpl(Collections.singletonList(hop), true); + assertThrows(IllegalAccessError.class, () -> cut.createJoin()); + } + + @Test + void testOrderByOfTransientThrowsException() { + final var cb = mock(CriteriaBuilder.class); + final var attribute = mock(JPAAttribute.class); + when(hop.getPath()).thenReturn(Collections.singletonList(attribute)); + when(attribute.toString()).thenReturn("Test"); + when(attribute.isTransient()).thenReturn(true); + cut = new JPAProcessorCountAttributeImpl(Collections.singletonList(hop), true); + final var act = assertThrows(ODataJPAQueryException.class, () -> cut.createOrderBy(cb, Collections.emptyList())); + assertEquals(501, act.getStatusCode()); + } + +} \ No newline at end of file diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorDescriptionAttributeImplTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorDescriptionAttributeImplTest.java new file mode 100644 index 000000000..a3e0ef45f --- /dev/null +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorDescriptionAttributeImplTest.java @@ -0,0 +1,289 @@ +package com.sap.olingo.jpa.processor.core.properties; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.stream.Stream; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.From; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.JoinType; +import jakarta.persistence.criteria.Path; +import jakarta.persistence.criteria.Predicate; + +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAssociationPath; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAttribute; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPADescriptionAttribute; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAElement; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAPath; +import com.sap.olingo.jpa.processor.core.exception.ODataJPAIllegalArgumentException; +import com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException; + +class JPAProcessorDescriptionAttributeImplTest extends AbstractJPAProcessorAttributeTest { + + @Override + protected void createCutSortOrder(final boolean descending) { + cut = new JPAProcessorDescriptionAttributeImpl(path, Collections.emptyList(), descending, Locale.ENGLISH); + } + + @Test + @Override + void testJoinRequired() { + cut = new JPAProcessorDescriptionAttributeImpl(path, Collections.emptyList(), false, Locale.ENGLISH); + assertTrue(cut.requiresJoin()); + } + + @Override + void testJoinNotRequired() { + // Description Properties allays require join + } + + @Test + void testIsSortable() { + cut = new JPAProcessorDescriptionAttributeImpl(path, Collections.emptyList(), true, Locale.ENGLISH); + assertTrue(cut.isSortable()); + } + + @Test + void testAliasFromPath() { + cut = new JPAProcessorDescriptionAttributeImpl(path, Collections.emptyList(), true, Locale.UK); + assertEquals("path", cut.getAlias()); + } + + @Test + void testExceptionOnMoreThanOneHop() { + final var hop2 = mock(JPAAssociationPath.class); + final ODataJPAIllegalArgumentException act = assertThrows(ODataJPAIllegalArgumentException.class, // NOSONAR + () -> new JPAProcessorDescriptionAttributeImpl(path, Arrays.asList(hop, hop2), true, Locale.ENGLISH)); + assertEquals(1, act.getParams().length); + } + + private static Stream provideJoinPreconditions() { + return Stream.of( + Arguments.of(true), + Arguments.of(false)); + } + + @ParameterizedTest + @MethodSource("provideJoinPreconditions") + void testCreateJoin(final boolean isLocale) { + final var cb = mock(CriteriaBuilder.class); + final var from = mock(From.class); + final var join = mock(Join.class); + final var localePath = createLocaleFiledPath(join); + createDescriptionProperty(isLocale, localePath); + + final var target = mock(JPAElement.class); + when(target.getInternalName()).thenReturn("description"); + when(from.join("description", JoinType.LEFT)).thenReturn(join); + when(hop.getPath()).thenReturn(Collections.singletonList(target)); + + cut = new JPAProcessorDescriptionAttributeImpl(path, Collections.emptyList(), true, Locale.UK); + cut.setTarget(from, Collections.emptyMap(), cb); + final var act = cut.createJoin(); + assertEquals(join, act); + verify(cb).equal(localePath.right, isLocale ? Locale.UK.toString() : Locale.UK.getLanguage()); + } + + @Test + void testCreateJoinWithOnCondition() { + final var cb = mock(CriteriaBuilder.class); + final var from = mock(From.class); + final var join = mock(Join.class); + final var onCondition = mock(Predicate.class); + final var localeOnCondition = mock(Predicate.class); + final var localePath = createLocaleFiledPath(join); + createDescriptionProperty(true, localePath); + + final var target = mock(JPAElement.class); + when(target.getInternalName()).thenReturn("description"); + when(from.join("description", JoinType.LEFT)).thenReturn(join); + when(hop.getPath()).thenReturn(Collections.singletonList(target)); + when(join.getOn()).thenReturn(onCondition); + when(cb.equal(localePath.right, Locale.UK.toString())).thenReturn(localeOnCondition); + + cut = new JPAProcessorDescriptionAttributeImpl(path, Collections.emptyList(), true, Locale.UK); + cut.setTarget(from, Collections.emptyMap(), cb); + final var act = cut.createJoin(); + assertEquals(join, act); + verify(cb).and(onCondition, localeOnCondition); + } + + @Test + void testCreateJoinWithFixedValues() { + final var cb = mock(CriteriaBuilder.class); + final var from = mock(From.class); + final var join = mock(Join.class); + final var localeOnCondition = mock(Predicate.class); + final ImmutablePair, String>, Map> fixedValues = getFixValues(join); + final var localePath = createLocaleFiledPath(join); + final var descriptionProperty = createDescriptionProperty(true, localePath); + + final var target = mock(JPAElement.class); + when(target.getInternalName()).thenReturn("description"); + when(from.join("description", JoinType.LEFT)).thenReturn(join); + when(hop.getPath()).thenReturn(Collections.singletonList(target)); + when(descriptionProperty.getFixedValueAssignment()).thenReturn(fixedValues.right); + + when(cb.equal(localePath.right, Locale.UK.toString())).thenReturn(localeOnCondition); + + cut = new JPAProcessorDescriptionAttributeImpl(path, Collections.emptyList(), true, Locale.UK); + cut.setTarget(from, Collections.emptyMap(), cb); + final var act = cut.createJoin(); + assertEquals(join, act); + for (final var fixValue : fixedValues.left.entrySet()) + verify(cb).equal(fixValue.getKey(), fixValue.getValue()); + } + + @Test + @Override + void testCreateJoinThrowsExceptionIfSetTargetNotCalled() { + + cut = new JPAProcessorDescriptionAttributeImpl(path, Collections.emptyList(), true, Locale.UK); + assertThrows(IllegalAccessError.class, () -> cut.createJoin()); + } + + @Test + void testGetPath() { + final var cb = mock(CriteriaBuilder.class); + final var from = mock(From.class); + final var join = mock(Join.class); + final var localePath = createLocaleFiledPath(join); + final JPADescriptionAttribute attribute = createDescriptionProperty(false, localePath); + final JPAAttribute descriptionAttribute = mock(JPAAttribute.class); + when(path.getLeaf()).thenReturn(attribute); + when(attribute.getDescriptionAttribute()).thenReturn(descriptionAttribute); + when(descriptionAttribute.getInternalName()).thenReturn("name"); + + final var criteriaPath = mock(Path.class); + final var target = mock(JPAElement.class); + when(target.getInternalName()).thenReturn("description"); + when(from.join("description", JoinType.LEFT)).thenReturn(join); + when(hop.getPath()).thenReturn(Collections.singletonList(target)); + when(join.get("name")).thenReturn(criteriaPath); + + cut = new JPAProcessorDescriptionAttributeImpl(path, Collections.emptyList(), true, Locale.UK); + cut.setTarget(from, Collections.emptyMap(), cb); + assertNotNull(cut.getPath()); + assertEquals(criteriaPath, cut.getPath()); + } + + @Test + void testCreateJoinTakenFromCache() { + final var cb = mock(CriteriaBuilder.class); + final var from = mock(From.class); + final var join = mock(Join.class); + final var target = mock(JPAElement.class); + when(target.getInternalName()).thenReturn("target"); + when(hop.getPath()).thenReturn(Collections.singletonList(target)); + + cut = new JPAProcessorSimpleAttributeImpl(path, Collections.singletonList(hop), true); + cut.setTarget(from, Collections.singletonMap("hophop", join), cb); + final var act = cut.createJoin(); + assertEquals(join, act); + verify(from, times(0)).join(anyString(), any()); + } + + @Test + void testOrderByOfTransientThrowsException() { + final var cb = mock(CriteriaBuilder.class); + final var attribute = mock(JPAAttribute.class); + when(path.isTransient()).thenReturn(true); + when(path.getLeaf()).thenReturn(attribute); + when(attribute.toString()).thenReturn("Test"); + when(path.isPartOfGroups(any())).thenReturn(true); + cut = new JPAProcessorDescriptionAttributeImpl(path, Collections.emptyList(), true, Locale.UK); + final var act = assertThrows(ODataJPAQueryException.class, () -> cut.createOrderBy(cb, Collections.emptyList())); + assertEquals(400, act.getStatusCode()); + } + + @Test + void testOrderByNotInGroupsThrowsException() { + final var cb = mock(CriteriaBuilder.class); + final var attribute = mock(JPAAttribute.class); + when(path.isTransient()).thenReturn(false); + when(path.getLeaf()).thenReturn(attribute); + when(attribute.toString()).thenReturn("Test"); + when(path.isPartOfGroups(any())).thenReturn(false); + cut = new JPAProcessorDescriptionAttributeImpl(path, Collections.emptyList(), true, Locale.UK); + final var act = assertThrows(ODataJPAQueryException.class, () -> cut.createOrderBy(cb, Collections.emptyList())); + assertEquals(403, act.getStatusCode()); + } + + private ImmutablePair, String>, Map> + getFixValues(final Join join) { + final Map, String> fixedPath = new HashMap<>(); + final Map fixedValues = new HashMap<>(); + + final var valueOnePath = getFixValue(join, "one"); + final var valueTwoPath = getFixValue(join, "two"); + + fixedValues.put(valueOnePath.right, "One"); + fixedPath.put(valueOnePath.left, "One"); + fixedValues.put(valueTwoPath.right, "Two"); + fixedPath.put(valueTwoPath.left, "Two"); + return new ImmutablePair<>(fixedPath, fixedValues); + } + + @SuppressWarnings("unchecked") + private ImmutablePair, JPAPath> getFixValue(final Join join, final String name) { + final var valueOnePath = mock(JPAPath.class); + final var valueOneElement = mock(JPAElement.class); + final var valueOneExpression = mock(Path.class); + when(valueOneElement.getInternalName()).thenReturn(name); + when(valueOnePath.getPath()).thenReturn(Collections.singletonList(valueOneElement)); + when(join.get(name)).thenReturn(valueOneExpression); + return new ImmutablePair<>(valueOneExpression, valueOnePath); + } + + protected JPADescriptionAttribute createDescriptionProperty(final boolean isLocale, + final ImmutablePair> localePath) { + final var descriptionProperty = mock(JPADescriptionAttribute.class); + when(descriptionProperty.getLocaleFieldName()).thenReturn(localePath.left); + when(descriptionProperty.isLocationJoin()).thenReturn(isLocale); + when(descriptionProperty.getInternalName()).thenReturn("description"); + + when(path.getLeaf()).thenReturn(descriptionProperty); + when(path.getPath()).thenReturn(Arrays.asList(descriptionProperty)); + return descriptionProperty; + } + + @SuppressWarnings("unchecked") + private ImmutablePair> createLocaleFiledPath(final Join join) { + final var complex = mock(JPAElement.class); + final var locale = mock(JPAElement.class); + final var path = mock(JPAPath.class); + + final var complexPath = mock(Path.class); + final var localePath = mock(Path.class); + + when(path.getPath()).thenReturn(Arrays.asList(complex, locale)); + when(complex.getInternalName()).thenReturn("complex"); + when(locale.getInternalName()).thenReturn("locale"); + + when(join.get("complex")).thenReturn(complexPath); + when(complexPath.get("locale")).thenReturn(localePath); + + return new ImmutablePair<>(path, localePath); + } +} diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorSimpleAttributeImplTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorSimpleAttributeImplTest.java new file mode 100644 index 000000000..dda098217 --- /dev/null +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/properties/JPAProcessorSimpleAttributeImplTest.java @@ -0,0 +1,253 @@ +package com.sap.olingo.jpa.processor.core.properties; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.Collections; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.From; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.JoinType; +import jakarta.persistence.criteria.Order; +import jakarta.persistence.criteria.Path; + +import org.apache.olingo.commons.api.ex.ODataException; +import org.junit.jupiter.api.Test; + +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAssociationPath; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAttribute; +import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAElement; +import com.sap.olingo.jpa.processor.core.exception.ODataJPAIllegalArgumentException; +import com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException; + +class JPAProcessorSimpleAttributeImplTest extends AbstractJPAProcessorAttributeTest { + + @Override + protected void createCutSortOrder(final boolean descending) { + cut = new JPAProcessorSimpleAttributeImpl(path, Collections.emptyList(), descending); + + } + + @Test + @Override + void testJoinRequired() { + cut = new JPAProcessorSimpleAttributeImpl(path, Collections.singletonList(hop), true); + assertTrue(cut.requiresJoin()); + } + + @Test + @Override + void testJoinNotRequired() { + cut = new JPAProcessorSimpleAttributeImpl(path, Collections.emptyList(), true); + assertFalse(cut.requiresJoin()); + } + + @Test + void testIsSortable() { + when(path.isTransient()).thenReturn(false); + cut = new JPAProcessorSimpleAttributeImpl(path, Collections.singletonList(hop), true); + assertTrue(cut.isSortable()); + } + + @Test + void testNotIsSortable() { + when(path.isTransient()).thenReturn(true); + cut = new JPAProcessorSimpleAttributeImpl(path, Collections.emptyList(), true); + assertFalse(cut.isSortable()); + } + + @Test + void testAliasFromHop() { + cut = new JPAProcessorSimpleAttributeImpl(path, Collections.singletonList(hop), true); + assertEquals("hophop", cut.getAlias()); + } + + @Test + void testAliasFromPath() { + cut = new JPAProcessorSimpleAttributeImpl(path, Collections.emptyList(), true); + assertEquals("path", cut.getAlias()); + } + + @Test + void testExceptionOnMoreThanOneHop() { + final var hop2 = mock(JPAAssociationPath.class); + final ODataJPAIllegalArgumentException act = assertThrows(ODataJPAIllegalArgumentException.class, // NOSONAR + () -> new JPAProcessorSimpleAttributeImpl(path, Arrays.asList(hop, hop2), true)); + assertEquals(1, act.getParams().length); + } + + @Test + void testCreateJoinReturnsNullIfNotNeeded() { + final var cb = mock(CriteriaBuilder.class); + final var from = mock(From.class); + cut = new JPAProcessorSimpleAttributeImpl(path, Collections.emptyList(), true); + cut.setTarget(from, Collections.emptyMap(), cb); + assertNull(cut.createJoin()); + } + + @Test + void testCreateJoin() { + final var cb = mock(CriteriaBuilder.class); + final var from = mock(From.class); + final var join = mock(Join.class); + final var target = mock(JPAElement.class); + when(target.getInternalName()).thenReturn("target"); + when(from.join("target", JoinType.LEFT)).thenReturn(join); + when(hop.getPath()).thenReturn(Collections.singletonList(target)); + + cut = new JPAProcessorSimpleAttributeImpl(path, Collections.singletonList(hop), true); + cut.setTarget(from, Collections.emptyMap(), cb); + final var act = cut.createJoin(); + assertEquals(join, act); + } + + @Test + void testCreateJoinTakenFromCache() { + final var cb = mock(CriteriaBuilder.class); + final var from = mock(From.class); + final var join = mock(Join.class); + final var target = mock(JPAElement.class); + when(target.getInternalName()).thenReturn("target"); + when(hop.getPath()).thenReturn(Collections.singletonList(target)); + + cut = new JPAProcessorSimpleAttributeImpl(path, Collections.singletonList(hop), true); + cut.setTarget(from, Collections.singletonMap("hophop", join), cb); + final var act = cut.createJoin(); + assertEquals(join, act); + verify(from, times(0)).join(anyString(), any()); + } + + @Test + void testCreateJoinViaNavigation() { + final var cb = mock(CriteriaBuilder.class); + final var from = mock(From.class); + + final var path1 = mock(Join.class); + final var path2 = mock(Join.class); + final var join = mock(Join.class); + + final var complex1 = mock(JPAElement.class); + final var complex2 = mock(JPAElement.class); + final var target = mock(JPAElement.class); + + when(complex1.getInternalName()).thenReturn("first"); + when(complex2.getInternalName()).thenReturn("second"); + when(target.getInternalName()).thenReturn("target"); + + when(from.join(eq("first"), any(JoinType.class))).thenReturn(path1); + when(path1.join(eq("second"), any(JoinType.class))).thenReturn(path2); + when(path2.join("target", JoinType.LEFT)).thenReturn(join); + + when(hop.getPath()).thenReturn(Arrays.asList(complex1, complex2, target)); + + cut = new JPAProcessorSimpleAttributeImpl(path, Collections.singletonList(hop), true); + cut.setTarget(from, Collections.emptyMap(), cb); + final var act = cut.createJoin(); + assertEquals(join, act); + } + +// @Test +// void checkFromListOrderByOuterJoinOnConditionOne() throws ODataApplicationException, +// JPANoSelectionException { +// final List orderBy = new ArrayList<>(); +// buildRoleAssociationPath(orderBy); +// +// final Map> act = cut.createFromClause2(orderBy, new ArrayList<>(), cut.cq, null); +// +// @SuppressWarnings("unchecked") +// final Root root = (Root) act.get(jpaEntityType.getExternalFQN() +// .getFullQualifiedNameAsString()); +// final Set> joins = root.getJoins(); +// assertEquals(1, joins.size()); +// +// for (final Join join : joins) { +// assertNull(join.getOn()); +// } +// } + + @Test + @Override + void testCreateJoinThrowsExceptionIfSetTargetNotCalled() { + + cut = new JPAProcessorSimpleAttributeImpl(path, Collections.singletonList(hop), true); + assertThrows(IllegalAccessError.class, () -> cut.createJoin()); + } + + @Test + void testGetPath() { + final var cb = mock(CriteriaBuilder.class); + final var from = mock(From.class); + final var join = mock(Join.class); + final var target = mock(JPAElement.class); + when(target.getInternalName()).thenReturn("target"); + when(from.join("target", JoinType.LEFT)).thenReturn(join); + when(hop.getPath()).thenReturn(Collections.singletonList(target)); + + cut = new JPAProcessorSimpleAttributeImpl(path, Collections.singletonList(hop), true); + cut.setTarget(from, Collections.emptyMap(), cb); + assertNotNull(cut.getPath()); + } + + @Test + void testOrderByOfTransientThrowsException() { + final var cb = mock(CriteriaBuilder.class); + final var attribute = mock(JPAAttribute.class); + when(path.isTransient()).thenReturn(true); + when(path.getLeaf()).thenReturn(attribute); + when(attribute.toString()).thenReturn("Test"); + when(path.isPartOfGroups(any())).thenReturn(true); + cut = new JPAProcessorSimpleAttributeImpl(path, Collections.singletonList(hop), true); + final var act = assertThrows(ODataJPAQueryException.class, () -> cut.createOrderBy(cb, Collections.emptyList())); + assertEquals(400, act.getStatusCode()); + } + + @Test + void testOrderByNotInGroupsThrowsException() { + final var cb = mock(CriteriaBuilder.class); + final var attribute = mock(JPAAttribute.class); + when(path.isTransient()).thenReturn(false); + when(path.getLeaf()).thenReturn(attribute); + when(attribute.toString()).thenReturn("Test"); + when(path.isPartOfGroups(any())).thenReturn(false); + cut = new JPAProcessorSimpleAttributeImpl(path, Collections.singletonList(hop), true); + final var act = assertThrows(ODataJPAQueryException.class, () -> cut.createOrderBy(cb, Collections.emptyList())); + assertEquals(403, act.getStatusCode()); + } + + @Test + void testOrderByWithinGroupsOneGroup() throws ODataException { + + final var cb = mock(CriteriaBuilder.class); + final var attribute = mock(JPAAttribute.class); + final var groups = Collections.singletonList("Person"); + final var from = mock(From.class); + final var attributePath = mock(Path.class); + final var order = mock(Order.class); + when(path.isTransient()).thenReturn(false); + when(path.getLeaf()).thenReturn(attribute); + when(path.getPath()).thenReturn(Collections.singletonList(attribute)); + when(attribute.getInternalName()).thenReturn("test"); + when(path.isPartOfGroups(groups)).thenReturn(true); + when(from.get("test")).thenReturn(attributePath); + when(cb.desc(any())).thenReturn(order); + when(cb.asc(any())).thenReturn(order); + + cut = new JPAProcessorSimpleAttributeImpl(path, Collections.emptyList(), true); + cut.setTarget(from, Collections.emptyMap(), cb); + final var act = cut.createOrderBy(cb, groups); + assertNotNull(act); + } +} diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPAAbstractQueryTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPAAbstractQueryTest.java index 194adbc6c..fcbad63c1 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPAAbstractQueryTest.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPAAbstractQueryTest.java @@ -72,7 +72,7 @@ void testExtractOrderByNavigationAttributesThrowsExceptionIfNotSupported(final E final OrderByItem item = mock(OrderByItem.class); when(orderBy.getOrders()).thenReturn(Collections.singletonList(item)); when(item.getExpression()).thenReturn(orderByExpression); - assertThrows(ODataJPAQueryException.class, () -> cut.extractOrderByNavigationAttributes(orderBy)); + assertThrows(ODataJPAQueryException.class, () -> cut.getOrderByAttributes(orderBy)); } private static class Query extends JPAAbstractQuery { @@ -94,7 +94,7 @@ public From getRoot() { @Override protected Locale getLocale() { - throw new IllegalAccessError(); + return Locale.CANADA_FRENCH; } @Override diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPAOrderByBuilderTest.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPAOrderByBuilderTest.java index b3c253a4a..48e985a90 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPAOrderByBuilderTest.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/JPAOrderByBuilderTest.java @@ -2,7 +2,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; @@ -12,10 +11,13 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; -import java.util.Set; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.From; +import jakarta.persistence.criteria.Order; import org.apache.olingo.commons.api.edm.EdmNavigationProperty; import org.apache.olingo.commons.api.edm.EdmProperty; @@ -25,9 +27,7 @@ import org.apache.olingo.server.api.uri.UriInfo; import org.apache.olingo.server.api.uri.UriInfoResource; import org.apache.olingo.server.api.uri.UriResource; -import org.apache.olingo.server.api.uri.UriResourceComplexProperty; import org.apache.olingo.server.api.uri.UriResourceCount; -import org.apache.olingo.server.api.uri.UriResourceFunction; import org.apache.olingo.server.api.uri.UriResourceKind; import org.apache.olingo.server.api.uri.UriResourceNavigation; import org.apache.olingo.server.api.uri.UriResourcePrimitiveProperty; @@ -41,27 +41,17 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAnnotatable; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType; import com.sap.olingo.jpa.processor.core.api.JPAODataPage; -import com.sap.olingo.jpa.processor.core.exception.ODataJPANotImplementedException; -import com.sap.olingo.jpa.processor.core.exception.ODataJPAProcessorException; import com.sap.olingo.jpa.processor.core.exception.ODataJPAQueryException; +import com.sap.olingo.jpa.processor.core.properties.JPAOrderByPropertyFactory; +import com.sap.olingo.jpa.processor.core.properties.JPAProcessorAttribute; import com.sap.olingo.jpa.processor.core.testmodel.AdministrativeDivisionDescription; import com.sap.olingo.jpa.processor.core.testmodel.AssociationOneToOneSource; -import com.sap.olingo.jpa.processor.core.testmodel.AssociationOneToOneTarget; -import com.sap.olingo.jpa.processor.core.testmodel.BusinessPartnerRole; -import com.sap.olingo.jpa.processor.core.testmodel.BusinessPartnerWithGroups; -import com.sap.olingo.jpa.processor.core.testmodel.CollectionDeep; import com.sap.olingo.jpa.processor.core.testmodel.Organization; import com.sap.olingo.jpa.processor.core.testmodel.Person; import com.sap.olingo.jpa.processor.core.util.TestBase; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.From; -import jakarta.persistence.criteria.Order; -import jakarta.persistence.criteria.Path; - class JPAOrderByBuilderTest extends TestBase { private JPAOrderByBuilder cut; private Map> joinTables; @@ -70,24 +60,24 @@ class JPAOrderByBuilderTest extends TestBase { private SkipOption skip; private OrderByOption orderBy; private From adminTarget; - private From orgTarget; + private From organizationTarget; private From toOneSourceTarget; private CriteriaBuilder cb; private JPAEntityType jpaAdminEntity; - private JPAEntityType jpaOrgEntity; + private JPAEntityType jpaOrganizationEntity; private JPAEntityType toOneSourceEntity; private List groups; private JPAODataPage page; - private Set> orderByPaths; + private List orderByAttributes; @BeforeEach void setup() throws ODataException { cb = emf.getCriteriaBuilder(); jpaAdminEntity = getHelper().getJPAEntityType(AdministrativeDivisionDescription.class); adminTarget = cb.createQuery().from(getHelper().getEntityType(AdministrativeDivisionDescription.class)); - jpaOrgEntity = getHelper().getJPAEntityType(Organization.class); + jpaOrganizationEntity = getHelper().getJPAEntityType(Organization.class); toOneSourceEntity = getHelper().getJPAEntityType(AssociationOneToOneSource.class); - orgTarget = cb.createQuery().from(getHelper().getEntityType(Organization.class)); + organizationTarget = cb.createQuery().from(getHelper().getEntityType(Organization.class)); toOneSourceTarget = cb.createQuery().from(getHelper().getEntityType(AssociationOneToOneSource.class)); groups = new ArrayList<>(); @@ -96,23 +86,23 @@ void setup() throws ODataException { skip = mock(SkipOption.class); orderBy = mock(OrderByOption.class); uriResource = mock(UriInfoResource.class); - page = null; + page = mock(JPAODataPage.class); joinTables = new HashMap<>(); - orderByPaths = new HashSet<>(); + orderByAttributes = new ArrayList<>(); + when(page.top()).thenReturn(Integer.MAX_VALUE); } @Test void testNoTopSkipOrderByReturnsEmptyList() throws ODataException { - final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); + final List act = cut.createOrderByList(joinTables, orderByAttributes, page); assertEquals(0, act.size()); } @Test void testTopReturnsByPrimaryKey() throws ODataException { - when(uriResource.getTopOption()).thenReturn(top); - when(top.getValue()).thenReturn(5); + when(page.top()).thenReturn(5); - final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); + final List act = cut.createOrderByList(joinTables, orderByAttributes, page); assertEquals(4, act.size()); assertEquals(4, act.stream() @@ -123,10 +113,8 @@ void testTopReturnsByPrimaryKey() throws ODataException { @Test void testSkipReturnsByPrimaryKey() throws ODataException { - when(uriResource.getSkipOption()).thenReturn(skip); - when(skip.getValue()).thenReturn(5); - - final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); + when(page.skip()).thenReturn(5); + final List act = cut.createOrderByList(joinTables, orderByAttributes, page); assertEquals(4, act.size()); assertEquals(4, act.stream() @@ -137,12 +125,10 @@ void testSkipReturnsByPrimaryKey() throws ODataException { @Test void testTopSkipReturnsByPrimaryKey() throws ODataException { - when(uriResource.getTopOption()).thenReturn(top); - when(top.getValue()).thenReturn(5); - when(uriResource.getSkipOption()).thenReturn(skip); - when(skip.getValue()).thenReturn(5); + when(page.top()).thenReturn(5); + when(page.skip()).thenReturn(5); - final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); + final List act = cut.createOrderByList(joinTables, orderByAttributes, page); assertEquals(4, act.size()); assertEquals(4, act.stream() @@ -151,60 +137,19 @@ void testTopSkipReturnsByPrimaryKey() throws ODataException { assertOrder(act); } - @Test - void testOrderByEmptyReturnsEmptyList() throws ODataApplicationException { - when(uriResource.getOrderByOption()).thenReturn(orderBy); - when(orderBy.getOrders()).thenReturn(Collections.emptyList()); - final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); - assertEquals(0, act.size()); - } - @Test void testOrderByOneProperty() throws ODataApplicationException { createOrderByItem("Name"); - when(uriResource.getOrderByOption()).thenReturn(orderBy); - final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); + final List act = cut.createOrderByList(joinTables, orderByAttributes, page); assertEquals(1, act.size()); assertFalse(act.get(0).isAscending()); } - @Test - void testOrderByOneComplexProperty() throws ODataApplicationException { - cut = new JPAOrderByBuilder(jpaOrgEntity, orgTarget, cb, groups); - createComplexOrderByItem(); - when(uriResource.getOrderByOption()).thenReturn(orderBy); - final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); - assertEquals(1, act.size()); - assertFalse(act.get(0).isAscending()); - } - - @Test - void testThrowsNotImplementedOnOrderByFunction() { - final List pathParts = createOrderByClause(null); - final UriResourceFunction part = mock(UriResourceFunction.class); - pathParts.add(part); - when(part.getKind()).thenReturn(UriResourceKind.function); - - assertThrows(ODataJPANotImplementedException.class, - () -> cut.createOrderByList(joinTables, uriResource, page, orderByPaths)); - } - @Test void testOrderByNavigationCountDefault() throws ODataException { - cut = new JPAOrderByBuilder(jpaOrgEntity, orgTarget, cb, groups); - final List pathParts = createOrderByClause(null); - final UriResourceNavigation navigationPart = mock(UriResourceNavigation.class); - final UriResourceCount countPart = mock(UriResourceCount.class); - final EdmNavigationProperty navigationProperty = mock(EdmNavigationProperty.class); - pathParts.add(navigationPart); - pathParts.add(countPart); - - when(navigationPart.getProperty()).thenReturn(navigationProperty); - when(navigationPart.isCollection()).thenReturn(true); - when(navigationProperty.getName()).thenReturn("Roles"); - joinTables.put("Roles", cb.createQuery().from(getHelper().getEntityType(BusinessPartnerRole.class))); - - final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); + createCountOrderByItem(false); + cut = new JPAOrderByBuilder(jpaOrganizationEntity, organizationTarget, cb, groups); + final List act = cut.createOrderByList(joinTables, orderByAttributes, page); assertEquals(1, act.size()); assertTrue(act.get(0).isAscending()); @@ -213,101 +158,15 @@ void testOrderByNavigationCountDefault() throws ODataException { @Test void testOrderByNavigationCountDescending() throws ODataException { - cut = new JPAOrderByBuilder(jpaOrgEntity, orgTarget, cb, groups); - final List pathParts = createOrderByClause(Boolean.TRUE); - final UriResourceNavigation navigationPart = mock(UriResourceNavigation.class); - final UriResourceCount countPart = mock(UriResourceCount.class); - final EdmNavigationProperty navigationProperty = mock(EdmNavigationProperty.class); - pathParts.add(navigationPart); - pathParts.add(countPart); - - when(navigationPart.getProperty()).thenReturn(navigationProperty); - when(navigationPart.isCollection()).thenReturn(true); - when(navigationProperty.getName()).thenReturn("Roles"); - joinTables.put("Roles", cb.createQuery().from(getHelper().getEntityType(BusinessPartnerRole.class))); - - final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); + createCountOrderByItem(true); + cut = new JPAOrderByBuilder(jpaOrganizationEntity, organizationTarget, cb, groups); + final List act = cut.createOrderByList(joinTables, orderByAttributes, page); assertEquals(1, act.size()); assertFalse(act.get(0).isAscending()); assertEquals("COUNT", ((FunctionExpressionImpl) act.get(0).getExpression()).getOperation()); } - @Test - void testOrderByCollectionOrderByCountAsc() throws ODataException { - final JPAEntityType jpaEntity = getHelper().getJPAEntityType(CollectionDeep.class); - final From target = cb.createQuery().from(getHelper().getEntityType(CollectionDeep.class)); - cut = new JPAOrderByBuilder(jpaEntity, target, cb, groups); - - final List pathParts = createOrderByClause(Boolean.FALSE); - final UriResourceProperty firstLevelPart = mock(UriResourceComplexProperty.class); - final UriResourceProperty secondLevelPart = mock(UriResourceComplexProperty.class); - final UriResourceProperty commentPart = mock(UriResourceProperty.class); - final UriResourceCount countPart = mock(UriResourceCount.class); - when(commentPart.isCollection()).thenReturn(Boolean.TRUE); - pathParts.add(firstLevelPart); - pathParts.add(secondLevelPart); - pathParts.add(commentPart); - pathParts.add(countPart); - - createComplexEdmProperty(firstLevelPart, "FirstLevel"); - createComplexEdmProperty(secondLevelPart, "SecondLevel"); - createPrimitiveEdmProperty(commentPart, "Comment"); - joinTables.put("FirstLevel/SecondLevel/Comment", - cb.createQuery().from(getHelper().getEntityType(BusinessPartnerRole.class))); - - final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); - - assertEquals(1, act.size()); - assertTrue(act.get(0).isAscending()); - assertEquals("COUNT", ((FunctionExpressionImpl) act.get(0).getExpression()).getOperation()); - } - - @Test - void testThrowsBadRequestExceptionOnUnknownProperty() { - createOrderByItem("Name"); - when(uriResource.getOrderByOption()).thenReturn(orderBy); - cut = new JPAOrderByBuilder(jpaOrgEntity, orgTarget, cb, groups); - assertThrows(ODataJPAProcessorException.class, - () -> cut.createOrderByList(joinTables, uriResource, page, orderByPaths)); - } - - @Test - void testThrowsBadRequestExceptionOnUnknownComplex() { - createComplexOrderByItem(); - when(uriResource.getOrderByOption()).thenReturn(orderBy); - cut = new JPAOrderByBuilder(jpaAdminEntity, adminTarget, cb, groups); - assertThrows(ODataJPAProcessorException.class, - () -> cut.createOrderByList(joinTables, uriResource, page, orderByPaths)); - } - - @Test - void testThrowExceptionOrderByGroupedPropertyWithoutGroup() throws ODataException { - final JPAEntityType jpaEntity = getHelper().getJPAEntityType(BusinessPartnerWithGroups.class); - final From target = cb.createQuery().from(getHelper().getEntityType(BusinessPartnerWithGroups.class)); - cut = new JPAOrderByBuilder(jpaEntity, target, cb, groups); - createOrderByItem("Country"); - - cut = new JPAOrderByBuilder(jpaEntity, target, cb, groups); - final ODataJPAQueryException act = assertThrows(ODataJPAQueryException.class, - () -> cut.createOrderByList(joinTables, uriResource, page, orderByPaths)); - assertEquals(HttpStatusCode.FORBIDDEN.getStatusCode(), act.getStatusCode()); - } - - @Test - void testOrderByPropertyWithGroupsOneGroup() throws ODataException { - final JPAEntityType jpaEntity = getHelper().getJPAEntityType(BusinessPartnerWithGroups.class); - final From target = cb.createQuery().from(getHelper().getEntityType(BusinessPartnerWithGroups.class)); - groups.add("Person"); - cut = new JPAOrderByBuilder(jpaEntity, target, cb, groups); - createOrderByItem("Country"); - - final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); - - assertEquals(1, act.size()); - assertFalse(act.get(0).isAscending()); - } - @Test void testOrderByPropertyAndTop() throws ODataException { createOrderByItem("DivisionCode"); @@ -315,7 +174,7 @@ void testOrderByPropertyAndTop() throws ODataException { when(uriResource.getOrderByOption()).thenReturn(orderBy); when(uriResource.getTopOption()).thenReturn(top); - final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); + final List act = cut.createOrderByList(joinTables, orderByAttributes, page); assertFalse(act.isEmpty()); assertEquals(String.class, act.get(0).getExpression().getJavaType()); @@ -327,18 +186,18 @@ void testThrowExceptionOrderByTransientPrimitiveSimpleProperty() throws ODataExc final JPAEntityType jpaEntity = getHelper().getJPAEntityType(Person.class); final From target = cb.createQuery().from(getHelper().getEntityType(Person.class)); cut = new JPAOrderByBuilder(jpaEntity, target, cb, groups); - createOrderByItem("FullName"); + createOrderByItem("FullName", jpaEntity, target); final ODataJPAQueryException act = assertThrows(ODataJPAQueryException.class, - () -> cut.createOrderByList(joinTables, uriResource, page, orderByPaths)); - assertEquals(HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), act.getStatusCode()); + () -> cut.createOrderByList(joinTables, orderByAttributes, page)); + assertEquals(HttpStatusCode.BAD_REQUEST.getStatusCode(), act.getStatusCode()); } @Test void testPagePresentOnlyTopValue() throws ODataException { page = new JPAODataPage(null, 0, 10, skip); - final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); + final List act = cut.createOrderByList(joinTables, orderByAttributes, page); assertEquals(4, act.size()); assertEquals(4, act.stream() @@ -351,7 +210,7 @@ void testPagePresentOnlyTopValue() throws ODataException { void testPagePresentMaxTopValueNoOrdering() throws ODataException { page = new JPAODataPage(null, 0, Integer.MAX_VALUE, skip); - final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); + final List act = cut.createOrderByList(joinTables, orderByAttributes, page); assertEquals(0, act.size()); } @@ -360,7 +219,7 @@ void testPagePresentMaxTopValueNoOrdering() throws ODataException { void testPagePresentOnlySkipValue() throws ODataException { page = new JPAODataPage(null, 10, 0, null); - final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); + final List act = cut.createOrderByList(joinTables, orderByAttributes, page); assertEquals(4, act.size()); assertEquals(4, act.stream() @@ -375,7 +234,7 @@ void testPageAndTopPresent() throws ODataException { when(top.getValue()).thenReturn(5); when(uriResource.getTopOption()).thenReturn(top); - final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); + final List act = cut.createOrderByList(joinTables, orderByAttributes, page); assertEquals(4, act.size()); assertEquals(4, act.stream() @@ -390,7 +249,7 @@ void testPageMaxTopValueAndTopPresent() throws ODataException { when(top.getValue()).thenReturn(5); when(uriResource.getTopOption()).thenReturn(top); - final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); + final List act = cut.createOrderByList(joinTables, orderByAttributes, page); assertEquals(4, act.size()); assertEquals(4, act.stream() @@ -405,7 +264,7 @@ void testOrderByPropertyAndPage() throws ODataException { page = new JPAODataPage(null, 10, 0, null); when(uriResource.getOrderByOption()).thenReturn(orderBy); - final List act = cut.createOrderByList(joinTables, uriResource, page, orderByPaths); + final List act = cut.createOrderByList(joinTables, orderByAttributes, page); assertFalse(act.isEmpty()); assertEquals(String.class, act.get(0).getExpression().getJavaType()); @@ -413,47 +272,6 @@ void testOrderByPropertyAndPage() throws ODataException { assertEquals(5, act.size()); } - @Test - void testOrderByNavigationToOne() throws ODataException { - final JPAAnnotatable annotatable = getHelper().sd.getEntitySet(toOneSourceEntity); - final OrderByItem order = mock(OrderByItem.class); - final Member member = mock(Member.class); - final UriInfoResource orderResourceInfo = mock(UriInfoResource.class); - final UriResourceNavigation navigationProperty = mock(UriResourceNavigation.class); - final EdmNavigationProperty edmNavigationProperty = mock(EdmNavigationProperty.class); - final UriResourcePrimitiveProperty property = mock(UriResourcePrimitiveProperty.class); - final EdmProperty edmProperty = mock(EdmProperty.class); - final From orderByFrom = cb.createQuery().from(getHelper().getEntityType(AssociationOneToOneTarget.class)); - - when(uriResource.getOrderByOption()).thenReturn(orderBy); - - when(orderBy.getText()).thenReturn("ColumnTarget/Source"); - when(orderBy.getOrders()).thenReturn(Collections.singletonList(order)); - when(order.getExpression()).thenReturn(member); - when(member.getResourcePath()).thenReturn(orderResourceInfo); - when(orderResourceInfo.getUriResourceParts()).thenReturn(Arrays.asList(navigationProperty, property)); - - when(navigationProperty.isCollection()).thenReturn(false); - when(navigationProperty.getProperty()).thenReturn(edmNavigationProperty); - when(edmNavigationProperty.getName()).thenReturn("ColumnTarget"); - - when(property.getProperty()).thenReturn(edmProperty); - when(edmProperty.getName()).thenReturn("Source"); - - joinTables.put("AssociationOneToOneSource", toOneSourceTarget); - joinTables.put("ColumnTarget", orderByFrom); - - cut = new JPAOrderByBuilder(annotatable, toOneSourceEntity, toOneSourceTarget, cb, Collections.emptyList()); - final var act = cut.createOrderByList(joinTables, uriResource, null, orderByPaths); - assertNotNull(act); - assertEquals(1, act.size()); - assertEquals(1, orderByPaths.size()); - assertTrue(orderByPaths.stream() - .filter(p -> p.getAlias().equals("Source")) - .findFirst() - .isPresent()); - } - private List createOrderByClause(final Boolean isDescending) { final OrderByItem item = mock(OrderByItem.class); final Member expression = mock(Member.class); @@ -471,26 +289,42 @@ private List createOrderByClause(final Boolean isDescending) { } private void createOrderByItem(final String externalName) { + createOrderByItem(externalName, jpaAdminEntity, adminTarget); + } + + private void createOrderByItem(final String externalName, final JPAEntityType jpaEntity, final From target) { final List pathParts = createOrderByClause(Boolean.TRUE); final UriResourceProperty part = mock(UriResourcePrimitiveProperty.class); - + when(part.getKind()).thenReturn(UriResourceKind.primitiveProperty); pathParts.add(part); - createPrimitiveEdmProperty(part, externalName); + final var attribute = new JPAOrderByPropertyFactory().createProperty(orderBy.getOrders().get(0), jpaEntity, + Locale.ENGLISH); + attribute.setTarget(target, joinTables, cb); + orderByAttributes.add(attribute); } - private void createComplexOrderByItem() { + private void createCountOrderByItem(final boolean descending) { - final List pathParts = createOrderByClause(Boolean.TRUE); - final UriResourceProperty complexPart = mock(UriResourceComplexProperty.class); - final UriResourceProperty primitivePart = mock(UriResourcePrimitiveProperty.class); + final OrderByItem item = mock(OrderByItem.class); + final var navigation = mock(UriResourceNavigation.class); + final var count = mock(UriResourceCount.class); + final var edmNavigation = mock(EdmNavigationProperty.class); + final var uriInfo = mock(UriInfo.class); + final Member expression = mock(Member.class); + when(uriInfo.getUriResourceParts()).thenReturn(Arrays.asList(navigation, count)); + when(navigation.getProperty()).thenReturn(edmNavigation); + when(edmNavigation.getName()).thenReturn("Roles"); + when(count.getKind()).thenReturn(UriResourceKind.count); + when(item.isDescending()).thenReturn(descending); + when(item.getExpression()).thenReturn(expression); + when(expression.getResourcePath()).thenReturn(uriInfo); - pathParts.add(complexPart); - pathParts.add(primitivePart); + final var attribute = new JPAOrderByPropertyFactory().createProperty(item, jpaOrganizationEntity, Locale.ENGLISH); + attribute.setTarget(organizationTarget, joinTables, cb); + orderByAttributes.add(attribute); - createComplexEdmProperty(complexPart, "Address"); - createPrimitiveEdmProperty(primitivePart, "Region"); } private void createPrimitiveEdmProperty(final UriResourceProperty primitivePart, final String name) { diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAExpandQueryCreateResult.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAExpandQueryCreateResult.java index 7d46f33fe..48d802126 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAExpandQueryCreateResult.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAExpandQueryCreateResult.java @@ -46,7 +46,7 @@ void setup() throws ODataException, ODataJPAIllegalAccessException { createHeaders(); final EdmEntityType targetEntity = new EdmEntityTypeDouble(nameBuilder, "BusinessPartnerRole"); sessionContext = new JPAODataContextAccessDouble(new JPAEdmProvider(PUNIT_NAME, emf, null, - TestBase.enumPackages), dataSource, null, null); + TestBase.enumPackages), dataSource, null, null, null); final JPAODataRequestContext externalContext = mock(JPAODataRequestContext.class); when(externalContext.getEntityManager()).thenReturn(emf.createEntityManager()); diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAProcessorExpand.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAProcessorExpand.java index c9bce4f88..f5ed5cae8 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAProcessorExpand.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAProcessorExpand.java @@ -355,6 +355,21 @@ void testExpandWithOrderByDescTopSkip() throws IOException, ODataException { assertEquals("BE23", children.get(0).get("DivisionCode").asText()); } + @Test + void testExpandWithOrderByDescriptionProperty() throws IOException, ODataException { + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, + "Persons?$select=ID&$expand=SupportedOrganizations($select=ID,LocationName;$orderby=LocationName desc)"); + helper.assertStatus(200); + } + + @Test + void testExpandWithOrderByToOneDescriptionProperty() throws IOException, ODataException { + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, + "Organizations?$expand=Roles($orderby=Organization/LocationName desc)"); + helper.assertStatus(200); + + } + @Test void testExpandWithCount() throws IOException, ODataException { final IntegrationTestHelper helper = new IntegrationTestHelper(emf, @@ -372,9 +387,10 @@ void testExpandWithCount() throws IOException, ODataException { @Test void testExpandWithCount2Level() throws IOException, ODataException { final IntegrationTestHelper helper = new IntegrationTestHelper(emf, - "AdministrativeDivisions?$count=true" - + "&$expand=Children($count=true;$expand=Children($count=true))" - + "&$filter=CodeID eq 'NUTS1' and startswith(DivisionCode,'BE')"); + """ + AdministrativeDivisions?$count=true\ + &$expand=Children($count=true;$expand=Children($count=true))\ + &$filter=CodeID eq 'NUTS1' and startswith(DivisionCode,'BE')"""); helper.assertStatus(200); final ArrayNode grands = helper.getValues(); diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryBuildSelectionPathList.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryBuildSelectionPathList.java index 3b263b031..633fa8b98 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryBuildSelectionPathList.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryBuildSelectionPathList.java @@ -55,7 +55,7 @@ void setup() throws ODataException, ODataJPAIllegalAccessException { nameBuilder = new JPADefaultEdmNameBuilder(PUNIT_NAME); createHeaders(); sessionContext = new JPAODataContextAccessDouble(new JPAEdmProvider(PUNIT_NAME, emf, null, TestBase.enumPackages), - dataSource, null, null); + dataSource, null, null, null); odata = mock(OData.class); final JPAODataRequestContext externalContext = mock(JPAODataRequestContext.class); when(externalContext.getEntityManager()).thenReturn(emf.createEntityManager()); diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryFromClause.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryFromClause.java index 727d75de0..896e3b382 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryFromClause.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryFromClause.java @@ -2,8 +2,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -12,12 +12,9 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; import jakarta.persistence.criteria.From; import jakarta.persistence.criteria.Join; -import jakarta.persistence.criteria.JoinType; -import jakarta.persistence.criteria.Root; import org.apache.olingo.commons.api.edm.EdmEntitySet; import org.apache.olingo.commons.api.edm.EdmEntityType; @@ -35,7 +32,6 @@ import com.sap.olingo.jpa.metadata.api.JPAEdmProvider; import com.sap.olingo.jpa.metadata.api.JPARequestParameterMap; -import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAssociationPath; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAAttribute; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAEntityType; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAPath; @@ -46,7 +42,8 @@ import com.sap.olingo.jpa.processor.core.api.JPAODataSessionContextAccess; import com.sap.olingo.jpa.processor.core.exception.ODataJPAIllegalAccessException; import com.sap.olingo.jpa.processor.core.processor.JPAODataInternalRequestContext; -import com.sap.olingo.jpa.processor.core.testmodel.Organization; +import com.sap.olingo.jpa.processor.core.properties.JPAProcessorAttribute; +import com.sap.olingo.jpa.processor.core.properties.JPAProcessorSimpleAttribute; import com.sap.olingo.jpa.processor.core.util.TestBase; import com.sap.olingo.jpa.processor.core.util.TestHelper; @@ -78,7 +75,7 @@ void setup() throws ODataException, ODataJPAIllegalAccessException { helper = new TestHelper(emf, PUNIT_NAME); jpaEntityType = helper.getJPAEntityType("Organizations"); sessionContext = new JPAODataContextAccessDouble(new JPAEdmProvider(PUNIT_NAME, emf, null, TestBase.enumPackages), - dataSource, null, null); + dataSource, null, null, null); createHeaders(); final JPAODataRequestContext externalContext = mock(JPAODataRequestContext.class); when(externalContext.getEntityManager()).thenReturn(emf.createEntityManager()); @@ -98,57 +95,19 @@ void checkFromListContainsRoot() throws ODataApplicationException, JPANoSelectio } @Test - void checkFromListOrderByContainsOne() throws ODataJPAModelException, ODataApplicationException, + void checkFromListOrderByContainsOne() throws ODataApplicationException, JPANoSelectionException { - final List orderBy = new ArrayList<>(); - final JPAAssociationPath exp = buildRoleAssociationPath(orderBy); + final List orderBy = new ArrayList<>(); + final JPAProcessorAttribute exp = buildRoleAssociationPath(orderBy); final Map> act = cut.createFromClause(orderBy, new ArrayList<>(), cut.cq, null); assertNotNull(act.get(exp.getAlias())); } - @Test - void checkFromListOrderByOuterJoinOne() throws ODataJPAModelException, ODataApplicationException, - JPANoSelectionException { - final List orderBy = new ArrayList<>(); - buildRoleAssociationPath(orderBy); - - final Map> act = cut.createFromClause(orderBy, new ArrayList<>(), cut.cq, null); - - @SuppressWarnings("unchecked") - final Root root = (Root) act.get(jpaEntityType.getExternalFQN() - .getFullQualifiedNameAsString()); - final Set> joins = root.getJoins(); - assertEquals(1, joins.size()); - - for (final Join join : joins) { - assertEquals(JoinType.LEFT, join.getJoinType()); - } - } - - @Test - void checkFromListOrderByOuterJoinOnConditionOne() throws ODataJPAModelException, ODataApplicationException, - JPANoSelectionException { - final List orderBy = new ArrayList<>(); - buildRoleAssociationPath(orderBy); - - final Map> act = cut.createFromClause(orderBy, new ArrayList<>(), cut.cq, null); - - @SuppressWarnings("unchecked") - final Root root = (Root) act.get(jpaEntityType.getExternalFQN() - .getFullQualifiedNameAsString()); - final Set> joins = root.getJoins(); - assertEquals(1, joins.size()); - - for (final Join join : joins) { - assertNull(join.getOn()); - } - } - @Test void checkFromListDescriptionAssociationAllFields() throws ODataApplicationException, ODataJPAModelException, JPANoSelectionException { - final List orderBy = new ArrayList<>(); + final List orderBy = new ArrayList<>(); final List descriptionPathList = new ArrayList<>(); final JPAEntityType entity = helper.getJPAEntityType("Organizations"); descriptionPathList.add(entity.getPath("Address/CountryName")); @@ -158,13 +117,13 @@ void checkFromListDescriptionAssociationAllFields() throws ODataApplicationExcep final Map> act = cut.createFromClause(orderBy, descriptionPathList, cut.cq, null); assertEquals(2, act.size()); - assertNotNull(act.get(exp.getInternalName())); + assertNotNull(act.get("Address/CountryName")); } @Test void checkFromListDescriptionAssociationAllFields2() throws ODataApplicationException, ODataJPAModelException, JPANoSelectionException { - final List orderBy = new ArrayList<>(); + final List orderBy = new ArrayList<>(); final List descriptionPathList = new ArrayList<>(); final JPAEntityType entity = helper.getJPAEntityType("Organizations"); descriptionPathList.add(entity.getPath("Address/RegionName")); @@ -174,7 +133,7 @@ void checkFromListDescriptionAssociationAllFields2() throws ODataApplicationExce final Map> act = cut.createFromClause(orderBy, descriptionPathList, cut.cq, null); assertEquals(2, act.size()); - assertNotNull(act.get(exp.getInternalName())); + assertNotNull(act.get("Address/RegionName")); } @Test @@ -247,11 +206,17 @@ private JPAODataInternalRequestContext buildRequestContextToTestGroups(final JPA return requestContext; } - private JPAAssociationPath buildRoleAssociationPath(final List orderBy) - throws ODataJPAModelException { - final JPAAssociationPath exp = helper.getJPAAssociationPath("Organizations", "Roles"); - orderBy.add(exp); - return exp; + @SuppressWarnings("unchecked") + private JPAProcessorAttribute buildRoleAssociationPath(final List orderBy) { + + final var attribute = mock(JPAProcessorSimpleAttribute.class); + final var join = mock(Join.class); + when(attribute.requiresJoin()).thenReturn(true); + when(attribute.getAlias()).thenReturn("Roles"); + when(attribute.createJoin()).thenReturn(join); + when(attribute.setTarget(any(), any(), any())).thenReturn(attribute); + orderBy.add(attribute); + return attribute; } } diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryOrderByClause.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryOrderByClause.java index be91fd1c5..091e77554 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryOrderByClause.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryOrderByClause.java @@ -145,7 +145,7 @@ void testOrderByTwoPropertiesDescDesc() throws IOException, ODataException { void testOrderBy$CountAsc() throws IOException, ODataException { final IntegrationTestHelper helper = new IntegrationTestHelper(emf, - "Organizations?$orderby=Roles/$count asc"); + "Organizations?$select=ID&$orderby=Roles/$count asc"); helper.assertStatus(200); final ArrayNode orgs = helper.getValues(); @@ -247,7 +247,7 @@ void testOrderByOnTransientPrimitiveSimpleProperty() throws IOException, ODataEx final JPAODataGroupsProvider groups = new JPAODataGroupsProvider(); final IntegrationTestHelper helper = new IntegrationTestHelper(emf, "Persons?$orderby=FullName", groups); - helper.assertStatus(501); + helper.assertStatus(400); } @Test @@ -256,7 +256,7 @@ void testOrderByOnTransientSimpleComplexPartProperty() throws IOException, OData final JPAODataGroupsProvider groups = new JPAODataGroupsProvider(); final IntegrationTestHelper helper = new IntegrationTestHelper(emf, "Persons?$select=ID&$orderby=Address/Street", groups); - helper.assertStatus(501); + helper.assertStatus(400); } @Test @@ -264,7 +264,7 @@ void testOrderByOnTransientCollectionProperty() throws IOException, ODataExcepti final IntegrationTestHelper helper = new IntegrationTestHelper(emf, "CollectionDeeps?$orderby=FirstLevel/TransientCollection/$count asc"); - helper.assertStatus(501); + helper.assertStatus(400); } @Test @@ -275,6 +275,14 @@ void testOrderByToOneProperty() throws IOException, ODataException { helper.assertStatus(200); } + @Test + void testOrderByToOnePropertyViaComplex() throws IOException, ODataException { + + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, + "Persons?$select=ID&$orderby=AdministrativeInformation/Created/User/LastName asc"); + helper.assertStatus(200); + } + @Test void testOrderByToOnePropertyWithCollectionProperty() throws IOException, ODataException { @@ -282,4 +290,33 @@ void testOrderByToOnePropertyWithCollectionProperty() throws IOException, ODataE "BusinessPartnerRoles?$expand=BusinessPartner($expand=Roles;$select=ID)&$orderby=BusinessPartner/Country asc"); helper.assertStatus(200); } + + @Test + void testOrderByToTwoPropertyWithCollectionProperty() throws IOException, ODataException { + + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, + "BusinessPartnerRoles?$orderby=BusinessPartner/Country asc,BusinessPartner/ID asc"); + helper.assertStatus(200); + } + + @Test + void testOrderByDescriptionProperty() throws IOException, ODataException { + + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, + "Organizations?$select=ID&$orderby=LocationName asc"); + helper.assertStatus(200); + + final ArrayNode orgs = helper.getValues(); + assertEquals(10, orgs.size()); + assertEquals("10", orgs.get(0).get("ID").asText()); + } + + @Test + void testOrderByDescriptionViaToOneProperty() throws IOException, ODataException { + + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, + "BusinessPartnerRoles?$orderby=Organization/LocationName desc"); + helper.assertStatus(200); + } + } diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQuerySelectClause.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQuerySelectClause.java index 15469761e..8e1b6a4d5 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQuerySelectClause.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQuerySelectClause.java @@ -77,7 +77,7 @@ void checkSelectAllWithSelectionNull() throws ODataApplicationException, ODataJP } @Test - void checkSelectExpandViaIgnoredProperties() throws ODataApplicationException, ODataJPAModelException { + void checkSelectExpandViaIgnoredProperties() throws ODataApplicationException { // Organizations('3')/Address?$expand=AdministrativeDivision fillJoinTable(root); final List expItems = new ArrayList<>(); @@ -102,7 +102,7 @@ void checkSelectExpandViaIgnoredProperties() throws ODataApplicationException, O } @Test - void checkSelectOnePropertyCreatedAt() throws ODataApplicationException, ODataJPAModelException { + void checkSelectOnePropertyCreatedAt() throws ODataApplicationException { final List> selectClause = cut.createSelectClause(joinTables, cut.buildSelectionPathList( new UriInfoDouble(new SelectOptionDouble("CreationDateTime"))).joinedPersistent(), root, Collections .emptyList()); @@ -113,7 +113,7 @@ void checkSelectOnePropertyCreatedAt() throws ODataApplicationException, ODataJP } @Test - void checkSelectOnePropertyID() throws ODataApplicationException, ODataJPAModelException { + void checkSelectOnePropertyID() throws ODataApplicationException { final List> selectClause = cut.createSelectClause(joinTables, cut.buildSelectionPathList( new UriInfoDouble(new SelectOptionDouble("ID"))).joinedPersistent(), root, Collections.emptyList()); assertEquals(2, selectClause.size()); @@ -142,7 +142,7 @@ void checkSelectOnePropertyPartKey() throws ODataException, ODataJPAIllegalAcces } @Test - void checkSelectPropertyTypeCreatedAt() throws ODataApplicationException, ODataJPAModelException { + void checkSelectPropertyTypeCreatedAt() throws ODataApplicationException { final List> selectClause = cut.createSelectClause(joinTables, cut.buildSelectionPathList( new UriInfoDouble(new SelectOptionDouble("Type,CreationDateTime"))).joinedPersistent(), root, Collections .emptyList()); @@ -417,7 +417,7 @@ private void assertContains(final List> selectClause, final String fail(alias + " not found"); } - private class UriResourceValueDouble implements UriResourceValue { + private static class UriResourceValueDouble implements UriResourceValue { @Override public UriResourceKind getKind() { @@ -430,7 +430,7 @@ public String getSegmentValue() { } } - private class UriResourceComplexPropertyDouble implements UriResourceComplexProperty { + private static class UriResourceComplexPropertyDouble implements UriResourceComplexProperty { private final EdmProperty property; public UriResourceComplexPropertyDouble(final EdmProperty property) { @@ -493,7 +493,7 @@ public EdmComplexType getComplexTypeFilter() { } - private class UriResourceEntitySetDouble implements UriResourceEntitySet { + private static class UriResourceEntitySetDouble implements UriResourceEntitySet { @Override public EdmType getType() { diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryTopSkip.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryTopSkip.java index 32e587996..203143d9e 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryTopSkip.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAQueryTopSkip.java @@ -108,6 +108,7 @@ void testTopReturnsAllIfToLarge() throws IOException, ODataException { assertNull(value.get("@odata.nextLink")); } + @Tag(Assertions.CB_ONLY_TEST) @Test void testExpandTopSkipWithoutError() throws IOException, ODataException { diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAServerDrivenPaging.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAServerDrivenPaging.java index d2cfb0d6c..ae48a3f1b 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAServerDrivenPaging.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestJPAServerDrivenPaging.java @@ -328,6 +328,7 @@ private UriInfo buildUriInfo() throws EdmPrimitiveTypeException { when(orderExpression.getResourcePath()).thenReturn(orderResourcePath); when(orderResourcePath.getUriResourceParts()).thenReturn(orderResourcePathItems); when(orderResourcePathItem.getProperty()).thenReturn(orderProperty); + when(orderResourcePathItem.getKind()).thenReturn(UriResourceKind.primitiveProperty); when(orderProperty.getName()).thenReturn("ID"); when(order.getOrders()).thenReturn(orderItems); final List resourceParts = new ArrayList<>(); diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestTopSkipCountOnDerby.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestTopSkipCountOnDerby.java new file mode 100644 index 000000000..8869a4e8c --- /dev/null +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/query/TestTopSkipCountOnDerby.java @@ -0,0 +1,86 @@ +package com.sap.olingo.jpa.processor.core.query; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; + +import javax.sql.DataSource; + +import jakarta.persistence.EntityManagerFactory; + +import org.apache.olingo.commons.api.ex.ODataException; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.sap.olingo.jpa.metadata.api.JPAEntityManagerFactory; +import com.sap.olingo.jpa.processor.core.database.JPADerbySqlPatternProvider; +import com.sap.olingo.jpa.processor.core.testmodel.DataSourceHelper; +import com.sap.olingo.jpa.processor.core.util.IntegrationTestHelper; + +/** + * Some databases, like derby do not support LIMIT OFFSET and return an Integer on $count + *

+ *

+ * 2024-06-30 + */ +class TestTopSkipCountOnDerby { + private static final String PUNIT_NAME = "com.sap.olingo.jpa"; + private static EntityManagerFactory emf; + private static DataSource dataSource; + + @BeforeAll + public static void setupClass() { + dataSource = DataSourceHelper.createDataSource(DataSourceHelper.DB_DERBY); + emf = JPAEntityManagerFactory.getEntityManagerFactory(PUNIT_NAME, dataSource); + } + + @Test + void testTop() throws IOException, ODataException { + + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, + "AdministrativeDivisions?$top=10", new JPADerbySqlPatternProvider()); + helper.assertStatus(200); + final ObjectNode collection = helper.getValue(); + final ArrayNode act = ((ArrayNode) collection.get("value")); + assertEquals(10, act.size()); + assertEquals("Eurostat", act.get(0).get("CodePublisher").asText()); + assertEquals("LAU2", act.get(0).get("CodeID").asText()); + assertEquals("31003", act.get(0).get("DivisionCode").asText()); + } + + @Test + void testSkip() throws IOException, ODataException { + + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, + "AdministrativeDivisions?$skip=5", new JPADerbySqlPatternProvider()); + helper.assertStatus(200); + final ObjectNode collection = helper.getValue(); + final ArrayNode act = ((ArrayNode) collection.get("value")); + assertEquals(243, act.size()); + assertEquals("Eurostat", act.get(0).get("CodePublisher").asText()); + assertEquals("LAU2", act.get(0).get("CodeID").asText()); + assertEquals("31022", act.get(0).get("DivisionCode").asText()); + } + + @Test + void testTopSkip() throws IOException, ODataException { + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, + "AdministrativeDivisions?$top=10&$skip=5", new JPADerbySqlPatternProvider()); + helper.assertStatus(200); + } + + @ParameterizedTest + @ValueSource(strings = { + "AdministrativeDivisions/$count", + "Persons?$expand=Roles/$count", + "Persons('97')/InhouseAddress/$count" }) + void testCount(final String query) throws IOException, ODataException { + + final IntegrationTestHelper helper = new IntegrationTestHelper(emf, query); + helper.assertStatus(200); + } +} diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/testobjects/TestFunctionActionConstructor.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/testobjects/TestFunctionActionConstructor.java index b4d3dffdf..6f73efd34 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/testobjects/TestFunctionActionConstructor.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/testobjects/TestFunctionActionConstructor.java @@ -13,7 +13,6 @@ import com.sap.olingo.jpa.metadata.core.edm.mapper.extension.ODataAction; import com.sap.olingo.jpa.metadata.core.edm.mapper.extension.ODataFunction; -@SuppressWarnings("unused") public class TestFunctionActionConstructor implements ODataFunction, ODataAction { private final EntityManager em; diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/IntegrationTestHelper.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/IntegrationTestHelper.java index 9a34444a2..dbfec071c 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/IntegrationTestHelper.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/IntegrationTestHelper.java @@ -48,6 +48,7 @@ import com.sap.olingo.jpa.metadata.api.JPARequestParameterMap; import com.sap.olingo.jpa.metadata.core.edm.extension.vocabularies.AnnotationProvider; import com.sap.olingo.jpa.metadata.core.edm.mapper.api.JPAServiceDocument; +import com.sap.olingo.jpa.processor.cb.ProcessorSqlPatternProvider; import com.sap.olingo.jpa.processor.core.api.JPAODataBatchProcessor; import com.sap.olingo.jpa.processor.core.api.JPAODataClaimsProvider; import com.sap.olingo.jpa.processor.core.api.JPAODataContextAccessDouble; @@ -74,17 +75,22 @@ public IntegrationTestHelper(final EntityManagerFactory localEmf, final String u public IntegrationTestHelper(final EntityManagerFactory localEmf, final String urlPath, final AnnotationProvider annotationsProvider) throws IOException, ODataException { - this(localEmf, null, urlPath, null, null, null, null, null, null, annotationsProvider); + this(localEmf, null, urlPath, null, null, null, null, null, null, annotationsProvider, null); + } + + public IntegrationTestHelper(final EntityManagerFactory localEmf, final String urlPath, + final ProcessorSqlPatternProvider sqlPattern) throws IOException, ODataException { + this(localEmf, null, urlPath, null, null, null, null, null, null, null, sqlPattern); } public IntegrationTestHelper(final EntityManagerFactory localEmf, final String urlPath, final Map> headers) throws IOException, ODataException { - this(localEmf, null, urlPath, null, null, null, headers, null, null, null); + this(localEmf, null, urlPath, null, null, null, headers, null, null, null, null); } public IntegrationTestHelper(final EntityManagerFactory localEmf, final String urlPath, final JPAODataGroupProvider groups) throws IOException, ODataException { - this(localEmf, null, urlPath, null, null, null, null, null, groups, null); + this(localEmf, null, urlPath, null, null, null, null, null, groups, null, null); } public IntegrationTestHelper(final EntityManagerFactory localEmf, final DataSource dataSource, final String urlPath) @@ -118,30 +124,30 @@ public IntegrationTestHelper(final EntityManagerFactory localEmf, final String u public IntegrationTestHelper(final EntityManagerFactory localEmf, final String urlPath, final JPAODataClaimsProvider claims) throws IOException, ODataException { - this(localEmf, null, urlPath, null, null, null, null, claims, null, null); + this(localEmf, null, urlPath, null, null, null, null, claims, null, null, null); } public IntegrationTestHelper(final EntityManagerFactory localEmf, final String urlPath, final JPAODataPagingProvider provider, final JPAODataClaimsProvider claims) throws IOException, ODataException { - this(localEmf, null, urlPath, null, null, provider, null, claims, null, null); + this(localEmf, null, urlPath, null, null, provider, null, claims, null, null, null); } public IntegrationTestHelper(final EntityManagerFactory emf, final String urlPath, final JPAODataPagingProvider provider, final Map> headers) throws IOException, ODataException { - this(emf, null, urlPath, null, null, provider, headers, null, null, null); + this(emf, null, urlPath, null, null, provider, headers, null, null, null, null); } public IntegrationTestHelper(final EntityManagerFactory localEmf, final DataSource dataSource, final String urlPath, final StringBuffer requestBody, final String functionPackage, final JPAODataPagingProvider provider) throws IOException, ODataException { - this(localEmf, dataSource, urlPath, requestBody, functionPackage, provider, null, null, null, null); + this(localEmf, dataSource, urlPath, requestBody, functionPackage, provider, null, null, null, null, null); } public IntegrationTestHelper(final EntityManagerFactory localEmf, final DataSource dataSource, final String urlPath, final StringBuffer requestBody, final String functionPackage, final JPAODataPagingProvider pagingProvider, final Map> headers, final JPAODataClaimsProvider claims, final JPAODataGroupProvider groups, - final AnnotationProvider annotationsProvider) + final AnnotationProvider annotationsProvider, final ProcessorSqlPatternProvider sqlPattern) throws IOException, ODataException { super(); @@ -158,7 +164,7 @@ public IntegrationTestHelper(final EntityManagerFactory localEmf, final DataSour final JPAEdmProvider edmProvider = buildEdmProvider(localEmf, annotationsProvider, packages); final JPAODataSessionContextAccess sessionContext = new JPAODataContextAccessDouble(edmProvider, dataSource, - pagingProvider, annotationsProvider, functionPackage); + pagingProvider, annotationsProvider, sqlPattern, functionPackage); final ODataHttpHandler handler = odata.createHandler(odata.createServiceMetadata(sessionContext.getEdmProvider(), new ArrayList<>())); @@ -175,7 +181,10 @@ public IntegrationTestHelper(final EntityManagerFactory localEmf, final DataSour public JPAODataInternalRequestContext buildRequestContext(final EntityManagerFactory localEmf, final JPAODataClaimsProvider claims, final JPAODataGroupProvider groups, final JPAEdmProvider edmProvider, final JPAODataSessionContextAccess sessionContext, final OData odata) throws ODataException { - final EntityManager em = createEmfWrapper(localEmf, edmProvider).createEntityManager(); + + final EntityManager em = createEmfWrapper(localEmf, edmProvider, + sessionContext.getSqlPatternProvider() != null ? sessionContext.getSqlPatternProvider() : null) + .createEntityManager(); final JPAODataRequestContext externalContext = mock(JPAODataRequestContext.class); when(externalContext.getEntityManager()).thenReturn(em); @@ -369,15 +378,15 @@ public static HttpServletResponse getResponseMock() throws IOException { @SuppressWarnings("unchecked") private EntityManagerFactory createEmfWrapper(@Nonnull final EntityManagerFactory emf, - @Nonnull final JPAEdmProvider jpaEdm) throws ODataException { + @Nonnull final JPAEdmProvider jpaEdm, final ProcessorSqlPatternProvider sqlPattern) throws ODataException { try { final Class wrapperClass = (Class) Class .forName("com.sap.olingo.jpa.processor.cb.api.EntityManagerFactoryWrapper"); try { - return wrapperClass.getConstructor(EntityManagerFactory.class, - JPAServiceDocument.class).newInstance(emf, jpaEdm.getServiceDocument()); + return wrapperClass.getConstructor(EntityManagerFactory.class, JPAServiceDocument.class, + ProcessorSqlPatternProvider.class).newInstance(emf, jpaEdm.getServiceDocument(), sqlPattern); } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { throw new ODataException(e.getMessage()); diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/TestBase.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/TestBase.java index bed52127a..87fbe26f0 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/TestBase.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/TestBase.java @@ -1,6 +1,5 @@ package com.sap.olingo.jpa.processor.core.util; -import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -9,9 +8,12 @@ import javax.sql.DataSource; import jakarta.persistence.EntityManagerFactory; +import jakarta.persistence.criteria.From; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.JoinType; +import jakarta.persistence.criteria.Root; import org.apache.olingo.commons.api.ex.ODataException; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import com.sap.olingo.jpa.metadata.api.JPAEntityManagerFactory; @@ -28,6 +30,7 @@ public class TestBase { protected Map> headers; protected static JPAEdmNameBuilder nameBuilder; protected static DataSource dataSource; + protected HashMap> joinTables; @BeforeAll public static void setupClass() { @@ -35,12 +38,6 @@ public static void setupClass() { emf = JPAEntityManagerFactory.getEntityManagerFactory(PUNIT_NAME, dataSource); nameBuilder = new JPADefaultEdmNameBuilder(PUNIT_NAME); } - - @AfterAll - public static void teardownClass() throws SQLException { - emf.close(); - dataSource.getConnection().close(); - } protected void createHeaders() { headers = new HashMap<>(); @@ -60,4 +57,15 @@ protected TestHelper getHelper() throws ODataException { helper = new TestHelper(emf, PUNIT_NAME); return helper; } + + protected void fillJoinTable(final Root joinRoot) { + Join join = joinRoot.join("locationName", JoinType.LEFT); + joinTables.put("LocationName", join); + join = joinRoot.join("address", JoinType.LEFT); + join = join.join("countryName", JoinType.LEFT); + joinTables.put("Address/CountryName", join); + join = joinRoot.join("address", JoinType.LEFT); + join = join.join("regionName", JoinType.LEFT); + joinTables.put("Address/RegionName", join); + } } \ No newline at end of file diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/TestGroupBase.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/TestGroupBase.java index 658f58cfe..9e6f75b0c 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/TestGroupBase.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/TestGroupBase.java @@ -7,9 +7,6 @@ import java.util.HashMap; import java.util.List; -import jakarta.persistence.criteria.From; -import jakarta.persistence.criteria.Join; -import jakarta.persistence.criteria.JoinType; import jakarta.persistence.criteria.Root; import org.apache.olingo.commons.api.edm.EdmEntitySet; @@ -37,7 +34,6 @@ public class TestGroupBase extends TestBase { protected JPAAbstractJoinQuery cut; protected JPAEntityType jpaEntityType; - protected HashMap> joinTables; protected Root root; protected JPAODataSessionContextAccess context; protected UriInfo uriInfo; @@ -57,8 +53,7 @@ public void setup() throws ODataException, ODataJPAIllegalAccessException { jpaEntityType = helper.getJPAEntityType("BusinessPartnerWithGroupss"); createHeaders(); context = new JPAODataContextAccessDouble(new JPAEdmProvider(PUNIT_NAME, emf, null, TestBase.enumPackages), - dataSource, - null, null); + dataSource, null, null, null); final JPAODataRequestContext externalContext = mock(JPAODataRequestContext.class); when(externalContext.getEntityManager()).thenReturn(emf.createEntityManager()); odata = OData.newInstance(); @@ -90,14 +85,4 @@ protected UriInfo buildUriInfo(final String esName, final String etName) { return uriInfo; } - protected void fillJoinTable(final Root joinRoot) { - Join join = joinRoot.join("locationName", JoinType.LEFT); - joinTables.put("locationName", join); - join = joinRoot.join("address", JoinType.LEFT); - join = join.join("countryName", JoinType.LEFT); - joinTables.put("countryName", join); - join = joinRoot.join("address", JoinType.LEFT); - join = join.join("regionName", JoinType.LEFT); - joinTables.put("regionName", join); - } } \ No newline at end of file diff --git a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/TestQueryBase.java b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/TestQueryBase.java index ce3e09962..f8d054c32 100644 --- a/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/TestQueryBase.java +++ b/jpa/odata-jpa-processor/src/test/java/com/sap/olingo/jpa/processor/core/util/TestQueryBase.java @@ -7,9 +7,6 @@ import java.util.HashMap; import java.util.List; -import jakarta.persistence.criteria.From; -import jakarta.persistence.criteria.Join; -import jakarta.persistence.criteria.JoinType; import jakarta.persistence.criteria.Root; import org.apache.olingo.commons.api.edm.EdmEntitySet; @@ -38,7 +35,6 @@ public class TestQueryBase extends TestBase { protected JPAAbstractJoinQuery cut; protected JPAEntityType jpaEntityType; - protected HashMap> joinTables; protected Root root; protected JPAODataSessionContextAccess context; protected UriInfo uriInfo; @@ -59,8 +55,7 @@ public void setup() throws ODataException, ODataJPAIllegalAccessException { jpaEntityType = helper.getJPAEntityType("BusinessPartners"); createHeaders(); context = new JPAODataContextAccessDouble(new JPAEdmProvider(PUNIT_NAME, emf, null, TestBase.enumPackages), - dataSource, - null, null); + dataSource, null, null, null); externalContext = mock(JPAODataRequestContext.class); when(externalContext.getEntityManager()).thenReturn(emf.createEntityManager()); requestContext = new JPAODataInternalRequestContext(externalContext, context, odata); @@ -100,16 +95,4 @@ protected EdmType buildRequestContext(final String esName, final String etName) requestContext.setUriInfo(uriInfo); return odataType; } - - protected void fillJoinTable(final Root joinRoot) { - Join join = joinRoot.join("locationName", JoinType.LEFT); - joinTables.put("locationName", join); - join = joinRoot.join("address", JoinType.LEFT); - join = join.join("countryName", JoinType.LEFT); - joinTables.put("countryName", join); - join = joinRoot.join("address", JoinType.LEFT); - join = join.join("regionName", JoinType.LEFT); - joinTables.put("regionName", join); - } - } \ No newline at end of file diff --git a/jpa/odata-jpa-test/pom.xml b/jpa/odata-jpa-test/pom.xml index a30b8bcd7..88a5b8854 100644 --- a/jpa/odata-jpa-test/pom.xml +++ b/jpa/odata-jpa-test/pom.xml @@ -46,6 +46,14 @@ com.h2database h2 + + org.apache.derby + derby + + + org.apache.derby + derbytools + org.flywaydb flyway-core @@ -54,6 +62,10 @@ org.flywaydb flyway-database-hsqldb + + org.flywaydb + flyway-database-derby + com.fasterxml.jackson.core jackson-databind diff --git a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/DataSourceHelper.java b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/DataSourceHelper.java index 24627c9b9..53838601a 100644 --- a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/DataSourceHelper.java +++ b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/DataSourceHelper.java @@ -19,7 +19,7 @@ public class DataSourceHelper { private static final String H2_URL = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MODE=MySQL"; private static final String HSQLDB_URL = "jdbc:hsqldb:mem:com.sample"; private static final String DERBY_URL = - "jdbc:derby:test;create=true;traceFile=derby_trace.log;trace_level=0xFFFFFFFF"; + "jdbc:derby:memory:test;create=true"; private static final String REMOTE_URL = "jdbc:$DBNAME$:$Host$:$Port$"; public static final int DB_H2 = 1; diff --git a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/TimestampLongConverter.java b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/TimestampLongConverter.java index 861eca00a..bb6d34d6c 100644 --- a/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/TimestampLongConverter.java +++ b/jpa/odata-jpa-test/src/main/java/com/sap/olingo/jpa/processor/core/testmodel/TimestampLongConverter.java @@ -10,7 +10,7 @@ * Default converter to convert from {@link Long} to {@link java.sql.Timestamp}. * * @author Oliver Grande - * @version 1.0.0-RC + * @since 2.1.3 */ @Converter(autoApply = false) public class TimestampLongConverter implements AttributeConverter { diff --git a/jpa/odata-jpa-test/src/main/java/db/migration/V1_1__SchemaMigration.java b/jpa/odata-jpa-test/src/main/java/db/migration/V1_1__SchemaMigration.java index a6f687e00..3d0dd3085 100644 --- a/jpa/odata-jpa-test/src/main/java/db/migration/V1_1__SchemaMigration.java +++ b/jpa/odata-jpa-test/src/main/java/db/migration/V1_1__SchemaMigration.java @@ -33,6 +33,7 @@ public void migrate(final Context context) throws Exception { case "SAP HANA" -> createFunctionHANA(connection); case "HSQLDB" -> createFunctionHSQLDB(connection); case "PostgreSQL" -> createFunctionPostgres(connection); + case "Derby" -> createFunctionDerby(); default -> raiseUnsupportedDbException(dbType); }; @@ -155,6 +156,11 @@ RETURNS TABLE ( return Arrays.asList(Optional.of(connection.prepareStatement(sql))); } + private List> createFunctionDerby() { + + return Collections.emptyList(); + } + private List> createFunctionH2() { return Collections.emptyList(); } diff --git a/jpa/odata-jpa-test/src/main/resources/META-INF/persistence.xml b/jpa/odata-jpa-test/src/main/resources/META-INF/persistence.xml index 958e82f02..b383fd9c6 100644 --- a/jpa/odata-jpa-test/src/main/resources/META-INF/persistence.xml +++ b/jpa/odata-jpa-test/src/main/resources/META-INF/persistence.xml @@ -105,7 +105,6 @@ - diff --git a/jpa/odata-jpa-test/src/main/resources/db/migration/P_V1_0__olingo.sql b/jpa/odata-jpa-test/src/main/resources/db/migration/P_V1_0__olingo.sql new file mode 100644 index 000000000..200a506c7 --- /dev/null +++ b/jpa/odata-jpa-test/src/main/resources/db/migration/P_V1_0__olingo.sql @@ -0,0 +1,1020 @@ +SET schema 'OLINGO'; + +CREATE TABLE "BusinessPartner" ( + + "ID" VARCHAR(32) NOT NULL , + "ETag" BIGINT, + "Type" VARCHAR(2), + "CustomString1" VARCHAR(250), + "CustomString2" VARCHAR(250), + "CustomNum1" DECIMAL(16,5), + "CustomNum2" DECIMAL(31,0), + "NameLine1" VARCHAR(250), + "NameLine2" VARCHAR(250), + "BirthDay" DATE, + "Address.StreetName" VARCHAR(200), + "Address.StreetNumber" VARCHAR(60), + "Address.PostOfficeBox" VARCHAR(60), + "Address.City" VARCHAR(100), + "Address.PostalCode" VARCHAR(60), + "Address.RegionCodePublisher" VARCHAR(10) NOT NULL, + "Address.RegionCodeID" VARCHAR(10) NOT NULL, + "Address.Region" VARCHAR(100), + "Address.Country" VARCHAR(100), + "Telecom.Phone" VARCHAR(100), + "Telecom.Mobile" VARCHAR(100), + "Telecom.Fax" VARCHAR(100), + "Telecom.Email" VARCHAR(100), + "CreatedBy" VARCHAR(32) NOT NULL , + "CreatedAt" TIMESTAMP, + "UpdatedBy" VARCHAR(32) NOT NULL , + "UpdatedAt" TIMESTAMP, + "Country" VARCHAR(4), + "ABCClass" int, + "AccessRights" int, + "Image_ID" VARCHAR(32) , + PRIMARY KEY ("ID")); + +insert into "BusinessPartner" values ('1', 0, '2', '','',6000.5,null,'First Org.','',null,'Test Road', '23','', 'Test City','94321','ISO', '3166-2','US-CA', 'USA', '', '','','', '99','2016-01-20 09:21:23', '', null, 'USA', 0, null, null); +insert into "BusinessPartner" values ('2', 0, '2', '','',null,null,'Second Org.','',null,'Test Road', '45','', 'Test City','76321','ISO', '3166-2','US-TX', 'USA', '', '','','', '97','2016-01-20 09:21:23', '', null, 'USA', null, null, null); +insert into "BusinessPartner" values ('3', 0, '2', '','',null,null,'Third Org.','',null,'Test Road', '223','', 'Test City','94322','ISO', '3166-2','US-CA', 'USA', '', '','','', '99','2016-01-20 09:21:23', '', null, 'USA', null, null, null); +insert into "BusinessPartner" values ('4', 0, '2', '','',null,null,'Fourth Org.','',null,'Test Road', '56','', 'Test City','84321','ISO', '3166-2','US-UT', 'USA', '', '','','', '98','2016-01-20 09:21:23', '', null, 'USA', 1, null, null); +insert into "BusinessPartner" values ('5', 0, '2', '','',null,null,'Fifth Org.','',null,'Test Road', '35','', 'Test City','59321','ISO', '3166-2','US-MT', 'USA', '', '','','', '99','2016-01-20 09:21:23', '', null, 'USA', null, null, null); +insert into "BusinessPartner" values ('6', 0, '2', '','',null,null,'Sixth Org.','',null,'Test Road', '7856','', 'Test City','94324','ISO', '3166-2','US-CA', 'USA', '', '','','', '99','2016-01-20 09:21:23', '', null, 'USA', null, null, null); +insert into "BusinessPartner" values ('7', 0, '2', '','',null,null,'Seventh Org.','',null,'Test Road', '4','', 'Test City','29321','ISO', '3166-2','US-SC', 'USA', '', '','','', '99','2016-01-20 09:21:23', '', null, 'USA', null, null, null); +insert into "BusinessPartner" values ('8', 0, '2', '','',null,null,'Eighth Org.','',null,'Test Road', '453','', 'Test City','29221','ISO', '3166-2','US-SC', 'USA', '', '','','', '99','2016-01-20 09:21:23', '', null, 'USA', 2, null, null); +insert into "BusinessPartner" values ('9', 0, '2', '','',null,null,'Ninth Org.','',null,'Test Road', '93','', 'Test City','55021','ISO', '3166-2','US-MN', 'USA', '', '','','', '99','2016-01-20 09:21:23', '', null, 'USA', null, null, '9'); +insert into "BusinessPartner" values ('10', 0, '2', '','',null,null,'Tenth Org.','',null,'Test Road', '12','', 'Test City','03921','ISO', '3166-2','US-ME', 'USA', '', '','','', '99','2016-01-20 09:21:23', '', null, 'DEU', null, null, null); +insert into "BusinessPartner" values ('99', 0, '1', '','',null,null,'Max','Mustermann','1999-04-01','Test Starße', '12','', 'Teststadt','10115','ISO', '3166-2','DE-BE', 'DEU', '', '','','', '99','2016-01-20 09:21:23', '98','2016-01-21 19:35:12', 'DEU', null, 2, '99'); +insert into "BusinessPartner" values ('98', 0, '1', '','',null,null,'John','Doe',null,'Test Road', '55','', 'Test City','76321','ISO', '3166-2','US-TX', 'USA', '', '','','', '99','2016-01-20 09:21:23', '', null, 'DEU', null, null, null); +insert into "BusinessPartner" values ('97', 0, '1', '','',null,null,'Urs','Müller',null,'Test Straße', '23','', 'Test Dorf','4123','ISO', '3166-2','CH-BL', 'CHE', null,null,null,null, '99','2016-07-20 09:21:23', '', null, 'CHE', null, 9, null); + +--CREATE TABLE "PersonImage" ( +-- "ID" VARCHAR(32) NOT NULL , +-- "Image" BLOB, +-- "CreatedBy" VARCHAR(32) NOT NULL , +-- "CreatedAt" TIMESTAMP, +-- "UpdatedBy" VARCHAR(32) NOT NULL , +-- "UpdatedAt" TIMESTAMP, +-- PRIMARY KEY ("ID")); +--insert into "PersonImage" values ('99',null,'99','2016-01-20 09:21:23', '', null); + +--CREATE TABLE "OrganizationImage" ( +-- "ID" VARCHAR(32) NOT NULL , +-- "Image" BLOB, +-- "MimeType" VARCHAR(100), +-- "CreatedBy" VARCHAR(32) NOT NULL , +-- "CreatedAt" TIMESTAMP, +-- "UpdatedBy" VARCHAR(32) NOT NULL , +-- "UpdatedAt" TIMESTAMP, +-- PRIMARY KEY ("ID")); +--insert into "OrganizationImage" values ('9',null,'image/svg+xml','99','2016-01-20 09:21:23', '', null); + +CREATE TABLE "BusinessPartnerRole" ( + "BusinessPartnerID" VARCHAR(32) NOT NULL , + "BusinessPartnerRole" VARCHAR(10) NOT NULL, + "Details" VARCHAR(256), + PRIMARY KEY ("BusinessPartnerID","BusinessPartnerRole")); + +insert into "BusinessPartnerRole" values ('1', 'A', null); +insert into "BusinessPartnerRole" values ('3', 'A', 'Test'); +insert into "BusinessPartnerRole" values ('3', 'B', 'YAT'); +insert into "BusinessPartnerRole" values ('3', 'C', 'Last Detail'); +insert into "BusinessPartnerRole" values ('2', 'A', null); +insert into "BusinessPartnerRole" values ('2', 'C', null); +insert into "BusinessPartnerRole" values ('7', 'C', null); +insert into "BusinessPartnerRole" values ('98', 'X', null); +insert into "BusinessPartnerRole" values ('99', 'X', null); +insert into "BusinessPartnerRole" values ('99', 'Z', null); +insert into "BusinessPartnerRole" values ('97', 'Y', null); + +CREATE TABLE "JoinPartnerRoleRelation"( + "SourceID" VARCHAR(32) NOT NULL, + "TargetID" VARCHAR(10) NOT NULL, + PRIMARY KEY ("SourceID","TargetID")); + +insert into "JoinPartnerRoleRelation" values ('1', 'A'); +insert into "JoinPartnerRoleRelation" values ('3', 'A'); +insert into "JoinPartnerRoleRelation" values ('3', 'B'); +insert into "JoinPartnerRoleRelation" values ('3', 'C'); +insert into "JoinPartnerRoleRelation" values ('2', 'A'); +insert into "JoinPartnerRoleRelation" values ('2', 'C'); +insert into "JoinPartnerRoleRelation" values ('7', 'C'); +insert into "JoinPartnerRoleRelation" values ('98', 'X'); +insert into "JoinPartnerRoleRelation" values ('99', 'X'); +insert into "JoinPartnerRoleRelation" values ('99', 'Z'); +insert into "JoinPartnerRoleRelation" values ('97', 'Y'); + +CREATE TABLE "CountryDescription" ( + "ISOCode" VARCHAR(4) NOT NULL , + "LanguageISO" VARCHAR(4) NOT NULL , + "Name" VARCHAR(100) NOT NULL, + PRIMARY KEY ("ISOCode","LanguageISO")); + +insert into "CountryDescription" values( 'DEU','de','Deutschland'); +insert into "CountryDescription" values( 'USA','de','Vereinigte Staaten von Amerika'); +insert into "CountryDescription" values( 'DEU','en','Germany'); +insert into "CountryDescription" values( 'USA','en','United States of America'); +insert into "CountryDescription" values( 'BEL','de','Belgien'); +insert into "CountryDescription" values( 'BEL','en','Belgium'); +insert into "CountryDescription" values( 'CHE','de','Schweiz'); +insert into "CountryDescription" values( 'CHE','en','Switzerland'); + +CREATE TABLE "AdministrativeDivisionDescription"( + "CodePublisher" VARCHAR(10) NOT NULL, + "CodeID" VARCHAR(10) NOT NULL, + "DivisionCode" VARCHAR(10) NOT NULL, + "LanguageISO" VARCHAR(4) NOT NULL , + "Name" VARCHAR(100) NOT NULL, + PRIMARY KEY ("CodePublisher", "CodeID", "DivisionCode","LanguageISO")); + +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS1','CH0','de','Schweiz'); + +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-1','DEU','de','Deutschland'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-1','USA','de','Vereinigte Staaten von Amerika'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-1','DEU','en','Germany'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-1','USA','en','United States of America'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-1','BEL','de','Belgien'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-1','BEL','en','Belgium'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-1','CHE','de','Schweiz'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-1','CHE','en','Switzerland'); + +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','DE-BW','de','Baden-Württemberg'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','DE-BY','de','Bayern Bayern'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','DE-BE','de','Berlin'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','DE-BB','de','Brandenburg'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','DE-HB','de','Bremen'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','DE-HH','de','Hamburg'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','DE-HE','de','Hessen'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','DE-MV','de','Mecklenburg-Vorpommern'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','DE-NI','de','Niedersachsen'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','DE-NW','de','Nordrhein-Westfalen'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','DE-RP','de','Rheinland-Pfalz'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','DE-SL','de','Saarland'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','DE-SN','de','Sachsen'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','DE-ST','de','Sachsen-Anhalt'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','DE-SH','de','Schleswig-Holstein'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','DE-TH','de','Thüringen'); + +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','DE-BW','en','Baden-Württemberg'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','DE-BY','en','Bavaria'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','DE-BE','en','Berlin'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','DE-BB','en','Brandenburg'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','DE-HB','en','Bremen'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','DE-HH','en','Hamburg'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','DE-HE','en','Hesse'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','DE-MV','en','Mecklenburg-Western Pomerania'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','DE-NI','en','Lower Saxony'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','DE-NW','en','North Rhine-Westphalia'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','DE-RP','en','Rhineland-Palatinate'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','DE-SL','en','Saarland'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','DE-SN','en','Saxony'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','DE-ST','en','Saxony-Anhalt'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','DE-SH','en','Schleswig-Holstein'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','DE-TH','en','Thuringia'); + + +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','CH-AG','de','Aargau'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','CH-AR','de','Appenzell Ausserrhoden'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','CH-AI','de','Appenzell Innerrhoden'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','CH-BL','de','Basel-Landschaft'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','CH-BS','de','Basel-Stadt'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','CH-BE','de','Bern'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','CH-FR','de','Freiburg'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','CH-GE','de','Genf'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','CH-GL','de','Glarus'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','CH-GR','de','Graubünden'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','CH-JU','de','Jura'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','CH-LU','de','Luzern'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','CH-NE','de','Neuenburg'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','CH-NW','de','Nidwalden'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','CH-OW','de','Obwalden'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','CH-SH','de','Schaffhausen'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','CH-SZ','de','Schwyz'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','CH-SO','de','Solothurn'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','CH-SG','de','St. Gallen'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','CH-TI','de','Tessin'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','CH-TG','de','Thurgau'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','CH-UR','de','Uri'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','CH-VD','de','Waadt'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','CH-VS','de','Wallis'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','CH-ZG','de','Zug'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','CH-ZH','de','Zürich'); + +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-AL','de','Alabama'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-AK','de','Alaska'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-AZ','de','Arizona'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-AR','de','Arkansas'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-CO','de','Colorado'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-CT','de','Connecticut'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-DE','de','Delaware'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-DC','de','District of Columbia'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-FL','de','Florida'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-GA','de','Georgia'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-HI','de','Hawaii'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-ID','de','Idaho'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-IL','de','Illinois'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-IN','de','Indiana'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-IA','de','Iowa'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-CA','de','Kalifornien'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-KS','de','Kansas'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-KY','de','Kentucky'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-LA','de','Louisiana'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-ME','de','Maine'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-MD','de','Maryland'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-MA','de','Massachusetts'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-MI','de','Michigan'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-MN','de','Minnesota'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-MS','de','Mississippi'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-MO','de','Missouri'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-MT','de','Montana'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-NE','de','Nebraska'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-NV','de','Nevada'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-NH','de','New Hampshire'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-NJ','de','New Jersey'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-NM','de','New Mexico'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-NY','de','New York'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-NC','de','North Carolina'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-ND','de','North Dakota'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-OH','de','Ohio'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-OK','de','Oklahoma'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-OR','de','Oregon'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-PA','de','Pennsylvania'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-RI','de','Rhode Island'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-SC','de','South Carolina'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-SD','de','South Dakota'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-TN','de','Tennessee'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-TX','de','Texas'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-UT','de','Utah'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-VT','de','Vermont'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-VA','de','Virginia'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-WA','de','Washington'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-WV','de','West Virginia'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-WI','de','Wisconsin'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-WY','de','Wyoming'); + +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-AL','en','Alabama'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-AK','en','Alaska'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-AZ','en','Arizona'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-AR','en','Arkansas'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-CA','en','California'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-CO','en','Colorado'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-CT','en','Connecticut'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-DE','en','Delaware'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-FL','en','Florida'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-GA','en','Georgia'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-HI','en','Hawaii'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-ID','en','Idaho'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-IL','en','Illinois'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-IN','en','Indiana'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-IA','en','Iowa'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-KS','en','Kansas'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-KY','en','Kentucky'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-LA','en','Louisiana'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-ME','en','Maine'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-MD','en','Maryland'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-MA','en','Massachusetts'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-MI','en','Michigan'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-MN','en','Minnesota'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-MS','en','Mississippi'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-MO','en','Missouri'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-MT','en','Montana'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-NE','en','Nebraska'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-NV','en','Nevada'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-NH','en','New Hampshire'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-NJ','en','New Jersey'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-NM','en','New Mexico'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-NY','en','New York'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-NC','en','North Carolina'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-ND','en','North Dakota'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-OH','en','Ohio'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-OK','en','Oklahoma'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-OR','en','Oregon'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-PA','en','Pennsylvania'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-RI','en','Rhode Island'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-SC','en','South Carolina'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-SD','en','South Dakota'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-TN','en','Tennessee'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-TX','en','Texas'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-UT','en','Utah'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-VT','en','Vermont'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-VA','en','Virginia'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-WA','en','Washington'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-WV','en','West Virginia'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-WI','en','Wisconsin'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-WY','en','Wyoming'); +insert into "AdministrativeDivisionDescription" values( 'ISO', '3166-2','US-DC','en','District of Columbia'); + +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS1','BE1','de','Region Brüssel-Hauptstadt'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS1','BE2','de','Flämische Region'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS1','BE3','de','Wallonische Region'); + +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS2','BE10','de','Region Brüssel-Hauptstadt'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS2','BE21','de','Provinz Antwerpen'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS2','BE22','de','Provinz Limburg'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS2','BE23','de','Provinz Ostflandern'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS2','BE24','de','Provinz Flämisch-Brabant'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS2','BE25','de','Provinz Westflandern'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS2','BE31','de','Provinz Wallonisch-Brabant'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS2','BE32','de','Provinz Hennegau'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS2','BE33','de','Provinz Lüttich'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS2','BE34','de','Provinz Luxemburg'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS2','BE35','de','Provinz Namur'); + +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE100','de','Bezirk Brüssel-Hauptstadt'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE211','de','Bezirk Antwerpen'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE212','de','Bezirk Mechelen'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE213','de','Bezirk Turnhout'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE221','de','Bezirk Hasselt'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE222','de','Bezirk Maaseik'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE223','de','Bezirk Tongeren'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE231','de','Bezirk Aalst'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE232','de','Bezirk Dendermonde'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE233','de','Bezirk Eeklo'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE234','de','Bezirk Gent'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE235','de','Bezirk Oudenaarde'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE236','de','Bezirk Sint-Niklaas'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE241','de','Bezirk Halle-Vilvoorde'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE242','de','Bezirk Löwen'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE251','de','Bezirk Brügge'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE252','de','Bezirk Diksmuide'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE253','de','Bezirk Ypern'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE254','de','Bezirk Kortrijk'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE255','de','Bezirk Ostende'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE256','de','Bezirk Roeselare'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE257','de','Bezirk Tielt'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE258','de','Bezirk Veurne'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE310','de','Bezirk Nivelles'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE321','de','Bezirk Ath'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE322','de','Bezirk Charleroi'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE323','de','Bezirk Mons'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE324','de','Bezirk Mouscron'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE325','de','Bezirk Soignies'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE326','de','Bezirk Thuin'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE327','de','Bezirk Tournai'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE331','de','Bezirk Huy'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE332','de','Bezirk Lüttich'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE334','de','Bezirk Waremme'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE335','de','Bezirk Verviers – frz. Sprachgebiet'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE336','de','Bezirk Verviers – deu. Sprachgebiet'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE341','de','Bezirk Arlon'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE342','de','Bezirk Bastogne'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE343','de','Bezirk Marche-en-Famenne'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE344','de','Bezirk Neufchâteau'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE345','de','Bezirk Virton'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE351','de','Bezirk Dinant'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE352','de','Bezirk Namur'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE353','de','Bezirk Philippeville'); + +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS1','BE1','en','Brussels-Capital Region'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS1','BE2','en','Flemish Region'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS1','BE3','en','Walloon Region'); + +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS2','BE10','en','Brussels-Capital Region'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS2','BE21','en','Antwerp'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS2','BE22','en','Limburg'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS2','BE23','en','East Flanders'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS2','BE24','en','Flemish Brabant'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS2','BE25','en','West Flanders'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS2','BE31','en','Walloon Brabant'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS2','BE32','en','Hainaut'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS2','BE33','en','Liège'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS2','BE34','en','Luxembourg (Belgium)'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS2','BE35','en','Namur'); + +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE100','en','Arrondissement of Brussels-Capital'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE211','en','Arrondissement of Antwerp'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE212','en','Arrondissement of Mechelen'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE213','en','Arrondissement of Turnhout'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE221','en','Arrondissement of Hasselt'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE222','en','Arrondissement of Maaseik'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE223','en','Arrondissement of Tongeren'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE231','en','Arrondissement of Aalst'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE232','en','Arrondissement of Dendermonde'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE233','en','Arrondissement of Eeklo'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE234','en','Arrondissement of Ghent'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE235','en','Arrondissement of Oudenaarde'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE236','en','Arrondissement of Sint-Niklaas'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE241','en','Arrondissement of Halle-Vilvoorde'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE242','en','Arrondissement of Leuven'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE251','en','Arrondissement of Bruges'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE252','en','Arrondissement of Diksmuide'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE253','en','Arrondissement of Ypres'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE254','en','Arrondissement of Kortrijk'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE255','en','Arrondissement of Ostend'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE256','en','Arrondissement of Roeselare'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE257','en','Arrondissement of Tielt'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE258','en','Arrondissement of Veurne'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE310','en','Arrondissement of Nivelles'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE321','en','Arrondissement of Ath'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE322','en','Arrondissement of Charleroi'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE323','en','Arrondissement of Mons'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE324','en','Arrondissement of Mouscron'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE325','en','Arrondissement of Soignies'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE326','en','Arrondissement of Thuin'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE327','en','Arrondissement of Tournai'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE331','en','Arrondissement of Huy'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE332','en','Arrondissement of Liège'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE334','en','Arrondissement of Waremme'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE335','en','Arrondissement of Verviers, municipalities of the French Community'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE336','en','Arrondissement of Verviers,municipalities of the German Community'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE341','en','Arrondissement of Arlon'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE342','en','Arrondissement of Bastogne'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE343','en','Arrondissement of Marche-en-Famenne'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE344','en','Arrondissement of Neufchâteau'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE345','en','Arrondissement of Virton'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE351','en','Arrondissement of Dinant'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE352','en','Arrondissement of Namur'); +insert into "AdministrativeDivisionDescription" values( 'Eurostat','NUTS3','BE353','en','Arrondissement of Philippeville'); + +CREATE TABLE "AdministrativeDivision"( + "CodePublisher" VARCHAR(10) NOT NULL, + "CodeID" VARCHAR(10) NOT NULL, + "DivisionCode" VARCHAR(10) NOT NULL, + "CountryISOCode" VARCHAR(4) NOT NULL , + "ParentCodeID" VARCHAR(10), + "ParentDivisionCode" VARCHAR(10), + "AlternativeCode" VARCHAR(10), + "Area" BIGINT, --DECIMAL(31,0), + "Population" BIGINT, + PRIMARY KEY ("CodePublisher", "CodeID", "DivisionCode")); + +insert into "AdministrativeDivision" values( 'ISO', '3166-1','BEL','BEL',null,null,null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-1','DEU','DEU',null,null,null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-1','USA','USA',null,null,null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-1','CHE','CHE',null,null,null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','CH-AG','CHE','3166-1','CHE',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','CH-AR','CHE','3166-1','CHE',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','CH-AI','CHE','3166-1','CHE',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','CH-BL','CHE','3166-1','CHE',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','CH-BS','CHE','3166-1','CHE',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','CH-BE','CHE','3166-1','CHE',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','CH-FR','CHE','3166-1','CHE',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','CH-GE','CHE','3166-1','CHE',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','CH-GL','CHE','3166-1','CHE',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','CH-GR','CHE','3166-1','CHE',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','CH-JU','CHE','3166-1','CHE',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','CH-LU','CHE','3166-1','CHE',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','CH-NE','CHE','3166-1','CHE',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','CH-NW','CHE','3166-1','CHE',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','CH-OW','CHE','3166-1','CHE',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','CH-SH','CHE','3166-1','CHE',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','CH-SZ','CHE','3166-1','CHE',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','CH-SO','CHE','3166-1','CHE',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','CH-SG','CHE','3166-1','CHE',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','CH-TI','CHE','3166-1','CHE',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','CH-TG','CHE','3166-1','CHE',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','CH-UR','CHE','3166-1','CHE',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','CH-VD','CHE','3166-1','CHE',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','CH-VS','CHE','3166-1','CHE',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','CH-ZG','CHE','3166-1','CHE',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','CH-ZH','CHE','3166-1','CHE',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','BE-BRU','BEL','3166-1','BEL',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','BE-VLG','BEL','3166-1','BEL',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','BE-WAL','BEL','3166-1','BEL',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','BE-VAN','BEL','3166-2','BE-VLG',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','BE-VLI','BEL','3166-2','BE-VLG',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','BE-VOV','BEL','3166-2','BE-VLG',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','BE-VBR','BEL','3166-2','BE-VLG',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','BE-VWV','BEL','3166-2','BE-VLG',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','BE-WBR','BEL','3166-2','BE-WAL',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','BE-WHT','BEL','3166-2','BE-WAL',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','BE-WLG','BEL','3166-2','BE-WAL',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','BE-WLX','BEL','3166-2','BE-WAL',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','BE-WNA','BEL','3166-2','BE-WAL',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2', 'DE-BW','DEU', '3166-1','DEU',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2', 'DE-BY','DEU', '3166-1','DEU',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2', 'DE-BE','DEU', '3166-1','DEU',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2', 'DE-BB','DEU', '3166-1','DEU',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2', 'DE-HB','DEU', '3166-1','DEU',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2', 'DE-HH','DEU', '3166-1','DEU',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2', 'DE-HE','DEU', '3166-1','DEU',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2', 'DE-MV','DEU', '3166-1','DEU',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2', 'DE-NI','DEU', '3166-1','DEU',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2', 'DE-NW','DEU', '3166-1','DEU',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2', 'DE-RP','DEU', '3166-1','DEU',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2', 'DE-SL','DEU', '3166-1','DEU',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2', 'DE-SN','DEU', '3166-1','DEU',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2', 'DE-ST','DEU', '3166-1','DEU',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2', 'DE-SH','DEU', '3166-1','DEU',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2', 'DE-TH','DEU', '3166-1','DEU',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-AL','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-AK','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-AZ','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-AR','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-CA','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-CO','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-CT','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-DE','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-FL','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-GA','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-HI','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-ID','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-IL','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-IN','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-IA','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-KS','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-KY','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-LA','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-ME','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-MD','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-MA','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-MI','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-MN','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-MS','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-MO','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-MT','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-NE','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-NV','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-NH','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-NJ','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-NM','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-NY','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-NC','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-ND','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-OH','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-OK','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-OR','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-PA','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-RI','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-SC','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-SD','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-TN','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-TX','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-UT','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-VT','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-VA','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-WA','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-WV','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-WI','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-WY','USA','3166-1','USA',null,0,0); +insert into "AdministrativeDivision" values( 'ISO', '3166-2','US-DC','USA','3166-1','USA',null,0,0); +--Eurostat +insert into "AdministrativeDivision" values( 'Eurostat','NUTS1','BE1','BEL',null,null,null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS1','BE2','BEL',null,null,null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS1','BE3','BEL',null,null,null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS1','DE1','DEU',null,null,null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS1','DE2','DEU',null,null,null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS1','DE3','DEU',null,null,null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS1','DE4','DEU',null,null,null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS1','DE5','DEU',null,null,null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS1','DE6','DEU',null,null,null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS1','DE7','DEU',null,null,null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS1','DE8','DEU',null,null,null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS1','DE9','DEU',null,null,null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS1','DEA','DEU',null,null,null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS1','DEB','DEU',null,null,null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS1','DEC','DEU',null,null,null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS1','DED','DEU',null,null,null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS1','DEE','DEU',null,null,null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS1','DEF','DEU',null,null,null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS1','DEG','DEU',null,null,null,0,0); + +insert into "AdministrativeDivision" values( 'Eurostat','NUTS2','BE10','BEL','NUTS1','BE1',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS2','BE21','BEL','NUTS1','BE2',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS2','BE22','BEL','NUTS1','BE2',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS2','BE23','BEL','NUTS1','BE2',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS2','BE24','BEL','NUTS1','BE2',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS2','BE25','BEL','NUTS1','BE2',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS2','BE31','BEL','NUTS1','BE3',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS2','BE32','BEL','NUTS1','BE3',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS2','BE33','BEL','NUTS1','BE3',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS2','BE34','BEL','NUTS1','BE3',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS2','BE35','BEL','NUTS1','BE3',null,0,0); + +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE100','BEL','NUTS2','BE10',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE211','BEL','NUTS2','BE21',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE212','BEL','NUTS2','BE21',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE213','BEL','NUTS2','BE21',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE221','BEL','NUTS2','BE22',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE222','BEL','NUTS2','BE22',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE223','BEL','NUTS2','BE22',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE231','BEL','NUTS2','BE23',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE232','BEL','NUTS2','BE23',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE233','BEL','NUTS2','BE23',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE234','BEL','NUTS2','BE23',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE235','BEL','NUTS2','BE23',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE236','BEL','NUTS2','BE23',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE241','BEL','NUTS2','BE24',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE242','BEL','NUTS2','BE24',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE251','BEL','NUTS2','BE25',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE252','BEL','NUTS2','BE25',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE253','BEL','NUTS2','BE25',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE254','BEL','NUTS2','BE25',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE255','BEL','NUTS2','BE25',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE256','BEL','NUTS2','BE25',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE257','BEL','NUTS2','BE25',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE258','BEL','NUTS2','BE25',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE310','BEL','NUTS2','BE31',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE321','BEL','NUTS2','BE32',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE322','BEL','NUTS2','BE32',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE323','BEL','NUTS2','BE32',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE324','BEL','NUTS2','BE32',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE325','BEL','NUTS2','BE32',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE326','BEL','NUTS2','BE32',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE327','BEL','NUTS2','BE32',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE331','BEL','NUTS2','BE33',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE332','BEL','NUTS2','BE33',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE334','BEL','NUTS2','BE33',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE335','BEL','NUTS2','BE33',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE336','BEL','NUTS2','BE33',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE341','BEL','NUTS2','BE34',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE342','BEL','NUTS2','BE34',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE343','BEL','NUTS2','BE34',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE344','BEL','NUTS2','BE34',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE345','BEL','NUTS2','BE34',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE351','BEL','NUTS2','BE35',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE352','BEL','NUTS2','BE35',null,0,0); +insert into "AdministrativeDivision" values( 'Eurostat','NUTS3','BE353','BEL','NUTS2','BE35',null,0,0); + +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '33011','BEL','NUTS3','BE253',null,130610415,35098); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '33016','BEL','NUTS3','BE253',null,3578335,1037); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '33021','BEL','NUTS3','BE253',null,119330610,19968); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '33029','BEL','NUTS3','BE253',null,43612479,18456); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '33037','BEL','NUTS3','BE253',null,67573324,12400); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '33039','BEL','NUTS3','BE253',null,94235304,7888); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '33040','BEL','NUTS3','BE253',null,52529046,8144); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '33041','BEL','NUTS3','BE253',null,38148241,3625); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '31003','BEL','NUTS3','BE251',null,71675809,15493); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '31004','BEL','NUTS3','BE251',null,17411180,20028); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '31005','BEL','NUTS3','BE251',null,138402202,118335); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '31006','BEL','NUTS3','BE251',null,89520475,10885); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '31012','BEL','NUTS3','BE251',null,53764838,13861); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '31022','BEL','NUTS3','BE251',null,79645460,23133); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '31033','BEL','NUTS3','BE251',null,45232765,20371); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '31040','BEL','NUTS3','BE251',null,60335913,22424); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '31042','BEL','NUTS3','BE251',null,48862499,2720); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '31043','BEL','NUTS3','BE251',null,56443228,33485); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '32003','BEL','NUTS3','BE252',null,149401818,16564); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '32006','BEL','NUTS3','BE252',null,55893936,9995); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '32010','BEL','NUTS3','BE252',null,39185258,8712); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '32011','BEL','NUTS3','BE252',null,54999796,12357); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '32030','BEL','NUTS3','BE252',null,62938759,3282); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '34002','BEL','NUTS3','BE254',null,41788652,14580); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '34003','BEL','NUTS3','BE254',null,21748127,9803); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '34009','BEL','NUTS3','BE254',null,16815679,11687); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '34013','BEL','NUTS3','BE254',null,29140007,27476); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '34022','BEL','NUTS3','BE254',null,80020386,75577); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '34023','BEL','NUTS3','BE254',null,10008346,13113); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '34025','BEL','NUTS3','BE254',null,13150180,5756); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '34027','BEL','NUTS3','BE254',null,33070836,33099); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '34040','BEL','NUTS3','BE254',null,44343752,37385); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '34041','BEL','NUTS3','BE254',null,38761463,31345); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '34042','BEL','NUTS3','BE254',null,63242438,24301); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '34043','BEL','NUTS3','BE254',null,10776650,2155); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '35002','BEL','NUTS3','BE255',null,13079087,17333); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '35005','BEL','NUTS3','BE255',null,42254065,11771); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '35006','BEL','NUTS3','BE255',null,45334695,13972); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '35011','BEL','NUTS3','BE255',null,75653353,19312); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '35013','BEL','NUTS3','BE255',null,37723883,70813); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '35014','BEL','NUTS3','BE255',null,35383003,9231); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '35029','BEL','NUTS3','BE255',null,42169175,12611); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '36006','BEL','NUTS3','BE256',null,37836247,10079); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '36007','BEL','NUTS3','BE256',null,16157265,10748); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '36008','BEL','NUTS3','BE256',null,25483137,27449); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '36010','BEL','NUTS3','BE256',null,24758200,9519); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '36011','BEL','NUTS3','BE256',null,25931415,8625); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '36012','BEL','NUTS3','BE256',null,35341058,10964); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '36015','BEL','NUTS3','BE256',null,59793935,60707); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '36019','BEL','NUTS3','BE256',null,46240034,11196); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '37002','BEL','NUTS3','BE257',null,25935511,8376); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '37007','BEL','NUTS3','BE257',null,29348057,11039); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '37010','BEL','NUTS3','BE257',null,16621841,7715); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '37011','BEL','NUTS3','BE257',null,34421545,6798); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '37012','BEL','NUTS3','BE257',null,30201523,5266); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '37015','BEL','NUTS3','BE257',null,68504357,20110); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '37017','BEL','NUTS3','BE257',null,21760376,9441); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '37018','BEL','NUTS3','BE257',null,68420735,14283); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '37020','BEL','NUTS3','BE257',null,34576094,9072); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '38002','BEL','NUTS3','BE258',null,80009790,5007); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '38008','BEL','NUTS3','BE258',null,23896896,10854); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '38014','BEL','NUTS3','BE258',null,43959624,22202); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '38016','BEL','NUTS3','BE258',null,31004430,11434); +insert into "AdministrativeDivision" values( 'Eurostat', 'LAU2', '38025','BEL','NUTS3','BE258',null,96339703,11509); + +-------------------------------------------- + +CREATE TABLE "Comment" ( + "BusinessPartnerID" VARCHAR(32) NOT NULL , + "Order" INTEGER NOT NULL, + "Text" VARCHAR(280), + PRIMARY KEY ("BusinessPartnerID", "Order")); + +insert into "Comment" values( '1', 1, 'This is just a test'); +insert into "Comment" values( '1', 3, 'This is another test'); +insert into "Comment" values( '7', 1, 'This is another test'); +insert into "Comment" values( '501', 3, 'This is another test'); +insert into "Comment" values( '502', 3, 'This is another test'); +insert into "Comment" values( '502', 4, 'This is also test'); + +CREATE TABLE "InhouseAddress" ( + "ParentID" VARCHAR(32) NOT NULL , + "Task" VARCHAR(32) NOT NULL , + "Building" VARCHAR(10), + "Floor" SMALLINT, + "RoomNumber" INTEGER, + PRIMARY KEY ("ParentID", "Task")); +insert into "InhouseAddress" values( '97', 'DEV', '2', 1, 32 ); +insert into "InhouseAddress" values( '99', 'DEV', '1',-1 ,245 ); +insert into "InhouseAddress" values( '99', 'MAIN', '7', 2 ,32 ); +insert into "InhouseAddress" values( '501', 'MAIN', '7', 2 ,32 ); +insert into "InhouseAddress" values( '502', 'DEV', '1',-1 ,245 ); +insert into "InhouseAddress" values( '502', 'MAIN', '7',6 ,12 ); + +CREATE TABLE "Collections" ( + "ID" VARCHAR(32) NOT NULL , + "Number" INTEGER, + "Timestamp" TIMESTAMP, -- WITH TIME ZONE, + PRIMARY KEY ("ID")); +insert into "Collections" values( '501',-1, null ); +insert into "Collections" values( '502', 32, null ); +--insert into "Collections" values( '503',6541, '2020-11-01 14:43:47-12:00' ); +--insert into "Collections" values( '504', 3, '2020-11-01 14:43:47+01:00'); +insert into "Collections" values( '503',6541, '2020-11-01 14:43:47' ); +insert into "Collections" values( '504', 3, '2020-11-01 14:43:47'); + + +CREATE TABLE "NestedComplex" ( + "ID" VARCHAR(32) NOT NULL, + "Number" INTEGER NOT NULL, + "Figure1" INTEGER, + "Figure2" INTEGER, + "Figure3" BIGINT, + PRIMARY KEY ("ID", "Number")); + +insert into "NestedComplex" values('501',1, 1, 1, 1); +insert into "NestedComplex" values('501',3, 1, 1, 1); +insert into "NestedComplex" values('503',1, 4, 5, 6); +insert into "NestedComplex" values('504',1, 1, 3, 6); +insert into "NestedComplex" values('504',3, 1, 1, 7); + +CREATE TABLE "CollectionsDeep" ( + "ID" VARCHAR(32) NOT NULL , + "LevelID" INTEGER, + "Number" INTEGER, + PRIMARY KEY ("ID")); +insert into "CollectionsDeep" values( '501',1,-1 ); +insert into "CollectionsDeep" values( '502',1, 3 ); +insert into "CollectionsDeep" values( '506',1, 100 ); + +-------------------------------------------- + +CREATE TABLE "SupportRelationship" ( + "ID" INTEGER NOT NULL , + "OrganizationID" VARCHAR(32) NOT NULL , + "PersonID" VARCHAR(32) NOT NULL , + PRIMARY KEY ("ID")); + +insert into "SupportRelationship" values (1,'1','97'); +insert into "SupportRelationship" values (2,'1','98'); +insert into "SupportRelationship" values (3,'2','97'); + + +CREATE TABLE "Team" ( + "TeamKey" VARCHAR(32) NOT NULL , + "Name" VARCHAR(100), + PRIMARY KEY ("TeamKey")); + +insert into "Team" values ('A', 'Team Java'); +insert into "Team" values ('B', 'Team Scala'); +insert into "Team" values ('C', 'Team Phyton'); +insert into "Team" values ('D', 'Team Go'); + +CREATE TABLE "SalesTeam" ( + "TeamKey" VARCHAR(32) NOT NULL , + "Name" VARCHAR(100), + "SalesArea" VARCHAR(100), + "GroupLead" VARCHAR(32), + PRIMARY KEY ("TeamKey")); + +insert into "SalesTeam" values ('S0', 'S0', 'APJ', '97'); +insert into "SalesTeam" values ('S1', 'S1', 'APJ North', '98'); +insert into "SalesTeam" values ('S2', 'S2', 'APJ East', '98'); + +CREATE TABLE "Membership" ( + "ID" INTEGER NOT NULL , + "PersonID" VARCHAR(32) NOT NULL , + "TeamID" VARCHAR(32) NOT NULL , + PRIMARY KEY ("ID")); + +insert into "Membership" values (1,'97','A'); +insert into "Membership" values (2,'97','D'); +insert into "Membership" values (5,'97','C'); +insert into "Membership" values (3,'99','A'); +insert into "Membership" values (4,'99','B'); + +--Just for Join Table test +CREATE TABLE "JoinSource" ( + "SourceKey" INTEGER NOT NULL , + "Number" INTEGER, + PRIMARY KEY ("SourceKey")); + +CREATE TABLE "JoinTarget" ( + "TargetKey" INTEGER NOT NULL , + PRIMARY KEY ("TargetKey")); + +CREATE TABLE "JoinRelation" ( + "SourceID" INTEGER NOT NULL , + "TargetID" INTEGER NOT NULL , + PRIMARY KEY ("SourceID", "TargetID")); + +insert into "JoinSource" values (1,-1); +insert into "JoinSource" values (2,-2); +insert into "JoinTarget" values (20); +insert into "JoinTarget" values (21); +insert into "JoinRelation" values (1, 20); +insert into "JoinRelation" values (1, 21); + +CREATE TABLE "JoinHiddenRelation" ( + "SourceID" INTEGER NOT NULL , + "TargetID" INTEGER NOT NULL , + PRIMARY KEY ("SourceID", "TargetID")); + +insert into "JoinHiddenRelation" values (2, 20); +insert into "JoinHiddenRelation" values (2, 21); + +------Top Level----------------------------- + +CREATE TABLE "GeneralSettings"( + "Name" VARCHAR(255)); + +CREATE TABLE "DetailSettings"( + "Id" INTEGER NOT NULL , + "Name" VARCHAR(255), + "GeneralName" VARCHAR(255), + PRIMARY KEY ("Id")); + +------Authorizations------------------------ +--top-secret; --logo +CREATE TABLE "User" ( + "UserName" VARCHAR(60) NOT NULL , + "Password" VARCHAR(60), + "Enabled" BOOLEAN, + "UserType" VARCHAR(20), + PRIMARY KEY ("UserName")); +insert into "User" values ('Willi', '$2a$10$ekL4q.jeDmuc2AhZF/ARUe2KTMczEBHZlML.bN985noWuJcdilbg6', true, 'INTERACTIVE'); +insert into "User" values ('Marvin', '$2a$10$dPD0o8lEbOy0vYtpWkE78.vVBKWElJjiezkFo1nr6hG3EBRx4Gpl.', true, 'INTERACTIVE'); + +CREATE TABLE "CountryRestriction" ( + "UserName" VARCHAR(60) NOT NULL , + "SequenceNumber" INTEGER NOT NULL, + "From" VARCHAR(4) NOT NULL , + "To" VARCHAR(4), + PRIMARY KEY ("UserName","SequenceNumber")); +insert into "CountryRestriction" values ('Willi', 1, 'DEU', 'DEU'); +insert into "CountryRestriction" values ('Marvin', 1, 'CHE', 'ZAF'); + +CREATE TABLE "RoleRestriction" ( + "UserName" VARCHAR(60) NOT NULL , + "SequenceNumber" INTEGER NOT NULL, + "From" VARCHAR(10) NOT NULL , + "To" VARCHAR(10), + PRIMARY KEY ("UserName","SequenceNumber")); +insert into "RoleRestriction" values ('Marvin', 1, 'A', 'B'); +insert into "RoleRestriction" values ('Willi', 1, 'A', 'A'); +insert into "RoleRestriction" values ('Willi', 2, 'C', 'C'); + +CREATE VIEW "BusinessPartnerProtected" + AS + SELECT DISTINCT + b."ID", + b."ETag", + b."Type", + b."NameLine1", + b."NameLine2", + b."Country", + r."UserName", + b."AccessRights", + b."BirthDay", + b."CreatedBy", + b."CreatedAt", + b."UpdatedBy", + b."UpdatedAt", + a."Task" as "AddressType", + a."Task", + a."Building", + a."Floor", + a."RoomNumber" + FROM "BusinessPartner" as b + INNER JOIN "CountryRestriction" as r + ON b."Country" >= r."From" + AND b."Country" <= r."To" + LEFT OUTER JOIN "InhouseAddress" as a + ON b."ID" = a."ParentID" + AND a."Task" = 'DEV'; + + +CREATE VIEW "PersonProtected" + AS + SELECT + b."ID", + b."ETag", + b."Type", + b."NameLine1", + b."NameLine2", + b."CreatedBy", + b."CreatedAt", + b."UpdatedBy", + b."UpdatedAt", + a."Task" as "AddressType", + a."Task", + a."Building", + a."Floor", + a."RoomNumber" + FROM "BusinessPartner" as b + LEFT OUTER JOIN "InhouseAddress" as a + ON b."ID" = a."ParentID" + WHERE b."Type" = '1' + AND a."Task" = 'DEV'; + +CREATE VIEW "RoleProtected" + AS + SELECT + role."BusinessPartnerID", + role."BusinessPartnerRole", + r."UserName" + FROM "BusinessPartnerRole" as role + INNER JOIN "RoleRestriction" as r + ON role."BusinessPartnerRole" >= r."From" + AND role."BusinessPartnerRole" <= r."To"; + +------OneToOne------------------------------ +CREATE TABLE "AssociationOneToOneSource"( + "ID" VARCHAR(32) NOT NULL, + "DEFAULTTARGET_ID" VARCHAR(32), + "TARGET" VARCHAR(32), + PRIMARY KEY ("ID")); + +insert into "AssociationOneToOneSource" values ('SA', 'TA', 'TB'); +insert into "AssociationOneToOneSource" values ('SB', 'TB', 'TA'); +insert into "AssociationOneToOneSource" values ('SC', 'TA', 'TB'); +insert into "AssociationOneToOneSource" values ('SD', null, null); + +CREATE TABLE "AssociationOneToOneTarget"( + "ID" VARCHAR(32) NOT NULL, + "SOURCE" VARCHAR(32), + PRIMARY KEY ("ID")); + +insert into "AssociationOneToOneTarget" values ('TA', 'SC'); +insert into "AssociationOneToOneTarget" values ('TB', 'SC'); + + +------Temporal Data------------------------- + +CREATE TABLE "TemporalWithValidityPeriod"( + "ID" VARCHAR(32) NOT NULL, + "StartDate" DATE NOT NULL, + "EndDate" DATE NOT NULL, + "Value" VARCHAR(255), + PRIMARY KEY ("ID", "StartDate")); + + INSERT INTO "TemporalWithValidityPeriod" values ('99', '2022-01-01', '2022-10-31','Plumber'); + INSERT INTO "TemporalWithValidityPeriod" values ('99', '2022-11-01', '9999-12-31','Electrician'); + INSERT INTO "TemporalWithValidityPeriod" values ('98', '2022-01-01', '9999-12-31','Architect'); + +-------------------------------------------- + +-- CREATE TABLE "Pages"( +-- "token" uuid NOT NULL, +-- "skip" INTEGER NOT NULL, +-- "top" INTEGER NOT NULL, +-- "baseUri" VARCHAR(255), +-- "oDataPath" VARCHAR(255), +-- "queryPath" VARCHAR(255), +-- "fragments" VARCHAR(255), +-- PRIMARY KEY ("ID", "StartDate")); + +-------------------------------------------- +CREATE TABLE "DummyToBeIgnored" ( + "ID" VARCHAR(32) NOT NULL , + --"uuid" VARCHAR(32) FOR BIT DATA , + PRIMARY KEY ("ID")); + +--UDF called SQL Language Routines at HSQLDB +--CREATE FUNCTION "Siblings" ("Publisher" VARCHAR(10), "ID" VARCHAR(10), "Division" VARCHAR(10)) +-- RETURNS TABLE( +-- "CodePublisher" VARCHAR(10), +-- "CodeID" VARCHAR(10), +-- "DivisionCode" VARCHAR(10), +-- "CountryISOCode" VARCHAR(4), +-- "ParentCodeID" VARCHAR(10), +-- "ParentDivisionCode" VARCHAR(10), +-- "AlternativeCode" VARCHAR(10), +-- "Area" int, +-- "Population" BIGINT) +-- READS SQL DATA +-- RETURN TABLE( SELECT * +-- FROM "AdministrativeDivision" as a +-- WHERE +-- EXISTS (SELECT "CodePublisher" +-- FROM "AdministrativeDivision" as b +-- WHERE b."CodeID" = "ID" +-- AND b."DivisionCode" = "Division" +-- AND b."CodePublisher" = a."CodePublisher" +-- AND b."ParentCodeID" = a."ParentCodeID" +-- AND b."ParentDivisionCode" = a."ParentDivisionCode") +-- AND NOT( a."CodePublisher" = "Publisher" +-- AND a."CodeID" = "ID" +-- AND a."DivisionCode" = "Division" ) +-- ); + \ No newline at end of file diff --git a/jpa/odata-jpa-test/src/main/resources/db/migration/V1_0__olingo.sql b/jpa/odata-jpa-test/src/main/resources/db/migration/V1_0__olingo.sql index 745159a35..d933d6d7a 100644 --- a/jpa/odata-jpa-test/src/main/resources/db/migration/V1_0__olingo.sql +++ b/jpa/odata-jpa-test/src/main/resources/db/migration/V1_0__olingo.sql @@ -1017,3 +1017,4 @@ CREATE TABLE "DummyToBeIgnored" ( -- AND a."CodeID" = "ID" -- AND a."DivisionCode" = "Division" ) -- ); + diff --git a/jpa/odata-jpa-test/src/test/java/com/sap/olingo/jpa/processor/test/TimestampLongConverterTest.java b/jpa/odata-jpa-test/src/test/java/com/sap/olingo/jpa/processor/test/TimestampLongConverterTest.java new file mode 100644 index 000000000..158f8ee61 --- /dev/null +++ b/jpa/odata-jpa-test/src/test/java/com/sap/olingo/jpa/processor/test/TimestampLongConverterTest.java @@ -0,0 +1,18 @@ +package com.sap.olingo.jpa.processor.test; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.time.Month; + +import org.junit.jupiter.api.BeforeEach; + +import com.sap.olingo.jpa.processor.core.testmodel.TimestampLongConverter; + +final class TimestampLongConverterTest extends AbstractConverterTest { + + @BeforeEach + void setup() { + cut = new TimestampLongConverter(); + exp = Timestamp.valueOf(LocalDateTime.of(2020, Month.FEBRUARY, 29, 12, 0, 0)); + } +} diff --git a/jpa/pom.xml b/jpa/pom.xml index 0d60e7772..c4cd1e451 100644 --- a/jpa/pom.xml +++ b/jpa/pom.xml @@ -27,7 +27,8 @@ 6.1.10 4.0.3 6.4.0.Final - 3.2.2 + 4.0.0 + 10.16.1.1 5.10.3 1.10.3 5.12.0 @@ -155,6 +156,16 @@ h2 2.2.224 + + org.apache.derby + derby + ${derby.version} + + + org.apache.derby + derbytools + ${derby.version} + org.flywaydb flyway-core @@ -165,6 +176,11 @@ flyway-database-hsqldb ${flyway.version} + + org.flywaydb + flyway-database-derby + ${flyway.version} + org.apache.openjpa openjpa-all