├── LICENSE ├── README.md ├── codecov.yml ├── pom.xml └── src ├── main └── java │ └── be │ └── bendem │ └── sqlstreams │ ├── BatchUpdate.java │ ├── Execute.java │ ├── ParameterProvider.java │ ├── PreparedStatementBinderByIndex.java │ ├── Query.java │ ├── ResultSetRetrieverByIndex.java │ ├── ResultSetRetrieverByName.java │ ├── Sql.java │ ├── StatementHolder.java │ ├── Transaction.java │ ├── UncheckedSqlException.java │ ├── Update.java │ ├── impl │ ├── BatchUpdateImpl.java │ ├── ExecuteImpl.java │ ├── ParameterProviderImpl.java │ ├── QueryImpl.java │ ├── ResultSetSpliterator.java │ ├── SqlBindings.java │ ├── SqlImpl.java │ ├── TransactionImpl.java │ └── UpdateImpl.java │ ├── package-info.java │ └── util │ ├── Closeable.java │ ├── DummyDataSource.java │ ├── SingleConnectionDataSource.java │ ├── SqlAction.java │ ├── SqlBiFunction.java │ ├── SqlConsumer.java │ ├── SqlFunction.java │ ├── SqlSupplier.java │ └── Wrap.java └── test └── java └── be └── bendem └── sqlstreams ├── BaseTests.java ├── BatchTests.java ├── CustomMappingTest.java ├── EnumTests.java ├── GeneratedValuesTests.java ├── QueryTests.java └── TransactionTests.java /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-2017 bendem 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## sql-streams 2 | 3 | [![Codeship Status for bendem/sql-streams](https://codeship.com/projects/55cad800-f141-0133-11e7-5a649d8f4ff2/status?branch=master)](https://codeship.com/projects/149357) 4 | 5 | `sql-streams` is a tool for the people that don't need or want to use an ORM but 6 | don't want to deal with the [JDBC API] either. It provides a light abstraction 7 | over JDBC without ever making it inaccessible. 8 | 9 | ## Features 10 | 11 | + Simple setup if you already have a `Connection` or a `DataSource` available 12 | + Fluent API 13 | + `ResultSet` is abstracted to a `Stream` 14 | + Classes that can be closed are `AutoCloseable` 15 | + `SQLException` are wrapped into `UncheckedSqlException` 16 | + Doesn't try to hide the JDBC primitives, they are never further than a method 17 | call away 18 | + Automatic type deduction with the `with` method 19 | 20 | ## Getting started 21 | 22 | To get started, all you need to do is to add `sql-streams` to your dependencies: 23 | ```xml 24 | 25 | be.bendem 26 | sql-streams 27 | [current version] 28 | 29 | ``` 30 | ```groovy 31 | compile 'be.bendem:sql-streams:[current version]' 32 | ``` 33 | 34 | Once it is done, you can create an instance of `Sql` using one of the two 35 | `connect` methods. 36 | 37 | ```java 38 | try (Sql sql = Sql.connect(datasource)) { 39 | Optional userEmail = sql 40 | .first("select email from users where user_id = ?") 41 | .with(userId); 42 | } 43 | ``` 44 | 45 | If you are using spring-boot, checkout [sql-streams-spring]! 46 | 47 | ## Development 48 | 49 | You will need [maven] to compile and install this library 50 | ```sh 51 | mvn install 52 | ``` 53 | 54 | In addition to the SQLite and H2 tests, you can run the tests with PostgreSQL by providing a jdbc 55 | connection url: 56 | ```sh 57 | PGURL=jdbc:postgresql:test mvn test 58 | # or with user and password if not using peer authentication 59 | PGURL=jdbc:postgresql://localhost/test PGUSER=test PGPASSWORD=test mvn test 60 | ``` 61 | 62 | [ORM]: http://www.oracle.com/technetwork/java/javaee/tech/persistence-jsp-140049.html 63 | [JDBC API]: https://docs.oracle.com/javase/8/docs/technotes/guides/jdbc/ 64 | [maven]: https://maven.apache.org/ 65 | [sql-streams-spring]: https://github.com/bendem/sql-streams-spring 66 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | range: "20...80" 3 | status: 4 | project: false 5 | patch: false 6 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | be.bendem 5 | sql-streams 6 | 0.1.1 7 | 8 | 9 | 1.8 10 | UTF-8 11 | 12 | 13 | 14 | 15 | maven-bendem-be 16 | ${env.MAVEN_REPOSITORY_URL} 17 | 18 | 19 | 20 | 21 | 22 | junit 23 | junit 24 | 4.12 25 | test 26 | 27 | 28 | org.xerial 29 | sqlite-jdbc 30 | 3.21.0.1 31 | test 32 | 33 | 34 | com.h2database 35 | h2 36 | 1.4.196 37 | test 38 | 39 | 40 | org.postgresql 41 | postgresql 42 | 42.1.4 43 | test 44 | 45 | 46 | 47 | 48 | 49 | 50 | org.apache.maven.wagon 51 | wagon-ssh 52 | 3.0.0 53 | 54 | 55 | 56 | 57 | 58 | org.apache.maven.plugins 59 | maven-compiler-plugin 60 | 3.7.0 61 | 62 | ${java.version} 63 | ${java.version} 64 | 65 | -Xlint 66 | 67 | 68 | 69 | 70 | org.apache.maven.plugins 71 | maven-javadoc-plugin 72 | 3.0.0 73 | 74 | 75 | attach-javadocs 76 | 77 | jar 78 | 79 | 80 | 81 | 82 | 83 | -Xdoclint:none 84 | 85 | 86 | 87 | org.apache.maven.plugins 88 | maven-source-plugin 89 | 3.0.1 90 | 91 | 92 | attach-sources 93 | 94 | jar 95 | 96 | 97 | 98 | 99 | 100 | org.apache.maven.plugins 101 | maven-surefire-plugin 102 | 2.20.1 103 | 104 | 105 | org.apache.maven.plugins 106 | maven-deploy-plugin 107 | 2.8.2 108 | 109 | 110 | org.jacoco 111 | jacoco-maven-plugin 112 | 0.7.9 113 | 114 | 115 | default-prepare-agent 116 | 117 | prepare-agent 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /src/main/java/be/bendem/sqlstreams/BatchUpdate.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams; 2 | 3 | import java.sql.PreparedStatement; 4 | 5 | public interface BatchUpdate extends ParameterProvider { 6 | 7 | /** 8 | * Ends the current batch of parameters. 9 | *

10 | * Calling this method before providing data will generally cause an exception. 11 | *

12 | * Not calling this method after your last batch of parameters will cause them 13 | * to be ignored. 14 | * 15 | * @return {code this} for chaining 16 | * @see PreparedStatement#addBatch() 17 | */ 18 | BatchUpdate next(); 19 | 20 | /** 21 | * Ends the current batch of parameters. 22 | *

23 | * Calling this method before providing data will generally cause an exception. 24 | *

25 | * Not calling this method after your last batch of parameters will cause them 26 | * to be ignored. 27 | * 28 | * @return {code this} for chaining 29 | * @see PreparedStatement#addBatch() 30 | */ 31 | BatchUpdate next(String sql); 32 | 33 | /** 34 | * Executes the query and return the amount of rows modified by this query. 35 | * 36 | * @return the amount of rows modified by this query 37 | * @see PreparedStatement#executeBatch() 38 | */ 39 | int[] counts(); 40 | 41 | /** 42 | * Executes the query and return the amount of rows modified by this query as a long. 43 | * 44 | * @return the amount of rows modified by this query 45 | * @see PreparedStatement#executeLargeBatch() 46 | */ 47 | long[] largeCounts(); 48 | 49 | /** 50 | * Executes the query and return the sum of the amount of rows modified by each batch. 51 | * 52 | * @return the amount of rows modified by this query 53 | * @see PreparedStatement#executeBatch() 54 | */ 55 | int count(); 56 | 57 | /** 58 | * Executes the query and return the sum of the amount of rows modified by each batch. 59 | * 60 | * @return the amount of rows modified by this query 61 | * @see PreparedStatement#executeLargeBatch() 62 | */ 63 | long largeCount(); 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/be/bendem/sqlstreams/Execute.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams; 2 | 3 | import java.sql.PreparedStatement; 4 | 5 | public interface Execute 6 | extends ParameterProvider, Statement> { 7 | 8 | /** 9 | * Executes this statement. 10 | * 11 | * @return {@code true} if the first result is a {@link java.sql.ResultSet} 12 | * object; {@code false} if the first result is an update count or 13 | * there is no result 14 | * @see PreparedStatement#execute() 15 | * @see StatementHolder#getStatement() 16 | */ 17 | boolean execute(); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/be/bendem/sqlstreams/ParameterProvider.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams; 2 | 3 | import be.bendem.sqlstreams.util.SqlConsumer; 4 | 5 | import java.io.InputStream; 6 | import java.io.Reader; 7 | import java.math.BigDecimal; 8 | import java.net.URL; 9 | import java.sql.*; 10 | import java.util.Calendar; 11 | 12 | /** 13 | * Represents an object holding a {@link PreparedStatement} and providing methods 14 | * to set it up for a sql query. 15 | * 16 | * @param the type of the parent to return from each method of this class 17 | * @param the type of the statement 18 | */ 19 | public interface ParameterProvider, Statement extends PreparedStatement> 20 | extends StatementHolder { 21 | 22 | /** 23 | * Prepares an instance of {@link PreparedStatement} to be executed. 24 | * 25 | * @param preparator an operation to execute on the statement 26 | * @return {@code this} for chaining 27 | */ 28 | Provider prepare(SqlConsumer preparator); 29 | 30 | /** 31 | * Sets multiple parameters using magic bindings. 32 | *

33 | * See here for the types 34 | * supported by this method. 35 | * 36 | * @param params parameters to set 37 | * @return {@code this} for chaining 38 | */ 39 | Provider with(Object... params); 40 | 41 | /** 42 | * Sets the designated parameter based on the type of the parameter given. 43 | *

44 | * See here for the types 45 | * supported by this method. 46 | * 47 | * @param index the index of the parameter to set (starting from 1) 48 | * @param x the value to set 49 | * @return {@code this} for chaining 50 | * @throws NullPointerException if {@code x} is {@code null} 51 | */ 52 | Provider set(int index, Object x); 53 | 54 | /** 55 | * Sets the designated parameter to the given {@link Array}. 56 | * 57 | * @param index the index of the parameter to set (starting from 1) 58 | * @param x the value to set 59 | * @return {@code this} for chaining 60 | * @see PreparedStatement#setArray(int, Array) 61 | */ 62 | Provider setArray(int index, Array x); 63 | 64 | /** 65 | * Sets the designated parameter to the given {@link InputStream}. 66 | * 67 | * @param index the index of the parameter to set (starting from 1) 68 | * @param x the value to set 69 | * @return {@code this} for chaining 70 | * @see PreparedStatement#setAsciiStream(int, InputStream) 71 | */ 72 | Provider setAsciiStream(int index, InputStream x); 73 | 74 | /** 75 | * Sets the designated parameter to the given {@link InputStream}. 76 | * 77 | * @param index the index of the parameter to set (starting from 1) 78 | * @param x the value to set 79 | * @param length the number of bytes in the stream 80 | * @return {@code this} for chaining 81 | * @see PreparedStatement#setAsciiStream(int, InputStream, int) 82 | */ 83 | Provider setAsciiStream(int index, InputStream x, int length); 84 | 85 | /** 86 | * Sets the designated parameter to the given {@link InputStream}. 87 | * 88 | * @param index the index of the parameter to set (starting from 1) 89 | * @param x the value to set 90 | * @param length the number of bytes in the stream 91 | * @return {@code this} for chaining 92 | * @see PreparedStatement#setAsciiStream(int, InputStream, long) 93 | */ 94 | Provider setAsciiStream(int index, InputStream x, long length); 95 | 96 | /** 97 | * Sets the designated parameter to the given {@link BigDecimal}. 98 | * 99 | * @param index the index of the parameter to set (starting from 1) 100 | * @param x the value to set 101 | * @return {@code this} for chaining 102 | * @see PreparedStatement#setBigDecimal(int, BigDecimal) 103 | */ 104 | Provider setBigDecimal(int index, BigDecimal x); 105 | 106 | /** 107 | * Sets the designated parameter to the given {@link InputStream}. 108 | * 109 | * @param index the index of the parameter to set (starting from 1) 110 | * @param x the value to set 111 | * @return {@code this} for chaining 112 | * @see PreparedStatement#setBinaryStream(int, InputStream) 113 | */ 114 | Provider setBinaryStream(int index, InputStream x); 115 | 116 | /** 117 | * Sets the designated parameter to the given {@link InputStream}. 118 | * 119 | * @param index the index of the parameter to set (starting from 1) 120 | * @param x the value to set 121 | * @param length the number of bytes in the stream 122 | * @return {@code this} for chaining 123 | * @see PreparedStatement#setBinaryStream(int, InputStream, int) 124 | */ 125 | Provider setBinaryStream(int index, InputStream x, int length); 126 | 127 | /** 128 | * Sets the designated parameter to the given {@link InputStream}. 129 | * 130 | * @param index the index of the parameter to set (starting from 1) 131 | * @param x the value to set 132 | * @param length the number of bytes in the stream 133 | * @return {@code this} for chaining 134 | * @see PreparedStatement#setBinaryStream(int, InputStream, long) 135 | */ 136 | Provider setBinaryStream(int index, InputStream x, long length); 137 | 138 | /** 139 | * Sets the designated parameter to the given {@link Blob}. 140 | * 141 | * @param index the index of the parameter to set (starting from 1) 142 | * @param x the value to set 143 | * @return {@code this} for chaining 144 | * @see PreparedStatement#setBlob(int, Blob) 145 | */ 146 | Provider setBlob(int index, Blob x); 147 | 148 | /** 149 | * Sets the designated parameter to the given {@link InputStream}. 150 | * 151 | * @param index the index of the parameter to set (starting from 1) 152 | * @param x the value to set 153 | * @return {@code this} for chaining 154 | * @see PreparedStatement#setBlob(int, InputStream) 155 | */ 156 | Provider setBlob(int index, InputStream x); 157 | 158 | /** 159 | * Sets the designated parameter to the given {@link InputStream}. 160 | * 161 | * @param index the index of the parameter to set (starting from 1) 162 | * @param x the value to set 163 | * @param length the number of bytes in the stream 164 | * @return {@code this} for chaining 165 | * @see PreparedStatement#setBlob(int, InputStream, long) 166 | */ 167 | Provider setBlob(int index, InputStream x, long length); 168 | 169 | /** 170 | * Sets the designated parameter to the given {@code boolean}. 171 | * 172 | * @param index the index of the parameter to set (starting from 1) 173 | * @param x the value to set 174 | * @return {@code this} for chaining 175 | * @see PreparedStatement#setBoolean(int, boolean) 176 | */ 177 | Provider setBoolean(int index, boolean x); 178 | 179 | /** 180 | * Sets the designated parameter to the given {@code byte}. 181 | * 182 | * @param index the index of the parameter to set (starting from 1) 183 | * @param x the value to set 184 | * @return {@code this} for chaining 185 | * @see PreparedStatement#setByte(int, byte) 186 | */ 187 | Provider setByte(int index, byte x); 188 | 189 | /** 190 | * Sets the designated parameter to the given {@code byte[]}. 191 | * 192 | * @param index the index of the parameter to set (starting from 1) 193 | * @param x the value to set 194 | * @return {@code this} for chaining 195 | * @see PreparedStatement#setBytes(int, byte[]) 196 | */ 197 | Provider setBytes(int index, byte x[]); 198 | 199 | /** 200 | * Sets the designated parameter to the given {@link Reader}. 201 | * 202 | * @param index the index of the parameter to set (starting from 1) 203 | * @param x the value to set 204 | * @return {@code this} for chaining 205 | * @see PreparedStatement#setCharacterStream(int, Reader) 206 | */ 207 | Provider setCharacterStream(int index, Reader x); 208 | 209 | /** 210 | * Sets the designated parameter to the given {@link Reader}. 211 | * 212 | * @param index the index of the parameter to set (starting from 1) 213 | * @param x the value to set 214 | * @param length the number of characters in the stream 215 | * @return {@code this} for chaining 216 | * @see PreparedStatement#setCharacterStream(int, Reader, int) 217 | */ 218 | Provider setCharacterStream(int index, Reader x, int length); 219 | 220 | /** 221 | * Sets the designated parameter to the given {@link Reader}. 222 | * 223 | * @param index the index of the parameter to set (starting from 1) 224 | * @param x the value to set 225 | * @param length the number of characters in the stream 226 | * @return {@code this} for chaining 227 | * @see PreparedStatement#setCharacterStream(int, Reader, long) 228 | */ 229 | Provider setCharacterStream(int index, Reader x, long length); 230 | 231 | /** 232 | * Sets the designated parameter to the given {@link Clob}. 233 | * 234 | * @param index the index of the parameter to set (starting from 1) 235 | * @param x the value to set 236 | * @return {@code this} for chaining 237 | * @see PreparedStatement#setClob(int, Clob) 238 | */ 239 | Provider setClob(int index, Clob x); 240 | 241 | /** 242 | * Sets the designated parameter to the given {@link Reader}. 243 | * 244 | * @param index the index of the parameter to set (starting from 1) 245 | * @param x the value to set 246 | * @return {@code this} for chaining 247 | * @see PreparedStatement#setClob(int, Reader) 248 | */ 249 | Provider setClob(int index, Reader x); 250 | 251 | /** 252 | * Sets the designated parameter to the given {@link Reader}. 253 | * 254 | * @param index the index of the parameter to set (starting from 1) 255 | * @param x the value to set 256 | * @param length the number of characters in the stream 257 | * @return {@code this} for chaining 258 | * @see PreparedStatement#setClob(int, Reader, long) 259 | */ 260 | Provider setClob(int index, Reader x, long length); 261 | 262 | /** 263 | * Sets the designated parameter to the given {@link Date}. 264 | * 265 | * @param index the index of the parameter to set (starting from 1) 266 | * @param x the value to set 267 | * @return {@code this} for chaining 268 | * @see PreparedStatement#setDate(int, Date) 269 | */ 270 | Provider setDate(int index, Date x); 271 | 272 | /** 273 | * Sets the designated parameter to the given {@link Date}. 274 | * 275 | * @param index the index of the parameter to set (starting from 1) 276 | * @param x the value to set 277 | * @param cal the {@link Calendar} object the driver will use to construct the date 278 | * @return {@code this} for chaining 279 | * @see PreparedStatement#setDate(int, Date, Calendar) 280 | */ 281 | Provider setDate(int index, Date x, Calendar cal); 282 | 283 | /** 284 | * Sets the designated parameter to the given {@code double}. 285 | * 286 | * @param index the index of the parameter to set (starting from 1) 287 | * @param x the value to set 288 | * @return {@code this} for chaining 289 | * @see PreparedStatement#setDouble(int, double) 290 | */ 291 | Provider setDouble(int index, double x); 292 | 293 | /** 294 | * Sets the designated parameter to the given {@code float}. 295 | * 296 | * @param index the index of the parameter to set (starting from 1) 297 | * @param x the value to set 298 | * @return {@code this} for chaining 299 | * @see PreparedStatement#setFloat(int, float) 300 | */ 301 | Provider setFloat(int index, float x); 302 | 303 | /** 304 | * Sets the designated parameter to the given {@code int}. 305 | * 306 | * @param index the index of the parameter to set (starting from 1) 307 | * @param x the value to set 308 | * @return {@code this} for chaining 309 | * @see PreparedStatement#setInt(int, int) 310 | */ 311 | Provider setInt(int index, int x); 312 | 313 | /** 314 | * Sets the designated parameter to the given {@code long}. 315 | * 316 | * @param index the index of the parameter to set (starting from 1) 317 | * @param x the value to set 318 | * @return {@code this} for chaining 319 | * @see PreparedStatement#setLong(int, long) 320 | */ 321 | Provider setLong(int index, long x); 322 | 323 | /** 324 | * Sets the designated parameter to the given {@link Reader}. 325 | * 326 | * @param index the index of the parameter to set (starting from 1) 327 | * @param x the value to set 328 | * @return {@code this} for chaining 329 | * @see PreparedStatement#setNCharacterStream(int, Reader) 330 | */ 331 | Provider setNCharacterStream(int index, Reader x); 332 | 333 | /** 334 | * Sets the designated parameter to the given {@link Reader}. 335 | * 336 | * @param index the index of the parameter to set (starting from 1) 337 | * @param x the value to set 338 | * @param length the number of characters in the stream 339 | * @return {@code this} for chaining 340 | * @see PreparedStatement#setNCharacterStream(int, Reader, long length) 341 | */ 342 | Provider setNCharacterStream(int index, Reader x, long length); 343 | 344 | /** 345 | * Sets the designated parameter to the given {@link NClob}. 346 | * 347 | * @param index the index of the parameter to set (starting from 1) 348 | * @param x the value to set 349 | * @return {@code this} for chaining 350 | * @see PreparedStatement#setNClob(int, NClob) 351 | */ 352 | Provider setNClob(int index, NClob x); 353 | 354 | /** 355 | * Sets the designated parameter to the given {@link Reader}. 356 | * 357 | * @param index the index of the parameter to set (starting from 1) 358 | * @param x the value to set 359 | * @return {@code this} for chaining 360 | * @see PreparedStatement#setNClob(int, Reader) 361 | */ 362 | Provider setNClob(int index, Reader x); 363 | 364 | /** 365 | * Sets the designated parameter to the given {@link Reader}. 366 | * 367 | * @param index the index of the parameter to set (starting from 1) 368 | * @param x the value to set 369 | * @param length the number of characters in the stream 370 | * @return {@code this} for chaining 371 | * @see PreparedStatement#setNClob(int, Reader, long) 372 | */ 373 | Provider setNClob(int index, Reader x, long length); 374 | 375 | /** 376 | * Sets the designated parameter to the given {@link String}. 377 | * 378 | * @param index the index of the parameter to set (starting from 1) 379 | * @param x the value to set 380 | * @return {@code this} for chaining 381 | * @see PreparedStatement#setNString(int, String) 382 | */ 383 | Provider setNString(int index, String x); 384 | 385 | /** 386 | * Sets the designated parameter to the given {@code int}. 387 | * 388 | * @param index the index of the parameter to set (starting from 1) 389 | * @param sqlType the SQL type code defined in {@link Types} 390 | * @return {@code this} for chaining 391 | * @see PreparedStatement#setNull(int, int) 392 | */ 393 | Provider setNull(int index, int sqlType); 394 | 395 | /** 396 | * Sets the designated parameter to the given {@code int}. 397 | * 398 | * @param index the index of the parameter to set (starting from 1) 399 | * @param sqlType the SQL type code defined in {@link Types} 400 | * @param typeName the fully-qualified name of an SQL user-defined type; 401 | * ignored if the parameter is not a user-defined type or REF 402 | * @return {@code this} for chaining 403 | * @see PreparedStatement#setNull(int, int, String) 404 | */ 405 | Provider setNull(int index, int sqlType, String typeName); 406 | 407 | /** 408 | * Sets the designated parameter to the given {@link Object}. 409 | * 410 | * @param index the index of the parameter to set (starting from 1) 411 | * @param x the value to set 412 | * @return {@code this} for chaining 413 | * @see PreparedStatement#setObject(int, Object) 414 | */ 415 | Provider setObject(int index, Object x); 416 | 417 | /** 418 | * Sets the designated parameter to the given {@link Object}. 419 | * 420 | * @param index the index of the parameter to set (starting from 1) 421 | * @param x the value to set 422 | * @param targetSqlType the SQL type code defined in {@link Types} 423 | * @return {@code this} for chaining 424 | * @see PreparedStatement#setObject(int, Object, int) 425 | */ 426 | Provider setObject(int index, Object x, int targetSqlType); 427 | 428 | /** 429 | * Sets the designated parameter to the given {@link Object}. 430 | * 431 | * @param index the index of the parameter to set (starting from 1) 432 | * @param x the value to set 433 | * @param targetSqlType the SQL type code defined in {@link Types} 434 | * @param scaleOrLength for {@code java.sql.Types.DECIMAL} 435 | * or {@code java.sql.Types.NUMERIC}, this is the number of digits 436 | * after the decimal point. For Java Object types {@link InputStream} 437 | * and {@link Reader}, this is the length of the data in the stream 438 | * or reader. For all other types, this value will be ignored. 439 | * @return {@code this} for chaining 440 | * @see PreparedStatement#setObject(int, Object, int, int) 441 | */ 442 | Provider setObject(int index, Object x, int targetSqlType, int scaleOrLength); 443 | 444 | /** 445 | * Sets the designated parameter to the given {@link Object}. 446 | * 447 | * @param index the index of the parameter to set (starting from 1) 448 | * @param x the value to set 449 | * @param targetSqlType the SQL type code defined in {@link Types} 450 | * @return {@code this} for chaining 451 | * @see PreparedStatement#setObject(int, Object, SQLType) 452 | */ 453 | Provider setObject(int index, Object x, SQLType targetSqlType); 454 | 455 | /** 456 | * Sets the designated parameter to the given {@link Object}. 457 | * 458 | * @param index the index of the parameter to set (starting from 1) 459 | * @param x the value to set 460 | * @param targetSqlType the SQL type code defined in {@link Types} 461 | * @param scaleOrLength for {@code java.sql.Types.DECIMAL} 462 | * or {@code java.sql.Types.NUMERIC}, this is the number of digits 463 | * after the decimal point. For Java Object types {@link InputStream} 464 | * and {@link Reader}, this is the length of the data in the stream 465 | * or reader. For all other types, this value will be ignored. 466 | * @return {@code this} for chaining 467 | * @see PreparedStatement#setObject(int, Object, SQLType, int) 468 | */ 469 | Provider setObject(int index, Object x, SQLType targetSqlType, int scaleOrLength); 470 | 471 | /** 472 | * Sets the designated parameter to the given {@link Ref}. 473 | * 474 | * @param index the index of the parameter to set (starting from 1) 475 | * @param x the value to set 476 | * @return {@code this} for chaining 477 | * @see PreparedStatement#setRef(int, Ref) 478 | */ 479 | Provider setRef(int index, Ref x); 480 | 481 | /** 482 | * Sets the designated parameter to the given {@link RowId}. 483 | * 484 | * @param index the index of the parameter to set (starting from 1) 485 | * @param x the value to set 486 | * @return {@code this} for chaining 487 | * @see PreparedStatement#setRowId(int, RowId) 488 | */ 489 | Provider setRowId(int index, RowId x); 490 | 491 | /** 492 | * Sets the designated parameter to the given {@code short}. 493 | * 494 | * @param index the index of the parameter to set (starting from 1) 495 | * @param x the value to set 496 | * @return {@code this} for chaining 497 | * @see PreparedStatement#setShort(int, short) 498 | */ 499 | Provider setShort(int index, short x); 500 | 501 | /** 502 | * Sets the designated parameter to the given {@link SQLXML}. 503 | * 504 | * @param index the index of the parameter to set (starting from 1) 505 | * @param x the value to set 506 | * @return {@code this} for chaining 507 | * @see PreparedStatement#setSQLXML(int, SQLXML) 508 | */ 509 | Provider setSQLXML(int index, SQLXML x); 510 | 511 | /** 512 | * Sets the designated parameter to the given {@link String}. 513 | * 514 | * @param index the index of the parameter to set (starting from 1) 515 | * @param x the value to set 516 | * @return {@code this} for chaining 517 | * @see PreparedStatement#setString(int, String) 518 | */ 519 | Provider setString(int index, String x); 520 | 521 | /** 522 | * Sets the designated parameter to the given {@link Time}. 523 | * 524 | * @param index the index of the parameter to set (starting from 1) 525 | * @param x the value to set 526 | * @return {@code this} for chaining 527 | * @see PreparedStatement#setTime(int, Time) 528 | */ 529 | Provider setTime(int index, Time x); 530 | 531 | /** 532 | * Sets the designated parameter to the given {@link Time}. 533 | * 534 | * @param index the index of the parameter to set (starting from 1) 535 | * @param x the value to set 536 | * @param cal the {@link Calendar} object the driver will use to construct the date 537 | * @return {@code this} for chaining 538 | * @see PreparedStatement#setTime(int, Time, Calendar) 539 | */ 540 | Provider setTime(int index, Time x, Calendar cal); 541 | 542 | /** 543 | * Sets the designated parameter to the given {@link Timestamp}. 544 | * 545 | * @param index the index of the parameter to set (starting from 1) 546 | * @param x the value to set 547 | * @return {@code this} for chaining 548 | * @see PreparedStatement#setTimestamp(int, Timestamp) 549 | */ 550 | Provider setTimestamp(int index, Timestamp x); 551 | 552 | /** 553 | * Sets the designated parameter to the given {@link Timestamp}. 554 | * 555 | * @param index the index of the parameter to set (starting from 1) 556 | * @param x the value to set 557 | * @param cal the {@link Calendar} object the driver will use to construct the date 558 | * @return {@code this} for chaining 559 | * @see PreparedStatement#setTimestamp(int, Timestamp, Calendar) 560 | */ 561 | Provider setTimestamp(int index, Timestamp x, Calendar cal); 562 | 563 | /** 564 | * Sets the designated parameter to the given {@link URL}. 565 | * 566 | * @param index the index of the parameter to set (starting from 1) 567 | * @param x the value to set 568 | * @return {@code this} for chaining 569 | * @see PreparedStatement#setURL(int, URL) 570 | */ 571 | Provider setURL(int index, URL x); 572 | } 573 | -------------------------------------------------------------------------------- /src/main/java/be/bendem/sqlstreams/PreparedStatementBinderByIndex.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams; 2 | 3 | import java.sql.PreparedStatement; 4 | import java.sql.SQLException; 5 | 6 | @FunctionalInterface 7 | public interface PreparedStatementBinderByIndex { 8 | 9 | void bind(PreparedStatement statement, int index, T value) throws SQLException; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/be/bendem/sqlstreams/Query.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams; 2 | 3 | import be.bendem.sqlstreams.util.SqlFunction; 4 | 5 | import java.sql.PreparedStatement; 6 | import java.sql.ResultSet; 7 | import java.util.Optional; 8 | import java.util.stream.Stream; 9 | 10 | public interface Query extends ParameterProvider { 11 | 12 | /** 13 | * Returns the first row of the current query using the provided mapping function. 14 | * 15 | * @param mapping the mapping function 16 | * @param the type of the returned element 17 | * @return the first mapped value of this query if any 18 | */ 19 | default Optional first(SqlFunction mapping) { 20 | return map(mapping).findFirst(); 21 | } 22 | 23 | /** 24 | * Maps each row returned by this query using the provided mapping function. 25 | *

26 | * This method should be called with a {@code try}-with-resources construct 27 | * to ensure that the underlying {@link java.sql.Statement} and {@link 28 | * ResultSet} are correctly closed. 29 | * 30 | * @param mapping the mapping function 31 | * @param the type of the elements of the returned stream 32 | * @return a lazily populated stream of each element returned by the query 33 | */ 34 | Stream map(SqlFunction mapping); 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/be/bendem/sqlstreams/ResultSetRetrieverByIndex.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams; 2 | 3 | import java.sql.ResultSet; 4 | import java.sql.SQLException; 5 | 6 | @FunctionalInterface 7 | public interface ResultSetRetrieverByIndex { 8 | 9 | T retrieve(ResultSet resultSet, int index) throws SQLException; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/be/bendem/sqlstreams/ResultSetRetrieverByName.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams; 2 | 3 | import java.sql.ResultSet; 4 | import java.sql.SQLException; 5 | 6 | @FunctionalInterface 7 | public interface ResultSetRetrieverByName { 8 | 9 | T retrieve(ResultSet resultSet, String name) throws SQLException; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/be/bendem/sqlstreams/Sql.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams; 2 | 3 | import be.bendem.sqlstreams.impl.SqlImpl; 4 | import be.bendem.sqlstreams.util.Closeable; 5 | import be.bendem.sqlstreams.util.SingleConnectionDataSource; 6 | import be.bendem.sqlstreams.util.SqlFunction; 7 | 8 | import java.sql.CallableStatement; 9 | import java.sql.Connection; 10 | import java.sql.PreparedStatement; 11 | import java.sql.ResultSet; 12 | import java.util.Optional; 13 | 14 | import javax.sql.DataSource; 15 | 16 | /** 17 | * This class is the main entry point from this library. It provides static 18 | * methods to set it up from a {@link Connection} or a {@link DataSource} 19 | * as well as all methods to query it. 20 | */ 21 | public interface Sql extends Closeable { 22 | 23 | /** 24 | * Constructs a {@link Sql} instance holding a single connection. 25 | * 26 | * @param connection the connection to use 27 | * @return the newly created {@code Sql} instance 28 | * @see #connect(DataSource) 29 | */ 30 | static Sql connect(Connection connection) { 31 | return connect(new SingleConnectionDataSource(connection)); 32 | } 33 | 34 | /** 35 | * Constructs a {@link Sql} instance retrieving new {@link Connection}s 36 | * from the provided {@link DataSource} as needed. 37 | * 38 | * @param dataSource the datasource supplying connections 39 | * @return the newly created {@code Sql} instance 40 | */ 41 | static Sql connect(DataSource dataSource) { 42 | return new SqlImpl(dataSource); 43 | } 44 | 45 | Sql registerCustomBinding(Class clazz, PreparedStatementBinderByIndex preparedStatementBinderByIndex); 46 | 47 | /** 48 | * Opens a new transaction bound to a single connection. 49 | * 50 | * @return the new transaction 51 | */ 52 | Transaction transaction(); 53 | 54 | /** 55 | * Opens a new transaction bound to a single connection. 56 | * 57 | * @param isolationLevel the isolation level of the underlying transaction 58 | * @return the new transaction 59 | * @see Connection#setTransactionIsolation(int) 60 | */ 61 | Transaction transaction(Transaction.IsolationLevel isolationLevel); 62 | 63 | /** 64 | * Manually prepares a query from a {@link Connection}. 65 | * 66 | * @param preparer the code creating a {@link PreparedStatement} from a 67 | * {@link Connection} 68 | * @return an object to parametrize the statement and map the query result 69 | */ 70 | Query query(SqlFunction preparer); 71 | 72 | /** 73 | * Prepares a query to be executed. 74 | *

75 | * Note that the query is not actually executed until a mapping method 76 | * of {@link Query} is called. 77 | * 78 | * @param sql the sql query 79 | * @return an object to parametrize the statement and map the query result 80 | */ 81 | default Query query(String sql) { 82 | return query(conn -> conn.prepareStatement(sql)); 83 | } 84 | 85 | /** 86 | * Manually prepares a DML query from a {@link Connection}. 87 | * 88 | * @param preparer the code creating a {@link PreparedStatement} from a 89 | * {@link Connection} 90 | * @return an object to parametrize and execute the DML statement 91 | */ 92 | Update update(SqlFunction preparer); 93 | 94 | /** 95 | * Prepares a DML sql statement. 96 | *

97 | * Not that the query is not actually executed until you invoke a 98 | * method from {@link Update}. 99 | * 100 | * @param sql the sql query 101 | * @return an object to parametrize the statement and retrieve the number 102 | * of rows affected by this query 103 | */ 104 | default Update update(String sql) { 105 | return update(conn -> conn.prepareStatement(sql)); 106 | } 107 | 108 | /** 109 | * Prepares a DML statement to provide it multiple batches of parameters. 110 | *

111 | * Note that the query is not actually executed until you invoke a 112 | * count method from {@link BatchUpdate}. 113 | * 114 | * @param sql the sql query 115 | * @return an object to parametrize the statement and retrieve counts 116 | * of affected rows 117 | */ 118 | BatchUpdate batchUpdate(String sql); 119 | 120 | /** 121 | * Prepares a query. 122 | *

123 | * Note that this method is not executed until you call {@link Execute#execute()}. 124 | * 125 | * @param sql the sql query 126 | * @return an object to parametrize the statement and execute the query 127 | */ 128 | Execute execute(String sql); 129 | 130 | /** 131 | * Prepares a call and provides it the given parameters. 132 | *

133 | * Note that this method is not executed until you call {@link Execute#execute()}. 134 | * 135 | * @param sql the sql query 136 | * @return an object to parametrize the statement and execute the query 137 | * @see Connection#prepareCall(String) 138 | */ 139 | Execute call(String sql); 140 | 141 | /** 142 | * Shortcut for {@link #query(String) query(sql).with(parameters).first(mapping)}. 143 | * 144 | * @param sql the sql query 145 | * @param mapping a function to map each row to an object 146 | * @param parameters parameters to apply in order to the provided query 147 | * @param the type of the elements of the returned stream 148 | * @return a stream of elements mapped from the result set 149 | * @see #query(String) 150 | * @see Query#first(SqlFunction) 151 | */ 152 | default Optional first(String sql, SqlFunction mapping, Object... parameters) { 153 | try (Query query = query(sql).with(parameters)) { 154 | return query.first(mapping); 155 | } 156 | } 157 | 158 | /** 159 | * Shortcut for {@link #execute(String) execute(sql).with(parameters).execute()}. 160 | * 161 | * @param sql the sql query 162 | * @param parameters parameters to apply in order to the provided query 163 | */ 164 | default void exec(String sql, Object... parameters) { 165 | try (Execute execute = execute(sql).with(parameters)) { 166 | execute.execute(); 167 | } 168 | } 169 | 170 | /** 171 | * Closes the underlying {@link DataSource}. 172 | */ 173 | void close(); 174 | } 175 | -------------------------------------------------------------------------------- /src/main/java/be/bendem/sqlstreams/StatementHolder.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams; 2 | 3 | import be.bendem.sqlstreams.util.Closeable; 4 | import be.bendem.sqlstreams.util.Wrap; 5 | 6 | import java.sql.PreparedStatement; 7 | 8 | /** 9 | * Represents an object holding an instance of {@link PreparedStatement}. 10 | * 11 | * @param the type of the statement 12 | */ 13 | public interface StatementHolder extends Closeable { 14 | 15 | /** 16 | * Returns the underlying statement handled by this object. 17 | * 18 | * @return the statement 19 | */ 20 | Statement getStatement(); 21 | 22 | /** 23 | * Executes the statement held by this object. 24 | * 25 | * @return {@code true} if the first result is a {@link java.sql.ResultSet} 26 | * object; {@code false} if the first result is an update count or 27 | * there is no result 28 | * @see PreparedStatement#execute() 29 | * @see StatementHolder#getStatement() 30 | */ 31 | default boolean execute() { 32 | return Wrap.get(getStatement()::execute); 33 | } 34 | 35 | /** 36 | * Closes the statement held by this object. 37 | */ 38 | default void close() { 39 | Wrap.execute(getStatement()::close); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/be/bendem/sqlstreams/Transaction.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams; 2 | 3 | import java.sql.Connection; 4 | 5 | /** 6 | * Represents a transaction bound to a specific {@link Connection}. 7 | *

8 | * This object implements {@link AutoCloseable} so that you can write code like 9 | *

{@code try (Transaction tr = sql.transaction()) {
10 |  *     // Code in transaction
11 |  * } // Automatic rollback
12 |  * }
13 | */ 14 | public interface Transaction extends Sql { 15 | 16 | enum IsolationLevel { 17 | NONE(Connection.TRANSACTION_NONE), 18 | READ_UNCOMMITTED(Connection.TRANSACTION_READ_UNCOMMITTED), 19 | READ_COMMITTED(Connection.TRANSACTION_READ_COMMITTED), 20 | REPEATABLE_READ(Connection.TRANSACTION_REPEATABLE_READ), 21 | SERIALIZABLE(Connection.TRANSACTION_SERIALIZABLE); 22 | 23 | public final int isolationLevel; 24 | 25 | IsolationLevel(int isolationLevel) { 26 | this.isolationLevel = isolationLevel; 27 | } 28 | } 29 | 30 | /** 31 | * Commits the current transaction. 32 | * 33 | * @return {@code this} for chaining 34 | * @see Connection#commit() 35 | */ 36 | Transaction commit(); 37 | 38 | /** 39 | * Rollbacks the current transaction. 40 | * 41 | * @return {@code this} for chaining 42 | * @see Connection#rollback() 43 | */ 44 | Transaction rollback(); 45 | 46 | /** 47 | * Gets the underlying {@link Connection} of this transaction. 48 | * 49 | * @return the underlying Connection object 50 | */ 51 | Connection getConnection(); 52 | 53 | /** 54 | * Closes the current transaction, rolling back any changes not committed. 55 | *

56 | * Closing the transaction closes the underlying connection. 57 | */ 58 | @Override 59 | void close(); 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/be/bendem/sqlstreams/UncheckedSqlException.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams; 2 | 3 | import java.sql.SQLException; 4 | 5 | /** 6 | * Wraps a {@link SQLException} into an unchecked exception. 7 | */ 8 | public class UncheckedSqlException extends RuntimeException { 9 | 10 | public UncheckedSqlException(SQLException exception) { 11 | super(exception); 12 | } 13 | 14 | public UncheckedSqlException(String message, SQLException exception) { 15 | super(message, exception); 16 | } 17 | 18 | @Override 19 | public SQLException getCause() { 20 | return (SQLException) super.getCause(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/be/bendem/sqlstreams/Update.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams; 2 | 3 | import java.sql.PreparedStatement; 4 | 5 | public interface Update extends ParameterProvider { 6 | 7 | /** 8 | * Executes the query and return the amount of rows modified by this query. 9 | * 10 | * @return the amount of rows modified by this query 11 | * @see PreparedStatement#executeUpdate() 12 | */ 13 | int count(); 14 | 15 | /** 16 | * Executes the query and return the amount of rows modified by this query as a long. 17 | * 18 | * @return the amount of rows modified by this query 19 | * @see PreparedStatement#executeLargeUpdate() 20 | */ 21 | long largeCount(); 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/be/bendem/sqlstreams/impl/BatchUpdateImpl.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams.impl; 2 | 3 | import be.bendem.sqlstreams.BatchUpdate; 4 | import be.bendem.sqlstreams.util.Wrap; 5 | 6 | import java.sql.Connection; 7 | import java.sql.PreparedStatement; 8 | import java.util.stream.IntStream; 9 | import java.util.stream.LongStream; 10 | 11 | class BatchUpdateImpl extends ParameterProviderImpl implements BatchUpdate { 12 | 13 | private final Connection connection; 14 | private final boolean closeConnection; 15 | 16 | BatchUpdateImpl(SqlImpl sql, Connection connection, PreparedStatement statement, boolean closeConnection) { 17 | super(statement, sql.bindings); 18 | this.connection = connection; 19 | this.closeConnection = closeConnection; 20 | } 21 | 22 | @Override 23 | public BatchUpdate next() { 24 | Wrap.execute(statement::addBatch); 25 | return this; 26 | } 27 | 28 | @Override 29 | public BatchUpdate next(String sql) { 30 | Wrap.execute(() -> statement.addBatch(sql)); 31 | return this; 32 | } 33 | 34 | @Override 35 | public int[] counts() { 36 | return Wrap.get(statement::executeBatch); 37 | } 38 | 39 | @Override 40 | public long[] largeCounts() { 41 | return Wrap.get(statement::executeLargeBatch); 42 | } 43 | 44 | @Override 45 | public int count() { 46 | return IntStream.of(Wrap.get(statement::executeBatch)).sum(); 47 | } 48 | 49 | @Override 50 | public long largeCount() { 51 | return LongStream.of(Wrap.get(statement::executeLargeBatch)).sum(); 52 | } 53 | 54 | public void close() { 55 | super.close(); 56 | if (closeConnection) { 57 | Wrap.execute(connection::close); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/be/bendem/sqlstreams/impl/ExecuteImpl.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams.impl; 2 | 3 | import be.bendem.sqlstreams.Execute; 4 | import be.bendem.sqlstreams.util.Wrap; 5 | 6 | import java.sql.Connection; 7 | import java.sql.PreparedStatement; 8 | 9 | class ExecuteImpl 10 | extends ParameterProviderImpl, Statement> implements Execute { 11 | 12 | private final Connection connection; 13 | private final boolean closeConnection; 14 | 15 | ExecuteImpl(SqlImpl sql, Connection connection, Statement statement, boolean closeConnection) { 16 | super(statement, sql.bindings); 17 | this.connection = connection; 18 | this.closeConnection = closeConnection; 19 | } 20 | 21 | @Override 22 | public boolean execute() { 23 | return super.execute(); 24 | } 25 | 26 | @Override 27 | public void close() { 28 | super.close(); 29 | if (closeConnection) { 30 | Wrap.execute(connection::close); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/be/bendem/sqlstreams/impl/ParameterProviderImpl.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams.impl; 2 | 3 | import be.bendem.sqlstreams.ParameterProvider; 4 | import be.bendem.sqlstreams.util.SqlConsumer; 5 | import be.bendem.sqlstreams.util.Wrap; 6 | 7 | import java.io.InputStream; 8 | import java.io.Reader; 9 | import java.math.BigDecimal; 10 | import java.net.URL; 11 | import java.sql.*; 12 | import java.util.Calendar; 13 | import java.util.Objects; 14 | 15 | @SuppressWarnings("unchecked") 16 | class ParameterProviderImpl, Statement extends PreparedStatement> 17 | implements ParameterProvider { 18 | 19 | protected final Statement statement; 20 | private final SqlBindings bindings; 21 | 22 | ParameterProviderImpl(Statement statement, SqlBindings bindings) { 23 | this.statement = statement; 24 | this.bindings = bindings; 25 | } 26 | 27 | @Override 28 | public Statement getStatement() { 29 | return statement; 30 | } 31 | 32 | @Override 33 | public Provider prepare(SqlConsumer preparator) { 34 | Wrap.execute(() -> preparator.accept(statement)); 35 | return (Provider) this; 36 | } 37 | 38 | @Override 39 | public Provider with(Object... params) { 40 | return prepare(statement -> bindings.bind(statement, params, 0)); 41 | } 42 | 43 | @Override 44 | public Provider set(int index, Object x) { 45 | return prepare(statement -> bindings.bind(statement, index, Objects.requireNonNull(x))); 46 | } 47 | 48 | @Override 49 | public Provider setArray(int index, Array x) { 50 | return prepare(statement -> statement.setArray(index, x)); 51 | } 52 | 53 | @Override 54 | public Provider setAsciiStream(int index, InputStream x) { 55 | return prepare(statement -> statement.setAsciiStream(index, x)); 56 | } 57 | 58 | @Override 59 | public Provider setAsciiStream(int index, InputStream x, int length) { 60 | return prepare(statement -> statement.setAsciiStream(index, x, length)); 61 | } 62 | 63 | @Override 64 | public Provider setAsciiStream(int index, InputStream x, long length) { 65 | return prepare(statement -> statement.setAsciiStream(index, x, length)); 66 | } 67 | 68 | @Override 69 | public Provider setBigDecimal(int index, BigDecimal x) { 70 | return prepare(statement -> statement.setBigDecimal(index, x)); 71 | } 72 | 73 | @Override 74 | public Provider setBinaryStream(int index, InputStream x) { 75 | return prepare(statement -> statement.setBinaryStream(index, x)); 76 | } 77 | 78 | @Override 79 | public Provider setBinaryStream(int index, InputStream x, int length) { 80 | return prepare(statement -> statement.setBinaryStream(index, x, length)); 81 | } 82 | 83 | @Override 84 | public Provider setBinaryStream(int index, InputStream x, long length) { 85 | return prepare(statement -> statement.setBinaryStream(index, x, length)); 86 | } 87 | 88 | @Override 89 | public Provider setBlob(int index, Blob x) { 90 | return prepare(statement -> statement.setBlob(index, x)); 91 | } 92 | 93 | @Override 94 | public Provider setBlob(int index, InputStream inputStream) { 95 | return prepare(statement -> statement.setBlob(index, inputStream)); 96 | } 97 | 98 | @Override 99 | public Provider setBlob(int index, InputStream inputStream, long length) { 100 | return prepare(statement -> statement.setBlob(index, inputStream, length)); 101 | } 102 | 103 | @Override 104 | public Provider setBoolean(int index, boolean x) { 105 | return prepare(statement -> statement.setBoolean(index, x)); 106 | } 107 | 108 | @Override 109 | public Provider setByte(int index, byte x) { 110 | return prepare(statement -> statement.setByte(index, x)); 111 | } 112 | 113 | @Override 114 | public Provider setBytes(int index, byte[] x) { 115 | return prepare(statement -> statement.setBytes(index, x)); 116 | } 117 | 118 | @Override 119 | public Provider setCharacterStream(int index, Reader reader) { 120 | return prepare(statement -> statement.setCharacterStream(index, reader)); 121 | } 122 | 123 | @Override 124 | public Provider setCharacterStream(int index, Reader reader, int length) { 125 | return prepare(statement -> statement.setCharacterStream(index, reader, length)); 126 | } 127 | 128 | @Override 129 | public Provider setCharacterStream(int index, Reader reader, long length) { 130 | return prepare(statement -> statement.setCharacterStream(index, reader, length)); 131 | } 132 | 133 | @Override 134 | public Provider setClob(int index, Clob x) { 135 | return prepare(statement -> statement.setClob(index, x)); 136 | } 137 | 138 | @Override 139 | public Provider setClob(int index, Reader reader) { 140 | return prepare(statement -> statement.setClob(index, reader)); 141 | } 142 | 143 | @Override 144 | public Provider setClob(int index, Reader reader, long length) { 145 | return prepare(statement -> statement.setClob(index, reader, length)); 146 | } 147 | 148 | @Override 149 | public Provider setDate(int index, Date x) { 150 | return prepare(statement -> statement.setDate(index, x)); 151 | } 152 | 153 | @Override 154 | public Provider setDate(int index, Date x, Calendar cal) { 155 | return prepare(statement -> statement.setDate(index, x, cal)); 156 | } 157 | 158 | @Override 159 | public Provider setDouble(int index, double x) { 160 | return prepare(statement -> statement.setDouble(index, x)); 161 | } 162 | 163 | @Override 164 | public Provider setFloat(int index, float x) { 165 | return prepare(statement -> statement.setFloat(index, x)); 166 | } 167 | 168 | @Override 169 | public Provider setInt(int index, int x) { 170 | return prepare(statement -> statement.setInt(index, x)); 171 | } 172 | 173 | @Override 174 | public Provider setLong(int index, long x) { 175 | return prepare(statement -> statement.setLong(index, x)); 176 | } 177 | 178 | @Override 179 | public Provider setNCharacterStream(int index, Reader value) { 180 | return prepare(statement -> statement.setNCharacterStream(index, value)); 181 | } 182 | 183 | @Override 184 | public Provider setNCharacterStream(int index, Reader value, long length) { 185 | return prepare(statement -> statement.setNCharacterStream(index, value, length)); 186 | } 187 | 188 | @Override 189 | public Provider setNClob(int index, NClob value) { 190 | return prepare(statement -> statement.setNClob(index, value)); 191 | } 192 | 193 | @Override 194 | public Provider setNClob(int index, Reader reader) { 195 | return prepare(statement -> statement.setNClob(index, reader)); 196 | } 197 | 198 | @Override 199 | public Provider setNClob(int index, Reader reader, long length) { 200 | return prepare(statement -> statement.setNClob(index, reader, length)); 201 | } 202 | 203 | @Override 204 | public Provider setNString(int index, String value) { 205 | return prepare(statement -> statement.setNString(index, value)); 206 | } 207 | 208 | @Override 209 | public Provider setNull(int index, int sqlType) { 210 | return prepare(statement -> statement.setNull(index, sqlType)); 211 | } 212 | 213 | @Override 214 | public Provider setNull(int index, int sqlType, String typeName) { 215 | return prepare(statement -> statement.setNull(index, sqlType, typeName)); 216 | } 217 | 218 | @Override 219 | public Provider setObject(int index, Object x) { 220 | return prepare(statement -> statement.setObject(index, x)); 221 | } 222 | 223 | @Override 224 | public Provider setObject(int index, Object x, int targetSqlType) { 225 | return prepare(statement -> statement.setObject(index, x, targetSqlType)); 226 | } 227 | 228 | @Override 229 | public Provider setObject(int index, Object x, int targetSqlType, int scaleOrLength) { 230 | return prepare(statement -> statement.setObject(index, x, targetSqlType, scaleOrLength)); 231 | } 232 | 233 | @Override 234 | public Provider setObject(int index, Object x, SQLType targetSqlType) { 235 | return prepare(statement -> statement.setObject(index, x, targetSqlType)); 236 | } 237 | 238 | @Override 239 | public Provider setObject(int index, Object x, SQLType targetSqlType, int scaleOrLength) { 240 | return prepare(statement -> statement.setObject(index, x, targetSqlType, scaleOrLength)); 241 | } 242 | 243 | @Override 244 | public Provider setRef(int index, Ref x) { 245 | return prepare(statement -> statement.setRef(index, x)); 246 | } 247 | 248 | @Override 249 | public Provider setRowId(int index, RowId x) { 250 | return prepare(statement -> statement.setRowId(index, x)); 251 | } 252 | 253 | @Override 254 | public Provider setShort(int index, short x) { 255 | return prepare(statement -> statement.setShort(index, x)); 256 | } 257 | 258 | @Override 259 | public Provider setSQLXML(int index, SQLXML xmlObject) { 260 | return prepare(statement -> statement.setSQLXML(index, xmlObject)); 261 | } 262 | 263 | @Override 264 | public Provider setString(int index, String x) { 265 | return prepare(statement -> statement.setString(index, x)); 266 | } 267 | 268 | @Override 269 | public Provider setTime(int index, Time x) { 270 | return prepare(statement -> statement.setTime(index, x)); 271 | } 272 | 273 | @Override 274 | public Provider setTime(int index, Time x, Calendar cal) { 275 | return prepare(statement -> statement.setTime(index, x, cal)); 276 | } 277 | 278 | @Override 279 | public Provider setTimestamp(int index, Timestamp x) { 280 | return prepare(statement -> statement.setTimestamp(index, x)); 281 | } 282 | 283 | @Override 284 | public Provider setTimestamp(int index, Timestamp x, Calendar cal) { 285 | return prepare(statement -> statement.setTimestamp(index, x, cal)); 286 | } 287 | 288 | @Override 289 | public Provider setURL(int index, URL x) { 290 | return prepare(statement -> statement.setURL(index, x)); 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /src/main/java/be/bendem/sqlstreams/impl/QueryImpl.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams.impl; 2 | 3 | import be.bendem.sqlstreams.Query; 4 | import be.bendem.sqlstreams.util.SqlFunction; 5 | import be.bendem.sqlstreams.util.Wrap; 6 | 7 | import java.sql.Connection; 8 | import java.sql.PreparedStatement; 9 | import java.sql.ResultSet; 10 | import java.util.stream.Stream; 11 | 12 | class QueryImpl extends ParameterProviderImpl implements Query { 13 | 14 | private final Connection connection; 15 | private final boolean closeConnection; 16 | 17 | QueryImpl(SqlImpl sql, Connection connection, PreparedStatement statement, boolean closeConnection) { 18 | super(statement, sql.bindings); 19 | this.connection = connection; 20 | this.closeConnection = closeConnection; 21 | } 22 | 23 | @Override 24 | public Stream map(SqlFunction mapping) { 25 | return ResultSetSpliterator.stream(mapping, Wrap.get(statement::executeQuery)) 26 | .onClose(this::close); 27 | } 28 | 29 | @Override 30 | public void close() { 31 | super.close(); 32 | if (closeConnection) { 33 | Wrap.execute(connection::close); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/be/bendem/sqlstreams/impl/ResultSetSpliterator.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams.impl; 2 | 3 | import be.bendem.sqlstreams.util.SqlFunction; 4 | import be.bendem.sqlstreams.util.Wrap; 5 | 6 | import java.sql.ResultSet; 7 | import java.util.Spliterator; 8 | import java.util.Spliterators; 9 | import java.util.function.Consumer; 10 | import java.util.stream.Stream; 11 | import java.util.stream.StreamSupport; 12 | 13 | public class ResultSetSpliterator extends Spliterators.AbstractSpliterator { 14 | 15 | static Stream stream(SqlFunction mapping, ResultSet resultSet) { 16 | return StreamSupport 17 | .stream(new ResultSetSpliterator<>(mapping, resultSet), false) 18 | .onClose(() -> Wrap.execute(resultSet::close)); 19 | } 20 | 21 | private final SqlFunction mapping; 22 | private final ResultSet resultSet; 23 | 24 | private ResultSetSpliterator(SqlFunction mapping, ResultSet resultSet) { 25 | super(Long.MAX_VALUE, Spliterator.ORDERED); 26 | this.resultSet = resultSet; 27 | this.mapping = mapping; 28 | } 29 | 30 | @Override 31 | public boolean tryAdvance(Consumer consumer) { 32 | return Wrap.get(() -> { 33 | boolean hasNext; 34 | if (hasNext = resultSet.next()) { 35 | consumer.accept(mapping.apply(resultSet)); 36 | } 37 | return hasNext; 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/be/bendem/sqlstreams/impl/SqlBindings.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams.impl; 2 | 3 | import be.bendem.sqlstreams.PreparedStatementBinderByIndex; 4 | import be.bendem.sqlstreams.ResultSetRetrieverByIndex; 5 | import be.bendem.sqlstreams.ResultSetRetrieverByName; 6 | 7 | import java.net.URL; 8 | import java.sql.*; 9 | import java.time.LocalDate; 10 | import java.time.LocalDateTime; 11 | import java.time.LocalTime; 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | /** 16 | * Provides type based bindings for {@link ResultSet} and {@link PreparedStatement} 17 | * parameters. 18 | *

19 | * Note that this class is not part of the public API of this library and as such, is 20 | * not required to stay compatible between versions. 21 | * @see ParameterProviderImpl#with(Object...) 22 | * @see ParameterProviderImpl#set(int, Object) 23 | */ 24 | public final class SqlBindings { 25 | 26 | public class Bindings { 27 | private final ResultSetRetrieverByIndex retrieverByIndex; 28 | private final ResultSetRetrieverByName retrieverByName; 29 | 30 | public Bindings(ResultSetRetrieverByIndex retrieverByIndex, 31 | ResultSetRetrieverByName retrieverByName) { 32 | this.retrieverByIndex = retrieverByIndex; 33 | this.retrieverByName = retrieverByName; 34 | } 35 | } 36 | 37 | private final Map, PreparedStatementBinderByIndex> binders; 38 | private final Map, Bindings> retrievers; 39 | 40 | SqlBindings() { 41 | this.binders = new HashMap<>(); 42 | this.retrievers = new HashMap<>(); 43 | 44 | addMapping(Date.class, ResultSet::getDate, ResultSet::getDate, PreparedStatement::setDate); 45 | addMapping(Time.class, ResultSet::getTime, ResultSet::getTime, PreparedStatement::setTime); 46 | addMapping(Timestamp.class, ResultSet::getTimestamp, ResultSet::getTimestamp, PreparedStatement::setTimestamp); 47 | 48 | addMapping(LocalDate.class, (rs, i) -> rs.getDate(i).toLocalDate(), (rs, name) -> rs.getDate(name).toLocalDate(), (statement, index, value) -> statement.setDate(index, Date.valueOf(value))); 49 | addMapping(LocalDateTime.class, (rs, i) -> rs.getTimestamp(i).toLocalDateTime(), (rs, name) -> rs.getTimestamp(name).toLocalDateTime(), (statement, index, value) -> statement.setTimestamp(index, Timestamp.valueOf(value))); 50 | addMapping(LocalTime.class, (rs, i) -> rs.getTime(i).toLocalTime(), (rs, name) -> rs.getTime(name).toLocalTime(), (statement, index, value) -> statement.setTime(index, Time.valueOf(value))); 51 | 52 | addMapping(String.class, ResultSet::getString, ResultSet::getString, PreparedStatement::setString); 53 | 54 | addMapping(Long.class, ResultSet::getLong, ResultSet::getLong, PreparedStatement::setLong); 55 | addMapping(long.class, ResultSet::getLong, ResultSet::getLong, PreparedStatement::setLong); 56 | addMapping(Integer.class, ResultSet::getInt, ResultSet::getInt, PreparedStatement::setInt); 57 | addMapping(int.class, ResultSet::getInt, ResultSet::getInt, PreparedStatement::setInt); 58 | addMapping(Short.class, ResultSet::getShort, ResultSet::getShort, PreparedStatement::setShort); 59 | addMapping(short.class, ResultSet::getShort, ResultSet::getShort, PreparedStatement::setShort); 60 | addMapping(Byte.class, ResultSet::getByte, ResultSet::getByte, PreparedStatement::setByte); 61 | addMapping(byte.class, ResultSet::getByte, ResultSet::getByte, PreparedStatement::setByte); 62 | addMapping(Boolean.class, ResultSet::getBoolean, ResultSet::getBoolean, PreparedStatement::setBoolean); 63 | addMapping(boolean.class, ResultSet::getBoolean, ResultSet::getBoolean, PreparedStatement::setBoolean); 64 | addMapping(URL.class, ResultSet::getURL, ResultSet::getURL, PreparedStatement::setURL); 65 | addMapping(byte[].class, ResultSet::getBytes, ResultSet::getBytes, PreparedStatement::setBytes); 66 | } 67 | 68 | void addMapping(Class clazz, 69 | ResultSetRetrieverByIndex fromBindingWithIndex, 70 | ResultSetRetrieverByName fromBindingWithName, 71 | PreparedStatementBinderByIndex preparedStatementBinderByIndex) { 72 | binders.put(clazz, preparedStatementBinderByIndex); 73 | retrievers.put(clazz, new Bindings<>(fromBindingWithIndex, fromBindingWithName)); 74 | } 75 | 76 | public Statement bind(Statement stmt, Object[] params, int offset) throws SQLException { 77 | for (int i = 0; i < params.length; ++i) { 78 | bind(stmt, i + offset + 1, params[i]); 79 | } 80 | return stmt; 81 | } 82 | 83 | @SuppressWarnings("unchecked") 84 | public void bind(PreparedStatement stmt, int index, T value) throws SQLException { 85 | PreparedStatementBinderByIndex toSqlBinding; 86 | 87 | if (value.getClass().isEnum()) { 88 | toSqlBinding = (s, i, v) -> s.setInt(i, ((Enum) v).ordinal()); 89 | } else { 90 | toSqlBinding = (PreparedStatementBinderByIndex) this.binders.get(value.getClass()); 91 | if (toSqlBinding == null) { 92 | throw new IllegalArgumentException("No binding for " + value.getClass()); 93 | } 94 | } 95 | 96 | toSqlBinding.bind(stmt, index, value); 97 | } 98 | 99 | public T retrieve(ResultSet resultSet, int index, Class clazz) throws SQLException { 100 | ResultSetRetrieverByIndex fromSqlBinding; 101 | 102 | if (clazz.isEnum()) { 103 | fromSqlBinding = (rs, i) -> clazz.getEnumConstants()[rs.getInt(i)]; 104 | } else { 105 | @SuppressWarnings("unchecked") 106 | Bindings bindings = (Bindings) this.retrievers.get(clazz); 107 | if (bindings == null) { 108 | throw new IllegalArgumentException("No binding for " + clazz); 109 | } 110 | 111 | fromSqlBinding = bindings.retrieverByIndex; 112 | } 113 | 114 | T retrieved = fromSqlBinding.retrieve(resultSet, index); 115 | return resultSet.wasNull() ? null : retrieved; 116 | } 117 | 118 | public T retrieve(ResultSet resultSet, String name, Class clazz) throws SQLException { 119 | ResultSetRetrieverByName fromSqlBinding; 120 | 121 | if (clazz.isEnum()) { 122 | fromSqlBinding = (rs, i) -> clazz.getEnumConstants()[rs.getInt(i)]; 123 | } else { 124 | @SuppressWarnings("unchecked") 125 | Bindings bindings = (Bindings) this.retrievers.get(clazz); 126 | if (bindings == null) { 127 | throw new IllegalArgumentException("No binding for " + clazz); 128 | } 129 | 130 | fromSqlBinding = bindings.retrieverByName; 131 | } 132 | 133 | T retrieved = fromSqlBinding.retrieve(resultSet, name); 134 | return resultSet.wasNull() ? null : retrieved; 135 | } 136 | 137 | public boolean hasBinder(Class clazz) { 138 | return binders.containsKey(clazz); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/main/java/be/bendem/sqlstreams/impl/SqlImpl.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams.impl; 2 | 3 | import be.bendem.sqlstreams.*; 4 | import be.bendem.sqlstreams.util.SqlBiFunction; 5 | import be.bendem.sqlstreams.util.SqlFunction; 6 | import be.bendem.sqlstreams.util.Wrap; 7 | 8 | import java.sql.*; 9 | import java.util.Objects; 10 | 11 | import javax.sql.DataSource; 12 | 13 | public class SqlImpl implements Sql { 14 | 15 | private final DataSource dataSource; 16 | final SqlBindings bindings; 17 | 18 | SqlImpl(SqlBindings bindings) { 19 | this.dataSource = null; 20 | this.bindings = bindings; 21 | } 22 | 23 | public SqlImpl(DataSource dataSource) { 24 | this.dataSource = Objects.requireNonNull(dataSource); 25 | this.bindings = new SqlBindings(); 26 | } 27 | 28 | protected Connection getConnection() { 29 | return Wrap.get(dataSource::getConnection); 30 | } 31 | 32 | protected boolean closeConnectionAfterAction() { 33 | return true; 34 | } 35 | 36 | @Override 37 | public SqlImpl registerCustomBinding(Class clazz, 38 | PreparedStatementBinderByIndex preparedStatementBinderByIndex) { 39 | bindings.addMapping(clazz, null, null, preparedStatementBinderByIndex); 40 | return this; 41 | } 42 | 43 | @Override 44 | public Transaction transaction() { 45 | return new TransactionImpl(this); 46 | } 47 | 48 | @Override 49 | public Transaction transaction(Transaction.IsolationLevel isolationLevel) { 50 | return new TransactionImpl(this, isolationLevel.isolationLevel); 51 | } 52 | 53 | @Override 54 | public Query query(SqlFunction preparer) { 55 | return prepare(QueryImpl::new, preparer); 56 | } 57 | 58 | @Override 59 | public Update update(SqlFunction preparer) { 60 | return prepare(UpdateImpl::new, preparer); 61 | } 62 | 63 | @Override 64 | public BatchUpdate batchUpdate(String sql) { 65 | return prepare(sql, BatchUpdateImpl::new, Connection::prepareStatement); 66 | } 67 | 68 | @Override 69 | public Execute execute(String sql) { 70 | return prepare(sql, ExecuteImpl::new, Connection::prepareStatement); 71 | } 72 | 73 | @Override 74 | public Execute call(String sql) { 75 | return prepare(sql, ExecuteImpl::new, Connection::prepareCall); 76 | } 77 | 78 | @Override 79 | public void close() { 80 | if (dataSource instanceof AutoCloseable) { 81 | try { 82 | ((AutoCloseable) dataSource).close(); 83 | } catch (SQLException e) { 84 | throw new UncheckedSqlException(e); 85 | } catch (Exception e) { 86 | throw new RuntimeException(e); 87 | } 88 | } 89 | } 90 | 91 | @FunctionalInterface 92 | private interface Creator { 93 | T create(SqlImpl impl, Connection connection, S statement, boolean closeConnectionAfterAction); 94 | } 95 | 96 | private T prepare(Creator creator, 97 | SqlFunction statementCreator) { 98 | Connection connection = getConnection(); 99 | return creator.create( 100 | this, 101 | connection, 102 | Wrap.get(() -> statementCreator.apply(connection)), 103 | closeConnectionAfterAction()); 104 | } 105 | 106 | private T prepare(String sql, 107 | Creator creator, 108 | SqlBiFunction statementCreator) { 109 | Connection connection = getConnection(); 110 | return creator.create( 111 | this, 112 | connection, 113 | Wrap.get(() -> statementCreator.apply(connection, sql)), 114 | closeConnectionAfterAction()); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/be/bendem/sqlstreams/impl/TransactionImpl.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams.impl; 2 | 3 | import be.bendem.sqlstreams.Transaction; 4 | import be.bendem.sqlstreams.util.Wrap; 5 | 6 | import java.sql.Connection; 7 | 8 | class TransactionImpl extends SqlImpl implements Transaction { 9 | 10 | private final Connection connection; 11 | 12 | TransactionImpl(SqlImpl sql) { 13 | super(sql.bindings); 14 | this.connection = sql.getConnection(); 15 | Wrap.execute(() -> connection.setAutoCommit(false)); 16 | } 17 | 18 | TransactionImpl(SqlImpl sql, int isolationLevel) { 19 | super(sql.bindings); 20 | this.connection = sql.getConnection(); 21 | Wrap.execute(() -> { 22 | connection.setAutoCommit(false); 23 | connection.setTransactionIsolation(isolationLevel); 24 | }); 25 | } 26 | 27 | @Override 28 | public Connection getConnection() { 29 | return connection; 30 | } 31 | 32 | @Override 33 | protected boolean closeConnectionAfterAction() { 34 | return false; 35 | } 36 | 37 | @Override 38 | public Transaction commit() { 39 | Wrap.execute(connection::commit); 40 | return this; 41 | } 42 | 43 | @Override 44 | public Transaction rollback() { 45 | Wrap.execute(connection::rollback); 46 | return this; 47 | } 48 | 49 | @Override 50 | public void close() { 51 | Wrap.execute(() -> { 52 | connection.rollback(); 53 | connection.close(); 54 | }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/be/bendem/sqlstreams/impl/UpdateImpl.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams.impl; 2 | 3 | import be.bendem.sqlstreams.Update; 4 | import be.bendem.sqlstreams.util.Wrap; 5 | 6 | import java.sql.Connection; 7 | import java.sql.PreparedStatement; 8 | 9 | class UpdateImpl extends ParameterProviderImpl implements Update { 10 | 11 | private final Connection connection; 12 | private final boolean closeConnection; 13 | 14 | UpdateImpl(SqlImpl sql, Connection connection, PreparedStatement statement, boolean closeConnection) { 15 | super(statement, sql.bindings); 16 | this.connection = connection; 17 | this.closeConnection = closeConnection; 18 | } 19 | 20 | @Override 21 | public int count() { 22 | return Wrap.get(statement::executeUpdate); 23 | } 24 | 25 | @Override 26 | public long largeCount() { 27 | return Wrap.get(statement::executeLargeUpdate); 28 | } 29 | 30 | public void close() { 31 | super.close(); 32 | if (closeConnection) { 33 | Wrap.execute(connection::close); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/be/bendem/sqlstreams/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This package contains the public API for the {@code sql-streams} package. 3 | *

4 | * {@code sql-streams} abstracts the {@link java.sql JDBC API} by providing tools to 5 | * easily handle set parameters of prepared statements and map database result sets to 6 | * classes. 7 | *

8 | * The heart of this library is the {@link be.bendem.sqlstreams.Sql} class which provides 9 | * static factories as well as the methods used to execute DML and DDL sql queries. These 10 | * methods generally come in two flavors: prepared and non prepared. 11 | *

12 | * The prepared methods return a {@code Prepared_____} instance implementing the {@link 13 | * be.bendem.sqlstreams.ParameterProvider} interface so as to allow you to set your query 14 | * parameters before executing your query. You'll most likely want to use the {@link 15 | * be.bendem.sqlstreams.ParameterProvider#with(java.lang.Object...)} method to set 16 | * parameters of the most common types. 17 | *

18 | * The non prepared methods are generally shortcuts for the prepared methods. 19 | *

20 | * You should make sure to always close instances you get from the library that 21 | * implement {@link java.lang.AutoCloseable} (applies to {@link java.util.stream.Stream}s 22 | * as well). This is generally best done using the {@code try}-with-resources construct 23 | * as such: 24 | *

{@code try (PreparedUpdate update = sql.update("update salaries set amount = amount * 2")) {
25 |  *     // use update here
26 |  * }}
27 | * 28 | *

Transactions

29 | * Transactions are handled using {@link be.bendem.sqlstreams.Sql#transaction()}. Unlike 30 | * the JDBC API, closing a transaction (and thus the underlying connection) is guaranteed 31 | * to rollback your current transaction. As such, transactional code is best written 32 | * using the {@code try}-with-resources construct as such: 33 | *
{@code try(Transaction transaction = sql.transaction()) {
34 |  *     transaction.update("update salaries set amount = amount * 2");
35 |  *     transaction.commit();
36 |  * } catch (UncheckedSqlException e) {
37 |  *     // Handle the exception
38 |  * } // automatic rollback of uncommitted data}
39 | * 40 | *

Mapping

41 | * This library offers a few facilities to map each row of a result set to a class. 42 | * 43 | *

Auto-magic mapping

44 | * Magic mapping provides automatic mapping between sql types and java types for java 45 | * primitives (byte, short, int, long, boolean), their boxed equivalent, {@link 46 | * java.lang.String}, {@link java.sql.Date}, {@link java.sql.Time}, {@link 47 | * java.sql.Timestamp}, {@link java.time.LocalDate}, {@link java.time.LocalTime} and 48 | * {@link java.time.LocalDateTime}. 49 | * 50 | *

Manual mapping

51 | * If you need a more complex mapping method, you can use {@link 52 | * be.bendem.sqlstreams.Query#map(be.bendem.sqlstreams.util.SqlFunction)} to map 53 | * each row of the result set using your own code. 54 | */ 55 | package be.bendem.sqlstreams; 56 | -------------------------------------------------------------------------------- /src/main/java/be/bendem/sqlstreams/util/Closeable.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams.util; 2 | 3 | public interface Closeable extends AutoCloseable { 4 | 5 | @Override 6 | void close(); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/be/bendem/sqlstreams/util/DummyDataSource.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams.util; 2 | 3 | import javax.sql.DataSource; 4 | import java.io.PrintWriter; 5 | import java.sql.Connection; 6 | import java.sql.SQLException; 7 | import java.sql.SQLFeatureNotSupportedException; 8 | import java.util.logging.Logger; 9 | 10 | /** 11 | * A stupid {@link DataSource} implementation that throws {@link UnsupportedOperationException} for every method except 12 | * {@link #getConnection()} and {@link #getConnection(String, String)}. 13 | * 14 | * This {@code DataSource} is used by {@link be.bendem.sqlstreams.Sql#connect(Connection)}. 15 | */ 16 | abstract class DummyDataSource implements DataSource { 17 | 18 | /** 19 | * Parameters passed to this method are ignored, this is equivalent to calling {@link #getConnection()} directly. 20 | */ 21 | @Override 22 | public Connection getConnection(String username, String password) throws SQLException { 23 | return getConnection(); 24 | } 25 | 26 | @Override 27 | public T unwrap(Class iface) throws SQLException { 28 | throw new UnsupportedOperationException(); 29 | } 30 | 31 | @Override 32 | public boolean isWrapperFor(Class iface) throws SQLException { 33 | throw new UnsupportedOperationException(); 34 | } 35 | 36 | @Override 37 | public PrintWriter getLogWriter() throws SQLException { 38 | throw new UnsupportedOperationException(); 39 | } 40 | 41 | @Override 42 | public void setLogWriter(PrintWriter out) throws SQLException { 43 | throw new UnsupportedOperationException(); 44 | } 45 | 46 | @Override 47 | public void setLoginTimeout(int seconds) throws SQLException { 48 | throw new UnsupportedOperationException(); 49 | } 50 | 51 | @Override 52 | public int getLoginTimeout() throws SQLException { 53 | throw new UnsupportedOperationException(); 54 | } 55 | 56 | @Override 57 | public Logger getParentLogger() throws SQLFeatureNotSupportedException { 58 | throw new UnsupportedOperationException(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/be/bendem/sqlstreams/util/SingleConnectionDataSource.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams.util; 2 | 3 | import be.bendem.sqlstreams.UncheckedSqlException; 4 | 5 | import java.lang.reflect.InvocationTargetException; 6 | import java.lang.reflect.Proxy; 7 | import java.sql.Connection; 8 | import java.sql.SQLException; 9 | import java.util.concurrent.atomic.AtomicBoolean; 10 | 11 | /** 12 | * Wraps a single connection that is reused each time {@link #getConnection()} is called. 13 | * 14 | * Note that closing the connection you got using {@link #getConnection()} will make it available again. If it is not 15 | * closed, calling {@link #getConnection()} will throw an {@link IllegalStateException}. 16 | * 17 | * To actually close the connection, call {@link #close()} on this DataSource. 18 | * 19 | * Instances of this class are thread-safe, but the connections returned when calling {@link #getConnection()} aren't. 20 | */ 21 | public class SingleConnectionDataSource extends DummyDataSource implements Closeable { 22 | 23 | private final AtomicBoolean inUse; 24 | private final Connection connection; 25 | private final Connection proxy; 26 | 27 | /** 28 | * Creates a single single connection datasource. 29 | * 30 | * @param connection the underlying jdbc connection to use 31 | */ 32 | public SingleConnectionDataSource(Connection connection) { 33 | this.inUse = new AtomicBoolean(false); 34 | this.connection = connection; 35 | this.proxy = (Connection) Proxy.newProxyInstance( 36 | getClass().getClassLoader(), 37 | new Class[] { Connection.class }, 38 | (proxy, method, args) -> { 39 | try { 40 | if (method.getName().equals("close") && method.getParameterCount() == 0) { 41 | if (!connection.getAutoCommit()) { 42 | connection.rollback(); 43 | } 44 | releaseConnection(); 45 | return null; 46 | } else if (method.getName().equals("isClosed") && method.getParameterCount() == 0) { 47 | return !inUse.get(); 48 | } 49 | return method.invoke(connection, args); 50 | } catch (InvocationTargetException e) { 51 | Throwable cause = e.getCause(); 52 | Exception exception; 53 | if (cause instanceof SQLException) { 54 | exception = new UncheckedSqlException((SQLException) cause); 55 | } else if (cause instanceof RuntimeException) { 56 | cause.addSuppressed(e); 57 | throw (RuntimeException) cause; 58 | } else if (cause instanceof Error) { 59 | cause.addSuppressed(e); 60 | throw (Error) cause; 61 | } else { 62 | exception = new RuntimeException(cause); 63 | } 64 | exception.addSuppressed(e); 65 | throw exception; 66 | } 67 | }); 68 | } 69 | 70 | @Override 71 | public Connection getConnection() throws SQLException { 72 | if (inUse.getAndSet(true)) { 73 | throw new IllegalStateException("Connection already in use"); 74 | } 75 | 76 | return proxy; 77 | } 78 | 79 | @Override 80 | public void close() { 81 | Wrap.execute(connection::close); 82 | } 83 | 84 | private void releaseConnection() { 85 | Wrap.execute(() -> connection.setAutoCommit(true)); 86 | inUse.set(false); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/be/bendem/sqlstreams/util/SqlAction.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams.util; 2 | 3 | import java.sql.SQLException; 4 | 5 | /** 6 | * Represents an action. 7 | */ 8 | @FunctionalInterface 9 | public interface SqlAction { 10 | 11 | /** 12 | * Performs this action. 13 | * 14 | * @throws SQLException generally rethrown as {@link be.bendem.sqlstreams.UncheckedSqlException} 15 | */ 16 | void execute() throws SQLException; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/be/bendem/sqlstreams/util/SqlBiFunction.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams.util; 2 | 3 | import java.sql.SQLException; 4 | 5 | /** 6 | * Represents a function that accepts two arguments and produces a result. 7 | * 8 | * @param the type of the first argument 9 | * @param the type of the second argument 10 | * @param the type of the result of the function 11 | */ 12 | @FunctionalInterface 13 | public interface SqlBiFunction { 14 | 15 | /** 16 | * Applies this function to the given arguments. 17 | * 18 | * @param t1 the first argument 19 | * @param t2 the second argument 20 | * @return the function result 21 | * @throws SQLException generally rethrown as {@link be.bendem.sqlstreams.UncheckedSqlException} 22 | */ 23 | R apply(T1 t1, T2 t2) throws SQLException; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/be/bendem/sqlstreams/util/SqlConsumer.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams.util; 2 | 3 | import java.sql.SQLException; 4 | 5 | /** 6 | * Represents an operation that accepts a single input argument and returns no result. 7 | * 8 | * @param the type of the input to the operation 9 | * 10 | */ 11 | @FunctionalInterface 12 | public interface SqlConsumer { 13 | 14 | /** 15 | * Performs this operation on the given argument. 16 | * 17 | * @param t the input argument 18 | * @throws SQLException generally rethrown as {@link be.bendem.sqlstreams.UncheckedSqlException} 19 | */ 20 | void accept(T t) throws SQLException; 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/be/bendem/sqlstreams/util/SqlFunction.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams.util; 2 | 3 | import java.sql.SQLException; 4 | 5 | /** 6 | * Represents a function that accepts one argument and produces a result. 7 | * 8 | * @param the type of the input to the function 9 | * @param the type of the result of the function 10 | */ 11 | @FunctionalInterface 12 | public interface SqlFunction { 13 | 14 | /** 15 | * Applies this function to the given argument. 16 | * 17 | * @param t the function argument 18 | * @return the function result 19 | * @throws SQLException generally rethrown as {@link be.bendem.sqlstreams.UncheckedSqlException} 20 | */ 21 | R apply(T t) throws SQLException; 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/be/bendem/sqlstreams/util/SqlSupplier.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams.util; 2 | 3 | import java.sql.SQLException; 4 | 5 | /** 6 | * Represents a supplier of data. 7 | * 8 | * @param the type of the supplied data 9 | */ 10 | @FunctionalInterface 11 | public interface SqlSupplier { 12 | 13 | /** 14 | * Gets a result. 15 | * 16 | * @return the result 17 | * @throws SQLException generally rethrown as {@link be.bendem.sqlstreams.UncheckedSqlException} 18 | */ 19 | T get() throws SQLException; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/be/bendem/sqlstreams/util/Wrap.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams.util; 2 | 3 | import be.bendem.sqlstreams.UncheckedSqlException; 4 | 5 | import java.sql.SQLException; 6 | 7 | /** 8 | * Utility to execute code that might throw a checked {@link SQLException} so that 9 | * it throws {@link UncheckedSqlException} instead. 10 | */ 11 | public final class Wrap { 12 | 13 | private Wrap() {} 14 | 15 | /** 16 | * Executes a possibly throwing code, wrapping any {@code SQLException} into 17 | * a {@code UncheckedSqlException}. 18 | * 19 | * @param action the action to execute 20 | * @throws UncheckedSqlException if an {@code SQLException} was thrown 21 | */ 22 | public static void execute(SqlAction action) throws UncheckedSqlException { 23 | try { 24 | action.execute(); 25 | } catch (SQLException e) { 26 | throw new UncheckedSqlException(e); 27 | } 28 | } 29 | 30 | /** 31 | * Executes a possibly throwing code and returns the result, wrapping any {@code 32 | * SQLException} into a {@code UncheckedSqlException}. 33 | * 34 | * @param supplier the action to execute 35 | * @param the type of the object returned 36 | * @return the object returned by the supplier 37 | * @throws UncheckedSqlException if an {@code SQLException} was thrown 38 | */ 39 | public static T get(SqlSupplier supplier) throws UncheckedSqlException { 40 | try { 41 | return supplier.get(); 42 | } catch (SQLException e) { 43 | throw new UncheckedSqlException(e); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/be/bendem/sqlstreams/BaseTests.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams; 2 | 3 | import be.bendem.sqlstreams.util.Wrap; 4 | import org.junit.After; 5 | import org.junit.Before; 6 | import org.junit.runner.RunWith; 7 | import org.junit.runners.Parameterized; 8 | 9 | import java.sql.Connection; 10 | import java.sql.DriverManager; 11 | import java.util.ArrayList; 12 | import java.util.Collection; 13 | import java.util.Objects; 14 | import java.util.Optional; 15 | 16 | @RunWith(Parameterized.class) 17 | public abstract class BaseTests { 18 | 19 | protected static final String INSERT_INTO_TEST = "insert into test (b) values (?)"; 20 | 21 | protected enum Database { 22 | SQLITE, H2, POSTGRES 23 | } 24 | 25 | @Parameterized.Parameters(name = "{0}") 26 | public static Collection data() { 27 | // Just cheating multi runs 28 | ArrayList data = new ArrayList<>(); 29 | data.add(new Object[] { Database.SQLITE }); 30 | data.add(new Object[] { Database.H2 }); 31 | 32 | if (System.getenv("PGURL") != null) { 33 | data.add(new Object[] { Database.POSTGRES }); 34 | } 35 | 36 | return data; 37 | } 38 | 39 | public Sql sql; 40 | 41 | @Parameterized.Parameter 42 | public Database database; 43 | 44 | @Before 45 | public void setup() { 46 | switch (database) { 47 | case POSTGRES: { 48 | Connection connection = Wrap.get(() -> DriverManager.getConnection( 49 | System.getenv("PGURL"), 50 | System.getenv("PGUSER"), 51 | System.getenv("PGPASSWORD"))); 52 | sql = Sql.connect(connection); 53 | 54 | sql.exec("create table test (" + 55 | "a serial primary key not null," + 56 | "b integer" + 57 | ")"); 58 | sql.exec("create table users (" + 59 | "id serial primary key not null," + 60 | "name varchar(255) unique not null," + 61 | "password char(60) not null," + 62 | "activated boolean default false not null" + 63 | ")"); 64 | sql.exec("create table posts (" + 65 | "id serial primary key not null," + 66 | "user_id integer references users (id)," + 67 | "content text" + 68 | ")"); 69 | break; 70 | } 71 | case H2: 72 | Connection connection = Wrap.get(() -> DriverManager.getConnection("jdbc:h2:mem:test")); 73 | sql = Sql.connect(connection); 74 | 75 | sql.exec("create table test (" + 76 | "a serial primary key not null," + 77 | "b integer" + 78 | ")"); 79 | sql.exec("create table users (" + 80 | "id serial primary key not null," + 81 | "name varchar(255) unique not null," + 82 | "password char(60) not null," + 83 | "activated boolean default false not null" + 84 | ")"); 85 | sql.exec("create table posts (" + 86 | "id serial primary key not null," + 87 | "user_id integer references users (id)," + 88 | "content text" + 89 | ")"); 90 | break; 91 | case SQLITE: 92 | sql = Wrap.get(() -> Sql.connect(DriverManager.getConnection("jdbc:sqlite:"))); 93 | sql.exec("create table test (" + 94 | "a integer primary key autoincrement not null," + 95 | "b integer" + 96 | ")"); 97 | sql.exec("create table users (" + 98 | "id integer primary key autoincrement not null," + 99 | "name varchar(255) unique not null," + 100 | "password char(60) not null," + 101 | "activated boolean default false not null" + 102 | ")"); 103 | sql.exec("create table posts (" + 104 | "id integer primary key autoincrement not null," + 105 | "user_id integer references users (id)," + 106 | "content text" + 107 | ")"); 108 | break; 109 | } 110 | 111 | sql.exec("insert into users (name, password) values " + 112 | "('bob', 'bob_password')," + 113 | "('georges', 'georges_password')"); 114 | sql.exec("insert into posts (user_id, content) values " + 115 | "(1, 'whee')," + 116 | "(1, 'baah')," + 117 | "(2, 'bleh')"); 118 | } 119 | 120 | @After 121 | public void cleanup() throws Exception { 122 | sql.exec("drop table test"); 123 | sql.exec("drop table posts"); 124 | sql.exec("drop table users"); 125 | sql.close(); 126 | } 127 | 128 | static class User { 129 | int id; 130 | String name; 131 | String password; 132 | boolean activated; 133 | 134 | public User(int id, String name, String password, boolean activated) { 135 | this.id = id; 136 | this.name = name; 137 | this.password = password.trim(); 138 | this.activated = activated; 139 | } 140 | 141 | @Override 142 | public boolean equals(Object o) { 143 | if (this == o) return true; 144 | if (o == null || getClass() != o.getClass()) return false; 145 | User user = (User) o; 146 | return id == user.id; 147 | } 148 | 149 | @Override 150 | public int hashCode() { 151 | return Objects.hash(id); 152 | } 153 | 154 | @Override 155 | public String toString() { 156 | return "User{" + 157 | "activated=" + activated + 158 | ", id=" + id + 159 | ", name='" + name + '\'' + 160 | ", password='" + password + '\'' + 161 | '}'; 162 | } 163 | } 164 | 165 | static class Post { 166 | final int id; 167 | final int userId; 168 | final String content; 169 | 170 | public Post(int id, int userId, String content) { 171 | this.id = id; 172 | this.userId = userId; 173 | this.content = content; 174 | } 175 | 176 | public String getContent() { return content; } 177 | public int getId() { return id; } 178 | public int getUserId() { return userId; } 179 | 180 | @Override 181 | public String toString() { 182 | return "Post{" + 183 | "content='" + content + '\'' + 184 | ", id=" + id + 185 | ", userId=" + userId + 186 | '}'; 187 | } 188 | } 189 | 190 | } 191 | -------------------------------------------------------------------------------- /src/test/java/be/bendem/sqlstreams/BatchTests.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | import java.util.Arrays; 7 | import java.util.stream.Collectors; 8 | import java.util.stream.Stream; 9 | 10 | public class BatchTests extends BaseTests { 11 | 12 | @Test 13 | public void testCount() { 14 | try (BatchUpdate batch = sql.batchUpdate(INSERT_INTO_TEST)) { 15 | int count = batch 16 | .with(2).next() 17 | .with(3).next() 18 | .with(4).next() 19 | .count(); 20 | 21 | Assert.assertEquals(3, count); 22 | } 23 | 24 | try (Stream query = sql.query("select b from test order by 1").map(rs -> rs.getInt(1))) { 25 | Assert.assertEquals(Arrays.asList(2, 3, 4), query.collect(Collectors.toList())); 26 | } 27 | } 28 | 29 | @Test 30 | public void testCounts() { 31 | try (BatchUpdate batch = sql.batchUpdate(INSERT_INTO_TEST)) { 32 | int[] counts = batch 33 | .with(2).next() 34 | .with(3).next() 35 | .with(4).next() 36 | .counts(); 37 | 38 | Assert.assertArrayEquals(new int[]{1, 1, 1}, counts); 39 | } 40 | 41 | try (Stream query = sql.query("select b from test order by 1").map(rs -> rs.getInt(1))) { 42 | Assert.assertEquals(Arrays.asList(2, 3, 4), query.collect(Collectors.toList())); 43 | } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/be/bendem/sqlstreams/CustomMappingTest.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.Optional; 6 | 7 | import static org.junit.Assert.assertEquals; 8 | import static org.junit.Assert.assertTrue; 9 | 10 | public class CustomMappingTest extends BaseTests { 11 | 12 | public static class Stuff { 13 | private final int test; 14 | 15 | public Stuff(int test) { 16 | this.test = test; 17 | } 18 | } 19 | 20 | @Test 21 | public void testCustomMapping() { 22 | sql.registerCustomBinding(Stuff.class, (statement, index, value) -> statement.setInt(index, value.test)); 23 | 24 | try (Update update = sql.update(INSERT_INTO_TEST).with(new Stuff(4)).with()) { 25 | assertEquals(1, update.count()); 26 | } 27 | 28 | Optional stuff = sql.first("select b from test", rs -> rs.getInt(1)); 29 | 30 | assertTrue(stuff.isPresent()); 31 | assertEquals(4, (int) stuff.get()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/be/bendem/sqlstreams/EnumTests.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | import java.util.Optional; 7 | 8 | public class EnumTests extends BaseTests { 9 | 10 | public enum SomeEnum { 11 | VALUE_1, VALUE_2 12 | } 13 | 14 | @Test 15 | public void insertEnum() { 16 | try (Update update = sql.update(INSERT_INTO_TEST).with(SomeEnum.VALUE_2)) { 17 | Assert.assertEquals(1, update.count()); 18 | } 19 | Optional inserted = sql.first("select b from test", rs -> SomeEnum.values()[rs.getInt(1)]); 20 | 21 | Assert.assertTrue(inserted.isPresent()); 22 | Assert.assertEquals(SomeEnum.VALUE_2, inserted.get()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/be/bendem/sqlstreams/GeneratedValuesTests.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | import java.util.List; 7 | import java.util.stream.Collectors; 8 | 9 | import static org.junit.Assume.assumeTrue; 10 | 11 | public class GeneratedValuesTests extends BaseTests { 12 | static class Pair { 13 | public final int a; 14 | public final int b; 15 | 16 | Pair(int a, int b) { 17 | this.a = a; 18 | this.b = b; 19 | } 20 | } 21 | 22 | @Test 23 | public void testInsertReturning() throws Exception { 24 | assumeTrue(database == Database.POSTGRES); 25 | 26 | try (Query query = sql.query("insert into test (b) values (?) returning a, b").with(2)) { 27 | List update = query 28 | .map(rs -> new Pair(rs.getInt(1), rs.getInt(2))) 29 | .collect(Collectors.toList()); 30 | Assert.assertEquals(1, update.size()); 31 | Assert.assertEquals(1, update.get(0).a); 32 | Assert.assertEquals(2, update.get(0).b); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/be/bendem/sqlstreams/QueryTests.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | import java.util.Arrays; 7 | import java.util.stream.Collectors; 8 | import java.util.stream.Stream; 9 | 10 | public class QueryTests extends BaseTests { 11 | 12 | @Test 13 | public void testEmptySqlQuery() { 14 | try (Stream query = sql.query("select * from test").map(rs -> 0)) { 15 | Assert.assertEquals(0, query.count()); 16 | } 17 | } 18 | 19 | @Test 20 | public void testInsertAndSqlQuery() { 21 | try (Update update = sql.update("insert into test (b) values (1)")) { 22 | Assert.assertEquals(1, update.count()); 23 | } 24 | try (Update update = sql.update(INSERT_INTO_TEST).with(2)) { 25 | Assert.assertEquals(1, update.count()); 26 | } 27 | try (Update update = sql.update(INSERT_INTO_TEST).setInt(1, 3)) { 28 | Assert.assertEquals(1, update.count()); 29 | } 30 | try (Update update = sql.update(INSERT_INTO_TEST).with(4)) { 31 | Assert.assertEquals(1, update.count()); 32 | } 33 | 34 | try (Stream query = sql.query("select b from test order by 1").map(rs -> rs.getInt(1))) { 35 | Assert.assertEquals(Arrays.asList(1, 2, 3, 4), query.collect(Collectors.toList())); 36 | } 37 | } 38 | 39 | @Test 40 | public void testSingleConnectionDataSource() { 41 | Update update = sql.update(INSERT_INTO_TEST).with(1); 42 | 43 | try { 44 | sql.update(""); 45 | Assert.fail("connection should not be available"); 46 | } catch (IllegalStateException expected) {} 47 | 48 | Assert.assertEquals(1, update.count()); 49 | update.close(); 50 | try (Update update2 = sql.update(INSERT_INTO_TEST).with(1)) { 51 | Assert.assertEquals(1, update2.count()); 52 | } catch (IllegalStateException e) { 53 | Assert.fail(e.getMessage()); 54 | } 55 | } 56 | 57 | @Test 58 | public void testFirstResult() { 59 | Assert.assertFalse(sql.first("select * from test", rs -> 1).isPresent()); 60 | Assert.assertTrue(sql.first("select * from users", rs -> 1).isPresent()); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/test/java/be/bendem/sqlstreams/TransactionTests.java: -------------------------------------------------------------------------------- 1 | package be.bendem.sqlstreams; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | import java.util.stream.Stream; 7 | 8 | public class TransactionTests extends BaseTests { 9 | 10 | @Test 11 | public void testTransaction() { 12 | try (Transaction transaction = sql.transaction()) { 13 | Assert.assertEquals(1, transaction.update(INSERT_INTO_TEST).with(1).count()); 14 | transaction.rollback(); 15 | Assert.assertEquals(0, transaction.query("select * from test").map(rs -> 0).count()); 16 | 17 | Assert.assertEquals(1, transaction.update(INSERT_INTO_TEST).with(1).count()); 18 | transaction.commit(); 19 | Assert.assertEquals(1, transaction.query("select * from test").map(rs -> 0).count()); 20 | 21 | Assert.assertEquals(1, transaction.update(INSERT_INTO_TEST).with(1).count()); 22 | } // rollback 23 | 24 | // Make sure the connection is still usable 25 | try (Stream stream = sql.query("select * from test").map(rs -> 0)) { 26 | Assert.assertEquals(1, stream.count()); 27 | } 28 | } 29 | 30 | @Test 31 | public void emptyTransaction() { 32 | try (Transaction transaction = sql.transaction()) { 33 | } 34 | } 35 | 36 | @Test 37 | public void emptyTransactionWithIsolationLevel() { 38 | try (Transaction transaction = sql.transaction(Transaction.IsolationLevel.SERIALIZABLE)) { 39 | } 40 | } 41 | 42 | } 43 | --------------------------------------------------------------------------------