├── .gitignore
├── .idea
├── compiler.xml
├── gradle.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── jarRepositories.xml
├── misc.xml
└── vcs.xml
├── LICENSE
├── README.md
├── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── img
├── eclipse-annotation-factorypath.png
├── eclipse-annotation-processors.png
├── eclipse-errors.png
└── intellij-annotation-processors.png
├── query-validator.iml
├── settings.gradle
└── src
├── main
├── java
│ └── org
│ │ └── hibernate
│ │ └── query
│ │ └── validator
│ │ ├── ECJASTVisitor.java
│ │ ├── ECJErrorReporter.java
│ │ ├── ECJProcessor.java
│ │ ├── ECJSessionFactory.java
│ │ ├── EclipseChecker.groovy
│ │ ├── EclipseErrorReporter.groovy
│ │ ├── EclipseProcessor.groovy
│ │ ├── EclipseSessionFactory.groovy
│ │ ├── HQLProcessor.java
│ │ ├── JavacChecker.java
│ │ ├── JavacErrorReporter.java
│ │ ├── JavacProcessor.java
│ │ ├── JavacSessionFactory.java
│ │ ├── JavacTreeScanner.java
│ │ ├── MockCollectionPersister.java
│ │ ├── MockEntityPersister.java
│ │ ├── MockJdbcServicesInitiator.java
│ │ ├── MockSessionFactory.java
│ │ ├── Mocker.java
│ │ ├── ModularityWorkaround.java
│ │ ├── PanacheUtils.java
│ │ ├── Parent.java
│ │ ├── Permit.java
│ │ ├── ProcessorSessionFactory.java
│ │ └── Validation.java
└── resources
│ └── META-INF
│ └── services
│ └── javax.annotation.processing.Processor
└── test
├── java
└── org
│ └── hibernate
│ └── query
│ └── validator
│ └── test
│ └── HQLValidationTest.java
└── source
└── test
├── Address.java
├── BadQueries.java
├── Country.java
├── Email.java
├── Employee.java
├── GoodQueries.java
├── Pair.java
├── PanacheBadPerson.java
├── PanacheBadPersonRepository.java
├── PanachePerson.java
├── PanachePersonRepository.java
├── Person.java
├── Sex.java
├── package-info.java
└── test
├── Rating.java
└── package-info.java
/.gitignore:
--------------------------------------------------------------------------------
1 | **/.DS_Store
2 | .idea/workspace.xml
3 | out/
4 | target/
5 | build/
6 | .gradle
7 | dependency-reduced-pom.xml
8 | test-runtime-libs/
9 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
15 |
16 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ![Hibernate logo][]
2 |
3 | # Hibernate Query Validator
4 |
5 | Compile time validation for queries written in HQL, JPQL, and
6 | [Panache][].
7 |
8 | [Panache]: https://quarkus.io/guides/hibernate-orm-panache
9 | [Hibernate logo]: http://static.jboss.org/hibernate/images/hibernate_logo_whitebkg_200px.png
10 |
11 | ## Requirements
12 |
13 | This project now requires at least JDK 11, but JDK 15 or above
14 | is preferred.
15 |
16 | ## Building
17 |
18 | Type `./gradlew` from this project directory.
19 |
20 | This produces an artifact with the Maven coordinates
21 | `org.hibernate:query-validator:2.0-SNAPSHOT` in your local
22 | Maven repository.
23 |
24 | It also creates a far jar `query-validator-2.0-SNAPSHOT-all.jar`
25 | in the `build/libs` directory of this project.
26 |
27 | ## Usage
28 |
29 | The persistent entity classes *must* be annotated with the
30 | basic JPA metadata annotations like `@Entity`, `@ManyToOne`,
31 | `@Embeddable`, `@MappedSuperclass`, `@ElementCollection`, and
32 | `@Access`. You *may* use XML-based mappings to specify database
33 | mapping information like table and column names if that's what
34 | you prefer. But entities mapped *completely* in XML will not be
35 | discovered by the query validator.
36 |
37 | 1. Put `query-validator-2.0-SNAPSHOT-all.jar` in the
38 | compile-time classpath of your project. (Or depend on
39 | `org.hibernate:query-validator:2.0-SNAPSHOT`.)
40 | 2. Annotate a package or toplevel class with `@CheckHQL`.
41 |
42 | #### Usage with plain Hibernate or JPA
43 |
44 | The validator will check any static string argument of
45 |
46 | - the `createQuery()`, `createSelectionQuery()`, and
47 | `createMutationQuery()` methods,
48 | - the `@NamedQuery()` annotation, or
49 | - the `@HQL` annotation
50 |
51 | which occurs in a package, class, or interface annotated
52 | `@CheckHQL`.
53 |
54 | #### Usage with Panache
55 |
56 | Inside a Panache entity or repository, the following queries
57 | will be checked:
58 |
59 | - `list()`, `find()`, and `stream()`,
60 | - `count()`,
61 | - `delete()`, and
62 | - `update()`
63 |
64 | ### Errors and warnings
65 |
66 | The purpose of the query validator is to detect erroneous
67 | query strings and query parameter bindings when the Java code
68 | is compiled, instead of at runtime when the query is executed.
69 |
70 | #### Errors
71 |
72 | A compile-time error is produced if:
73 |
74 | - the query has syntax errors,
75 | - an entity name in the query doesn't reference a persistent
76 | entity class,
77 | - a member name in the query doesn't reference a mapped field
78 | or property of the entity, or
79 | - there is some other typing error, for example, incorrect
80 | function argument types.
81 |
82 | #### Warnings
83 |
84 | Additionally, any JPA `Query` instance that is created and
85 | immediately invoked in a single expression will have its
86 | parameter bindings validated. A warning is produced if:
87 |
88 | - the query string has a parameter with no argument specified
89 | using `setParameter()`, or
90 | - an argument is specified using `setParameter()`, but there
91 | is no matching parameter in the query string.
92 |
93 | All Panache queries have their parameters validated.
94 |
95 | ### Usage from command line
96 |
97 | When using a command line compiler, `gradle`, or `mvn`, errors
98 | from the query validator are displayed in the compiler output
99 | alongside other compilation errors.
100 |
101 | #### `javac` and ECJ
102 |
103 | Just compile your code with `javac`, or even with ECJ
104 | (`java -jar ecj-4.6.1.jar`), with the query validator `jar` in
105 | the classpath:
106 |
107 | -classpath query-validator-2.0-SNAPSHOT-all.jar
108 |
109 | Of course, you'll also need Hibernate core on the classpath.
110 |
111 | #### Gradle
112 |
113 | In principle, it's enough to declare dependencies on Hibernate core
114 | and on the query validator, just like this:
115 |
116 | dependencies {
117 | implementation 'org.hibernate.orm:hibernate-core:6.3.0-SNAPSHOT'
118 | annotationProcessor 'org.hibernate:query-validator:2.0-SNAPSHOT'
119 | }
120 |
121 | Unfortunately, this often results in some quite annoying warnings
122 | from `javac`. Get rid of them by also declaring an `implementation`
123 | dependency on the Query validator:
124 |
125 | dependencies {
126 | implementation 'org.hibernate:query-validator:2.0-SNAPSHOT'
127 | annotationProcessor 'org.hibernate:query-validator:2.0-SNAPSHOT'
128 | implementation 'org.hibernate:query-validator:2.0-SNAPSHOT'
129 | }
130 |
131 | #### Maven
132 |
133 | Maven handles annotation processors correctly. Just declare the
134 | dependency on the query validator:
135 |
136 |
137 |
138 | org.hibernate
139 | query-validator
140 | 2.0-SNAPSHOT
141 | true
142 |
143 |
144 |
145 | ### Usage in IDEs
146 |
147 | Both IntelliJ and Eclipse require that annotation processing
148 | be explicitly enabled.
149 |
150 | #### IntelliJ
151 |
152 | Select **Enable annotation processing** in IntelliJ IDEA
153 | preferences under **Build, Execution, Deployment > Compiler >
154 | AnnotationProcessors**.
155 |
156 | 
157 |
158 | You do not need to do this if you're using Gradle to build
159 | your project.
160 |
161 | IntelliJ only runs annotation processors during a build (that
162 | is, when you `Run` your code or explicitly `Build Project`).
163 | So you won't see errors in your Java editor as you're typing.
164 |
165 | #### Eclipse
166 |
167 | Eclipse IDE doesn't load annotation processors from the
168 | project classpath. So you'll need to add the query validator
169 | manually.
170 |
171 | 1. In **Project > Properties** go to **Java Compiler >
172 | Annotation Processing** and select **Enable annotation
173 | processing**.
174 | 2. Then go to **Java Compiler > Annotation Processing >
175 | Factory Path** and click **Add External JARs...** and
176 | add `build/libs/query-validator-2.0-SNAPSHOT-all.jar`
177 | from this project directory.
178 |
179 | Your project properties should look like this:
180 |
181 | 
182 | 
183 |
184 | Eclipse runs annotation processors during every incremental
185 | build (that is, every time you `Save`), so you'll see errors
186 | displayed inline in your Java editor.
187 |
188 | 
189 |
190 | If the query validator doesn't run, please ensure that:
191 |
192 | - Eclipse itself is running on a compatible JDK.
193 | - Your project is set up to compile with a compatible Java
194 | compiler, and the compiler compliance level is set to at
195 | least 1.8.
196 |
197 | ## Compatibility
198 |
199 | The query validator was developed and tested with:
200 |
201 | - JDK 15, JDK 17, JDK 20
202 | - Hibernate 6.3.0
203 | - ECJ 3.33.0
204 | - Eclipse IDE with JDT Core 3.33.0
205 |
206 | Other versions of `javac`, ECJ, and Hibernate may or may not
207 | work. The query validator depends on internal compiler APIs in
208 | `javac` and ECJ, and is therefore sensitive to changes in the
209 | compilers.
210 |
211 | ## Caveats
212 |
213 | Please be aware of the following issues.
214 |
215 | #### HQL is a superset of JPQL
216 |
217 | Queries are interpreted according to Hibernate's flavor of JPQL
218 | (i.e. HQL), which is a superset of the query language defined by
219 | the JPA specification. Queries accepted by the query validator
220 | may not execute correctly on other implementations of JPA.
221 |
222 | #### Explicit entity names are not supported in Eclipse/ECJ
223 |
224 | In ECJ, don't use `@Entity(name="Whatever")`, since, during an
225 | incremental build, the processor won't be able to discover the
226 | entity named `Whatever`. Just let the entity name default to
227 | the name of the class.
228 |
229 | #### Ugly error messages
230 |
231 | Please report ugly, confusing, or badly-formatted error messages
232 | as bugs.
233 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'java'
3 | id 'groovy'
4 | id 'maven-publish'
5 | id "com.github.johnrengelman.shadow" version "8.1.1"
6 | }
7 |
8 | defaultTasks 'assemble', 'publishToMavenLocal', 'shadowJar', 'test'
9 |
10 | repositories {
11 | mavenLocal()
12 | maven {
13 | url = 'https://repo.maven.apache.org/maven2'
14 | }
15 | }
16 |
17 | dependencies {
18 | implementation ('org.hibernate.orm:hibernate-core:6.3.0.Final') {
19 | transitive = false
20 | }
21 | //explicit the Hibernate dependencies we need:
22 | implementation 'org.antlr:antlr4-runtime:4.10.1'
23 | implementation 'jakarta.persistence:jakarta.persistence-api:3.1.0'
24 | runtimeOnly 'jakarta.transaction:jakarta.transaction-api:2.0.1'
25 | implementation 'net.bytebuddy:byte-buddy:1.14.5'
26 | runtimeOnly 'org.jboss.logging:jboss-logging:3.5.0.Final'
27 | runtimeOnly 'com.google.code.findbugs:jsr305:3.0.2'
28 | implementation 'org.hibernate.common:hibernate-commons-annotations:6.0.6.Final'
29 | implementation 'io.smallrye:jandex:3.1.1'
30 | runtimeOnly 'jakarta.xml.bind:jakarta.xml.bind-api:4.0.0'
31 |
32 | testRuntimeOnly ('io.quarkus:quarkus-hibernate-orm-panache:3.1.0.Final') {
33 | transitive = false
34 | }
35 | testRuntimeOnly ('io.quarkus:quarkus-panache-common:3.1.0.Final') {
36 | transitive = false
37 | }
38 |
39 | implementation 'org.apache.groovy:groovy:4.0.12'
40 |
41 | implementation 'org.eclipse.jdt:ecj:3.33.0'
42 |
43 | // testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.3'
44 | testImplementation 'junit:junit:4.13.2'
45 |
46 | implementation 'javax.xml.bind:jaxb-api:2.3.1'
47 | }
48 |
49 | group = 'org.hibernate'
50 | version = '2.0-SNAPSHOT'
51 | description = 'query-validator'
52 | sourceCompatibility = '8'
53 |
54 | sourceSets {
55 | main {
56 | groovy {
57 | srcDirs = ['src/main/java']
58 | }
59 | }
60 | }
61 |
62 | shadowJar {
63 | dependencies {
64 | exclude(dependency('org.eclipse.jdt:ecj'))
65 | // exclude "tools.jar"
66 | }
67 | relocate ('org.hibernate', 'org.hibernate.query.validator.orm') {
68 | exclude 'org.hibernate.query.validator.*'
69 | }
70 | relocate ('org.jboss', 'org.hibernate.query.validator') {
71 | exclude 'org.jboss.logging.*'
72 | }
73 | relocate 'jakarta.persistence', 'org.hibernate.query.validator.jakarta.jpa'
74 | relocate 'jakarta.transaction', 'org.hibernate.query.validator.jakarta.jta'
75 | exclude 'jakarta.activation'
76 | relocate 'net', 'org.hibernate.query.validator'
77 | relocate 'org.antlr.v4.runtime', 'org.hibernate.query.validator.antlr'
78 | relocate 'org.jboss.jandex', 'org.hibernate.query.validator.jandex'
79 | relocate 'org.apache.groovy', 'org.hibernate.query.validator.groovy.apache'
80 | relocate ('org.codehaus.groovy', 'org.hibernate.query.validator.groovy.codehaus') {
81 | exclude 'org.codehaus.groovy.runtime.*'
82 | exclude 'org.codehaus.groovy.runtime.callsite.*'
83 | }
84 | relocate ('groovy', 'org.hibernate.query.validator.groovy.groovy') {
85 | exclude 'groovy.lang.*'
86 | }
87 | relocate 'groovyjarjarantlr', 'org.hibernate.query.validator.groovy.antlr'
88 | relocate 'groovyjarjarasm', 'org.hibernate.query.validator.asm'
89 | relocate 'groovyjarjarcommonscli', 'org.hibernate.query.validator.cli'
90 | relocate 'groovyjarjarpicocli', 'org.hibernate.query.validator.picocli'
91 | }
92 |
93 | jar {
94 | duplicatesStrategy = DuplicatesStrategy.INCLUDE
95 | }
96 |
97 | publishing {
98 | publications {
99 | shadow(MavenPublication) { publication ->
100 | project.shadow.component(publication)
101 | }
102 | }
103 | }
104 |
105 | test {
106 | dependsOn 'copyDependencies'
107 | systemProperty 'gradle', 'true'
108 | }
109 |
110 | tasks.withType(JavaCompile) {
111 | options.encoding = 'UTF-8'
112 | }
113 |
114 | task copyDependencies(type: Copy) {
115 | from configurations.testRuntimeClasspath
116 | into 'test-runtime-libs'
117 | }
118 |
119 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hibernate/query-validator/bd1d1585535223e4e99bdc773e5a7da49309277e/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 | # Determine the Java command to use to start the JVM.
86 | if [ -n "$JAVA_HOME" ] ; then
87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
88 | # IBM's JDK on AIX uses strange locations for the executables
89 | JAVACMD="$JAVA_HOME/jre/sh/java"
90 | else
91 | JAVACMD="$JAVA_HOME/bin/java"
92 | fi
93 | if [ ! -x "$JAVACMD" ] ; then
94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
95 |
96 | Please set the JAVA_HOME variable in your environment to match the
97 | location of your Java installation."
98 | fi
99 | else
100 | JAVACMD="java"
101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
102 |
103 | Please set the JAVA_HOME variable in your environment to match the
104 | location of your Java installation."
105 | fi
106 |
107 | # Increase the maximum file descriptors if we can.
108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
109 | MAX_FD_LIMIT=`ulimit -H -n`
110 | if [ $? -eq 0 ] ; then
111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
112 | MAX_FD="$MAX_FD_LIMIT"
113 | fi
114 | ulimit -n $MAX_FD
115 | if [ $? -ne 0 ] ; then
116 | warn "Could not set maximum file descriptor limit: $MAX_FD"
117 | fi
118 | else
119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
120 | fi
121 | fi
122 |
123 | # For Darwin, add options to specify how the application appears in the dock
124 | if $darwin; then
125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
126 | fi
127 |
128 | # For Cygwin, switch paths to Windows format before running java
129 | if $cygwin ; then
130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
132 | JAVACMD=`cygpath --unix "$JAVACMD"`
133 |
134 | # We build the pattern for arguments to be converted via cygpath
135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
136 | SEP=""
137 | for dir in $ROOTDIRSRAW ; do
138 | ROOTDIRS="$ROOTDIRS$SEP$dir"
139 | SEP="|"
140 | done
141 | OURCYGPATTERN="(^($ROOTDIRS))"
142 | # Add a user-defined pattern to the cygpath arguments
143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
145 | fi
146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
147 | i=0
148 | for arg in "$@" ; do
149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
151 |
152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
154 | else
155 | eval `echo args$i`="\"$arg\""
156 | fi
157 | i=$((i+1))
158 | done
159 | case $i in
160 | (0) set -- ;;
161 | (1) set -- "$args0" ;;
162 | (2) set -- "$args0" "$args1" ;;
163 | (3) set -- "$args0" "$args1" "$args2" ;;
164 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
165 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
166 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
167 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
168 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
169 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
170 | esac
171 | fi
172 |
173 | # Escape application args
174 | save () {
175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
176 | echo " "
177 | }
178 | APP_ARGS=$(save "$@")
179 |
180 | # Collect all arguments for the java command, following the shell quoting and substitution rules
181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
182 |
183 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
184 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
185 | cd "$(dirname "$0")"
186 | fi
187 |
188 | exec "$JAVACMD" "$@"
189 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem http://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
34 |
35 | @rem Find java.exe
36 | if defined JAVA_HOME goto findJavaFromJavaHome
37 |
38 | set JAVA_EXE=java.exe
39 | %JAVA_EXE% -version >NUL 2>&1
40 | if "%ERRORLEVEL%" == "0" goto init
41 |
42 | echo.
43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
44 | echo.
45 | echo Please set the JAVA_HOME variable in your environment to match the
46 | echo location of your Java installation.
47 |
48 | goto fail
49 |
50 | :findJavaFromJavaHome
51 | set JAVA_HOME=%JAVA_HOME:"=%
52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
53 |
54 | if exist "%JAVA_EXE%" goto init
55 |
56 | echo.
57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
58 | echo.
59 | echo Please set the JAVA_HOME variable in your environment to match the
60 | echo location of your Java installation.
61 |
62 | goto fail
63 |
64 | :init
65 | @rem Get command-line arguments, handling Windows variants
66 |
67 | if not "%OS%" == "Windows_NT" goto win9xME_args
68 |
69 | :win9xME_args
70 | @rem Slurp the command line arguments.
71 | set CMD_LINE_ARGS=
72 | set _SKIP=2
73 |
74 | :win9xME_args_slurp
75 | if "x%~1" == "x" goto execute
76 |
77 | set CMD_LINE_ARGS=%*
78 |
79 | :execute
80 | @rem Setup the command line
81 |
82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
83 |
84 | @rem Execute Gradle
85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
86 |
87 | :end
88 | @rem End local scope for the variables with windows NT shell
89 | if "%ERRORLEVEL%"=="0" goto mainEnd
90 |
91 | :fail
92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
93 | rem the _cmd.exe /c_ return code!
94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
95 | exit /b 1
96 |
97 | :mainEnd
98 | if "%OS%"=="Windows_NT" endlocal
99 |
100 | :omega
101 |
--------------------------------------------------------------------------------
/img/eclipse-annotation-factorypath.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hibernate/query-validator/bd1d1585535223e4e99bdc773e5a7da49309277e/img/eclipse-annotation-factorypath.png
--------------------------------------------------------------------------------
/img/eclipse-annotation-processors.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hibernate/query-validator/bd1d1585535223e4e99bdc773e5a7da49309277e/img/eclipse-annotation-processors.png
--------------------------------------------------------------------------------
/img/eclipse-errors.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hibernate/query-validator/bd1d1585535223e4e99bdc773e5a7da49309277e/img/eclipse-errors.png
--------------------------------------------------------------------------------
/img/intellij-annotation-processors.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hibernate/query-validator/bd1d1585535223e4e99bdc773e5a7da49309277e/img/intellij-annotation-processors.png
--------------------------------------------------------------------------------
/query-validator.iml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'query-validator'
2 |
--------------------------------------------------------------------------------
/src/main/java/org/hibernate/query/validator/ECJASTVisitor.java:
--------------------------------------------------------------------------------
1 | package org.hibernate.query.validator;
2 |
3 | import org.eclipse.jdt.internal.compiler.ASTVisitor;
4 | import org.eclipse.jdt.internal.compiler.Compiler;
5 | import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
6 | import org.eclipse.jdt.internal.compiler.ast.Expression;
7 | import org.eclipse.jdt.internal.compiler.ast.IntLiteral;
8 | import org.eclipse.jdt.internal.compiler.ast.MemberValuePair;
9 | import org.eclipse.jdt.internal.compiler.ast.MessageSend;
10 | import org.eclipse.jdt.internal.compiler.ast.SingleNameReference;
11 | import org.eclipse.jdt.internal.compiler.ast.StringLiteral;
12 | import org.eclipse.jdt.internal.compiler.ast.ThisReference;
13 | import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
14 |
15 | import javax.annotation.processing.ProcessingEnvironment;
16 | import javax.lang.model.element.TypeElement;
17 | import java.util.HashSet;
18 | import java.util.Set;
19 |
20 | import static java.lang.Integer.parseInt;
21 | import static org.eclipse.jdt.core.compiler.CharOperation.charToString;
22 | import static org.hibernate.query.validator.ECJSessionFactory.qualifiedName;
23 | import static org.hibernate.query.validator.HQLProcessor.hibernate;
24 | import static org.hibernate.query.validator.HQLProcessor.jpa;
25 | import static org.hibernate.query.validator.Validation.validate;
26 |
27 | /**
28 | * @author Gavin King
29 | */
30 | class ECJASTVisitor extends ASTVisitor {
31 | final Set setParameterLabels;
32 | final Set setParameterNames;
33 | final Set setOrderBy;
34 | private final TypeElement panacheEntity;
35 | private final CompilationUnitDeclaration unit;
36 | private final Compiler compiler;
37 | private final ProcessingEnvironment processingEnv;
38 | boolean immediatelyCalled;
39 |
40 | public ECJASTVisitor(TypeElement panacheEntity, CompilationUnitDeclaration unit, Compiler compiler, ProcessingEnvironment processingEnv) {
41 | this.panacheEntity = panacheEntity;
42 | this.unit = unit;
43 | this.compiler = compiler;
44 | this.processingEnv = processingEnv;
45 | setParameterLabels = new HashSet<>();
46 | setParameterNames = new HashSet<>();
47 | setOrderBy = new HashSet<>();
48 | }
49 |
50 | @Override
51 | public boolean visit(MessageSend messageSend, BlockScope scope) {
52 | String name = charToString(messageSend.selector);
53 | switch (name) {
54 | case "getResultList":
55 | case "getSingleResult":
56 | case "getSingleResultOrNull":
57 | immediatelyCalled = true;
58 | break;
59 | case "count":
60 | case "delete":
61 | case "update":
62 | case "exists":
63 | case "stream":
64 | case "list":
65 | case "find":
66 | // Disable until we can make this type-safe for Javac
67 | // if (messageSend.receiver instanceof SingleNameReference) {
68 | // SingleNameReference ref = (SingleNameReference) messageSend.receiver;
69 | // String target = charToString(ref.token);
70 | // StringLiteral queryArg = firstArgument(messageSend);
71 | // if (queryArg != null) {
72 | // checkPanacheQuery(queryArg, target, name, charToString(queryArg.source()), messageSend.arguments);
73 | // }
74 | if (messageSend.receiver instanceof ThisReference && panacheEntity != null) {
75 | String target = panacheEntity.getSimpleName().toString();
76 | StringLiteral queryArg = firstArgument(messageSend);
77 | if (queryArg != null) {
78 | String panacheQl = charToString(queryArg.source());
79 | checkPanacheQuery(queryArg, target, name, panacheQl, messageSend.arguments);
80 | }
81 | }
82 | break;
83 | case "createQuery":
84 | case "createSelectionQuery":
85 | case "createMutationQuery":
86 | for (Expression argument : messageSend.arguments) {
87 | if (argument instanceof StringLiteral) {
88 | check((StringLiteral) argument, true);
89 | }
90 | break;
91 | }
92 | break;
93 | case "setParameter":
94 | for (Expression argument : messageSend.arguments) {
95 | if (argument instanceof StringLiteral) {
96 | String paramName =
97 | charToString(((StringLiteral) argument)
98 | .source());
99 | setParameterNames.add(paramName);
100 | } else if (argument instanceof IntLiteral) {
101 | int paramLabel = parseInt(new String(((IntLiteral) argument).source()));
102 | setParameterLabels.add(paramLabel);
103 | }
104 | //the remaining parameters aren't parameter ids!
105 | break;
106 | }
107 |
108 | break;
109 | }
110 | return true;
111 | }
112 |
113 | private StringLiteral firstArgument(MessageSend messageSend) {
114 | for (Expression argument : messageSend.arguments) {
115 | if (argument instanceof StringLiteral) {
116 | return (StringLiteral) argument;
117 | }
118 | }
119 | return null;
120 | }
121 |
122 | @Override
123 | public void endVisit(MessageSend messageSend, BlockScope scope) {
124 | String name = charToString(messageSend.selector);
125 | switch (name) {
126 | case "getResultList":
127 | case "getSingleResult":
128 | immediatelyCalled = false;
129 | break;
130 | }
131 | }
132 |
133 | @Override
134 | public boolean visit(MemberValuePair pair, BlockScope scope) {
135 | String qualifiedName = qualifiedName(pair.binding);
136 | if (qualifiedName.equals(jpa("NamedQuery.query"))
137 | || qualifiedName.equals(hibernate("NamedQuery.query"))
138 | || qualifiedName.equals(hibernate("processing.HQL.value"))) {
139 | if (pair.value instanceof StringLiteral) {
140 | check((StringLiteral) pair.value, false);
141 | }
142 | }
143 | return true;
144 | }
145 |
146 | void check(StringLiteral stringLiteral, boolean inCreateQueryMethod) {
147 | String hql = charToString(stringLiteral.source());
148 | ECJErrorReporter handler = new ECJErrorReporter(stringLiteral, unit, compiler, hql);
149 | validate(hql, inCreateQueryMethod && immediatelyCalled,
150 | setParameterLabels, setParameterNames, handler,
151 | // ProcessorSessionFactory.instance.make(processingEnv));
152 | ECJProcessor.sessionFactory.make(unit));
153 | }
154 |
155 | void checkPanacheQuery(StringLiteral stringLiteral, String targetType, String methodName,
156 | String panacheQl, Expression[] args) {
157 | ECJErrorReporter handler = new ECJErrorReporter(stringLiteral, unit, compiler, panacheQl);
158 | collectPanacheArguments(args);
159 | int[] offset = new int[1];
160 | String hql = PanacheUtils.panacheQlToHql(handler, targetType, methodName,
161 | panacheQl, offset, setParameterLabels, setOrderBy);
162 | if (hql != null) {
163 | validate(hql, true,
164 | setParameterLabels, setParameterNames, handler,
165 | // ProcessorSessionFactory.instance.make(processingEnv),
166 | ECJProcessor.sessionFactory.make(unit),
167 | offset[0]);
168 | }
169 | }
170 |
171 | private void collectPanacheArguments(Expression[] args) {
172 | // first arg is pql
173 | // second arg can be Sort, Object..., Map or Parameters
174 | setParameterLabels.clear();
175 | setParameterNames.clear();
176 | setOrderBy.clear();
177 | if (args.length > 1) {
178 | int firstArgIndex = 1;
179 | if (isPanacheSortCall(args[firstArgIndex])) {
180 | firstArgIndex++;
181 | }
182 |
183 | if (args.length > firstArgIndex) {
184 | Expression firstArg = args[firstArgIndex];
185 | isParametersCall(firstArg);
186 | if (setParameterNames.isEmpty()) {
187 | for (int i = 0; i < args.length - firstArgIndex; i++) {
188 | setParameterLabels.add(1 + i);
189 | }
190 | }
191 | }
192 | }
193 | }
194 |
195 | private boolean isParametersCall(Expression firstArg) {
196 | if (firstArg instanceof MessageSend) {
197 | MessageSend invocation = (MessageSend) firstArg;
198 | String fieldName = charToString(invocation.selector);
199 | if (fieldName.equals("and") && isParametersCall(invocation.receiver)) {
200 | StringLiteral queryArg = firstArgument(invocation);
201 | if (queryArg != null) {
202 | setParameterNames.add(charToString(queryArg.source()));
203 | return true;
204 | }
205 | }
206 | else if (fieldName.equals("with")
207 | && invocation.receiver instanceof SingleNameReference) {
208 | SingleNameReference receiver = (SingleNameReference) invocation.receiver;
209 | String target = charToString(receiver.token);
210 | if (target.equals("Parameters")) {
211 | StringLiteral queryArg = firstArgument(invocation);
212 | if (queryArg != null) {
213 | setParameterNames.add(charToString(queryArg.source()));
214 | return true;
215 | }
216 | }
217 | }
218 | }
219 | return false;
220 | }
221 |
222 | private boolean isPanacheSortCall(Expression firstArg) {
223 | if (firstArg instanceof MessageSend) {
224 | MessageSend invocation = (MessageSend) firstArg;
225 | String fieldName = charToString(invocation.selector);
226 | if ((fieldName.equals("and")
227 | || fieldName.equals("descending")
228 | || fieldName.equals("ascending")
229 | || fieldName.equals("direction"))
230 | && isPanacheSortCall(invocation.receiver)) {
231 | for (Expression e : invocation.arguments) {
232 | if (e instanceof StringLiteral) {
233 | StringLiteral lit = (StringLiteral) e;
234 | setOrderBy.add(charToString(lit.source()));
235 | }
236 | }
237 | return true;
238 | }
239 | else if ((fieldName.equals("by")
240 | || fieldName.equals("descending")
241 | || fieldName.equals("ascending"))
242 | && invocation.receiver instanceof SingleNameReference) {
243 | SingleNameReference receiver = (SingleNameReference) invocation.receiver;
244 | String target = charToString(receiver.token);
245 | if (target.equals("Sort")) {
246 | for (Expression e : invocation.arguments) {
247 | if (e instanceof StringLiteral) {
248 | StringLiteral lit = (StringLiteral) e;
249 | setOrderBy.add(charToString(lit.source()));
250 | }
251 | }
252 | return true;
253 | }
254 | }
255 | }
256 | return false;
257 | }
258 | }
259 |
--------------------------------------------------------------------------------
/src/main/java/org/hibernate/query/validator/ECJErrorReporter.java:
--------------------------------------------------------------------------------
1 | package org.hibernate.query.validator;
2 |
3 | import org.antlr.v4.runtime.LexerNoViableAltException;
4 | import org.antlr.v4.runtime.Parser;
5 | import org.antlr.v4.runtime.RecognitionException;
6 | import org.antlr.v4.runtime.Recognizer;
7 | import org.antlr.v4.runtime.Token;
8 | import org.antlr.v4.runtime.atn.ATNConfigSet;
9 | import org.antlr.v4.runtime.dfa.DFA;
10 | import org.eclipse.jdt.core.compiler.CategorizedProblem;
11 | import org.eclipse.jdt.internal.compiler.CompilationResult;
12 | import org.eclipse.jdt.internal.compiler.Compiler;
13 | import org.eclipse.jdt.internal.compiler.ast.ASTNode;
14 | import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
15 | import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities;
16 |
17 | import java.util.BitSet;
18 |
19 | import static org.eclipse.jdt.internal.compiler.util.Util.getLineNumber;
20 | import static org.eclipse.jdt.internal.compiler.util.Util.searchColumnNumber;
21 | import static org.hibernate.query.hql.internal.StandardHqlTranslator.prettifyAntlrError;
22 |
23 | /**
24 | * @author Gavin King
25 | */
26 | class ECJErrorReporter implements Validation.Handler {
27 |
28 | private final ASTNode node;
29 | private final CompilationUnitDeclaration unit;
30 | private final Compiler compiler;
31 | private final String hql;
32 | private int errorcount;
33 |
34 | ECJErrorReporter(ASTNode node,
35 | CompilationUnitDeclaration unit,
36 | Compiler compiler,
37 | String hql) {
38 | this.node = node;
39 | this.unit = unit;
40 | this.compiler = compiler;
41 | this.hql = hql;
42 | }
43 |
44 | @Override
45 | public int getErrorCount() {
46 | return errorcount;
47 | }
48 |
49 | @Override
50 | public void syntaxError(Recognizer, ?> recognizer, Object symbol, int line, int charInLine, String message, RecognitionException e) {
51 | message = prettifyAntlrError(symbol, line, charInLine, message, e, hql, false);
52 | errorcount++;
53 | CompilationResult result = unit.compilationResult();
54 | char[] fileName = result.fileName;
55 | int[] lineEnds = result.getLineSeparatorPositions();
56 | int startIndex;
57 | int stopIndex;
58 | int lineNum = getLineNumber(node.sourceStart, lineEnds, 0, lineEnds.length - 1);
59 | Token offendingToken = e.getOffendingToken();
60 | if (offendingToken != null) {
61 | startIndex = offendingToken.getStartIndex();
62 | stopIndex = offendingToken.getStopIndex();
63 | } else if (e instanceof LexerNoViableAltException) {
64 | startIndex = ((LexerNoViableAltException) e).getStartIndex();
65 | stopIndex = startIndex;
66 | } else {
67 | startIndex = lineEnds[line - 1] + charInLine;
68 | stopIndex = startIndex;
69 | }
70 | int startPosition = node.sourceStart + startIndex + 1;
71 | int endPosition = node.sourceStart + stopIndex + 1;
72 | if (endPosition < startPosition) {
73 | endPosition = startPosition;
74 | }
75 | CategorizedProblem problem =
76 | compiler.problemReporter.problemFactory
77 | .createProblem(fileName, 0,
78 | new String[]{message},
79 | new String[]{message},
80 | ProblemSeverities.Error,
81 | startPosition,
82 | endPosition,
83 | lineNum + line - 1, -1);
84 | compiler.problemReporter.record(problem, result, unit, true);
85 | }
86 |
87 | @Override
88 | public void reportAmbiguity(Parser parser, DFA dfa, int i, int i1, boolean b, BitSet bitSet, ATNConfigSet atnConfigSet) {
89 | }
90 |
91 | @Override
92 | public void reportAttemptingFullContext(Parser parser, DFA dfa, int i, int i1, BitSet bitSet, ATNConfigSet atnConfigSet) {
93 | }
94 |
95 | @Override
96 | public void reportContextSensitivity(Parser parser, DFA dfa, int i, int i1, int i2, ATNConfigSet atnConfigSet) {
97 | }
98 |
99 | @Override
100 | public void error(int start, int end, String message) {
101 | report(ProblemSeverities.Error, message, start, end);
102 | }
103 |
104 | @Override
105 | public void warn(int start, int end, String message) {
106 | report(ProblemSeverities.Warning, message, start, end);
107 | }
108 |
109 | private void report(int severity, String message, int offset, int endOffset) {
110 | errorcount++;
111 | CompilationResult result = unit.compilationResult();
112 | char[] fileName = result.fileName;
113 | int[] lineEnds = result.getLineSeparatorPositions();
114 | int startPosition;
115 | int endPosition;
116 | if (node != null) {
117 | startPosition = node.sourceStart + offset;
118 | endPosition = endOffset < 0 ?
119 | node.sourceEnd - 1 :
120 | node.sourceStart + endOffset;
121 | } else {
122 | startPosition = 0;
123 | endPosition = 0;
124 | }
125 | int lineNumber = startPosition >= 0
126 | ? getLineNumber(startPosition, lineEnds, 0, lineEnds.length - 1)
127 | : 0;
128 | int columnNumber = startPosition >= 0
129 | ? searchColumnNumber(lineEnds, lineNumber, startPosition)
130 | : 0;
131 |
132 | CategorizedProblem problem =
133 | compiler.problemReporter.problemFactory
134 | .createProblem(fileName, 0,
135 | new String[]{message},
136 | new String[]{message},
137 | severity,
138 | startPosition, endPosition,
139 | lineNumber, columnNumber);
140 | compiler.problemReporter.record(problem, result, unit, true);
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/src/main/java/org/hibernate/query/validator/ECJProcessor.java:
--------------------------------------------------------------------------------
1 | package org.hibernate.query.validator;
2 |
3 | import static org.hibernate.query.validator.ECJSessionFactory.getAnnotation;
4 | import static org.hibernate.query.validator.ECJSessionFactory.qualifiedName;
5 | import static org.hibernate.query.validator.HQLProcessor.CHECK_HQL;
6 |
7 | import java.util.Set;
8 |
9 | import javax.annotation.processing.AbstractProcessor;
10 | import javax.annotation.processing.ProcessingEnvironment;
11 | import javax.annotation.processing.RoundEnvironment;
12 | import javax.lang.model.SourceVersion;
13 | import javax.lang.model.element.TypeElement;
14 | import javax.lang.model.util.Elements;
15 | import javax.tools.Diagnostic;
16 |
17 | import org.eclipse.jdt.internal.compiler.Compiler;
18 | import org.eclipse.jdt.internal.compiler.apt.dispatch.BaseProcessingEnvImpl;
19 | import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
20 | import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
21 | import org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding;
22 | import org.eclipse.jdt.internal.compiler.lookup.Binding;
23 | import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
24 |
25 | /**
26 | * Annotation processor that validates HQL and JPQL queries
27 | * for ECJ.
28 | *
29 | * @see org.hibernate.annotations.processing.CheckHQL
30 | *
31 | * @author Gavin King
32 | */
33 | //@SupportedAnnotationTypes(CHECK_HQL)
34 | public class ECJProcessor extends AbstractProcessor {
35 |
36 | static Mocker sessionFactory = Mocker.variadic(ECJSessionFactory.class);
37 |
38 | @Override
39 | public synchronized void init(ProcessingEnvironment processingEnv) {
40 | super.init(processingEnv);
41 | processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Hibernate Query Validator for ECJ");
42 | }
43 |
44 | @Override
45 | public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
46 | Compiler compiler = ((BaseProcessingEnvImpl) processingEnv).getCompiler();
47 | if (!roundEnv.getRootElements().isEmpty()) {
48 | for (CompilationUnitDeclaration unit : compiler.unitsToProcess) {
49 | compiler.parser.getMethodBodies(unit);
50 | checkHQL(unit, compiler);
51 | }
52 | }
53 | return true;
54 | }
55 |
56 | private void checkHQL(CompilationUnitDeclaration unit, Compiler compiler) {
57 | for (TypeDeclaration type : unit.types) {
58 | if (isCheckable(type.binding, unit)) {
59 | // List whitelist = getWhitelist(type.binding, unit, compiler);
60 | Elements elements = processingEnv.getElementUtils();
61 | TypeElement typeElement = elements.getTypeElement(qualifiedName(type.binding));
62 | TypeElement panacheEntity = PanacheUtils.isPanache(typeElement, processingEnv.getTypeUtils(), elements);
63 | type.traverse(new ECJASTVisitor(panacheEntity, unit, compiler, processingEnv), unit.scope);
64 | }
65 | }
66 | }
67 |
68 | private static boolean isCheckable(TypeBinding type, CompilationUnitDeclaration unit) {
69 | return getCheckAnnotation(type, unit)!=null;
70 | }
71 | //
72 | // private static List getWhitelist(TypeBinding type,
73 | // CompilationUnitDeclaration unit,
74 | // Compiler compiler) {
75 | // ElementValuePair[] members =
76 | // getCheckAnnotation(type, unit).getElementValuePairs();
77 | // if (members==null || members.length==0) {
78 | // return emptyList();
79 | // }
80 | // List names = new ArrayList<>();
81 | // for (ElementValuePair pair: members) {
82 | // Object value = pair.value;
83 | // if (value instanceof Object[]) {
84 | // for (Object literal : (Object[]) value) {
85 | // if (literal instanceof StringConstant) {
86 | // names.add(((StringConstant) literal).stringValue());
87 | // }
88 | // }
89 | // }
90 | // else if (value instanceof StringConstant) {
91 | // names.add(((StringConstant) value).stringValue());
92 | // }
93 | // else if (value instanceof BinaryTypeBinding) {
94 | //// String name = qualifiedName((BinaryTypeBinding) value);
95 | // names.addAll(MockSessionFactory.functionRegistry.getValidFunctionKeys());
96 | // }
97 | // }
98 | // return names;
99 | // }
100 |
101 | private static AnnotationBinding getCheckAnnotation(TypeBinding type,
102 | CompilationUnitDeclaration unit) {
103 | AnnotationBinding result = getAnnotation(type, CHECK_HQL);
104 | if (result!=null) return result;
105 | Binding packInfo = unit.scope.getType("package-info".toCharArray());
106 | return getAnnotation(packInfo, CHECK_HQL);
107 | }
108 |
109 | @Override
110 | public SourceVersion getSupportedSourceVersion() {
111 | return SourceVersion.latestSupported();
112 | }
113 |
114 | }
115 |
--------------------------------------------------------------------------------
/src/main/java/org/hibernate/query/validator/EclipseChecker.groovy:
--------------------------------------------------------------------------------
1 | package org.hibernate.query.validator
2 |
3 | import static org.hibernate.query.validator.EclipseSessionFactory.getAnnotation
4 | import static org.hibernate.query.validator.HQLProcessor.CHECK_HQL
5 |
6 | /**
7 | * @author Gavin King
8 | */
9 | class EclipseChecker {
10 |
11 | Set setParameterLabels = new HashSet<>()
12 | Set setParameterNames = new HashSet<>()
13 | Set setOrderBy = new HashSet<>()
14 | boolean immediatelyCalled = false
15 |
16 | private def unit
17 | private def compiler
18 | // private List whitelist
19 | private def processingEnv
20 |
21 | EclipseChecker(unit, compiler, processingEnv) {
22 | this.compiler = compiler
23 | this.unit = unit
24 | this.processingEnv = processingEnv
25 | }
26 |
27 | void checkHQL() {
28 | for (type in unit.types) {
29 | if (isCheckable(type.binding, unit)) {
30 | // whitelist = getWhitelist(type.binding, unit, compiler)
31 | type.annotations.each { annotation ->
32 | switch (EclipseSessionFactory.qualifiedTypeName(annotation.resolvedType)) {
33 | case HQLProcessor.hibernate("processing.HQL"):
34 | annotation.memberValuePairs.each { pair ->
35 | if (EclipseSessionFactory.simpleVariableName(pair) == "value") {
36 | validateArgument(pair.value, false)
37 | }
38 | }
39 | break
40 | case HQLProcessor.jpa("NamedQuery"):
41 | case HQLProcessor.hibernate("NamedQuery"):
42 | annotation.memberValuePairs.each { pair ->
43 | if (EclipseSessionFactory.simpleVariableName(pair) == "query") {
44 | validateArgument(pair.value, false)
45 | }
46 | }
47 | break
48 | case HQLProcessor.jpa("NamedQueries"):
49 | annotation.memberValue.expressions.each { ann ->
50 | ann.memberValuePairs.each { pair ->
51 | if (EclipseSessionFactory.simpleVariableName(pair) == "query") {
52 | validateArgument(pair.value, false)
53 | }
54 | }
55 | }
56 | break
57 | }
58 | }
59 | def elements = processingEnv.getElementUtils()
60 | def typeElement = elements.getTypeElement(qualifiedName(type.binding))
61 | def panacheEntity =
62 | PanacheUtils.isPanache(typeElement, processingEnv.getTypeUtils(), elements)
63 | type.methods.each { method ->
64 | validateStatements(type, panacheEntity, method.statements)
65 | }
66 | }
67 | }
68 | }
69 |
70 | private static String qualifiedName(type) {
71 | String pkgName = charToString(type.qualifiedPackageName())
72 | String className = charToString(type.qualifiedSourceName())
73 | return pkgName.isEmpty() ? className : pkgName + "." + className
74 | }
75 |
76 | private void validateStatements(type, panacheEntity, statements) {
77 | statements.each { statement -> validateStatement(type, panacheEntity, statement) }
78 | }
79 |
80 | private void validateStatement(type, panacheEntity, statement) {
81 | if (statement != null) switch (statement.getClass().simpleName) {
82 | case "MessageSend":
83 | boolean ic = immediatelyCalled
84 | def name = EclipseSessionFactory.simpleMethodName(statement)
85 | switch (name) {
86 | case "getResultList":
87 | case "getSingleResult":
88 | case "getSingleResultOrNull":
89 | immediatelyCalled = true
90 | break
91 | case "count":
92 | case "delete":
93 | case "update":
94 | case "exists":
95 | case "stream":
96 | case "list":
97 | case "find":
98 | // Disabled until we find how to support this type-safe in Javac
99 | // if (statement.receiver.getClass().simpleName == "SingleNameReference") {
100 | // def ref = statement.receiver;
101 | // String target = charToString(ref.token);
102 | // def queryArg = firstArgument(statement);
103 | // if (queryArg != null) {
104 | // checkPanacheQuery(queryArg, target, name, charToString(queryArg.source()), statement.arguments)
105 | // }
106 | if (statement.receiver.getClass().simpleName == "ThisReference" && panacheEntity != null) {
107 | String target = panacheEntity.getSimpleName().toString()
108 | def queryArg = firstArgument(statement)
109 | if (queryArg != null) {
110 | checkPanacheQuery(queryArg, target, name, charToString(queryArg.source()), statement.arguments)
111 | }
112 | }
113 | break
114 | case "createQuery":
115 | case "createSelectionQuery":
116 | case "createMutationQuery":
117 | statement.arguments.each { arg ->
118 | if (arg.getClass().simpleName == "StringLiteral"
119 | || arg.getClass().simpleName == "ExtendedStringLiteral") {
120 | validateArgument(arg, true)
121 | }
122 | }
123 | break
124 | case "setParameter":
125 | def arg = statement.arguments.first()
126 | switch (arg.getClass().simpleName) {
127 | case "IntLiteral":
128 | setParameterLabels.add(Integer.parseInt(new String((char[])arg.source())))
129 | break
130 | case "StringLiteral":
131 | case "ExtendedStringLiteral":
132 | setParameterNames.add(new String((char[])arg.source()))
133 | break
134 | }
135 | break
136 | }
137 | validateStatement(type, panacheEntity, statement.receiver)
138 | setParameterLabels.clear()
139 | setParameterNames.clear()
140 | immediatelyCalled = ic
141 | validateStatements(type, panacheEntity, statement.arguments)
142 | break
143 | case "AbstractVariableDeclaration":
144 | validateStatement(type, panacheEntity, statement.initialization)
145 | break
146 | case "AssertStatement":
147 | validateStatement(type, panacheEntity, statement.assertExpression)
148 | break
149 | case "Block":
150 | validateStatements(type, panacheEntity, statement.statements)
151 | break
152 | case "SwitchStatement":
153 | validateStatement(type, panacheEntity, statement.expression)
154 | validateStatements(type, panacheEntity, statement.statements)
155 | break
156 | case "ForStatement":
157 | validateStatement(type, panacheEntity, statement.action)
158 | break
159 | case "ForeachStatement":
160 | validateStatement(type, panacheEntity, statement.collection)
161 | validateStatement(type, panacheEntity, statement.action)
162 | break
163 | case "DoStatement":
164 | case "WhileStatement":
165 | validateStatement(type, panacheEntity, statement.condition)
166 | validateStatement(type, panacheEntity, statement.action)
167 | break
168 | case "IfStatement":
169 | validateStatement(type, panacheEntity, statement.condition)
170 | validateStatement(type, panacheEntity, statement.thenStatement)
171 | validateStatement(type, panacheEntity, statement.elseStatement)
172 | break
173 | case "TryStatement":
174 | validateStatement(type, panacheEntity, statement.tryBlock)
175 | validateStatements(type, panacheEntity, statement.catchBlocks)
176 | validateStatement(type, panacheEntity, statement.finallyBlock)
177 | break
178 | case "SynchronizedStatement":
179 | validateStatement(type, panacheEntity, statement.expression)
180 | validateStatement(type, panacheEntity, statement.block)
181 | break
182 | case "BinaryExpression":
183 | validateStatement(type, panacheEntity, statement.left)
184 | validateStatement(type, panacheEntity, statement.right)
185 | break
186 | case "UnaryExpression":
187 | case "CastExpression":
188 | case "InstanceOfExpression":
189 | validateStatement(type, panacheEntity, statement.expression)
190 | break
191 | case "ConditionalExpression":
192 | validateStatement(type, panacheEntity, statement.condition)
193 | validateStatement(type, panacheEntity, statement.valueIfTrue)
194 | validateStatement(type, panacheEntity, statement.valueIfFalse)
195 | break
196 | case "LambdaExpression":
197 | validateStatement(type, panacheEntity, statement.body)
198 | break
199 | case "ArrayInitializer":
200 | validateStatements(type, panacheEntity, statement.expressions)
201 | break
202 | case "ArrayAllocationExpression":
203 | validateStatements(type, panacheEntity, statement.initializer)
204 | break
205 | case "Assignment":
206 | validateStatement(type, panacheEntity, statement.lhs)
207 | validateStatement(type, panacheEntity, statement.expression)
208 | break
209 | case "AllocationExpression":
210 | validateStatements(type, panacheEntity, statement.arguments)
211 | break
212 | case "ReturnStatement":
213 | validateStatement(type, panacheEntity, statement.expression)
214 | break
215 | case "ThrowStatement":
216 | validateStatement(type, panacheEntity, statement.exception)
217 | break
218 | case "LabeledStatement":
219 | validateStatement(type, panacheEntity, statement.statement)
220 | break
221 | }
222 | }
223 |
224 | static def firstArgument(messageSend) {
225 | for (argument in messageSend.arguments) {
226 | if (argument.getClass().simpleName == "StringLiteral" ||
227 | argument.getClass().simpleName == "ExtendedStringLiteral") {
228 | return argument
229 | }
230 | }
231 | return null
232 | }
233 |
234 | void validateArgument(arg, boolean inCreateQueryMethod) {
235 | String hql = new String((char[]) arg.source())
236 | EclipseErrorReporter handler = new EclipseErrorReporter(arg, unit, compiler, hql)
237 | Validation.validate(hql, inCreateQueryMethod && immediatelyCalled,
238 | setParameterLabels, setParameterNames, handler,
239 | EclipseProcessor.sessionFactory.make(unit))
240 | }
241 |
242 | void checkPanacheQuery(stringLiteral, targetType, methodName, panacheQl, args) {
243 | EclipseErrorReporter handler = new EclipseErrorReporter(stringLiteral, unit, compiler, panacheQl)
244 | collectPanacheArguments(args)
245 | int[] offset = new int[1]
246 | String hql = PanacheUtils.panacheQlToHql(handler, targetType, methodName,
247 | panacheQl, offset, setParameterLabels, setOrderBy)
248 | if (hql != null) {
249 | Validation.validate(hql, true,
250 | setParameterLabels, setParameterNames, handler,
251 | EclipseProcessor.sessionFactory.make(unit), offset[0])
252 | }
253 | }
254 |
255 | static String charToString(char[] charArray) {
256 | if (charArray == null) return null
257 | return new String(charArray)
258 | }
259 |
260 | void collectPanacheArguments(args) {
261 | // first arg is pql
262 | // second arg can be Sort, Object..., Map or Parameters
263 | setParameterLabels.clear()
264 | setParameterNames.clear()
265 | setOrderBy.clear()
266 | if (args.length > 1) {
267 | int firstArgIndex = 1
268 | if (isPanacheSortCall(args[firstArgIndex])) {
269 | firstArgIndex++
270 | }
271 |
272 | if (args.length > firstArgIndex) {
273 | def firstArg = args[firstArgIndex]
274 | isParametersCall(firstArg)
275 | if (setParameterNames.isEmpty()) {
276 | for (int i = 0 ; i < args.length - firstArgIndex ; i++) {
277 | setParameterLabels.add(1 + i)
278 | }
279 | }
280 | }
281 | }
282 | }
283 | boolean isParametersCall(firstArg) {
284 | if (firstArg.getClass().simpleName == "MessageSend") {
285 | def invocation = firstArg
286 | String fieldName = charToString(invocation.selector)
287 | if (fieldName.equals("and") && isParametersCall(invocation.receiver)) {
288 | def queryArg = firstArgument(invocation)
289 | if (queryArg != null) {
290 | setParameterNames.add(charToString(queryArg.source()))
291 | return true
292 | }
293 | }
294 | else if (fieldName.equals("with")
295 | && invocation.receiver.getClass().simpleName == "SingleNameReference") {
296 | def receiver = invocation.receiver
297 | String target = charToString(receiver.token)
298 | if (target.equals("Parameters")) {
299 | def queryArg = firstArgument(invocation)
300 | if (queryArg != null) {
301 | setParameterNames.add(charToString(queryArg.source()))
302 | return true
303 | }
304 | }
305 | }
306 | }
307 | return false
308 | }
309 |
310 | boolean isPanacheSortCall(firstArg) {
311 | if (firstArg.getClass().simpleName == "MessageSend") {
312 | def invocation = firstArg
313 | String fieldName = charToString(invocation.selector)
314 | if ((fieldName.equals("and")
315 | || fieldName.equals("descending")
316 | || fieldName.equals("ascending")
317 | || fieldName.equals("direction"))
318 | && isPanacheSortCall(invocation.receiver)) {
319 | for (e in invocation.arguments) {
320 | if (e.getClass().simpleName == "StringLiteral") {
321 | setOrderBy.add(charToString(e.source()))
322 | }
323 | }
324 | return true
325 | }
326 | else if ((fieldName.equals("by")
327 | || fieldName.equals("descending")
328 | || fieldName.equals("ascending"))
329 | && invocation.receiver.getClass().simpleName == "SingleNameReference") {
330 | def receiver = invocation.receiver
331 | String target = charToString(receiver.token)
332 | if (target.equals("Sort")) {
333 | for (e in invocation.arguments) {
334 | if (e.getClass().simpleName == "StringLiteral") {
335 | setOrderBy.add(charToString(e.source()))
336 | }
337 | }
338 | return true
339 | }
340 | }
341 | }
342 | return false
343 | }
344 |
345 | private static boolean isCheckable(type, unit) {
346 | return getCheckAnnotation(type, unit)!=null
347 | }
348 |
349 | private static def getCheckAnnotation(type, unit) {
350 | def result = getAnnotation(type, CHECK_HQL)
351 | if (result!=null) return result
352 | def packInfo = unit.scope.getType("package-info".toCharArray())
353 | return getAnnotation(packInfo, CHECK_HQL)
354 | }
355 | }
356 |
--------------------------------------------------------------------------------
/src/main/java/org/hibernate/query/validator/EclipseErrorReporter.groovy:
--------------------------------------------------------------------------------
1 | package org.hibernate.query.validator
2 |
3 | import org.antlr.v4.runtime.LexerNoViableAltException
4 | import org.antlr.v4.runtime.Parser
5 | import org.antlr.v4.runtime.RecognitionException
6 | import org.antlr.v4.runtime.Recognizer
7 | import org.antlr.v4.runtime.Token
8 | import org.antlr.v4.runtime.atn.ATNConfigSet
9 | import org.antlr.v4.runtime.dfa.DFA
10 | import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities
11 | import org.hibernate.query.hql.internal.StandardHqlTranslator
12 |
13 | /**
14 | * @author Gavin King
15 | */
16 | class EclipseErrorReporter implements Validation.Handler {
17 |
18 | private def node
19 | private def unit
20 | private def compiler
21 | private int errorcount
22 | private final String hql
23 |
24 | EclipseErrorReporter(node, unit, compiler, String hql) {
25 | this.hql = hql
26 | this.compiler = compiler
27 | this.node = node
28 | this.unit = unit
29 | }
30 |
31 | @Override
32 | int getErrorCount() {
33 | return errorcount
34 | }
35 |
36 | @Override
37 | void syntaxError(Recognizer, ?> recognizer, Object symbol, int line, int charInLine, String message, RecognitionException e) {
38 | message = StandardHqlTranslator.prettifyAntlrError(symbol, line, charInLine, message, e, hql, false)
39 | errorcount++
40 | def result = unit.compilationResult()
41 | char[] fileName = result.fileName
42 | int[] lineEnds = result.getLineSeparatorPositions()
43 | int startIndex
44 | int stopIndex
45 | int lineNum = getLineNumber(node.sourceStart, lineEnds, 0, lineEnds.length - 1)
46 | Token offendingToken = e.getOffendingToken()
47 | if ( offendingToken != null ) {
48 | startIndex = offendingToken.getStartIndex()
49 | stopIndex = offendingToken.getStopIndex()
50 | }
51 | else if ( e instanceof LexerNoViableAltException ) {
52 | startIndex = ((LexerNoViableAltException) e).getStartIndex()
53 | stopIndex = startIndex;
54 | }
55 | else {
56 | startIndex = lineEnds[line-1] + charInLine
57 | stopIndex = startIndex
58 | }
59 | int startPosition = node.sourceStart + startIndex + 1
60 | int endPosition = node.sourceStart + stopIndex + 1
61 | if ( endPosition < startPosition ) {
62 | endPosition = startPosition
63 | }
64 | def problem =
65 | compiler.problemReporter.problemFactory
66 | .createProblem(fileName, 0,
67 | new String[]{message},
68 | new String[]{message},
69 | ProblemSeverities.Error,
70 | startPosition,
71 | endPosition,
72 | lineNum+line-1, -1)
73 | compiler.problemReporter.record(problem, result, unit, true)
74 | }
75 |
76 | @Override
77 | void reportAmbiguity(Parser parser, DFA dfa, int i, int i1, boolean b, BitSet bitSet, ATNConfigSet atnConfigSet) {
78 | }
79 |
80 | @Override
81 | void reportAttemptingFullContext(Parser parser, DFA dfa, int i, int i1, BitSet bitSet, ATNConfigSet atnConfigSet) {
82 | }
83 |
84 | @Override
85 | void reportContextSensitivity(Parser parser, DFA dfa, int i, int i1, int i2, ATNConfigSet atnConfigSet) {
86 | }
87 |
88 | @Override
89 | void error(int start, int end, String message) {
90 | report(1, message, start, end)
91 | }
92 |
93 | @Override
94 | void warn(int start, int end, String message) {
95 | report(0, message, start, end)
96 | }
97 |
98 | private void report(int severity, String message, int offset, int endOffset) {
99 | errorcount++
100 | def result = unit.compilationResult()
101 | char[] fileName = result.fileName
102 | int[] lineEnds = result.getLineSeparatorPositions()
103 | int startPosition
104 | int endPosition
105 | if (node!=null) {
106 | startPosition = node.sourceStart + offset
107 | endPosition = endOffset < 0 ?
108 | node.sourceEnd - 1 :
109 | node.sourceStart + endOffset
110 | }
111 | else {
112 | startPosition = 0
113 | endPosition = 0
114 | }
115 | int lineNumber = startPosition >= 0 ?
116 | getLineNumber(startPosition, lineEnds, 0, lineEnds.length - 1) : 0
117 | int columnNumber = startPosition >= 0 ?
118 | searchColumnNumber(lineEnds, lineNumber, startPosition) : 0
119 | String[] args = [message]
120 | def problem =
121 | compiler.problemReporter.problemFactory.createProblem(
122 | fileName, 0,
123 | args, args, severity,
124 | startPosition, endPosition,
125 | lineNumber, columnNumber)
126 | compiler.problemReporter.record(problem, result, unit, true)
127 | }
128 |
129 | static int getLineNumber(int position, int[] lineEnds, int g, int d) {
130 | if (lineEnds == null)
131 | return 1
132 | if (d == -1)
133 | return 1
134 | int m = g, start
135 | while (g <= d) {
136 | m = g + (d - g) / 2
137 | if (position < (start = lineEnds[m])) {
138 | d = m - 1
139 | }
140 | else if (position > start) {
141 | g = m + 1
142 | }
143 | else {
144 | return m + 1
145 | }
146 | }
147 | if (position < lineEnds[m]) {
148 | return m + 1
149 | }
150 | return m + 2
151 | }
152 |
153 | static int searchColumnNumber(int[] startLineIndexes, int lineNumber, int position) {
154 | switch (lineNumber) {
155 | case 1:
156 | return position + 1
157 | case 2:
158 | return position - startLineIndexes[0]
159 | default:
160 | int line = lineNumber - 2
161 | int length = startLineIndexes.length
162 | if (line >= length) {
163 | return position - startLineIndexes[length - 1]
164 | }
165 | return position - startLineIndexes[line]
166 | }
167 | }
168 |
169 | }
170 |
--------------------------------------------------------------------------------
/src/main/java/org/hibernate/query/validator/EclipseProcessor.groovy:
--------------------------------------------------------------------------------
1 | //file:noinspection GroovyFallthrough
2 | package org.hibernate.query.validator
3 |
4 |
5 | import javax.annotation.processing.AbstractProcessor
6 | import javax.annotation.processing.ProcessingEnvironment
7 | import javax.annotation.processing.RoundEnvironment
8 | import javax.lang.model.SourceVersion
9 | import javax.lang.model.element.TypeElement
10 | import javax.tools.Diagnostic
11 |
12 | /**
13 | * Annotation processor that validates HQL and JPQL queries
14 | * for Eclipse.
15 | *
16 | * @see org.hibernate.annotations.processing.CheckHQL
17 | *
18 | * @author Gavin King
19 | */
20 | //@SupportedAnnotationTypes(CHECK_HQL)
21 | class EclipseProcessor extends AbstractProcessor {
22 |
23 | private ProcessingEnvironment processingEnv
24 |
25 | synchronized void init(ProcessingEnvironment processingEnv) {
26 | this.processingEnv = processingEnv
27 | super.init(processingEnv)
28 | processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Hibernate Query Validator for Eclipse");
29 | }
30 |
31 | static Mocker sessionFactory = Mocker.variadic(EclipseSessionFactory.class)
32 |
33 | @Override
34 | boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
35 | def compiler = processingEnv.getCompiler()
36 | if (!roundEnv.getRootElements().isEmpty()) {
37 | for (unit in compiler.unitsToProcess) {
38 | compiler.parser.getMethodBodies(unit)
39 | new EclipseChecker(unit, compiler, processingEnv).checkHQL()
40 | }
41 | }
42 | return false
43 | }
44 |
45 | // private final static String ORG_HIBERNATE =
46 | // new StringBuilder("org.")
47 | // .append("hibernate.")
48 | // .toString()
49 |
50 | // private static String shadow(String name) {
51 | // return name.replace(ORG_HIBERNATE + "dialect",
52 | // ORG_HIBERNATE + "query.validator.hibernate.dialect")
53 | // }
54 |
55 | @Override
56 | SourceVersion getSupportedSourceVersion() {
57 | return SourceVersion.latestSupported()
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/java/org/hibernate/query/validator/HQLProcessor.java:
--------------------------------------------------------------------------------
1 | package org.hibernate.query.validator;
2 |
3 | import javax.annotation.processing.AbstractProcessor;
4 | import javax.annotation.processing.ProcessingEnvironment;
5 | import javax.annotation.processing.RoundEnvironment;
6 | import javax.annotation.processing.SupportedAnnotationTypes;
7 | import javax.lang.model.SourceVersion;
8 | import javax.lang.model.element.TypeElement;
9 | import javax.tools.Diagnostic;
10 |
11 | import java.io.PrintWriter;
12 | import java.io.StringWriter;
13 | import java.util.Set;
14 |
15 | /**
16 | * @author Gavin King
17 | */
18 | @SupportedAnnotationTypes("*")
19 | public class HQLProcessor extends AbstractProcessor {
20 |
21 | static final String CHECK_HQL = hibernate("processing.CheckHQL");
22 |
23 | static String jpa(String name) {
24 | //sneak it past shadow
25 | return new StringBuilder("jakarta.")
26 | .append("persistence.")
27 | .append(name)
28 | .toString();
29 | }
30 |
31 | static String hibernate(String name) {
32 | //sneak it past shadow
33 | return new StringBuilder("org.")
34 | .append("hibernate.")
35 | .append("annotations.")
36 | .append(name)
37 | .toString();
38 | }
39 | public static boolean forceEclipseForTesting = false;
40 |
41 | private AbstractProcessor delegate;
42 |
43 | @Override
44 | public SourceVersion getSupportedSourceVersion() {
45 | return SourceVersion.latestSupported();
46 | }
47 |
48 | @Override
49 | public synchronized void init(ProcessingEnvironment processingEnv) {
50 | super.init(processingEnv);
51 | String compiler = processingEnv.getClass().getName();
52 | if (compiler.endsWith("IdeBuildProcessingEnvImpl")
53 | || forceEclipseForTesting) {
54 | //create it using reflection to allow
55 | //us to compile everything else w/o
56 | //the Groovy compiler being present
57 | delegate = newEclipseProcessor();
58 | }
59 | else if (compiler.endsWith("BatchProcessingEnvImpl")) {
60 | delegate = new ECJProcessor();
61 | }
62 | else if (compiler.endsWith("JavacProcessingEnvironment")) {
63 | delegate = new JavacProcessor();
64 | }
65 | if (delegate!=null) {
66 | delegate.init(processingEnv);
67 | }
68 | }
69 |
70 | private static AbstractProcessor newEclipseProcessor() {
71 | try {
72 | return (AbstractProcessor)
73 | Class.forName("org.hibernate.query.validator.EclipseProcessor")
74 | .newInstance();
75 | }
76 | catch (Exception e) {
77 | e.printStackTrace();
78 | return null;
79 | }
80 | }
81 |
82 | @Override
83 | public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
84 | if (delegate==null) {
85 | return false;
86 | }
87 | try {
88 | // processingEnv.getMessager()
89 | // .printMessage(Diagnostic.Kind.MANDATORY_WARNING,
90 | // "CALLED " + roundEnv.getRootElements().size());
91 | delegate.process(annotations, roundEnv);
92 | }
93 | catch (Throwable e) {
94 | String message = e.getMessage();
95 | if (message==null) message = e.getClass().getName();
96 | processingEnv.getMessager()
97 | .printMessage(Diagnostic.Kind.MANDATORY_WARNING, message);
98 | StringWriter writer = new StringWriter();
99 | e.printStackTrace(new PrintWriter(writer));
100 | processingEnv.getMessager()
101 | .printMessage(Diagnostic.Kind.MANDATORY_WARNING, writer.toString());
102 | }
103 | return false;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/main/java/org/hibernate/query/validator/JavacChecker.java:
--------------------------------------------------------------------------------
1 | package org.hibernate.query.validator;
2 |
3 | import com.sun.tools.javac.model.JavacElements;
4 | import com.sun.tools.javac.tree.JCTree;
5 |
6 | import javax.annotation.processing.ProcessingEnvironment;
7 | import javax.lang.model.element.AnnotationMirror;
8 | import javax.lang.model.element.Element;
9 | import javax.lang.model.element.TypeElement;
10 | import javax.lang.model.util.Elements;
11 |
12 | import static org.hibernate.query.validator.HQLProcessor.CHECK_HQL;
13 |
14 | /**
15 | * @author Gavin King
16 | */
17 | public class JavacChecker {
18 | private final JavacProcessor javacProcessor;
19 |
20 | public JavacChecker(JavacProcessor javacProcessor) {
21 | this.javacProcessor = javacProcessor;
22 | }
23 |
24 | ProcessingEnvironment getProcessingEnv() {
25 | return javacProcessor.getProcessingEnv();
26 | }
27 |
28 | JavacProcessor getJavacProcessor() {
29 | return javacProcessor;
30 | }
31 |
32 | void checkHQL(Element element) {
33 | Elements elementUtils = getProcessingEnv().getElementUtils();
34 | if (isCheckable(element) || isCheckable(element.getEnclosingElement())) {
35 | // List whitelist = getWhitelist(element);
36 | JCTree tree = ((JavacElements) elementUtils).getTree(element);
37 | TypeElement panacheEntity = PanacheUtils.isPanache(element, getProcessingEnv().getTypeUtils(), elementUtils);
38 | if (tree != null) {
39 | tree.accept(new JavacTreeScanner(this, element, panacheEntity));
40 | }
41 | }
42 | }
43 |
44 | private static boolean isCheckAnnotation(AnnotationMirror am) {
45 | return am.getAnnotationType().asElement().toString().equals(CHECK_HQL);
46 | }
47 |
48 | private static boolean isCheckable(Element element) {
49 | for (AnnotationMirror am: element.getAnnotationMirrors()) {
50 | if (isCheckAnnotation(am)) {
51 | return true;
52 | }
53 | }
54 | return false;
55 | }
56 |
57 | // private List getWhitelist(Element element) {
58 | // List list = new ArrayList<>();
59 | // element.getAnnotationMirrors().forEach(am -> {
60 | // if (isCheckAnnotation(am)) {
61 | // am.getElementValues().forEach((var, act) -> {
62 | // switch (var.getSimpleName().toString()) {
63 | // case "whitelist":
64 | // if (act instanceof Attribute.Array) {
65 | // for (Attribute a: ((Attribute.Array) act).values) {
66 | // Object value = a.getValue();
67 | // if (value instanceof String) {
68 | // list.add(value.toString());
69 | // }
70 | // }
71 | // }
72 | // break;
73 | // case "dialect":
74 | // if (act instanceof Attribute.Class) {
75 | // String name = act.getValue().toString().replace(".class","");
76 | // list.addAll(MockSessionFactory.functionRegistry.getValidFunctionKeys());
77 | // }
78 | // break;
79 | // }
80 | // });
81 | // }
82 | // });
83 | // return list;
84 | // }
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/src/main/java/org/hibernate/query/validator/JavacErrorReporter.java:
--------------------------------------------------------------------------------
1 | package org.hibernate.query.validator;
2 |
3 | import com.sun.tools.javac.model.JavacElements;
4 | import com.sun.tools.javac.tree.JCTree;
5 | import com.sun.tools.javac.util.Context;
6 | import com.sun.tools.javac.util.Log;
7 | import com.sun.tools.javac.util.Pair;
8 | import org.antlr.v4.runtime.LexerNoViableAltException;
9 | import org.antlr.v4.runtime.Parser;
10 | import org.antlr.v4.runtime.RecognitionException;
11 | import org.antlr.v4.runtime.Recognizer;
12 | import org.antlr.v4.runtime.Token;
13 | import org.antlr.v4.runtime.atn.ATNConfigSet;
14 | import org.antlr.v4.runtime.dfa.DFA;
15 |
16 | import javax.lang.model.element.Element;
17 | import javax.tools.JavaFileObject;
18 |
19 | import java.util.BitSet;
20 |
21 | import static com.sun.tools.javac.resources.CompilerProperties.Warnings.ProcMessager;
22 | import static org.hibernate.query.hql.internal.StandardHqlTranslator.prettifyAntlrError;
23 |
24 | /**
25 | * @author Gavin King
26 | */
27 | class JavacErrorReporter implements Validation.Handler {
28 |
29 | private static final String KEY = "proc.messager";
30 |
31 | private final Log log;
32 | private final JCTree.JCLiteral literal;
33 | private final String hql;
34 | private int errorcount;
35 |
36 | JavacErrorReporter(JavacProcessor processor, JCTree.JCLiteral literal, Element element, String hql) {
37 | this.literal = literal;
38 | this.hql = hql;
39 |
40 | Context context = processor.getContext();
41 | log = Log.instance(context);
42 | Pair pair =
43 | JavacElements.instance(context).getTreeAndTopLevel(element, null, null);
44 | JavaFileObject sourcefile = pair == null ? null : pair.snd.sourcefile;
45 | if (sourcefile != null) {
46 | log.useSource(sourcefile);
47 | }
48 | }
49 |
50 | @Override
51 | public int getErrorCount() {
52 | return errorcount;
53 | }
54 |
55 | @Override
56 | public void error(int start, int end, String message) {
57 | errorcount++;
58 | log.error(literal.pos + start, KEY, message);
59 | }
60 |
61 | @Override
62 | public void warn(int start, int end, String message) {
63 | log.warning(literal.pos + start, ProcMessager(message));
64 | // log.error(literal.pos + start, KEY, message);
65 | }
66 |
67 | @Override
68 | public void syntaxError(
69 | Recognizer, ?> recognizer,
70 | Object offendingSymbol,
71 | int line,
72 | int charPositionInLine,
73 | String message,
74 | RecognitionException e) {
75 | message = prettifyAntlrError(offendingSymbol, line, charPositionInLine, message, e, hql, false);
76 | errorcount++;
77 | Token offendingToken = e.getOffendingToken();
78 | if ( offendingToken != null ) {
79 | log.error(literal.pos+1+offendingToken.getStartIndex(), KEY, message);
80 | }
81 | else if ( e instanceof LexerNoViableAltException ) {
82 | log.error(literal.pos+1+((LexerNoViableAltException) e).getStartIndex(), KEY, message);
83 | }
84 | else {
85 | log.error(literal.pos+1, KEY, message);
86 | }
87 | }
88 |
89 | @Override
90 | public void reportAmbiguity(Parser parser, DFA dfa, int i, int i1, boolean b, BitSet bitSet, ATNConfigSet atnConfigSet) {
91 | }
92 |
93 | @Override
94 | public void reportAttemptingFullContext(Parser parser, DFA dfa, int i, int i1, BitSet bitSet, ATNConfigSet atnConfigSet) {
95 | }
96 |
97 | @Override
98 | public void reportContextSensitivity(Parser parser, DFA dfa, int i, int i1, int i2, ATNConfigSet atnConfigSet) {
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/main/java/org/hibernate/query/validator/JavacProcessor.java:
--------------------------------------------------------------------------------
1 | package org.hibernate.query.validator;
2 |
3 | import com.sun.tools.javac.processing.JavacProcessingEnvironment;
4 | import com.sun.tools.javac.util.Context;
5 |
6 | import javax.annotation.processing.AbstractProcessor;
7 | import javax.annotation.processing.ProcessingEnvironment;
8 | import javax.annotation.processing.RoundEnvironment;
9 | import javax.lang.model.SourceVersion;
10 | import javax.lang.model.element.Element;
11 | import javax.lang.model.element.PackageElement;
12 | import javax.lang.model.element.TypeElement;
13 | import javax.tools.Diagnostic;
14 | import java.util.Set;
15 |
16 |
17 | /**
18 | * Annotation processor that validates HQL and JPQL queries
19 | * for {@code javac}.
20 | *
21 | * @see org.hibernate.annotations.processing.CheckHQL
22 | *
23 | * @author Gavin King
24 | */
25 | //@SupportedAnnotationTypes(CHECK_HQL)
26 | public class JavacProcessor extends AbstractProcessor {
27 |
28 | // static Mocker sessionFactory = Mocker.variadic(JavacSessionFactory.class);
29 |
30 | @Override
31 | public synchronized void init(ProcessingEnvironment processingEnv) {
32 | ModularityWorkaround.addOpens();
33 | super.init(processingEnv);
34 | processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Hibernate Query Validator for Javac");
35 | }
36 |
37 | ProcessingEnvironment getProcessingEnv() {
38 | return processingEnv;
39 | }
40 |
41 | @Override
42 | public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
43 | final JavacChecker javacChecker = new JavacChecker(this);
44 | for (Element element : roundEnv.getRootElements()) {
45 | if (element instanceof PackageElement) {
46 | // for (Element member : element.getEnclosedElements()) {
47 | // checkHQL(member);
48 | // }
49 | }
50 | else {
51 | javacChecker.checkHQL(element);
52 | }
53 | }
54 | return false;
55 | }
56 |
57 | @Override
58 | public SourceVersion getSupportedSourceVersion() {
59 | return SourceVersion.latestSupported();
60 | }
61 |
62 | Context getContext() {
63 | return ((JavacProcessingEnvironment) processingEnv).getContext();
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/java/org/hibernate/query/validator/JavacTreeScanner.java:
--------------------------------------------------------------------------------
1 | package org.hibernate.query.validator;
2 |
3 | import com.sun.source.tree.ExpressionTree;
4 | import com.sun.source.tree.IdentifierTree;
5 | import com.sun.source.tree.MemberSelectTree;
6 | import com.sun.source.tree.Tree;
7 | import com.sun.tools.javac.tree.JCTree;
8 | import com.sun.tools.javac.tree.TreeScanner;
9 |
10 | import javax.lang.model.element.AnnotationMirror;
11 | import javax.lang.model.element.Element;
12 | import javax.lang.model.element.TypeElement;
13 | import java.util.HashSet;
14 | import java.util.Set;
15 |
16 | import static org.hibernate.query.validator.HQLProcessor.hibernate;
17 | import static org.hibernate.query.validator.HQLProcessor.jpa;
18 | import static org.hibernate.query.validator.Validation.validate;
19 |
20 | /**
21 | * @author Gavin King
22 | */
23 | class JavacTreeScanner extends TreeScanner {
24 | private final JavacChecker javacChecker;
25 | final Set setParameterLabels;
26 | final Set setParameterNames;
27 | final Set setOrderBy;
28 | private final Element element;
29 | private final TypeElement panacheEntity;
30 | boolean immediatelyCalled;
31 |
32 | public JavacTreeScanner(JavacChecker javacChecker, Element element, TypeElement panacheEntity) {
33 | this.javacChecker = javacChecker;
34 | this.element = element;
35 | this.panacheEntity = panacheEntity;
36 | setParameterLabels = new HashSet<>();
37 | setParameterNames = new HashSet<>();
38 | setOrderBy = new HashSet<>();
39 | }
40 |
41 | private void check(JCTree.JCLiteral jcLiteral, String hql,
42 | boolean inCreateQueryMethod) {
43 | JavacErrorReporter handler = new JavacErrorReporter(javacChecker.getJavacProcessor(), jcLiteral, element, hql);
44 | validate(hql, inCreateQueryMethod && immediatelyCalled,
45 | setParameterLabels, setParameterNames, handler,
46 | ProcessorSessionFactory.instance.make(javacChecker.getProcessingEnv()));
47 | // JavacProcessor.sessionFactory.make(javacChecker.getProcessingEnv()));
48 | }
49 |
50 | private void checkPanacheQuery(JCTree.JCLiteral jcLiteral, String targetType, String methodName, String panacheQl,
51 | com.sun.tools.javac.util.List args) {
52 | JavacErrorReporter handler =
53 | new JavacErrorReporter(javacChecker.getJavacProcessor(), jcLiteral, element, panacheQl);
54 | collectPanacheArguments(args);
55 | int[] offset = new int[1];
56 | String hql = PanacheUtils.panacheQlToHql(handler, targetType, methodName,
57 | panacheQl, offset, setParameterLabels, setOrderBy);
58 | if (hql == null)
59 | return;
60 | validate(hql, true,
61 | setParameterLabels, setParameterNames, handler,
62 | ProcessorSessionFactory.instance.make(javacChecker.getProcessingEnv()),
63 | offset[0]);
64 | }
65 |
66 | private void collectPanacheArguments(com.sun.tools.javac.util.List args) {
67 | // first arg is pql
68 | // second arg can be Sort, Object..., Map or Parameters
69 | setParameterLabels.clear();
70 | setParameterNames.clear();
71 | setOrderBy.clear();
72 | com.sun.tools.javac.util.List nonQueryArgs = args.tail;
73 | if (!nonQueryArgs.isEmpty()) {
74 | if (isPanacheSortCall(nonQueryArgs.head)) {
75 | nonQueryArgs = nonQueryArgs.tail;
76 | }
77 |
78 | if (!nonQueryArgs.isEmpty()) {
79 | JCTree.JCExpression firstArg = nonQueryArgs.head;
80 | isParametersCall(firstArg);
81 | if (setParameterNames.isEmpty()) {
82 | int i = 1;
83 | for (JCTree.JCExpression arg : nonQueryArgs) {
84 | setParameterLabels.add(i++);
85 | }
86 | }
87 | }
88 | }
89 | }
90 |
91 | private boolean isParametersCall(JCTree.JCExpression firstArg) {
92 | if (firstArg.getKind() == Tree.Kind.METHOD_INVOCATION) {
93 | JCTree.JCMethodInvocation invocation = (JCTree.JCMethodInvocation) firstArg;
94 | JCTree.JCExpression method = invocation.meth;
95 | if (method.getKind() == Tree.Kind.MEMBER_SELECT) {
96 | JCTree.JCFieldAccess fa = (JCTree.JCFieldAccess) method;
97 | if (fa.name.toString().equals("and") && isParametersCall(fa.selected)) {
98 | JCTree.JCLiteral queryArg = firstArgument(invocation);
99 | if (queryArg != null && queryArg.value instanceof String) {
100 | String name = (String) queryArg.value;
101 | setParameterNames.add(name);
102 | return true;
103 | }
104 | } else if (fa.name.toString().equals("with")
105 | && fa.selected.getKind() == Tree.Kind.IDENTIFIER) {
106 | String target = ((JCTree.JCIdent) fa.selected).name.toString();
107 | if (target.equals("Parameters")) {
108 | JCTree.JCLiteral queryArg = firstArgument(invocation);
109 | if (queryArg != null && queryArg.value instanceof String) {
110 | String name = (String) queryArg.value;
111 | setParameterNames.add(name);
112 | return true;
113 | }
114 | }
115 | }
116 | }
117 | }
118 | return false;
119 | }
120 |
121 | private boolean isPanacheSortCall(JCTree.JCExpression firstArg) {
122 | if (firstArg.getKind() == Tree.Kind.METHOD_INVOCATION) {
123 | JCTree.JCMethodInvocation invocation = (JCTree.JCMethodInvocation) firstArg;
124 | JCTree.JCExpression method = invocation.meth;
125 | if (method.getKind() == Tree.Kind.MEMBER_SELECT) {
126 | JCTree.JCFieldAccess fa = (JCTree.JCFieldAccess) method;
127 | String fieldName = fa.name.toString();
128 | if ((fieldName.equals("and")
129 | || fieldName.equals("descending")
130 | || fieldName.equals("ascending")
131 | || fieldName.equals("direction"))
132 | && isPanacheSortCall(fa.selected)) {
133 | for (JCTree.JCExpression e : invocation.args) {
134 | if (e instanceof JCTree.JCLiteral) {
135 | JCTree.JCLiteral lit = (JCTree.JCLiteral) e;
136 | if (lit.value instanceof String) {
137 | setOrderBy.add((String) lit.value);
138 | }
139 | }
140 | }
141 | return true;
142 | }
143 | else if ((fieldName.equals("by")
144 | || fieldName.equals("descending")
145 | || fieldName.equals("ascending"))
146 | && fa.selected.getKind() == Tree.Kind.IDENTIFIER) {
147 | String target = ((JCTree.JCIdent) fa.selected).name.toString();
148 | if (target.equals("Sort")) {
149 | for (JCTree.JCExpression e : invocation.args) {
150 | if (e instanceof JCTree.JCLiteral) {
151 | JCTree.JCLiteral lit = (JCTree.JCLiteral) e;
152 | if (lit.value instanceof String) {
153 | setOrderBy.add((String) lit.value);
154 | }
155 | }
156 | }
157 | return true;
158 | }
159 | }
160 | }
161 | }
162 | return false;
163 | }
164 |
165 | private JCTree.JCLiteral firstArgument(JCTree.JCMethodInvocation call) {
166 | for (JCTree.JCExpression e : call.args) {
167 | return e instanceof JCTree.JCLiteral
168 | ? (JCTree.JCLiteral) e
169 | : null;
170 | }
171 | return null;
172 | }
173 |
174 | @Override
175 | public void visitApply(JCTree.JCMethodInvocation jcMethodInvocation) {
176 | String name = getMethodName(jcMethodInvocation.meth);
177 | switch (name) {
178 | case "getResultList":
179 | case "getSingleResult":
180 | case "getSingleResultOrNull":
181 | immediatelyCalled = true;
182 | super.visitApply(jcMethodInvocation);
183 | immediatelyCalled = false;
184 | break;
185 | case "count":
186 | case "delete":
187 | case "update":
188 | case "exists":
189 | case "stream":
190 | case "list":
191 | case "find":
192 | switch (jcMethodInvocation.meth.getKind()) {
193 | // disable this until we figure out how to type the LHS
194 | // case MEMBER_SELECT:
195 | // JCTree.JCFieldAccess fa = (JCFieldAccess) jcMethodInvocation.meth;
196 | // switch (fa.selected.getKind()) {
197 | // case IDENTIFIER:
198 | // JCTree.JCIdent target = (JCIdent) fa.selected;
199 | // JCTree.JCLiteral queryArg = firstArgument(jcMethodInvocation);
200 | // if (queryArg != null && queryArg.value instanceof String) {
201 | // String panacheQl = (String) queryArg.value;
202 | // checkPanacheQuery(queryArg, target.name.toString(), name, panacheQl, jcMethodInvocation.args);
203 | // }
204 | // break;
205 | // }
206 | // break;
207 | case IDENTIFIER:
208 | JCTree.JCLiteral queryArg = firstArgument(jcMethodInvocation);
209 | if (queryArg != null
210 | && queryArg.value instanceof String
211 | && panacheEntity != null) {
212 | String panacheQl = (String) queryArg.value;
213 | String entityName = panacheEntity.getSimpleName().toString();
214 | checkPanacheQuery(queryArg, entityName, name, panacheQl, jcMethodInvocation.args);
215 | }
216 | break;
217 | }
218 | super.visitApply(jcMethodInvocation); //needed!
219 | break;
220 | case "createQuery":
221 | case "createSelectionQuery":
222 | case "createMutationQuery":
223 | JCTree.JCLiteral queryArg = firstArgument(jcMethodInvocation);
224 | if (queryArg != null && queryArg.value instanceof String) {
225 | String hql = (String) queryArg.value;
226 | check(queryArg, hql, true);
227 | }
228 | super.visitApply(jcMethodInvocation);
229 | break;
230 | case "setParameter":
231 | JCTree.JCLiteral paramArg = firstArgument(jcMethodInvocation);
232 | if (paramArg != null) {
233 | if (paramArg.value instanceof String) {
234 | setParameterNames.add((String) paramArg.value);
235 | }
236 | else if (paramArg.value instanceof Integer) {
237 | setParameterLabels.add((Integer) paramArg.value);
238 | }
239 | }
240 | super.visitApply(jcMethodInvocation);
241 | break;
242 | default:
243 | super.visitApply(jcMethodInvocation); //needed!
244 | break;
245 | }
246 | }
247 |
248 | @Override
249 | public void visitAnnotation(JCTree.JCAnnotation jcAnnotation) {
250 | AnnotationMirror annotation = jcAnnotation.attribute;
251 | String name = annotation.getAnnotationType().toString();
252 | if (jpa("NamedQuery").equals(name)
253 | || hibernate("NamedQuery").equals(name)) {
254 | for (JCTree.JCExpression arg : jcAnnotation.args) {
255 | if (arg instanceof JCTree.JCAssign) {
256 | JCTree.JCAssign assign = (JCTree.JCAssign) arg;
257 | if ("query".equals(assign.lhs.toString())
258 | && assign.rhs instanceof JCTree.JCLiteral) {
259 | JCTree.JCLiteral jcLiteral =
260 | (JCTree.JCLiteral) assign.rhs;
261 | if (jcLiteral.value instanceof String) {
262 | check(jcLiteral, (String) jcLiteral.value, false);
263 | }
264 | }
265 | }
266 | }
267 | }
268 | else if (hibernate("processing.HQL").equals(name)) {
269 | for (JCTree.JCExpression arg : jcAnnotation.args) {
270 | if (arg instanceof JCTree.JCAssign) {
271 | JCTree.JCAssign assign = (JCTree.JCAssign) arg;
272 | if ("value".equals(assign.lhs.toString())
273 | && assign.rhs instanceof JCTree.JCLiteral) {
274 | JCTree.JCLiteral jcLiteral =
275 | (JCTree.JCLiteral) assign.rhs;
276 | if (jcLiteral.value instanceof String) {
277 | check(jcLiteral, (String) jcLiteral.value, false);
278 | }
279 | }
280 | }
281 | }
282 | }
283 | else {
284 | super.visitAnnotation(jcAnnotation); //needed!
285 | }
286 | }
287 |
288 | private static String getMethodName(ExpressionTree select) {
289 | if (select instanceof MemberSelectTree) {
290 | MemberSelectTree ref = (MemberSelectTree) select;
291 | return ref.getIdentifier().toString();
292 | }
293 | else if (select instanceof IdentifierTree) {
294 | IdentifierTree ref = (IdentifierTree) select;
295 | return ref.getName().toString();
296 | }
297 | else {
298 | return null;
299 | }
300 | }
301 |
302 | }
303 |
--------------------------------------------------------------------------------
/src/main/java/org/hibernate/query/validator/MockCollectionPersister.java:
--------------------------------------------------------------------------------
1 | package org.hibernate.query.validator;
2 |
3 | import org.hibernate.FetchMode;
4 | import org.hibernate.QueryException;
5 | import org.hibernate.engine.spi.SessionFactoryImplementor;
6 | import org.hibernate.persister.collection.QueryableCollection;
7 | import org.hibernate.persister.entity.EntityPersister;
8 | import org.hibernate.type.CollectionType;
9 | import org.hibernate.type.ListType;
10 | import org.hibernate.type.MapType;
11 | import org.hibernate.type.Type;
12 |
13 | import static org.hibernate.internal.util.StringHelper.root;
14 | import static org.hibernate.query.validator.MockSessionFactory.typeConfiguration;
15 |
16 | /**
17 | * @author Gavin King
18 | */
19 | public abstract class MockCollectionPersister implements QueryableCollection {
20 |
21 | private static final String[] ID_COLUMN = {"id"};
22 | private static final String[] INDEX_COLUMN = {"pos"};
23 |
24 | private final String role;
25 | private final MockSessionFactory factory;
26 | private final CollectionType collectionType;
27 | private final String ownerEntityName;
28 | private final Type elementType;
29 |
30 | public MockCollectionPersister(String role, CollectionType collectionType, Type elementType, MockSessionFactory factory) {
31 | this.role = role;
32 | this.collectionType = collectionType;
33 | this.elementType = elementType;
34 | this.factory = factory;
35 | this.ownerEntityName = root(role);
36 | }
37 |
38 | String getOwnerEntityName() {
39 | return ownerEntityName;
40 | }
41 |
42 | @Override
43 | public String getRole() {
44 | return role;
45 | }
46 |
47 | @Override
48 | public String getName() {
49 | return role;
50 | }
51 |
52 | @Override
53 | public CollectionType getCollectionType() {
54 | return collectionType;
55 | }
56 |
57 | @Override
58 | public EntityPersister getOwnerEntityPersister() {
59 | return factory.getMetamodel().entityPersister(ownerEntityName);
60 | }
61 |
62 | abstract Type getElementPropertyType(String propertyPath);
63 |
64 | @Override
65 | public Type toType(String propertyName) throws QueryException {
66 | if ("index".equals(propertyName)) {
67 | //this is what AbstractCollectionPersister does!
68 | //TODO: move it to FromElementType:626 or all
69 | // the way to CollectionPropertyMapping
70 | return getIndexType();
71 | }
72 | Type type = getElementPropertyType(propertyName);
73 | if (type==null) {
74 | throw new QueryException(elementType.getName()
75 | + " has no mapped "
76 | + propertyName);
77 | }
78 | else {
79 | return type;
80 | }
81 | }
82 |
83 | @Override
84 | public Type getKeyType() {
85 | return getOwnerEntityPersister().getIdentifierType();
86 | }
87 |
88 | @Override
89 | public Type getIndexType() {
90 | if (collectionType instanceof ListType) {
91 | return typeConfiguration.getBasicTypeForJavaType(Integer.class);
92 | }
93 | else if (collectionType instanceof MapType) {
94 | //TODO!!! this is incorrect, return the correct key type
95 | return typeConfiguration.getBasicTypeForJavaType(String.class);
96 | }
97 | else {
98 | return null;
99 | }
100 | }
101 |
102 | @Override
103 | public Type getElementType() {
104 | return elementType;
105 | }
106 |
107 | @Override
108 | public Type getIdentifierType() {
109 | return typeConfiguration.getBasicTypeForJavaType(Long.class);
110 | }
111 |
112 | @Override
113 | public boolean hasIndex() {
114 | return getCollectionType() instanceof ListType
115 | || getCollectionType() instanceof MapType;
116 | }
117 |
118 | @Override
119 | public EntityPersister getElementPersister() {
120 | if (elementType.isEntityType()) {
121 | return factory.getMetamodel()
122 | .entityPersister(elementType.getName());
123 | }
124 | else {
125 | return null;
126 | }
127 | }
128 |
129 | @Override
130 | public SessionFactoryImplementor getFactory() {
131 | return factory;
132 | }
133 |
134 | @Override
135 | public boolean isOneToMany() {
136 | return elementType.isEntityType();
137 | }
138 |
139 | @Override
140 | public String[] getCollectionSpaces() {
141 | return new String[] {role};
142 | }
143 |
144 | @Override
145 | public String getMappedByProperty() {
146 | return null;
147 | }
148 |
149 | @Override
150 | public String[] getIndexColumnNames() {
151 | return INDEX_COLUMN;
152 | }
153 |
154 | @Override
155 | public String[] getIndexColumnNames(String alias) {
156 | return INDEX_COLUMN;
157 | }
158 |
159 | @Override
160 | public String[] getIndexFormulas() {
161 | return null;
162 | }
163 |
164 | @Override
165 | public String[] getElementColumnNames(String alias) {
166 | return new String[] {""};
167 | }
168 |
169 | @Override
170 | public String[] getElementColumnNames() {
171 | return new String[] {""};
172 | }
173 |
174 | @Override
175 | public FetchMode getFetchMode() {
176 | return FetchMode.DEFAULT;
177 | }
178 |
179 | @Override
180 | public String getTableName() {
181 | return role;
182 | }
183 |
184 | @Override
185 | public String[] getKeyColumnNames() {
186 | return ID_COLUMN;
187 | }
188 |
189 | @Override
190 | public boolean isCollection() {
191 | return true;
192 | }
193 |
194 | @Override
195 | public boolean consumesCollectionAlias() {
196 | return true;
197 | }
198 |
199 | @Override
200 | public String[] toColumns(String propertyName) {
201 | return new String[] {""};
202 | }
203 |
204 | }
205 |
--------------------------------------------------------------------------------
/src/main/java/org/hibernate/query/validator/MockEntityPersister.java:
--------------------------------------------------------------------------------
1 | package org.hibernate.query.validator;
2 |
3 | import jakarta.persistence.AccessType;
4 | import org.hibernate.QueryException;
5 | import org.hibernate.engine.spi.SessionFactoryImplementor;
6 | import org.hibernate.persister.entity.DiscriminatorMetadata;
7 | import org.hibernate.persister.entity.EntityPersister;
8 | import org.hibernate.persister.entity.Queryable;
9 | import org.hibernate.tuple.entity.EntityMetamodel;
10 | import org.hibernate.type.Type;
11 |
12 | import java.io.Serializable;
13 | import java.util.ArrayList;
14 | import java.util.HashMap;
15 | import java.util.HashSet;
16 | import java.util.List;
17 | import java.util.Map;
18 | import java.util.Objects;
19 | import java.util.Set;
20 |
21 | import static org.hibernate.query.validator.MockSessionFactory.typeConfiguration;
22 |
23 | /**
24 | * @author Gavin King
25 | */
26 | public abstract class MockEntityPersister implements EntityPersister, Queryable, DiscriminatorMetadata {
27 |
28 | private static final String[] ID_COLUMN = {"id"};
29 |
30 | private final String entityName;
31 | private final MockSessionFactory factory;
32 | private final List subclassPersisters = new ArrayList<>();
33 | final AccessType defaultAccessType;
34 | private final Map propertyTypesByName = new HashMap<>();
35 |
36 | public MockEntityPersister(String entityName, AccessType defaultAccessType, MockSessionFactory factory) {
37 | this.entityName = entityName;
38 | this.factory = factory;
39 | this.defaultAccessType = defaultAccessType;
40 | }
41 |
42 | void initSubclassPersisters() {
43 | for (MockEntityPersister other: factory.getMockEntityPersisters()) {
44 | other.addPersister(this);
45 | this.addPersister(other);
46 | }
47 | }
48 |
49 | private void addPersister(MockEntityPersister entityPersister) {
50 | if (isSubclassPersister(entityPersister)) {
51 | subclassPersisters.add(entityPersister);
52 | }
53 | }
54 |
55 | private Type getSubclassPropertyType(String propertyPath) {
56 | return subclassPersisters.stream()
57 | .map(sp -> sp.getPropertyType(propertyPath))
58 | .filter(Objects::nonNull)
59 | .findAny()
60 | .orElse(null);
61 | }
62 |
63 | abstract boolean isSubclassPersister(MockEntityPersister entityPersister);
64 |
65 | @Override
66 | public boolean isSubclassEntityName(String name) {
67 | return isSubclassPersister(subclassPersisters.stream()
68 | .filter(persister -> persister.entityName.equals(name))
69 | .findFirst().get());
70 | }
71 |
72 | @Override
73 | public SessionFactoryImplementor getFactory() {
74 | return factory;
75 | }
76 |
77 | @Override
78 | public EntityMetamodel getEntityMetamodel() {
79 | throw new UnsupportedOperationException();
80 | }
81 |
82 | @Override
83 | public String getEntityName() {
84 | return entityName;
85 | }
86 |
87 | @Override
88 | public String getName() {
89 | return entityName;
90 | }
91 |
92 | @Override
93 | public final Type getPropertyType(String propertyPath) {
94 | Type result = propertyTypesByName.get(propertyPath);
95 | if (result!=null) {
96 | return result;
97 | }
98 |
99 | result = createPropertyType(propertyPath);
100 | if (result == null) {
101 | //check subclasses, needed for treat()
102 | result = getSubclassPropertyType(propertyPath);
103 | }
104 |
105 | if (result!=null) {
106 | propertyTypesByName.put(propertyPath, result);
107 | }
108 | return result;
109 | }
110 |
111 | abstract Type createPropertyType(String propertyPath);
112 |
113 | @Override
114 | public Type getIdentifierType() {
115 | //TODO: propertyType(getIdentifierPropertyName())
116 | return typeConfiguration.getBasicTypeForJavaType(Long.class);
117 | }
118 |
119 | @Override
120 | public String getIdentifierPropertyName() {
121 | //TODO: return the correct @Id property name
122 | return "id";
123 | }
124 |
125 | @Override
126 | public Type toType(String propertyName) throws QueryException {
127 | Type type = getPropertyType(propertyName);
128 | if (type == null) {
129 | throw new QueryException(getEntityName()
130 | + " has no mapped "
131 | + propertyName);
132 | }
133 | return type;
134 | }
135 |
136 | @Override
137 | public String getRootEntityName() {
138 | for (MockEntityPersister persister : factory.getMockEntityPersisters()) {
139 | if (this != persister && persister.isSubclassPersister(this)) {
140 | return persister.getRootEntityName();
141 | }
142 | }
143 | return entityName;
144 | }
145 |
146 | @Override
147 | public Set getSubclassEntityNames() {
148 | Set names = new HashSet<>();
149 | names.add( entityName );
150 | for (MockEntityPersister persister : factory.getMockEntityPersisters()) {
151 | if (persister.isSubclassPersister(this)) {
152 | names.add(persister.entityName);
153 | }
154 | }
155 | return names;
156 | }
157 |
158 | @Override
159 | public Declarer getSubclassPropertyDeclarer(String s) {
160 | return Declarer.CLASS;
161 | }
162 |
163 | @Override
164 | public String[] toColumns(String propertyName) {
165 | return new String[] { "" };
166 | }
167 |
168 | @Override
169 | public String[] getPropertySpaces() {
170 | return new String[] {entityName};
171 | }
172 |
173 | @Override
174 | public Serializable[] getQuerySpaces() {
175 | return new Serializable[] {entityName};
176 | }
177 |
178 | @Override
179 | public EntityPersister getEntityPersister() {
180 | return this;
181 | }
182 |
183 | @Override
184 | public String[] getKeyColumnNames() {
185 | return getIdentifierColumnNames();
186 | }
187 |
188 | @Override
189 | public String[] getIdentifierColumnNames() {
190 | return ID_COLUMN;
191 | }
192 |
193 | @Override
194 | public DiscriminatorMetadata getTypeDiscriminatorMetadata() {
195 | return this;
196 | }
197 |
198 | @Override
199 | public Type getResolutionType() {
200 | return typeConfiguration.getBasicTypeForJavaType(Class.class);
201 | }
202 |
203 | @Override
204 | public String getTableName() {
205 | return entityName;
206 | }
207 |
208 | @Override
209 | public String toString() {
210 | return "MockEntityPersister[" + entityName + "]";
211 | }
212 |
213 | @Override
214 | public int getVersionProperty() {
215 | return -66;
216 | }
217 |
218 | @Override
219 | public String getMappedSuperclass() {
220 | return null;
221 | }
222 |
223 | @Override
224 | public boolean consumesEntityAlias() {
225 | return true;
226 | }
227 |
228 | @Override
229 | public Type getDiscriminatorType() {
230 | return typeConfiguration.getBasicTypeForJavaType(String.class);
231 | }
232 | }
233 |
--------------------------------------------------------------------------------
/src/main/java/org/hibernate/query/validator/MockJdbcServicesInitiator.java:
--------------------------------------------------------------------------------
1 | package org.hibernate.query.validator;
2 |
3 | import org.hibernate.annotations.processing.GenericDialect;
4 | import org.hibernate.boot.model.naming.Identifier;
5 | import org.hibernate.dialect.Dialect;
6 | import org.hibernate.engine.jdbc.env.internal.QualifiedObjectNameFormatterStandardImpl;
7 | import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
8 | import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport;
9 | import org.hibernate.engine.jdbc.env.spi.QualifiedObjectNameFormatter;
10 | import org.hibernate.engine.jdbc.internal.JdbcServicesInitiator;
11 | import org.hibernate.engine.jdbc.spi.JdbcServices;
12 | import org.hibernate.service.spi.ServiceRegistryImplementor;
13 | import org.hibernate.sql.ast.SqlAstTranslatorFactory;
14 | import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory;
15 |
16 | import java.util.Map;
17 |
18 | /**
19 | * @author Gavin King
20 | */
21 | class MockJdbcServicesInitiator extends JdbcServicesInitiator {
22 |
23 | static final JdbcServicesInitiator INSTANCE = new MockJdbcServicesInitiator();
24 |
25 | static final JdbcServices jdbcServices = Mocker.nullary(MockJdbcServices.class).get();
26 | static final GenericDialect genericDialect = new GenericDialect();
27 |
28 | public abstract static class MockJdbcServices implements JdbcServices, JdbcEnvironment {
29 | @Override
30 | public Dialect getDialect() {
31 | return genericDialect;
32 | }
33 |
34 | @Override
35 | public JdbcEnvironment getJdbcEnvironment() {
36 | return this;
37 | }
38 |
39 | @Override
40 | public SqlAstTranslatorFactory getSqlAstTranslatorFactory() {
41 | return new StandardSqlAstTranslatorFactory();
42 | }
43 |
44 | @Override
45 | public Identifier getCurrentCatalog() {
46 | return null;
47 | }
48 |
49 | @Override
50 | public Identifier getCurrentSchema() {
51 | return null;
52 | }
53 |
54 | @Override
55 | public QualifiedObjectNameFormatter getQualifiedObjectNameFormatter() {
56 | return new QualifiedObjectNameFormatterStandardImpl(getNameQualifierSupport());
57 | }
58 |
59 | @Override
60 | public NameQualifierSupport getNameQualifierSupport() {
61 | return genericDialect.getNameQualifierSupport();
62 | }
63 | }
64 |
65 | @Override
66 | public JdbcServices initiateService(Map configurationValues, ServiceRegistryImplementor registry) {
67 | return jdbcServices;
68 | }
69 | }
--------------------------------------------------------------------------------
/src/main/java/org/hibernate/query/validator/Mocker.java:
--------------------------------------------------------------------------------
1 | package org.hibernate.query.validator;
2 |
3 | import net.bytebuddy.ByteBuddy;
4 |
5 | import java.lang.reflect.Constructor;
6 | import java.util.HashMap;
7 | import java.util.Map;
8 | import java.util.function.Supplier;
9 |
10 | import static net.bytebuddy.implementation.FixedValue.value;
11 | import static net.bytebuddy.matcher.ElementMatchers.isAbstract;
12 | import static net.bytebuddy.matcher.ElementMatchers.returns;
13 |
14 | @FunctionalInterface
15 | public interface Mocker {
16 |
17 | T make(Object... args);
18 |
19 | Map, Class>> mocks = new HashMap<>();
20 |
21 | static Supplier nullary(Class clazz) {
22 | try {
23 | Class extends T> mock = load(clazz);
24 | return () -> {
25 | try {
26 | return mock.newInstance();
27 | }
28 | catch (Exception e) {
29 | throw new RuntimeException(e);
30 | }
31 | };
32 | }
33 | catch (Exception e) {
34 | throw new RuntimeException(e);
35 | }
36 | }
37 |
38 | @SuppressWarnings("unchecked")
39 | static Mocker variadic(Class clazz) {
40 | Constructor>[] constructors = load(clazz).getDeclaredConstructors();
41 | if (constructors.length>1) {
42 | throw new RuntimeException("more than one constructor for " + clazz);
43 | }
44 | Constructor> constructor = constructors[0];
45 | return (args) -> {
46 | try {
47 | return (T) constructor.newInstance(args);
48 | }
49 | catch (Exception e) {
50 | throw new RuntimeException(e);
51 | }
52 | };
53 | }
54 |
55 | @SuppressWarnings("unchecked")
56 | static Class extends T> load(Class clazz) {
57 | if (mocks.containsKey(clazz)) {
58 | return (Class extends T>) mocks.get(clazz);
59 | }
60 | Class extends T> mock =
61 | new ByteBuddy()
62 | .subclass(clazz)
63 | .method(returns(String.class).and(isAbstract()))
64 | .intercept(value(""))
65 | .method(returns(boolean.class).and(isAbstract()))
66 | .intercept(value(false))
67 | .method(returns(int.class).and(isAbstract()))
68 | .intercept(value(0))
69 | .method(returns(long.class).and(isAbstract()))
70 | .intercept(value(0L))
71 | .method(returns(int[].class).and(isAbstract()))
72 | .intercept(value(new int[0]))
73 | .method(returns(String[].class).and(isAbstract()))
74 | .intercept(value(new String[0]))
75 | .make()
76 | .load(clazz.getClassLoader())
77 | .getLoaded();
78 | mocks.put(clazz,mock);
79 | return mock;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/java/org/hibernate/query/validator/ModularityWorkaround.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018-2021 The Project Lombok Authors.
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is
9 | * furnished to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in
12 | * all copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | * THE SOFTWARE.
21 | */
22 | package org.hibernate.query.validator;
23 |
24 | import sun.misc.Unsafe;
25 |
26 | import java.lang.reflect.Field;
27 | import java.lang.reflect.Method;
28 |
29 | /**
30 | * COPY/PASTE from Lombok!
31 | */
32 | public class ModularityWorkaround {
33 |
34 | private static Object getJdkCompilerModule() {
35 | /* call public api: ModuleLayer.boot().findModule("jdk.compiler").get();
36 | but use reflection because we don't want this code to crash on jdk1.7 and below.
37 | In that case, none of this stuff was needed in the first place, so we just exit via
38 | the catch block and do nothing.
39 | */
40 |
41 | try {
42 | Class> cModuleLayer = Class.forName("java.lang.ModuleLayer");
43 | Method mBoot = cModuleLayer.getDeclaredMethod("boot");
44 | Object bootLayer = mBoot.invoke(null);
45 | Class> cOptional = Class.forName("java.util.Optional");
46 | Method mFindModule = cModuleLayer.getDeclaredMethod("findModule", String.class);
47 | Object oCompilerO = mFindModule.invoke(bootLayer, "jdk.compiler");
48 | return cOptional.getDeclaredMethod("get").invoke(oCompilerO);
49 | } catch (Exception e) {
50 | return null;
51 | }
52 | }
53 |
54 | /**
55 | * Useful from jdk9 and up; required from jdk16 and up.
56 | * This code is supposed to gracefully do nothing on jdk8
57 | * and below, as this operation isn't needed there. */
58 | public static void addOpens() {
59 | Class> cModule;
60 | try {
61 | cModule = Class.forName("java.lang.Module");
62 | } catch (ClassNotFoundException e) {
63 | return; //jdk8-; this is not needed.
64 | }
65 |
66 | Unsafe unsafe = getUnsafe();
67 | Object jdkCompilerModule = getJdkCompilerModule();
68 | Object ownModule = getOwnModule();
69 | String[] allPkgs = {
70 | "com.sun.tools.javac.code",
71 | "com.sun.tools.javac.model",
72 | "com.sun.tools.javac.processing",
73 | "com.sun.tools.javac.tree",
74 | "com.sun.tools.javac.util",
75 | "com.sun.tools.javac.resources"
76 | };
77 |
78 | try {
79 | Method m = cModule.getDeclaredMethod("implAddOpens", String.class, cModule);
80 | long firstFieldOffset = getFirstFieldOffset(unsafe);
81 | unsafe.putBooleanVolatile(m, firstFieldOffset, true);
82 | for (String p : allPkgs) {
83 | m.invoke(jdkCompilerModule, p, ownModule);
84 | }
85 | } catch (Exception ignore) {}
86 | }
87 |
88 | private static long getFirstFieldOffset(Unsafe unsafe) {
89 | try {
90 | return unsafe.objectFieldOffset(Parent.class.getDeclaredField("first"));
91 | } catch (NoSuchFieldException e) {
92 | // can't happen.
93 | throw new RuntimeException(e);
94 | } catch (SecurityException e) {
95 | // can't happen
96 | throw new RuntimeException(e);
97 | }
98 | }
99 |
100 | private static Unsafe getUnsafe() {
101 | try {
102 | Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
103 | theUnsafe.setAccessible(true);
104 | return (Unsafe) theUnsafe.get(null);
105 | } catch (Exception e) {
106 | return null;
107 | }
108 | }
109 |
110 | private static Object getOwnModule() {
111 | try {
112 | Method m = Permit.getMethod(Class.class, "getModule");
113 | return m.invoke(JavacProcessor.class);
114 | } catch (Exception e) {
115 | return null;
116 | }
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/main/java/org/hibernate/query/validator/PanacheUtils.java:
--------------------------------------------------------------------------------
1 | package org.hibernate.query.validator;
2 |
3 | import java.util.List;
4 | import java.util.Set;
5 |
6 | import javax.lang.model.element.Element;
7 | import javax.lang.model.element.ElementKind;
8 | import javax.lang.model.element.TypeElement;
9 | import javax.lang.model.type.DeclaredType;
10 | import javax.lang.model.type.TypeKind;
11 | import javax.lang.model.type.TypeMirror;
12 | import javax.lang.model.util.Elements;
13 | import javax.lang.model.util.Types;
14 |
15 | public class PanacheUtils {
16 | public static String panacheQlToHql(Validation.Handler handler, String targetType, String methodName,
17 | String panacheQl, int[] offset, Set setParameterLabels,
18 | Set setOrderBy) {
19 | String ret = null;
20 | switch (methodName) {
21 | case "find":
22 | case "list":
23 | case "stream":
24 | ret = panacheFindQueryToHql(handler, targetType, panacheQl, offset, setParameterLabels);
25 | break;
26 | // FIXME: those throw:
27 | /*
28 | warning: Method org/hibernate/query/validator/JavacSessionFactory$EntityPersister$ByteBuddy$BYYHBkRf.getDiscriminatorType()Lorg/hibernate/type/Type; is abstract
29 | warning: java.lang.AbstractMethodError: Method org/hibernate/query/validator/JavacSessionFactory$EntityPersister$ByteBuddy$BYYHBkRf.getDiscriminatorType()Lorg/hibernate/type/Type; is abstract
30 | at org.hibernate.query.validator.JavacSessionFactory$EntityPersister$ByteBuddy$BYYHBkRf.getDiscriminatorType(Unknown Source)
31 | at org.hibernate.hql.internal.ast.HqlSqlWalker.postProcessDML(HqlSqlWalker.java:840)
32 | at org.hibernate.hql.internal.ast.HqlSqlWalker.postProcessUpdate(HqlSqlWalker.java:855)
33 | at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.updateStatement(HqlSqlBaseWalker.java:417)
34 | at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.statement(HqlSqlBaseWalker.java:273)
35 | at org.hibernate.query.validator.Validation.validate(Validation.java:77)
36 | at org.hibernate.query.validator.JavacProcessor$1.checkPanacheQuery(JavacProcessor.java:97)
37 | at org.hibernate.query.validator.JavacProcessor$1.visitApply(JavacProcessor.java:374)
38 | */
39 | case "delete":
40 | return panacheDeleteQueryToHql(handler, targetType, panacheQl, offset, setParameterLabels);
41 | case "update":
42 | return panacheUpdateQueryToHql(handler, targetType, panacheQl, offset, setParameterLabels);
43 | case "exists":
44 | case "count":
45 | ret = panacheCountQueryToHql(handler, targetType, panacheQl, offset, setParameterLabels);
46 | break;
47 | }
48 | if (ret != null && !setOrderBy.isEmpty()) {
49 | ret += " ORDER BY "+String.join(", ", setOrderBy);
50 | }
51 | return ret;
52 | }
53 |
54 | private static String panacheCountQueryToHql(Validation.Handler handler, String targetType,
55 | String query, int[] offset, Set setParameterLabels) {
56 | String countPrefix = "SELECT COUNT(*) ";
57 | String fromPrefix = "FROM "+targetType;
58 | if (query == null) {
59 | offset[0] = countPrefix.length()+fromPrefix.length();
60 | return countPrefix + fromPrefix;
61 | }
62 |
63 | String trimmed = query.trim();
64 | if (trimmed.isEmpty()) {
65 | offset[0] = countPrefix.length()+fromPrefix.length();
66 | return countPrefix + fromPrefix;
67 | }
68 |
69 | String trimmedLc = trimmed.toLowerCase();
70 | if (trimmedLc.startsWith("from ")) {
71 | offset[0] = countPrefix.length();
72 | return countPrefix + query;
73 | }
74 | if (trimmedLc.startsWith("order by ")) {
75 | // ignore it
76 | offset[0] = countPrefix.length()+fromPrefix.length();
77 | return countPrefix + fromPrefix;
78 | }
79 | if (trimmedLc.indexOf(' ') == -1 && trimmedLc.indexOf('=') == -1) {
80 | if (missingRequiredSingleParam(handler, query, setParameterLabels))
81 | return null;
82 | query += " = ?1";
83 | }
84 | offset[0] = countPrefix.length()+fromPrefix.length() + 7;
85 | return countPrefix + fromPrefix + " WHERE "+query;
86 | }
87 |
88 | private static String panacheUpdateQueryToHql(Validation.Handler handler, String targetType,
89 | String query, int[] offset, Set setParameterLabels) {
90 | if (query == null) {
91 | return null;
92 | }
93 |
94 | String trimmed = query.trim();
95 | if (trimmed.isEmpty()) {
96 | return null;
97 | }
98 |
99 | String trimmedLc = trimmed.toLowerCase();
100 | if (trimmedLc.startsWith("update ")) {
101 | offset[0] = 0;
102 | return query;
103 | }
104 | if (trimmedLc.startsWith("from ")) {
105 | offset[0] = 7;
106 | return "UPDATE " + query;
107 | }
108 | if (trimmedLc.indexOf(' ') == -1 && trimmedLc.indexOf('=') == -1) {
109 | if (missingRequiredSingleParam(handler, query, setParameterLabels))
110 | return null;
111 | query += " = ?1";
112 | }
113 | String fromPrefix = "UPDATE " + targetType + " ";
114 | if (trimmedLc.startsWith("set ")) {
115 | offset[0] = fromPrefix.length();
116 | return fromPrefix + query;
117 | }
118 | offset[0] = fromPrefix.length() + 4;
119 | return fromPrefix + "SET " + query;
120 | }
121 |
122 | private static String panacheDeleteQueryToHql(Validation.Handler handler, String targetType,
123 | String query, int[] offset, Set setParameterLabels) {
124 | String deletePrefix = "DELETE FROM " + targetType;
125 | if (query == null) {
126 | offset[0] = deletePrefix.length();
127 | return deletePrefix;
128 | }
129 |
130 | String trimmed = query.trim();
131 | if (trimmed.isEmpty()) {
132 | offset[0] = deletePrefix.length();
133 | return deletePrefix;
134 | }
135 |
136 | String trimmedLc = trimmed.toLowerCase();
137 | if (trimmedLc.startsWith("from ")) {
138 | offset[0] = 7;
139 | return "DELETE " + query;
140 | }
141 | if (trimmedLc.startsWith("order by ")) {
142 | // ignore it
143 | offset[0] = deletePrefix.length();
144 | return deletePrefix;
145 | }
146 | if (trimmedLc.indexOf(' ') == -1 && trimmedLc.indexOf('=') == -1) {
147 | if (missingRequiredSingleParam(handler, query, setParameterLabels))
148 | return null;
149 | query += " = ?1";
150 | }
151 | offset[0] = deletePrefix.length() + 7;
152 | return deletePrefix + " WHERE " + query;
153 | }
154 |
155 | private static String panacheFindQueryToHql(Validation.Handler handler, String targetType,
156 | String query, int[] offset, Set setParameterLabels) {
157 | String fromPrefix = "FROM " + targetType;
158 | if (query == null) {
159 | offset[0] = fromPrefix.length();
160 | return fromPrefix;
161 | }
162 |
163 | String trimmed = query.trim();
164 | if (trimmed.isEmpty()) {
165 | offset[0] = fromPrefix.length();
166 | return fromPrefix;
167 | }
168 |
169 | // FIXME: support, by collecting and checking named queries
170 | if (isNamedQuery(query)) {
171 | return null;
172 | }
173 |
174 | String trimmedLc = trimmed.toLowerCase();
175 | if (trimmedLc.startsWith("from ") || trimmedLc.startsWith("select ")) {
176 | offset[0] = 0;
177 | return query;
178 | }
179 | if (trimmedLc.startsWith("order by ")) {
180 | offset[0] = fromPrefix.length()+1;
181 | return fromPrefix + " " + query;
182 | }
183 | if (trimmedLc.indexOf(' ') == -1 && trimmedLc.indexOf('=') == -1) {
184 | if (missingRequiredSingleParam(handler, query, setParameterLabels))
185 | return null;
186 | query += " = ?1";
187 | }
188 | offset[0] = fromPrefix.length() + 7;
189 | return fromPrefix + " WHERE " + query;
190 | }
191 |
192 | private static boolean isNamedQuery(String query) {
193 | return query != null && !query.isEmpty() && query.charAt(0) == '#';
194 | }
195 |
196 | private static boolean missingRequiredSingleParam(Validation.Handler handler, String query,
197 | Set setParameterLabels) {
198 | if (setParameterLabels.size() < 1) {
199 | handler.warn(0, 0, "Missing required argument for '" + query + "'");
200 | return true;
201 | }
202 | else if (setParameterLabels.size() > 1){
203 | handler.warn(0, 0, "Too many arguments for '" + query + "'");
204 | return true;
205 | }
206 | return false;
207 | }
208 |
209 | public static TypeElement isPanache(Element element, Types types, Elements elements) {
210 | if (element.getKind() == ElementKind.CLASS
211 | || element.getKind() == ElementKind.INTERFACE) {
212 | TypeMirror type = element.asType();
213 | TypeElement panacheEntityType = elements.getTypeElement("io.quarkus.hibernate.orm.panache.PanacheEntityBase");
214 | if (panacheEntityType == null)
215 | return null;
216 | if (types.isSubtype(type, panacheEntityType.asType()))
217 | return (TypeElement) types.asElement(type);
218 | TypeElement panacheRepositoryType = elements.getTypeElement("io.quarkus.hibernate.orm.panache.PanacheRepositoryBase");
219 | if (panacheRepositoryType == null)
220 | return null;
221 | if (types.isSubtype(type, types.erasure(panacheRepositoryType.asType()))) {
222 | TypeMirror ret = getFirstTypeArg(types, element.asType(), panacheRepositoryType);
223 | return ret != null ? (TypeElement)types.asElement(ret) : null;
224 | }
225 | }
226 | return null;
227 | }
228 |
229 | private static TypeMirror getFirstTypeArg(Types types, TypeMirror type, TypeElement superType) {
230 | // look at this first
231 | TypeElement typeElement = ((TypeElement)types.asElement(type));
232 | if (typeElement.getQualifiedName().contentEquals(superType.getQualifiedName())) {
233 | return superType.getTypeParameters().get(0).asType();
234 | }
235 | // look up
236 | TypeMirror superclass = typeElement.getSuperclass();
237 | if (superclass.getKind() != TypeKind.NONE) {
238 | // look at superclass
239 | TypeMirror ret = getFirstTypeArg(types, superclass, superType);
240 | if (ret != null)
241 | return mapGenerics(types, superclass, ret);
242 | }
243 | for (TypeMirror superInterface : typeElement.getInterfaces()) {
244 | TypeMirror ret = getFirstTypeArg(types, superInterface, superType);
245 | if (ret != null)
246 | return mapGenerics(types, superInterface, ret);
247 | }
248 | return null;
249 | }
250 |
251 | private static TypeMirror mapGenerics(Types types, TypeMirror superType, TypeMirror ret) {
252 | if (ret.getKind() == TypeKind.TYPEVAR) {
253 | TypeElement superElement = (TypeElement) types.asElement(superType);
254 | int typeParamIndex = superElement.getTypeParameters().indexOf(types.asElement(ret));
255 | DeclaredType superDeclaredType = (DeclaredType) superType;
256 | List extends TypeMirror> superTypeArgs = superDeclaredType.getTypeArguments();
257 | if (typeParamIndex != -1 && typeParamIndex < superTypeArgs.size()) {
258 | ret = superTypeArgs.get(typeParamIndex);
259 | return ret;
260 | }
261 | // not really found
262 | return null;
263 | }
264 | return ret;
265 | }
266 | }
267 |
--------------------------------------------------------------------------------
/src/main/java/org/hibernate/query/validator/Parent.java:
--------------------------------------------------------------------------------
1 | package org.hibernate.query.validator;
2 |
3 | import java.io.OutputStream;
4 |
5 | @SuppressWarnings("all")
6 | public class Parent {
7 | boolean first;
8 | static final Object staticObj = OutputStream.class;
9 | volatile Object second;
10 | private static volatile boolean staticSecond;
11 | private static volatile boolean staticThird;
12 | }
--------------------------------------------------------------------------------
/src/main/java/org/hibernate/query/validator/Permit.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018-2021 The Project Lombok Authors.
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is
9 | * furnished to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in
12 | * all copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | * THE SOFTWARE.
21 | */
22 | package org.hibernate.query.validator;
23 |
24 | import java.lang.reflect.AccessibleObject;
25 | import java.lang.reflect.Constructor;
26 | import java.lang.reflect.Field;
27 | import java.lang.reflect.InvocationTargetException;
28 | import java.lang.reflect.Method;
29 |
30 | /**
31 | * COPY/PASTE from Lombok!
32 | */
33 | // sunapi suppresses javac's warning about using Unsafe; 'all' suppresses
34 | // eclipse's warning about the unspecified 'sunapi' key. Leave them both.
35 | // Yes, javac's definition of the word 'all' is quite contrary to what the
36 | // dictionary says it means. 'all' does NOT include 'sunapi' according to javac.
37 | @SuppressWarnings({"sunapi", "all"})
38 | public class Permit {
39 | private Permit() {}
40 |
41 |
42 | private static final long ACCESSIBLE_OVERRIDE_FIELD_OFFSET;
43 | private static final IllegalAccessException INIT_ERROR;
44 | private static final sun.misc.Unsafe UNSAFE = (sun.misc.Unsafe) reflectiveStaticFieldAccess(sun.misc.Unsafe.class, "theUnsafe");
45 |
46 | static {
47 | Field f;
48 | long g;
49 | Throwable ex;
50 |
51 | try {
52 | g = getOverrideFieldOffset();
53 | ex = null;
54 | } catch (Throwable t) {
55 | f = null;
56 | g = -1L;
57 | ex = t;
58 | }
59 |
60 | ACCESSIBLE_OVERRIDE_FIELD_OFFSET = g;
61 | if (ex == null) INIT_ERROR = null;
62 | else if (ex instanceof IllegalAccessException) INIT_ERROR = (IllegalAccessException) ex;
63 | else {
64 | INIT_ERROR = new IllegalAccessException("Cannot initialize Unsafe-based permit");
65 | INIT_ERROR.initCause(ex);
66 | }
67 | }
68 |
69 | public static T setAccessible(T accessor) {
70 | if (INIT_ERROR == null) {
71 | UNSAFE.putBoolean(accessor, ACCESSIBLE_OVERRIDE_FIELD_OFFSET, true);
72 | } else {
73 | accessor.setAccessible(true);
74 | }
75 |
76 | return accessor;
77 | }
78 |
79 | private static long getOverrideFieldOffset() throws Throwable {
80 | Field f = null;
81 | Throwable saved = null;
82 | try {
83 | f = AccessibleObject.class.getDeclaredField("override");
84 | } catch (Throwable t) {
85 | saved = t;
86 | }
87 |
88 | if (f != null) {
89 | return UNSAFE.objectFieldOffset(f);
90 | }
91 | // The below seems very risky, but for all AccessibleObjects in java today it does work,
92 | // and starting with JDK12, making the field accessible is no longer possible.
93 | try {
94 | return UNSAFE.objectFieldOffset(Fake.class.getDeclaredField("override"));
95 | } catch (Throwable t) {
96 | throw saved;
97 | }
98 | }
99 |
100 | static class Fake {
101 | boolean override;
102 | Object accessCheckCache;
103 | }
104 |
105 | public static Method getMethod(Class> c, String mName, Class>... parameterTypes) throws NoSuchMethodException {
106 | Method m = null;
107 | Class> oc = c;
108 | while (c != null) {
109 | try {
110 | m = c.getDeclaredMethod(mName, parameterTypes);
111 | break;
112 | } catch (NoSuchMethodException e) {}
113 | c = c.getSuperclass();
114 | }
115 |
116 | if (m == null) throw new NoSuchMethodException(oc.getName() + " :: " + mName + "(args)");
117 | return setAccessible(m);
118 | }
119 |
120 | public static Method permissiveGetMethod(Class> c, String mName, Class>... parameterTypes) {
121 | try {
122 | return getMethod(c, mName, parameterTypes);
123 | } catch (Exception ignore) {
124 | return null;
125 | }
126 | }
127 |
128 | public static Field getField(Class> c, String fName) throws NoSuchFieldException {
129 | Field f = null;
130 | Class> oc = c;
131 | while (c != null) {
132 | try {
133 | f = c.getDeclaredField(fName);
134 | break;
135 | } catch (NoSuchFieldException e) {}
136 | c = c.getSuperclass();
137 | }
138 |
139 | if (f == null) throw new NoSuchFieldException(oc.getName() + " :: " + fName);
140 |
141 | return setAccessible(f);
142 | }
143 |
144 | public static Field permissiveGetField(Class> c, String fName) {
145 | try {
146 | return getField(c, fName);
147 | } catch (Exception ignore) {
148 | return null;
149 | }
150 | }
151 |
152 | public static T permissiveReadField(Class type, Field f, Object instance) {
153 | try {
154 | return type.cast(f.get(instance));
155 | } catch (Exception ignore) {
156 | return null;
157 | }
158 | }
159 |
160 | public static Constructor getConstructor(Class c, Class>... parameterTypes)
161 | throws NoSuchMethodException {
162 | return setAccessible(c.getDeclaredConstructor(parameterTypes));
163 | }
164 |
165 | private static Object reflectiveStaticFieldAccess(Class> c, String fName) {
166 | try {
167 | Field f = c.getDeclaredField(fName);
168 | f.setAccessible(true);
169 | return f.get(null);
170 | } catch (Exception e) {
171 | return null;
172 | }
173 | }
174 |
175 | public static boolean isDebugReflection() {
176 | return !"false".equals(System.getProperty("lombok.debug.reflection", "false"));
177 | }
178 |
179 | public static void handleReflectionDebug(Throwable t, Throwable initError) {
180 | if (!isDebugReflection()) return;
181 |
182 | System.err.println("** LOMBOK REFLECTION exception: " + t.getClass() + ": " +
183 | (t.getMessage() == null ? "(no message)" : t.getMessage()));
184 | t.printStackTrace(System.err);
185 | if (initError != null) {
186 | System.err.println("*** ADDITIONALLY, exception occurred setting up reflection: ");
187 | initError.printStackTrace(System.err);
188 | }
189 | }
190 |
191 | public static Object invoke(Method m, Object receiver, Object... args)
192 | throws IllegalAccessException, InvocationTargetException {
193 | return invoke(null, m, receiver, args);
194 | }
195 |
196 | public static Object invoke(Throwable initError, Method m, Object receiver, Object... args)
197 | throws IllegalAccessException, InvocationTargetException {
198 | try {
199 | return m.invoke(receiver, args);
200 | } catch (IllegalAccessException e) {
201 | handleReflectionDebug(e, initError);
202 | throw e;
203 | } catch (RuntimeException e) {
204 | handleReflectionDebug(e, initError);
205 | throw e;
206 | } catch (Error e) {
207 | handleReflectionDebug(e, initError);
208 | throw e;
209 | }
210 | }
211 |
212 | public static Object invokeSneaky(Method m, Object receiver, Object... args) {
213 | return invokeSneaky(null, m, receiver, args);
214 | }
215 |
216 | public static Object invokeSneaky(Throwable initError, Method m, Object receiver, Object... args) {
217 | try {
218 | return m.invoke(receiver, args);
219 | } catch (NoClassDefFoundError e) {
220 | handleReflectionDebug(e, initError);
221 | //ignore, we don't have access to the correct ECJ classes, so lombok can't possibly
222 | //do anything useful here.
223 | return null;
224 | } catch (NullPointerException e) {
225 | handleReflectionDebug(e, initError);
226 | //ignore, we don't have access to the correct ECJ classes, so lombok can't possibly
227 | //do anything useful here.
228 | return null;
229 | } catch (IllegalAccessException e) {
230 | handleReflectionDebug(e, initError);
231 | throw sneakyThrow(e);
232 | } catch (InvocationTargetException e) {
233 | throw sneakyThrow(e.getCause());
234 | } catch (RuntimeException e) {
235 | handleReflectionDebug(e, initError);
236 | throw e;
237 | } catch (Error e) {
238 | handleReflectionDebug(e, initError);
239 | throw e;
240 | }
241 | }
242 |
243 | public static T newInstance(Constructor c, Object... args)
244 | throws IllegalAccessException, InvocationTargetException, InstantiationException {
245 | return newInstance(null, c, args);
246 | }
247 |
248 | public static T newInstance(Throwable initError, Constructor c, Object... args)
249 | throws IllegalAccessException, InvocationTargetException, InstantiationException {
250 | try {
251 | return c.newInstance(args);
252 | } catch (IllegalAccessException e) {
253 | handleReflectionDebug(e, initError);
254 | throw e;
255 | } catch (InstantiationException e) {
256 | handleReflectionDebug(e, initError);
257 | throw e;
258 | } catch (RuntimeException e) {
259 | handleReflectionDebug(e, initError);
260 | throw e;
261 | } catch (Error e) {
262 | handleReflectionDebug(e, initError);
263 | throw e;
264 | }
265 | }
266 |
267 | public static T newInstanceSneaky(Constructor c, Object... args) {
268 | return newInstanceSneaky(null, c, args);
269 | }
270 |
271 | public static T newInstanceSneaky(Throwable initError, Constructor c, Object... args) {
272 | try {
273 | return c.newInstance(args);
274 | } catch (NoClassDefFoundError e) {
275 | handleReflectionDebug(e, initError);
276 | //ignore, we don't have access to the correct ECJ classes, so lombok can't possibly
277 | //do anything useful here.
278 | return null;
279 | } catch (NullPointerException e) {
280 | handleReflectionDebug(e, initError);
281 | //ignore, we don't have access to the correct ECJ classes, so lombok can't possibly
282 | //do anything useful here.
283 | return null;
284 | } catch (IllegalAccessException e) {
285 | handleReflectionDebug(e, initError);
286 | throw sneakyThrow(e);
287 | } catch (InstantiationException e) {
288 | handleReflectionDebug(e, initError);
289 | throw sneakyThrow(e);
290 | } catch (InvocationTargetException e) {
291 | throw sneakyThrow(e.getCause());
292 | } catch (RuntimeException e) {
293 | handleReflectionDebug(e, initError);
294 | throw e;
295 | } catch (Error e) {
296 | handleReflectionDebug(e, initError);
297 | throw e;
298 | }
299 | }
300 |
301 | public static Object get(Field f, Object receiver) throws IllegalAccessException {
302 | try {
303 | return f.get(receiver);
304 | } catch (IllegalAccessException e) {
305 | handleReflectionDebug(e, null);
306 | throw e;
307 | } catch (RuntimeException e) {
308 | handleReflectionDebug(e, null);
309 | throw e;
310 | } catch (Error e) {
311 | handleReflectionDebug(e, null);
312 | throw e;
313 | }
314 | }
315 |
316 | public static void set(Field f, Object receiver, Object newValue) throws IllegalAccessException {
317 | try {
318 | f.set(receiver, newValue);
319 | } catch (IllegalAccessException e) {
320 | handleReflectionDebug(e, null);
321 | throw e;
322 | } catch (RuntimeException e) {
323 | handleReflectionDebug(e, null);
324 | throw e;
325 | } catch (Error e) {
326 | handleReflectionDebug(e, null);
327 | throw e;
328 | }
329 | }
330 |
331 | public static void reportReflectionProblem(Throwable initError, String msg) {
332 | if (!isDebugReflection()) return;
333 | System.err.println("** LOMBOK REFLECTION issue: " + msg);
334 | if (initError != null) {
335 | System.err.println("*** ADDITIONALLY, exception occurred setting up reflection: ");
336 | initError.printStackTrace(System.err);
337 | }
338 | }
339 |
340 | public static RuntimeException sneakyThrow(Throwable t) {
341 | if (t == null) throw new NullPointerException("t");
342 | return Permit.sneakyThrow0(t);
343 | }
344 |
345 | @SuppressWarnings("unchecked")
346 | private static T sneakyThrow0(Throwable t) throws T {
347 | throw (T)t;
348 | }
349 |
350 | }
--------------------------------------------------------------------------------
/src/main/java/org/hibernate/query/validator/Validation.java:
--------------------------------------------------------------------------------
1 | package org.hibernate.query.validator;
2 |
3 | import org.antlr.v4.runtime.ANTLRErrorListener;
4 | import org.antlr.v4.runtime.BailErrorStrategy;
5 | import org.antlr.v4.runtime.DefaultErrorStrategy;
6 | import org.antlr.v4.runtime.Token;
7 | import org.antlr.v4.runtime.atn.PredictionMode;
8 | import org.antlr.v4.runtime.misc.ParseCancellationException;
9 | import org.hibernate.PropertyNotFoundException;
10 | import org.hibernate.QueryException;
11 | import org.hibernate.grammars.hql.HqlLexer;
12 | import org.hibernate.grammars.hql.HqlParser;
13 | import org.hibernate.query.hql.internal.HqlParseTreeBuilder;
14 | import org.hibernate.query.hql.internal.SemanticQueryBuilder;
15 | import org.hibernate.query.sqm.EntityTypeException;
16 | import org.hibernate.query.sqm.PathElementException;
17 | import org.hibernate.query.sqm.TerminalPathException;
18 | import org.hibernate.type.descriptor.java.spi.JdbcTypeRecommendationException;
19 |
20 | import java.util.ArrayList;
21 | import java.util.List;
22 | import java.util.Set;
23 |
24 | import static java.lang.Character.isJavaIdentifierStart;
25 | import static java.lang.Integer.parseInt;
26 | import static java.util.stream.Stream.concat;
27 |
28 | /**
29 | * @author Gavin King
30 | */
31 | class Validation {
32 |
33 | interface Handler extends ANTLRErrorListener {
34 | void error(int start, int end, String message);
35 | void warn(int start, int end, String message);
36 |
37 | int getErrorCount();
38 | }
39 |
40 | static void validate(String hql, boolean checkParams,
41 | Set setParameterLabels,
42 | Set setParameterNames,
43 | Handler handler,
44 | MockSessionFactory factory) {
45 | validate(hql, checkParams, setParameterLabels, setParameterNames, handler, factory, 0);
46 | }
47 |
48 | static void validate(String hql, boolean checkParams,
49 | Set setParameterLabels,
50 | Set setParameterNames,
51 | Handler handler,
52 | MockSessionFactory factory,
53 | int errorOffset) {
54 | // handler = new Filter(handler, errorOffset);
55 |
56 | try {
57 |
58 | final HqlLexer hqlLexer = HqlParseTreeBuilder.INSTANCE.buildHqlLexer( hql );
59 | final HqlParser hqlParser = HqlParseTreeBuilder.INSTANCE.buildHqlParser( hql, hqlLexer );
60 | hqlLexer.addErrorListener( handler );
61 | hqlParser.getInterpreter().setPredictionMode( PredictionMode.SLL );
62 | hqlParser.removeErrorListeners();
63 | hqlParser.addErrorListener( handler );
64 | hqlParser.setErrorHandler( new BailErrorStrategy() );
65 |
66 | HqlParser.StatementContext statementContext;
67 | try {
68 | statementContext = hqlParser.statement();
69 | }
70 | catch ( ParseCancellationException e) {
71 | // reset the input token stream and parser state
72 | hqlLexer.reset();
73 | hqlParser.reset();
74 |
75 | // fall back to LL(k)-based parsing
76 | hqlParser.getInterpreter().setPredictionMode( PredictionMode.LL );
77 | hqlParser.setErrorHandler( new DefaultErrorStrategy() );
78 |
79 | statementContext = hqlParser.statement();
80 |
81 | }
82 | if (handler.getErrorCount() == 0) {
83 | try {
84 | new SemanticQueryBuilder<>( Object[].class, () -> false, factory )
85 | .visitStatement( statementContext );
86 | }
87 | catch (JdbcTypeRecommendationException ignored) {
88 | // just squash these for now
89 | }
90 | catch (QueryException | PathElementException | TerminalPathException | EntityTypeException
91 | | PropertyNotFoundException se) { //TODO is this one really thrown by core? It should not be!
92 | handler.error( -errorOffset+1, -errorOffset + hql.length(), se.getMessage() );
93 | }
94 | }
95 |
96 | if (checkParams) {
97 | checkParameterBinding(hql, setParameterLabels, setParameterNames, handler, errorOffset);
98 | }
99 | }
100 | catch (Exception e) {
101 | e.printStackTrace();
102 | }
103 | }
104 |
105 | private static void checkParameterBinding(
106 | String hql,
107 | Set setParameterLabels,
108 | Set setParameterNames,
109 | Handler handler,
110 | int errorOffset) {
111 | try {
112 | String unsetParams = null;
113 | String notSet = null;
114 | String parameters = null;
115 | int start = -1;
116 | int end = -1;
117 | List names = new ArrayList<>();
118 | List labels = new ArrayList<>();
119 | final HqlLexer hqlLexer = HqlParseTreeBuilder.INSTANCE.buildHqlLexer( hql );
120 | loop:
121 | while (true) {
122 | Token token = hqlLexer.nextToken();
123 | int tokenType = token.getType();
124 | switch (tokenType) {
125 | case HqlLexer.EOF:
126 | break loop;
127 | case HqlLexer.QUESTION_MARK:
128 | case HqlLexer.COLON:
129 | Token next = hqlLexer.nextToken();
130 | String text = next.getText();
131 | switch (tokenType) {
132 | case HqlLexer.COLON:
133 | if (!text.isEmpty()
134 | && isJavaIdentifierStart(text.codePointAt(0))) {
135 | names.add(text);
136 | if (setParameterNames.contains(text)) {
137 | continue;
138 | }
139 | }
140 | else {
141 | continue;
142 | }
143 | break;
144 | case HqlLexer.QUESTION_MARK:
145 | if (next.getType() == HqlLexer.INTEGER_LITERAL) {
146 | int label;
147 | try {
148 | label = parseInt(text);
149 | }
150 | catch (NumberFormatException nfe) {
151 | continue;
152 | }
153 | labels.add(label);
154 | if (setParameterLabels.contains(label)) {
155 | continue;
156 | }
157 | }
158 | else {
159 | continue;
160 | }
161 | break;
162 | default:
163 | continue;
164 | }
165 | parameters = unsetParams == null ? "Parameter " : "Parameters ";
166 | notSet = unsetParams == null ? " is not set" : " are not set";
167 | unsetParams = unsetParams == null ? "" : unsetParams + ", ";
168 | unsetParams += token.getText() + text;
169 | if (start == -1)
170 | start = token.getCharPositionInLine(); //TODO: wrong for multiline query strings!
171 | end = token.getCharPositionInLine() + text.length() + 1;
172 | break;
173 | }
174 | }
175 | if (unsetParams != null) {
176 | handler.warn(start-errorOffset+1, end-errorOffset, parameters + unsetParams + notSet);
177 | }
178 |
179 | setParameterNames.removeAll(names);
180 | setParameterLabels.removeAll(labels);
181 |
182 | int count = setParameterNames.size() + setParameterLabels.size();
183 | if (count > 0) {
184 | String missingParams =
185 | concat(setParameterNames.stream().map(name -> ":" + name),
186 | setParameterLabels.stream().map(label -> "?" + label))
187 | .reduce((x, y) -> x + ", " + y)
188 | .orElse(null);
189 | String params =
190 | count == 1 ?
191 | "Parameter " :
192 | "Parameters ";
193 | String notOccur =
194 | count == 1 ?
195 | " does not occur in the query" :
196 | " do not occur in the query";
197 | handler.warn(0, 0, params + missingParams + notOccur);
198 | }
199 | }
200 | finally {
201 | setParameterNames.clear();
202 | setParameterLabels.clear();
203 | }
204 | }
205 |
206 |
207 | // private static class Filter implements Handler {
208 | // private final Handler delegate;
209 | // private final int errorOffset;
210 | // private int errorCount;
211 | //
212 | // @Override
213 | // public int getErrorCount() {
214 | // return errorCount;
215 | // }
216 | //
217 | // private Filter(Handler delegate, int errorOffset) {
218 | // this.delegate = delegate;
219 | // this.errorOffset = errorOffset;
220 | // }
221 | //
222 | // @Override
223 | // public void error(int start, int end, String message) {
224 | // delegate.error(start - errorOffset, end - errorOffset, message);
225 | // }
226 | //
227 | // @Override
228 | // public void warn(int start, int end, String message) {
229 | // delegate.warn(start - errorOffset, end - errorOffset, message);
230 | // }
231 | //
232 | // @Override
233 | // public void syntaxError(Recognizer, ?> recognizer,
234 | // Object offendingSymbol,
235 | // int line, int charPositionInLine,
236 | // String msg,
237 | // RecognitionException e) {
238 | //// if (errorCount > 0 && e instanceof NoViableAltException) {
239 | //// //ignore it, it's probably a useless "unexpected end of subtree"
240 | //// return;
241 | //// }
242 | // errorCount++;
243 | // delegate.syntaxError(recognizer, offendingSymbol, line, charPositionInLine, msg, e);
244 | // }
245 | //
246 | // @Override
247 | // public void reportAmbiguity(Parser parser, DFA dfa, int i, int i1, boolean b, BitSet bitSet, ATNConfigSet atnConfigSet) {
248 | // }
249 | //
250 | // @Override
251 | // public void reportAttemptingFullContext(Parser parser, DFA dfa, int i, int i1, BitSet bitSet, ATNConfigSet atnConfigSet) {
252 | // }
253 | //
254 | // @Override
255 | // public void reportContextSensitivity(Parser parser, DFA dfa, int i, int i1, int i2, ATNConfigSet atnConfigSet) {
256 | // }
257 | // }
258 | }
259 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/services/javax.annotation.processing.Processor:
--------------------------------------------------------------------------------
1 | org.hibernate.query.validator.HQLProcessor
--------------------------------------------------------------------------------
/src/test/java/org/hibernate/query/validator/test/HQLValidationTest.java:
--------------------------------------------------------------------------------
1 | package org.hibernate.query.validator.test;
2 |
3 | import org.eclipse.jdt.core.compiler.batch.BatchCompiler;
4 | import org.junit.Test;
5 |
6 | import java.io.ByteArrayOutputStream;
7 | import java.io.IOException;
8 | import java.io.PrintWriter;
9 | import java.nio.file.Files;
10 | import java.nio.file.Path;
11 | import java.nio.file.Paths;
12 | import java.util.ArrayList;
13 | import java.util.List;
14 |
15 | import static javax.tools.ToolProvider.getSystemJavaCompiler;
16 | import static org.hibernate.query.validator.HQLProcessor.forceEclipseForTesting;
17 | import static org.junit.Assert.assertFalse;
18 | import static org.junit.Assert.assertTrue;
19 |
20 | public class HQLValidationTest {
21 |
22 | private final Path TEST_LIBS = Paths.get("test-runtime-libs");
23 |
24 | @Test
25 | public void testJavac() throws Exception {
26 | String errors = compileWithJavac("test", "test.test");
27 |
28 | assertFalse(errors.contains("GoodQueries.java:"));
29 | assertFalse(errors.contains("PanachePerson.java:"));
30 | assertFalse(errors.contains("PanacheRepository.java:"));
31 |
32 | assertTrue(errors.contains("BadQueries.java:9: error: no viable alternative at input '*do'"));
33 | assertTrue(errors.contains("BadQueries.java:10: error: no viable alternative at input 'from"));
34 | assertTrue(errors.contains("BadQueries.java:11: error: no viable alternative at input '"));
35 | assertTrue(errors.contains("BadQueries.java:12: error: no viable alternative at input '"));
36 | assertTrue(errors.contains("BadQueries.java:13: error: Could not interpret path expression 'from"));
37 |
38 | assertTrue(errors.contains("BadQueries.java:15: error: Could not resolve class 'test.Nil' named for instantiation"));
39 | // assertTrue(errors.contains("BadQueries.java:16: error: test.Pair has no suitable constructor for types (Person)"));
40 | // assertTrue(errors.contains("BadQueries.java:17: error: test.Pair has no suitable constructor for types (Person, string)"));
41 | // assertTrue(errors.contains("BadQueries.java:53: error: test.Pair has no suitable constructor for types (integer, integer)"));
42 | // assertTrue(errors.contains("BadQueries.java:54: error: test.Pair has no suitable constructor for types (string, string)"));
43 |
44 | assertTrue(errors.contains("BadQueries.java:19: error: Could not resolve root entity 'People'"));
45 | assertTrue(errors.contains("BadQueries.java:20: error: Could not resolve attribute 'firstName' of 'Person'"));
46 | assertTrue(errors.contains("BadQueries.java:21: error: Could not resolve attribute 'addr' of 'Person'"));
47 | assertTrue(errors.contains("BadQueries.java:22: error: Could not resolve attribute 'town' of 'Address'"));
48 | assertTrue(errors.contains("BadQueries.java:23: error: Could not resolve attribute 'name' of 'Address'"));
49 | assertTrue(errors.contains("BadQueries.java:24: error: Could not resolve attribute 'type' of 'Country'"));
50 |
51 | assertTrue(errors.contains("BadQueries.java:26: error: Could not interpret attribute 'length' of basic-valued path"));
52 | assertTrue(errors.contains("BadQueries.java:27: error: Terminal path has no attribute 'length'"));
53 |
54 | assertTrue(errors.contains("BadQueries.java:29: error: Could not interpret path expression 'xxx'"));
55 | // assertTrue(errors.contains("BadQueries.java:30: warning: func is not defined"));
56 | // assertTrue(errors.contains("BadQueries.java:31: warning: custom is not defined"));
57 | // assertTrue(errors.contains("BadQueries.java:32: warning: p is not defined"));
58 | assertTrue(errors.contains("BadQueries.java:32: error: Could not interpret path expression 'p.name'"));
59 |
60 | //TODO re-enable!
61 | // assertTrue(errors.contains("BadQueries.java:34: error: key(), value(), or entry() argument must be map element"));
62 | // assertTrue(errors.contains("BadQueries.java:35: error: key(), value(), or entry() argument must be map element"));
63 | // assertTrue(errors.contains("BadQueries.java:36: error: key(), value(), or entry() argument must be map element"));
64 |
65 | assertTrue(errors.contains("BadQueries.java:39: error: mismatched input '.', expecting"));
66 |
67 | assertTrue(errors.contains("BadQueries.java:41: error: Unlabeled ordinal parameter ('?' rather than ?1)"));
68 |
69 | assertTrue(errors.contains("Person.java:22: error: Could not resolve attribute 'x' of 'Person'"));
70 |
71 | assertTrue(errors.contains("BadQueries.java:46: warning: Parameter ?2 is not set"));
72 | assertTrue(errors.contains("BadQueries.java:48: warning: Parameter :name is not set"));
73 |
74 | assertTrue(errors.contains("BadQueries.java:51: warning: Parameter :hello does not occur in the query"));
75 |
76 | assertTrue(errors.contains("BadQueries.java:56: error: token recognition error at: ''gavin'"));
77 | assertTrue(errors.contains("BadQueries.java:57: error: token recognition error at: ''gavin'"));
78 |
79 | assertTrue(errors.contains("BadQueries.java:59: error: no viable alternative at input '*fromPerson'"));
80 |
81 | assertTrue(errors.contains("BadQueries.java:62: error: Could not resolve treat target type 'Employe'"));
82 |
83 | assertPanacheErrors(errors, "PanacheBadPerson", 22);
84 | assertPanacheErrors(errors, "PanacheBadPersonRepository", 10);
85 | }
86 |
87 | private void assertPanacheErrors(String errors, String name, int start) {
88 | // assertTrue(errors.contains(name+".java:"+(start)+": warning: missing is not defined (add it to whitelist)"));
89 | assertTrue(errors.contains(name+".java:"+(start+1)+": warning: Parameter ?2 is not set"));
90 | assertTrue(errors.contains(name+".java:"+(start+2)+": warning: Parameter :id is not set"));
91 | assertTrue(errors.contains(name+".java:"+(start+3)+": warning: Missing required argument for 'name'"));
92 | assertTrue(errors.contains(name+".java:"+(start+4)+": warning: Parameter :bar is not set"));
93 | assertTrue(errors.contains(name+".java:"+(start+5)+": warning: Missing required argument for 'name'"));
94 | assertTrue(errors.contains(name+".java:"+(start+6)+": warning: Missing required argument for 'name'"));
95 | assertTrue(errors.contains(name+".java:"+(start+7)+": warning: Missing required argument for 'name'"));
96 | assertTrue(errors.contains(name+".java:"+(start+8)+": warning: Missing required argument for 'name'"));
97 | assertTrue(errors.contains(name+".java:"+(start+9)+": warning: Missing required argument for 'name'"));
98 | assertTrue(errors.contains(name+".java:"+(start+10)+": warning: Missing required argument for 'name'"));
99 | assertTrue(errors.contains(name+".java:"+(start+11)+": warning: Missing required argument for 'name'"));
100 | assertTrue(errors.contains(name+".java:"+(start+12)+": warning: Too many arguments for 'name'"));
101 | }
102 |
103 | @Test
104 | public void testECJ() throws Exception {
105 | String errors = compileWithECJ("test", "test.test");
106 |
107 | assertECJ(errors);
108 | }
109 |
110 | private void assertECJ(String errors) {
111 | assertFalse(errors.contains("GoodQueries.java"));
112 | assertFalse(errors.contains("PanachePerson.java"));
113 | assertFalse(errors.contains("PanachePersonRepository.java"));
114 |
115 | assertTrue(errors.contains("mismatched input 'do'") && errors.contains("BadQueries.java (at line 9)"));
116 | assertTrue(errors.contains("no viable alternative at input 'from") && errors.contains("BadQueries.java (at line 10)"));
117 | assertTrue(errors.contains("no viable alternative at input '") && errors.contains("BadQueries.java (at line 11)"));
118 | assertTrue(errors.contains("no viable alternative at input '") && errors.contains("BadQueries.java (at line 12)"));
119 | assertTrue(errors.contains("Could not interpret path expression 'from") && errors.contains("BadQueries.java (at line 13)"));
120 |
121 | assertTrue(errors.contains("Could not resolve class 'test.Nil' named for instantiation"));
122 | // assertTrue(errors.contains("test.Pair has no suitable constructor for types (Person)"));
123 | // assertTrue(errors.contains("test.Pair has no suitable constructor for types (Person, string)"));
124 | // assertTrue(errors.contains("test.Pair has no suitable constructor for types (string, string)"));
125 | // assertTrue(errors.contains("test.Pair has no suitable constructor for types (integer, integer)"));
126 |
127 | assertTrue(errors.contains("Could not resolve root entity 'People'") && errors.contains("BadQueries.java (at line 19)"));
128 | assertTrue(errors.contains("Could not resolve attribute 'firstName' of 'Person'") && errors.contains("BadQueries.java (at line 20)"));
129 | assertTrue(errors.contains("Could not resolve attribute 'addr' of 'Person'") && errors.contains("BadQueries.java (at line 21)"));
130 | assertTrue(errors.contains("Could not resolve attribute 'town' of 'Address'") && errors.contains("BadQueries.java (at line 22)"));
131 | assertTrue(errors.contains("Could not resolve attribute 'name' of 'Address'") && errors.contains("BadQueries.java (at line 13)"));
132 | assertTrue(errors.contains("Could not resolve attribute 'type' of 'Country'") && errors.contains("BadQueries.java (at line 24)"));
133 |
134 | assertTrue(errors.contains("Could not interpret attribute 'length' of basic-valued path") && errors.contains("BadQueries.java (at line 26)"));
135 | assertTrue(errors.contains("Terminal path has no attribute 'length'") && errors.contains("BadQueries.java (at line 27)"));
136 |
137 | assertTrue(errors.contains("Could not interpret path expression 'xxx'") && errors.contains("BadQueries.java (at line 29)"));
138 | // assertTrue(errors.contains("func is not defined") && errors.contains("BadQueries.java (at line 30)"));
139 | // assertTrue(errors.contains("custom is not defined") && errors.contains("BadQueries.java (at line 31)"));
140 | // assertTrue(errors.contains("p is not defined") && errors.contains("BadQueries.java (at line 32)"));
141 | assertTrue(errors.contains("Could not interpret path expression 'p.name'") && errors.contains("BadQueries.java (at line 32)"));
142 |
143 | assertTrue(errors.contains("mismatched input '.', expecting") && errors.contains("BadQueries.java (at line 39)"));
144 |
145 | assertTrue(errors.contains("Unlabeled ordinal parameter ('?' rather than ?1)") && errors.contains("BadQueries.java (at line 41)"));
146 |
147 | assertTrue(errors.contains("Could not resolve attribute 'x' of 'Person'") && errors.contains("Person.java (at line 22)"));
148 |
149 | assertTrue(errors.contains("Parameter ?2 is not set") && errors.contains("BadQueries.java (at line 46)"));
150 | assertTrue(errors.contains("Parameter :name is not set") && errors.contains("BadQueries.java (at line 48)"));
151 |
152 | assertTrue(errors.contains("Parameter :hello does not occur in the query") && errors.contains("BadQueries.java (at line 51)"));
153 |
154 | assertTrue(errors.contains("BadQueries.java (at line 46)") && errors.contains("Parameter ?2 is not set"));
155 | assertTrue(errors.contains("BadQueries.java (at line 48)") && errors.contains("Parameter :name is not set"));
156 |
157 | assertTrue(errors.contains("BadQueries.java (at line 51)") && errors.contains(":hello does not occur in the query"));
158 |
159 | assertTrue(errors.contains("no viable alternative at input '") && errors.contains("token recognition error at: ''gavin'")
160 | && errors.contains("BadQueries.java (at line 56)"));
161 | assertTrue(errors.contains("no viable alternative at input '") && errors.contains("token recognition error at: ''gavin'")
162 | && errors.contains("BadQueries.java (at line 57)"));
163 |
164 | assertTrue(errors.contains("no viable alternative at input '*fromPerson'") && errors.contains("BadQueries.java (at line 59)"));
165 |
166 | assertTrue(errors.contains("Could not resolve treat target type 'Employe'") && errors.contains("BadQueries.java (at line 62)"));
167 |
168 | assertPanacheErrorsEcj(errors, "PanacheBadPerson", 22);
169 | assertPanacheErrorsEcj(errors, "PanacheBadPersonRepository", 10);
170 | }
171 |
172 | private void assertPanacheErrorsEcj(String errors, String name, int start) {
173 | // assertTrue(errors.contains(name+".java (at line "+(start)+")") && errors.contains("Could not interpret path expression 'missing'"));
174 | assertTrue(errors.contains(name+".java (at line "+(start+1)+")") && errors.contains("Parameter ?2 is not set"));
175 | assertTrue(errors.contains(name+".java (at line "+(start+2)+")") && errors.contains("Parameter :id is not set"));
176 | assertTrue(errors.contains(name+".java (at line "+(start+3)+")") && errors.contains("Missing required argument for 'name'"));
177 | assertTrue(errors.contains(name+".java (at line "+(start+4)+")") && errors.contains("Parameter :bar is not set"));
178 | assertTrue(errors.contains(name+".java (at line "+(start+5)+")") && errors.contains("Missing required argument for 'name'"));
179 | assertTrue(errors.contains(name+".java (at line "+(start+6)+")") && errors.contains("Missing required argument for 'name'"));
180 | assertTrue(errors.contains(name+".java (at line "+(start+7)+")") && errors.contains("Missing required argument for 'name'"));
181 | assertTrue(errors.contains(name+".java (at line "+(start+8)+")") && errors.contains("Missing required argument for 'name'"));
182 | assertTrue(errors.contains(name+".java (at line "+(start+9)+")") && errors.contains("Missing required argument for 'name'"));
183 | assertTrue(errors.contains(name+".java (at line "+(start+10)+")") && errors.contains("Missing required argument for 'name'"));
184 | assertTrue(errors.contains(name+".java (at line "+(start+11)+")") && errors.contains("Missing required argument for 'name'"));
185 | assertTrue(errors.contains(name+".java (at line "+(start+12)+")") && errors.contains("Too many arguments for 'name'"));
186 | }
187 |
188 | @Test
189 | public void testEclipse() throws Exception {
190 | forceEclipseForTesting = true;
191 | String errors = compileWithECJ("test", "test.test");
192 |
193 | assertECJ(errors);
194 |
195 | forceEclipseForTesting = false;
196 | }
197 |
198 | private String compileWithJavac(String... packages) throws IOException {
199 | Path tempDir = Files.createTempDirectory("validator-test-out");
200 |
201 | List files = new ArrayList<>();
202 |
203 | // files.add("-verbose");
204 |
205 | files.add("-d");
206 | files.add(tempDir.toString());
207 |
208 | files.add("-classpath");
209 | StringBuilder cp = new StringBuilder();
210 |
211 | if (System.getProperty("gradle")!=null) {
212 | cp.append("build/libs/query-validator-2.0-SNAPSHOT.jar");
213 | cp.append(":build/classes/java/main:build/classes/groovy/main");
214 | }
215 | else {
216 | cp.append("out/production/query-validator");
217 | cp.append(":build/classes/java/main");
218 | }
219 |
220 | Files.list(TEST_LIBS)
221 | .map(Path::toString)
222 | .filter(s -> !s.contains("/ecj-") && ! s.contains("/org.eclipse.jdt.core_"))
223 | .forEach(s -> cp.append(":").append(s));
224 |
225 | System.out.println(cp);
226 | files.add(cp.toString());
227 |
228 | for (String pack: packages) {
229 | Files.list(Paths.get("src/test/source")
230 | .resolve(pack.replace('.', '/')))
231 | .map(Path::toString)
232 | .filter(s -> s.endsWith(".java"))
233 | .forEach(files::add);
234 | }
235 |
236 | String[] args = files.toArray(new String[0]);
237 | ByteArrayOutputStream err = new ByteArrayOutputStream();
238 |
239 | getSystemJavaCompiler().run(null, System.out, err, args);
240 |
241 | String errors = err.toString();
242 | System.out.println(errors);
243 | return errors;
244 | }
245 |
246 | private String compileWithECJ(String... packages) throws IOException {
247 | Path tempDir = Files.createTempDirectory("validator-test-out");
248 |
249 | List files = new ArrayList<>();
250 |
251 | // files.add("-verbose");
252 |
253 | files.add("-1.8");
254 |
255 | files.add("-d");
256 | files.add(tempDir.toString());
257 |
258 | files.add("-classpath");
259 | StringBuilder cp = new StringBuilder();
260 |
261 | boolean useFatjar;
262 | if (System.getProperty("gradle")!=null) {
263 | useFatjar = forceEclipseForTesting
264 | && Files.exists(Paths.get("build/libs/query-validator-2.0-SNAPSHOT-all.jar"));
265 | if (useFatjar) {
266 | cp.append("build/libs/query-validator-2.0-SNAPSHOT-all.jar");
267 | }
268 | else {
269 | cp.append("build/libs/query-validator-2.0-SNAPSHOT.jar");
270 | cp.append(":build/classes/java/main:build/classes/groovy/main");
271 | }
272 | }
273 | else {
274 | useFatjar = false;
275 | cp.append("build/classes/java/main:build/classes/groovy/main");
276 | }
277 |
278 | Files.list(TEST_LIBS)
279 | .map(Path::toString)
280 | .filter(s -> useFatjar ?
281 | s.contains("/jakarta.persistence")
282 | || s.contains("/quarkus-")
283 | || s.contains("/hibernate-core")
284 | || s.contains("/org.eclipse.jdt.core_") :
285 | !s.contains(forceEclipseForTesting ?
286 | "/ecj-" : "/org.eclipse.jdt.core_"))
287 |
288 | .forEach(s -> cp.append(":").append(s));
289 |
290 | System.out.println(cp);
291 | files.add(cp.toString());
292 |
293 | for (String pack: packages) {
294 | Files.list(Paths.get("src/test/source")
295 | .resolve(pack.replace('.', '/')))
296 | .map(Path::toString)
297 | .filter(s -> s.endsWith(".java"))
298 | .forEach(files::add);
299 | }
300 |
301 | String[] args = files.toArray(new String[0]);
302 | ByteArrayOutputStream err = new ByteArrayOutputStream();
303 |
304 | BatchCompiler.compile(args,
305 | new PrintWriter(System.out),
306 | new PrintWriter(err), null);
307 |
308 | String errors = err.toString();
309 | System.out.println(errors);
310 | return errors;
311 | }
312 | }
--------------------------------------------------------------------------------
/src/test/source/test/Address.java:
--------------------------------------------------------------------------------
1 | package test;
2 |
3 | import jakarta.persistence.Id;
4 | import jakarta.persistence.Entity;
5 | import jakarta.persistence.OneToMany;
6 | import java.util.Set;
7 |
8 | @Entity
9 | public class Address {
10 | @Id long id;
11 | public String street;
12 | public String city;
13 | public String zip;
14 | public Country country;
15 | @OneToMany(mappedBy = "address")
16 | public Set currentResidents;
17 | }
18 |
--------------------------------------------------------------------------------
/src/test/source/test/BadQueries.java:
--------------------------------------------------------------------------------
1 | package test;
2 |
3 | //leave blank line or update test error locations
4 | //leave blank line or update test error locations
5 |
6 | public class BadQueries {
7 |
8 | public void badQueries() {
9 | createQuery("do"); //syntax error
10 | createQuery("from"); //syntax error
11 | createQuery("from Person where p.name='gavin' select"); //syntax error
12 | createQuery("from Person p where p.name+='gavin'"); //syntax error
13 | createQuery("select from Person where p.name='gavin'"); //syntax error
14 | if (1 == ".".length()) {
15 | createQuery("select new test.Nil(p,a) from Person p join p.address a"); //error
16 | createQuery("select new test.Pair(p) from Person p"); //error
17 | createQuery("select new test.Pair(p,p.name) from Person p"); //error
18 | }
19 | createQuery("from People p where p.name='gavin'"); //error
20 | createQuery("from Person p where p.firstName='gavin'"); //error
21 | createQuery("from Person p join p.addr a"); //error
22 | createQuery("from Person p where p.address.town='barcelona'"); //error
23 | createQuery("from Person p where p.name in (select a.name from Address a)"); //error
24 | createQuery("from Address add where add.country.type='au'"); //error
25 | for (int i=0; i<100; i++) {
26 | createQuery("select e from Employee e join e.contacts c where key(c).length > 0"); //error
27 | createQuery("select p.name, n from Person p join p.notes n where n.length>0"); //error
28 | }
29 | createQuery("select xxx from Person"); //warning
30 | createQuery("select func(p.name), year(current_date) from Person p"); //warning
31 | createQuery("from Person p where p.name = function('custom', p.id)"); //warning
32 | createQuery("select upper(p.name), year(current_date) from Person"); //error + warning
33 |
34 | createQuery("select p.name, n from Person p join p.notes n where key(n) = 0"); //error
35 | createQuery("select p.name, n from Person p join p.notes n where value(n) = ''"); //error
36 | createQuery("select p.name, n from Person p join p.notes n where entry(n) is not null"); //error
37 | createQuery("select p.name, n from Person p join p.notes n where index(n) = 0"); //error
38 |
39 | createQuery("select e from Employee e join e.contacts c where entry(c).value.address is null"); //error
40 |
41 | createQuery("from Person p where p.name = ? and p.id > ?"); //error
42 |
43 | createQuery("from Person p where max(indices(p.notes)) > 1"); //should be error!
44 | createQuery("from Person p where sum(elements(p.notes)) = ''"); //should be error!
45 |
46 | createQuery("from Person p where p.name = ?1 and p.id > ?2")
47 | .setParameter(1, "").getResultList(); //JPQL positional args
48 | createQuery("from Person p where p.name = :name and p.id >= :minId")
49 | .setParameter("minId", 12).getResultList(); //JPQL named args
50 |
51 | createQuery("from Person p").setParameter("hello", "world").getResultList();
52 |
53 | createQuery("select new test.Pair(1,1) from Person p"); //"select new" with literals
54 | createQuery("select new test.Pair('','') from Person p"); //"select new" with literals
55 |
56 | createQuery("from Person p where p.name='gavin"); //error
57 | createQuery("from Person p where p.firstName='gavin"); //error
58 |
59 | createQuery("from" + "Person"); //error missing space in concatenated query
60 | createQuery("from " + "Person" + " order by 1");
61 |
62 | createQuery("from Person p where treat(p.emergencyContact as Employe).employeeId = 2"); //JPQL "treat as" operator
63 | }
64 |
65 | private static Query createQuery(String s) { return new Query(); }
66 | private static class Query {
67 | public Query setParameter(String s, Object o) { return this; }
68 | public Query setParameter(int i, Object o) { return this; }
69 | public Query getResultList() { return this; }
70 | }
71 | }
--------------------------------------------------------------------------------
/src/test/source/test/Country.java:
--------------------------------------------------------------------------------
1 | package test;
2 |
3 | import jakarta.persistence.Embeddable;
4 | import jakarta.persistence.OneToMany;
5 | import java.util.Set;
6 |
7 | @Embeddable
8 | public class Country {
9 | public String code;
10 | public String name;
11 | public int thing = 0;
12 | @OneToMany
13 | public Set residents;
14 | }
15 |
--------------------------------------------------------------------------------
/src/test/source/test/Email.java:
--------------------------------------------------------------------------------
1 | package test;
2 |
3 | import jakarta.persistence.Embeddable;
4 |
5 | @Embeddable
6 | public class Email {
7 | public String address;
8 | }
9 |
--------------------------------------------------------------------------------
/src/test/source/test/Employee.java:
--------------------------------------------------------------------------------
1 | package test;
2 |
3 | import java.util.Map;
4 | import jakarta.persistence.Entity;
5 | import jakarta.persistence.Access;
6 | import jakarta.persistence.ManyToMany;
7 |
8 | import static jakarta.persistence.AccessType.PROPERTY;
9 |
10 | @Entity @Access(PROPERTY)
11 | public class Employee extends Person {
12 | private Integer id;
13 | private Map contacts;
14 |
15 | public Integer getEmployeeId() { return id; }
16 |
17 | @ManyToMany
18 | Map getContacts() { return contacts; }
19 | }
20 |
--------------------------------------------------------------------------------
/src/test/source/test/GoodQueries.java:
--------------------------------------------------------------------------------
1 | package test;
2 | import org.hibernate.annotations.processing.CheckHQL;
3 |
4 | @CheckHQL(dialect=org.hibernate.dialect.HSQLDialect.class)
5 | public class GoodQueries {
6 |
7 | public void goodQueries() {
8 | createQuery("from Person");
9 | // createQuery("from " + "Person" + " order by 1");
10 | createQuery("from test.Person"); //allowed by HQL
11 | createQuery("from Person p where lower(p.name)='gavin'");
12 | createQuery("from Employee p where p.name='gavin' and p.employeeId=111");
13 | createQuery("from Person p join p.address a where a.city='barcelona'");
14 | createQuery("from Person p where p.address.city='barcelona'");
15 | createQuery("from Person p join p.pastAddresses a where a.city='barcelona'");
16 |
17 | createQuery("from Person p where p.name in (select p.name from Person p)"); //"in" operator with subquery
18 | createQuery("from Person p where exists (select a from p.pastAddresses a)"); //"exists" operator with correlated subquery
19 |
20 | createQuery("select new test.Pair(p,a) from Person p join p.address a"); //"select new"
21 | createQuery("select new test.Pair(p,p.address) from Person p join p.address a"); //"select new"
22 | createQuery("select new test.Pair('',1) from Person p"); //"select new" with literals
23 |
24 | createQuery("from Person p where size(p.pastAddresses) = 0"); //JPQL "size()" function
25 | createQuery("from Person p where exists elements(p.pastAddresses)"); //HQL "exists" operator with "elements()" function
26 |
27 | createQuery("from Person p where year(p.dob) > 1974"); //HQL "year()" function
28 | createQuery("select cast(p.dob as string) from Person p"); //HQL "cast()" function
29 |
30 | createQuery("select e, c, key(c) from Employee e join e.contacts c where key(c) = 'boss'"); //JPQL "key()" operator for Map
31 | createQuery("select e, entry(c) from Employee e join e.contacts c where c.address is null"); //JPQL "entry()" operator for Map
32 | createQuery("select e, value(c) from Employee e join e.contacts c where value(c).address is null"); //JPQL "value()" operator for Map
33 | createQuery("select distinct key(contact) from Employee e join e.contacts contact where length(key(contact))<10 and contact.address is not null and contact.address.city is not null");
34 |
35 | createQuery("select substring(note,0,50) from Person p join p.notes note where index(note)<10"); //JPQL "index()" function for List
36 | createQuery("select substring(note,0,50) from Person p join p.notes note where length(note)>3"); //JPQL "length()" function for String
37 | createQuery("from Person p where size(p.notes) > 1"); //JPQL "size()" operator for collections
38 |
39 | createQuery("from Person p where '' = minelement(p.notes)"); //HQL "minelement()" function
40 | createQuery("from Person p where '' = maxelement(p.notes)"); //HQL "maxelement()" function
41 | createQuery("from Person p where maxindex(p.notes) > 1"); //HQL "maxindex()" function
42 | createQuery("from Person p where minindex(p.notes) > 1"); //HQL "minindex()" function
43 | createQuery("from Person p where 1 in indices(p.notes)"); //"in" operator with HQL "indices()" function
44 | createQuery("from Person p where '' in elements(p.notes)"); //"in" operator with HQL "elements()" function
45 | createQuery("from Person p where p.notes is empty"); //JPQL "is empty" operator
46 | createQuery("from Person p where p.notes is not empty"); //JPQL "is not empty" operator
47 | createQuery("select e from Employee e join e.contacts c where value(c).address is null");
48 |
49 | createQuery("from Person p where type(p) = Employee"); //JPQL "type()" function
50 |
51 | createQuery("from Person p where all(select a.city from p.pastAddresses a) = 'barcelona'"); //"all" operator with correlated subquery
52 | createQuery("from Person p where any(select a.city from p.pastAddresses a) = 'barcelona'"); //"any" operator with correlated subquery
53 | createQuery("from Person p where '' = all elements(p.notes)"); //"all" operator with correlated HQL "elements()" function
54 | createQuery("from Person p where 1 < any indices(p.notes)"); //"any" operator with correlated HQL "indices()" function
55 | createQuery("from Employee e where 'boss' = some indices(e.contacts)"); //"some" operator with correlated HQL "indices()" function
56 |
57 | createQuery("select p.name, n from Person p join p.notes n where length(n)>0"); //join an element collection
58 | createQuery("from Person p where p.notes[0] is not null"); //HQL list indexing operator
59 | createQuery("from Employee e where e.contacts['boss'] is not null"); //HQL map indexing operator
60 | createQuery("from Employee e where e.contacts['boss'].id = 222"); //HQL map indexing operator
61 |
62 | createQuery("from Address add where add.country.code='au'");
63 |
64 | createQuery("select p.whatever from Person p where p.whatever is not null");
65 | createQuery("select p.address.country.thing from Person p where p.address.country.thing is null");
66 |
67 | createQuery("select p2 from Person p join p.address.country.residents p2"); //join association in embeddable
68 | createQuery("from Person p where p.notes is empty"); //JPQL "is empty" operator for element collection
69 | createQuery("from Person p where p.pastAddresses is empty"); //JPQL "is empty" operator for association
70 | createQuery("from Address a where a.country.residents is empty"); //JPQL "is empty" operator for association in embeddable
71 | createQuery("from Person p where p.address.country.residents is empty"); //JPQL "is empty" operator
72 | createQuery("from Person p where p.address.currentResidents is empty"); //JPQL "is empty" operator
73 |
74 | createQuery("select a, c from Address a join a.country c"); //fake join to a component
75 | createQuery("select p, c from Person p join p.address.country c"); //fake join to a component
76 | createQuery("select c.code, c.name from Address a join a.country c"); //fake join to a component
77 |
78 | createQuery("select e from Person p join p.emails e");
79 | createQuery("select e.address from Person p join p.emails e");
80 | createQuery("select e from Person p join p.emails e where e.address is not null and length(e.address)>0");
81 |
82 | createQuery("from Person p where treat(p.emergencyContact as Employee).employeeId = 2"); //JPQL "treat as" operator
83 | createQuery("from Person p join treat(p.emergencyContact as Employee) c where c.employeeId = 2"); //JPQL "treat as" operator
84 | createQuery("from Employee e join treat(e.contacts as Employee) c where c.employeeId = 2"); //JPQL "treat as" operator
85 |
86 | createQuery("from Employee e where e.emergencyContact member of e.contacts"); //JPQL "member of" operator with association
87 | createQuery("from Employee e where e.emergencyContact not member of e.contacts"); //JPQL "not member of" operator with association
88 | createQuery("from Person p where '' member of p.notes"); //JPQL "member of" operator with element collection
89 | createQuery("from Person p where '' not member of p.notes"); //JPQL "not member of" operator with element collection
90 |
91 | createQuery("select upper(p.name), year(current_date) from Person p");
92 | createQuery("select upper(p.name), year(current_date) from Person p where year(current_date) = 2019 and upper(p.name) = 'GAVIN'");
93 |
94 | createQuery("select nullif(e.name, ''), coalesce(e.employeeId, e.id, e.name) from Employee e"); //JPQL "nullif()" and "coalesce()"
95 | createQuery("select str(p.dob) from Person p"); //HQL "str()" function
96 | createQuery("select cast(p.dob as string) from Person p"); //SQL "cast()" function
97 | createQuery("select extract(month from p.dob), extract(year from p.dob) from Person p"); //SQL "extract()" function
98 | createQuery("select function('bit_length', e.name) from Employee e"); //JPQL "function()" passthrough
99 |
100 | // createQuery("from Person p where p.sex = test.Sex.FEMALE"); //TODO: FIX!
101 | // createQuery("from Person p where test.test.Rating.Good = test.test.Rating.Bad"); //TODO: FIX!
102 |
103 | createQuery("from Person p where p.name = ?1 and p.id > ?2"); //JPQL positional args
104 | createQuery("from Person p where p.name = :name and p.id >= :minId"); //JPQL named args
105 |
106 | // createQuery("from Person p where p.dob = {d '2008-12-31'}");
107 | // createQuery("from Person p where p.dob = date '2008-12-31'");
108 | createQuery("select (local datetime - datetime 1974-03-25 2:30:00) by second");
109 | }
110 |
111 | public void okQueries() {
112 | // createQuery("select current_thing from Person"); //warning
113 | createQuery("select func(p.name), year(current_date) from Person p"); //warning
114 | createQuery("from Person p where p.name = function('custom', p.id)"); //warning
115 | }
116 |
117 | private static void createQuery(String s) {}
118 | }
--------------------------------------------------------------------------------
/src/test/source/test/Pair.java:
--------------------------------------------------------------------------------
1 | package test;
2 |
3 | public class Pair {
4 | public Pair(Person person, Address address) {}
5 | public Pair(String string, int integer) {}
6 | }
7 |
--------------------------------------------------------------------------------
/src/test/source/test/PanacheBadPerson.java:
--------------------------------------------------------------------------------
1 | package test;
2 |
3 | import jakarta.persistence.Entity;
4 | import jakarta.persistence.Basic;
5 | import jakarta.persistence.NamedQuery;
6 | import jakarta.persistence.NamedQueries;
7 | import io.quarkus.hibernate.orm.panache.PanacheEntity;
8 | import io.quarkus.panache.common.Sort;
9 | import io.quarkus.panache.common.Parameters;
10 |
11 | @Entity
12 | @NamedQueries({
13 | @NamedQuery(name="ok", query="from PanacheBadPerson p where p.id=1"),
14 | @NamedQuery(name="broke", query="from PanacheBadPerson p where p.x=1")
15 | })
16 | public class PanacheBadPerson extends PanacheEntity {
17 | public String name;
18 | @Basic(optional=false)
19 | public Sex sex;
20 |
21 | public static void badQueries() {
22 | find("missing", "stef"); // property does not exist
23 | find("name = ?1 and id = ?2", "stef"); // missing positional arg
24 | find("name = :name and id = :id", Parameters.with("name", "stef")); // missing named arg
25 | find("name"); // missing required param for name
26 | find("name = :name and id = :id and id = :bar", Parameters.with("name", "stef").and("id", "foo")); // missing named arg
27 | find("name", Sort.descending("name")); // missing positional arg
28 | list("name"); // missing positional arg
29 | stream("name"); // missing positional arg
30 | delete("name"); // missing positional arg
31 | update("name"); // missing positional arg
32 | count("name"); // missing positional arg
33 | exists("name"); // missing positional arg
34 | find("name", 123, 345); // too many params for name
35 | find("", Sort.by("missing")); // property does not exist
36 | find("", Sort.by("name").and("missing")); // property does not exist
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/test/source/test/PanacheBadPersonRepository.java:
--------------------------------------------------------------------------------
1 | package test;
2 |
3 | import io.quarkus.hibernate.orm.panache.PanacheRepository;
4 | import io.quarkus.panache.common.Sort;
5 | import io.quarkus.panache.common.Parameters;
6 |
7 | public class PanacheBadPersonRepository implements PanacheRepository {
8 |
9 | public void badQueries() {
10 | find("missing", "stef"); // property does not exist
11 | find("name = ?1 and id = ?2", "stef"); // missing positional arg
12 | find("name = :name and id = :id", Parameters.with("name", "stef")); // missing named arg
13 | find("name"); // missing required param for name
14 | find("name = :name and id = :id and id = :bar", Parameters.with("name", "stef").and("id", "foo")); // missing named arg
15 | find("name", Sort.descending("name")); // missing positional arg
16 | list("name"); // missing positional arg
17 | stream("name"); // missing positional arg
18 | delete("name"); // missing positional arg
19 | update("name"); // missing positional arg
20 | count("name"); // missing positional arg
21 | exists("name"); // missing positional arg
22 | find("name", 123, 345); // too many params for name
23 | find("", Sort.by("missing")); // property does not exist
24 | find("", Sort.by("name").and("missing")); // property does not exist
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/test/source/test/PanachePerson.java:
--------------------------------------------------------------------------------
1 | package test;
2 |
3 | import jakarta.persistence.Entity;
4 | import jakarta.persistence.Basic;
5 | import jakarta.persistence.NamedQuery;
6 | import jakarta.persistence.NamedQueries;
7 | import io.quarkus.hibernate.orm.panache.PanacheEntity;
8 | import io.quarkus.panache.common.Sort;
9 | import io.quarkus.panache.common.Parameters;
10 |
11 | @Entity
12 | @NamedQueries({
13 | @NamedQuery(name="ok", query="from PanachePerson p where p.id=1"),
14 | })
15 | public class PanachePerson extends PanacheEntity {
16 | public String name;
17 | public String title;
18 | @Basic(optional=false)
19 | public Sex sex;
20 | public boolean completed;
21 |
22 | public static PanachePerson findByName(String name) {
23 | return find("name", name).firstResult();
24 | }
25 |
26 | public static void goodQueries() {
27 | find("name", "foo");
28 | find("name = ?1 and id = ?2", "foo", 2);
29 | find("name = :name and id = :id", Parameters.with("name", "foo").and("id", 2));
30 | find("name", Sort.by("name"), "foo");
31 | find("name = ?1 and id = ?2", Sort.by("name"), "foo", 2);
32 | find("name = :name and id = :id", Sort.by("name"), Parameters.with("name", "foo").and("id", 2));
33 | update("name", "foo");
34 | delete("name", "foo");
35 | count("name", "foo");
36 |
37 | find("order by name");
38 | find("from Person");
39 | String stef = "";
40 | find("completed = false and title like ?1", "%"+stef+"%");
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/test/source/test/PanachePersonRepository.java:
--------------------------------------------------------------------------------
1 | package test;
2 |
3 | import io.quarkus.hibernate.orm.panache.PanacheRepository;
4 | import io.quarkus.panache.common.Sort;
5 | import io.quarkus.panache.common.Parameters;
6 |
7 | public class PanachePersonRepository implements PanacheRepository {
8 |
9 | public PanachePerson findByName(String name) {
10 | return find("name", name).firstResult();
11 | }
12 |
13 | public void goodQueries() {
14 | find("name", "foo");
15 | find("name = ?1 and id = ?2", "foo", 2);
16 | find("name = :name and id = :id", Parameters.with("name", "foo").and("id", 2));
17 | find("name", Sort.by("name"), "foo");
18 | find("name = ?1 and id = ?2", Sort.by("name"), "foo", 2);
19 | find("name = :name and id = :id", Sort.by("name"), Parameters.with("name", "foo").and("id", 2));
20 | update("name", "foo");
21 | delete("name", "foo");
22 | count("name", "foo");
23 |
24 | find("order by name");
25 | find("from Person");
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/test/source/test/Person.java:
--------------------------------------------------------------------------------
1 | package test;
2 |
3 | import jakarta.persistence.Id;
4 | import jakarta.persistence.Entity;
5 | import jakarta.persistence.Basic;
6 | import jakarta.persistence.Access;
7 | import jakarta.persistence.ElementCollection;
8 | import jakarta.persistence.OneToMany;
9 | import jakarta.persistence.ManyToOne;
10 | import jakarta.persistence.OneToOne;
11 | import jakarta.persistence.NamedQuery;
12 | import jakarta.persistence.NamedQueries;
13 | import java.util.Set;
14 | import java.util.Date;
15 | import java.util.List;
16 |
17 | import static jakarta.persistence.AccessType.PROPERTY;
18 |
19 | @Entity
20 | @NamedQueries({
21 | @NamedQuery(name="ok", query="from Person p where p.id=1"),
22 | @NamedQuery(name="broke", query="from Person p where p.x=1")
23 | })
24 | public class Person {
25 | @Id long id;
26 | public String name;
27 | @Basic(optional=false)
28 | public Sex sex;
29 | public Date dob;
30 | @OneToOne
31 | public Address address;
32 | @OneToMany(targetEntity = Address.class)
33 | public Set pastAddresses;
34 | @ElementCollection
35 | public List notes;
36 | @Access(PROPERTY)
37 | public String getWhatever() { return "thing"; };
38 | @ElementCollection
39 | public Set emails;
40 | @ManyToOne
41 | public Person emergencyContact;
42 | }
43 |
--------------------------------------------------------------------------------
/src/test/source/test/Sex.java:
--------------------------------------------------------------------------------
1 | package test;
2 |
3 | public enum Sex {
4 | MALE, FEMALE
5 | }
--------------------------------------------------------------------------------
/src/test/source/test/package-info.java:
--------------------------------------------------------------------------------
1 | @CheckHQL
2 | package test;
3 |
4 | import org.hibernate.annotations.processing.CheckHQL;
5 |
--------------------------------------------------------------------------------
/src/test/source/test/test/Rating.java:
--------------------------------------------------------------------------------
1 | package test.test;
2 |
3 | public enum Rating {
4 | Good,
5 | Bad,
6 | Indifferent
7 | }
--------------------------------------------------------------------------------
/src/test/source/test/test/package-info.java:
--------------------------------------------------------------------------------
1 | package test.test;
2 |
--------------------------------------------------------------------------------