Giter Club home page Giter Club logo

pdb's Introduction

PDB

Build PDB
PDB tests with H2 embedded
PDB tests with PostgreSQL
PDB tests with SQLServer
PDB tests with CockroachDB
PDB tests with MySQL
PDB tests with Oracle
PDB tests with IBM DB2

PulseDB is a database-mapping software library written in Java, it provides a transparent access and manipulation to a great variety of database implementations. PDB provides a DSL that covers most of SQL functionalities and allows to easily integrate persistence into your projects and modules.

Using PDB

Add the following dependency to your Maven pom (example for PDB v2.8.14):

<dependencies>
    ...
	<dependency>
		<groupId>com.feedzai</groupId>
		<artifactId>pdb</artifactId>
		<version>2.8.14</version>
	</dependency>
	...
</dependencies>

Breaking changes

The timeout properties have been redefined in PdbProperties as numeric instead of strings since v2.4.6 - avoid using versions 2.4.4 and 2.4.5.

DatabaseEngine interface has a new method #dropView() since v2.5.3. This method was created without a default so this led to a breaking change. Use 2.5.5 and avoid using 2.5.3 and 2.5.4.

DatabaseEngine methods #getPSResultSet(final String name), #getPSIterator(final String name), #getPSIterator (final String name, final int fetchSize), since 2.7.0, throw an extra ConnectionResetException when the database connection is lost and recovered.

Changes from 2.0.0

  • It is now possible to call built-in database vendor functions [e.g. f("lower", column("COL1"))]
  • Added lower and upper functions
  • Fixed several connection leaks
  • Fixed MySQL large result fetching

Changes from 2.8.0

  • It now uses Guava 25.1-jre version, which might require the client to also upgrade it to match the same version

Changes from 2.8.10

  • H2 version upgraded to 2.1.210
  • H2Engine is now deprecated and uses the H2v2 legacy mode if this engine is used with the H2v2 driver (for more information regarding the legacy mode see: http://www.h2database.com/html/features.html)
  • The H2V2Engine was created and it is the engine that should be used from now on (it works on regular mode, not legacy)

Changes from 2.8.14

  • Added a multithreaded implementation for batches, that uses multiple connections to the database. This may improve write performance to the database, if it is not otherwise limited by resource usage.
  • Batch implementations should now be obtained by calling the method DatabaseEngine#createBatch(BatchConfig), where the type of config dictates the type of batch implementation that is created.
  • NOTE: versions 2.8.12 and 2.8.13 already had introduced this, but due to a bug, creating a batch would change the DatabaseEngine, possibly causing it to malfunction. If using that version, avoid the new method to create batches.

Changes from 2.8.16

Version 2.8.15 added back compatibility with Guice v4, but also changed the method SqlBuilder.k to return K instead of Expression. This breaks compatibility with existing code compiled with older PDB versions, so 2.8.16 reverts this change.

Compiling PDB

In order to compile PDB you will need to have the Oracle Driver JAR in your local repository.
The current version assumes Oracle Driver version 12.2.0.1 (even when compiled with this version it is possible to use version 11 drivers in runtime; it is also possible to compile with version 11 instead, but the oracle pom dependency has to be modified).
Please download the driver from the respective Oracle driver page and run the following to install the driver in your local maven repository.

mvn install:install-file -DgroupId=com.oracle.jdbc -DartifactId=ojdbc8 \
-Dversion=12.2.0.1 -Dpackaging=jar -Dfile=ojdbc8.jar

Alternatively you can setup the username/password for accessing Oracle's Maven repository in your settings.xml file.

Running PDB tests

To test PDB with different database engines there are several Maven profiles that can be used, one for each vendor (check list of supported vendors below, under Establishing a connection).

Run the following to run the tests for the chosen vendor specified in lowercase:

mvn test -P<vendor>

NOTE: there is also a "special" profile for H2 to test that engine in server mode (instead of the default H2 embedded); for that case the profile h2remote is used in the <vendor> placeholder.

This will start a docker container running the chosen vendor's database server, and run the tests. The container will be stopped at the end if all tests pass, otherwise will be kept running.

Note: the containers will be started assuming the respective vendor's license agreements have been read and accepted. More info:
Microsoft SQL Server: https://hub.docker.com/r/microsoft/mssql-server-linux/
IBM DB2: https://hub.docker.com/r/ibmcom/db2express-c/

Getting started

Index

Example Description

We describe a scenario where there are some data Providers that share Streams of data with the world. These Streams have a data Type, and they are consumed by some Modules. The entities and its relations are modeled into SQL using PDB in the following sections.

Establishing a connection

With PDB you connect to the database of your preference using the following code.

import static com.feedzai.commons.sql.abstraction.engine.configuration.PdbProperties.*;

(...)

Properties properties = new Properties() {
	{
		setProperty(JDBC, "<JDBC-CONNECTION-STRING>");
		setProperty(USERNAME, "username");
		setProperty(PASSWORD, "password");
		setProperty(ENGINE, "<PDB-ENGINE>");
		setProperty(SCHEMA_POLICY, "create");
	}
};

DatabaseEngine engine = DatabaseFactory.getConnection(properties);

The following table shows how to connect for the supported database vendors.

Vendor Engine JDBC
DB2 com.feedzai.commons.sql.abstraction.engine.impl.DB2Engine jdbc:db2://<HOST>:<PORT>/<DATABASE>
Oracle com.feedzai.commons.sql.abstraction.engine.impl.OracleEngine jdbc:oracle:thin:@<HOST>:1521:<DATABASE>
PostgreSQL com.feedzai.commons.sql.abstraction.engine.impl.PostgreSqlEngine jdbc:postgresql://<HOST>/<DATABASE>
MySQL com.feedzai.commons.sql.abstraction.engine.impl.MySqlEngine jdbc:mysql://<HOST>/<DATABASE>
H2 com.feedzai.commons.sql.abstraction.engine.impl.H2Engine jdbc:h2:<FILE> | jdbc:h2:mem
SQLServer com.feedzai.commons.sql.abstraction.engine.impl.SqlServerEngine jdbc:sqlserver://<HOST>;database=<DATABASE>

It is also important to select a schema policy. There are four possible schema policies:

  • create - New entities are created normally.
  • create-drop - Same as create policy but before the connection is closed all entries created during this session will be dropped.
  • drop-create - New entities are dropped before creation if they already exist.
  • none - The program is not allowed to create new entities.

PDB Pool Usage

PDB natively supports connection pools. You can create one using either a Properties or a Map<String, String> instance when defining the properties.

final DatabaseEnginePool dbPool = DatabaseEnginePool.getConnectionPool(properties);

Additionally, you can specify a modifier for the DatabaseEngine. This can be useful when you need to load entities to be able to insert entries on them.

final DatabaseEnginePool dbPool = DatabaseEnginePool.getConnectionPool(properties, engine -> engine.loadEntity(entity));

DatabaseEnginePool implements AutoClosable so you can close the pool when you need, for instance, before exiting the application.

Apart from the already described PDB configurations, you can configure your pool using the parameters in the following table.

The pool.generic.maxTotal, pool.generic.maxIdle, pool.generic.minIdle, and pool.generic.maxWaitMillis are the most common ones.

Property Description Default value
pool.generic.maxTotal The maximum number of active DatabaseEngine instances that can be allocated from the pool at the same time, or negative for no limit. 8
pool.generic.maxIdle The maximum number of DatabaseEngine instances that can remain idle in the pool, without extra ones being released, or negative for no limit. 8
pool.generic.minIdle The minimum number of DatabaseEngine instances that can remain idle in the pool, without extra ones being created, or zero to create none. 0
pool.generic.maxWaitMillis The maximum number of milliseconds that the pool will wait (when there are no available DatabaseEngine instances) for a DatabaseEngine instance to be returned before throwing an error, or -1 to wait indefinitely. -1
pool.generic.testOnCreate The indication of whether DatabaseEngine instances will be validated after creation. If the instance is invalid, the borrow attempt that triggered the DatabaseEngine instance creation will fail. false
pool.generic.testOnBorrow The indication of whether DatabaseEngine instances will be validated before being borrowed from the pool. If the instance is invalid, it is dropped from the pool and another one is tried to be borrowed. true
pool.generic.testOnReturn The indication of whether DatabaseEngine instances will be validated before being returned to the pool. false
pool.generic.testWhileIdle The indication of whether DatabaseEngine instances will be validated by the idle evictor (if any). If an instance is invalid, it will be dropped from the pool. false
pool.generic.timeBetweenEvictionRunsMillis The number of milliseconds to sleep between runs of the idle evictor thread. When non-positive, no idle evictor thread will be run. -1
pool.generic.numTestsPerEvictionRun The number of DatabaseEngine instances to examine during each run of the idle evictor thread (if any). 3
pool.generic.minEvictableIdleTimeMillis The minimum amount of time a DatabaseEngine instance may sit idle in the pool before it is eligible for eviction by the idle evictor (if any). 1000 * 60 * 30
pool.generic.lifo True means that the pool returns the most recently used ("last in") DatabaseEngine instance in the pool (if there are idle instances available). False means that the pool behaves as a FIFO queue - instances are taken from the idle instance pool in the order that they are returned to the pool. true
pool.abandoned.removeAbandonedOnMaintenance
pool.abandoned.removeAbandonedOnBorrow
Flags to remove abandoned DatabaseEngine instances if they exceed pool.abandoned.removeAbandonedTimout.
A DatabaseEngine instance is considered abandoned and eligible for removal if it has not been used for longer than pool.abandoned.removeAbandonedTimeout.
Setting pool.abandoned.removeAbandonedOnMaintenance to true removes abandoned DatabaseEngine instances on the maintenance cycle (when eviction ends). This property has no effect unless maintenance is enabled by setting pool.generic.timeBetweenEvictionRunsMillis to a positive value.
If pool.abandoned.removeAbandonedOnBorrow is true, abandoned DatabaseEngine instances are removed each time a instance is borrowed from the pool, with the additional requirements that [number of active instances] > pool.generic.maxTotal - 3 and [number of idle instances] < 2
false
pool.abandoned.removeAbandonedTimeout Timeout in seconds before an abandoned DatabaseEngine instance can be removed. 300
pool.abandoned.logAbandoned Flag to log stack traces for application code which abandoned a DatabaseEngine instance. false

If no pool.abandoned property is defined in the configuration file, then no policy to remove abandoned DatabaseEngine instances is applied.

Create Table

We start by creating the table to store the different data Types:

import static com.feedzai.commons.sql.abstraction.dml.dialect.SqlBuilder.*;

(...)

DbEntity data_type_table =
	dbEntity()
		.name("data_type")
		.addColumn("id", INT, UNIQUE, NOT_NULL)
		.addColumn("code", STRING, UNIQUE, NOT_NULL)
		.addColumn("description", CLOB)
		.pkFields("id")
		.build();

A table is represented with a DbEntity and its properties can be defined with methods:

Function Description
name Select the name for this table.
addColumn Create a column with a given name and type. Additionally you can had autoincrement behaviour and define some extra constraints. There are two possible constraints available: UNIQUE and NOT_NULL.
pkFields Define which columns are part of the primary key.

To create the data_type_table you call addEntity method on the previously created database engine. Depending on the policy you chose existing tables might be dropped before creation.

engine.addEntity(data_type_table);

Let's now create the Providers and Streams tables:

import static com.feedzai.commons.sql.abstraction.dml.dialect.SqlBuilder.*;

(...)

DbEntity provider_table =
	dbEntity()
		.name("provider")
			.addColumn("id", INT, true, UNIQUE, NOT_NULL)
			.addColumn("uri", STRING, UNIQUE, NOT_NULL)
			.addColumn("certified", BOOLEAN, NOT_NULL)
			.addColumn("description", CLOB)
			.pkFields("id")
			.build();

engine.addEntity(provider_table);

DbEntity stream_table =
	dbEntity()
	.name("stream")
		.addColumn("id", INT, true, UNIQUE, NOT_NULL)
		.addColumn("provider_id", INT, NOT_NULL)
		.addColumn("data_type_id", INT, NOT_NULL)
		.addColumn("description", CLOB)
		.pkFields("id")
		.addFk(
			dbFk()
				.addColumn("provider_id")
				.foreignTable("provider")
				.addForeignColumn("id"),
			dbFk()
				.addColumn("data_type_id")
				.foreignTable("data_type")
				.addForeignColumn("id"))
		.addIndex(false, "provider_id", "data_type_id")
		.build();

engine.addEntity(stream_table);

You may have noticed that this stream_table has some foreign keys, which we define using the addFK method. This method receives a list of the foreign keys constraints. A foreign key is created with dbFk(), and it is defined using these methods:

Function Description
addColumn Define which columns will be part of this constraint.
foreignTable Define the foreign table we are referring to.
addForeignColumn Selects the affected columns in the foreign table.

Wait! Looks like we also created an index in the Stream table.

Function Description
addIndex Creates and index for the listed columns. If not specified, an index is not unique.

The rest of the example case is created with the following code:

import static com.feedzai.commons.sql.abstraction.dml.dialect.SqlBuilder.*;

(...)

DbEntity module_table =
	dbEntity()
		.name("module")
        .addColumn("name", STRING, UNIQUE, NOT_NULL)
        .build();

engine.addEntity(module_table);

DbEntity stream_to_module_table =
	dbEntity()
		.name("stream_to_module")
			.addColumn("name", STRING, NOT_NULL)
			.addColumn("stream_id", INT, NOT_NULL)
			.addColumn("active", INT)
			.pkFields("name", "stream_id")
			.addFk(
				dbFk()
					.addColumn("name")
					.foreignTable("module")
					.addForeignColumn("name"),
				dbFk()
					.addColumn("stream_id")
					.foreignTable("stream")
					.addForeignColumn("id"))
            .build();

engine.addEntity(stream_to_module_table);

Drop Table

When you are done with this example you might want to clean the database.

engine.dropEntity("stream_to_module");
Function Description
dropEntity Drops an entity given the name.

Alter Table

With PDB you can change some aspects of a previously created tables. After calling the the addEntity method with the created entity you can continue to modify this local representation by calling the methods described in the previous sections. Then to synchronize the local representation with the actual table in the database you call the updateEntity method.

data_type_table = data_type_table
                    .newBuilder()
                    .removeColumn("description")
                    .build();

engine.updateEntity(data_type_table);
Function Description
removeColumn Removes a column from the local representation of the table.
updateEntity Synchronizes the entity representation with the table in the database. If schema policy is set to drop-create the whole table is dropped and created again.

Another mechanism to alter table is by using the AlterColumn expression creation and the executeUpdate method provided by the database engine. In this case changes are made to each column, one at a time.

import static com.feedzai.commons.sql.abstraction.dml.dialect.SqlBuilder.*;

(...)

Expression alterColumn = alterColumn(
                            table("stream_to_module"),
					        dbColumn("active", BOOLEAN).addConstraint(NOT_NULL).build());

engine.executeUpdate(alterColumn);
Function Description
alterColumn Creates a expression of changing a given table schema affecting a column.
dbColumn Column definition. Provide new type and autoincrement behavior.
addConstraint Define the constraints you want the column to oblige to.
addConstraints Define the constraints you want the column to oblige to.

It is also possible to remove the the primary key constraint.

import static com.feedzai.commons.sql.abstraction.dml.dialect.SqlBuilder.*;

(...)

Expression dropPrimaryKey = dropPK(table("TEST"));

engine.executeUpdate(dropPrimaryKey);
Function Description
dropPK Drops the primary key constraint on the given table.

Insertion Queries

Now that we have the structure of the database in place, let's play it with some data. An EntityEntry it's our representation of an entry that we want to add to the database.

import static com.feedzai.commons.sql.abstraction.dml.dialect.SqlBuilder.*;

(...)

EntityEntry data_type_entry =
	entry()
		.set("id", 1)
		.set("code", "INT16")
		.set("description", "The type of INT you always want!")
		.build();
Function Description
set Define the value that will be assigned to a given column.

Notice that the values for each column were defined using the set method. A new entry for the database is persisted with engine's method persist.

engine.persist("data_type", data_type_entry, false);
Function Description
persist Select the table in which the new entity will be inserted. If the affected table has an autoincrement column you might want to activate this flag. In case that the autoincrement behaviour is active, this method returns the generated key.

If you want to use the autoincrement behavior you must activate the autoincrement flag when defining the entity.

import static com.feedzai.commons.sql.abstraction.dml.dialect.SqlBuilder.*;

(...)

EntityEntry provider_entry =
	entry()
		.set("uri", "from.some.where")
		.set("certified", true)
		.build();

long generatedKey = engine.persist("provider", provider_entry, true);

Batches

PDB also provides support for batches. With batches you reduce the amount of communication overhead, thereby improving performance.

import static com.feedzai.commons.sql.abstraction.dml.dialect.SqlBuilder.*;

(...)

engine.beginTransaction();

try {
    EntityEntry entry = entry()
    	.set("code", "SINT")
    	.set("description", "A special kind of INT")
    	.build();

    engine.addBatch("data_type", entry);

    entry = entry()
    	.set("code", "VARBOOLEAN")
    	.set("description", "A boolean with a variable number of truth values")
    	.build();

    engine.addBatch("data_type", entry);

    // Perform more additions...

    engine.flush();
    engine.commit();
} finally {
    if (engine.isTransactionActive()) {
        engine.rollback();
    }
}
Function Description
beginTransaction Starts a transaction.
addBatch Adds an entry to the current batch.
flush Executes all entries registered in the batch.
commit Commits the current transaction transaction.
isTransactionActive Tests if the transaction is active.
rollback Rolls back the transaction.

Updating and Deleting Queries

Now you might want to the update data or simply erase them. Let's see how this can be done.

import static com.feedzai.commons.sql.abstraction.dml.dialect.SqlBuilder.*;

(...)

engine.executeUpdate(
	update(table("stream"))
	.set(
		eq(column("cd"), k("Double")),
		eq(column("description", k("Double precision floating point number")))
	.where(eq(column(id), k(1))));

Expressions that produce changes to the database are executed with engine's executeUpdate method. There are some defined static methods that allow you to create SQL queries. Update is one of them. In this section we describe queries that make changes to the database, while in the following section selection queries will be present in detail.

Function Description
update Creates an update query that will affect the table referred by the given expression.
set Expression that defines the values that will be assigned to each given column.
where Expression for filtering/selecting the affected entries.
table Creates a reference to a table of your choice.

Maybe you want to delete entries instead. In that case creating a delete query is required.

import static com.feedzai.commons.sql.abstraction.dml.dialect.SqlBuilder.*;

(...)

engine.executeUpdate(
	delete(table("stream")));

engine.executeUpdate(
	delete(table("stream"
		.where(eq(column(id), k(1))));
Function Description
delete Creates a delete query that will affect the table referred by the given expression.
where Expression for filtering/selecting the affected entries.

Truncate Queries

If what you seek is to delete all table entries at once, it is recommended to use the truncate query.

import static com.feedzai.commons.sql.abstraction.dml.dialect.SqlBuilder.*;

(...)

engine.executeUpdate(truncate(table("stream")));
Function Description
truncate Creates a truncate query that will affect the table referred by the given expression.

Selection Queries

Now things will get interesting. In this section we will see how PDB uses SQL to select data from the database. Using the query method we get the result for any given query as a list of entries. These entries are represented as a map of column name to content.

import static com.feedzai.commons.sql.abstraction.dml.dialect.SqlBuilder.*;

(...)

Expression query =
	select(all())
	.from(table("streams"));

// Fetches everything! Use with care when you know the result set is small.
List<Map<String, ResultColumn>> results = engine.query(query);

for(Map<String, ResultColumn> result : results) {
    Int id = result.get("id").toInt();
    String description = result.get("description").toString();
	System.out.println(id + ": "+ description);
}

// If your result set is large consider using the iterator.
ResultIterator it = engine.iterator(select(all()).from(table("streams")));
Map<String, ResultColumn> next;
while ((next = it.next()) != null) {
    Int id = next.get("id").toInt();
    String description = next.get("description").toString();
	System.out.println(id + ": "+ description);
}

The iterator closes automatically when it reaches the end of the result set, but you can close it on any time by calling it.close().

Function Description
query Processes a given query and computes the corresponding result. It returns a List of results if any. For each column a result is a Map that maps column names to ResultColumn objects.
iterator Returns an iterator to cycle through the result set. Preferable when dealing with large result sets.
toXXX ResultColumn provides methods to convert the data to the type of your preference. It throws an exception if you try to convert the underlying data to some incompatible type.

Let's see this simple query in more detail. Where we list all entries in table Streams and return all columns.

import static com.feedzai.commons.sql.abstraction.dml.dialect.SqlBuilder.*;

(...)

results = engine.query(
	select(all())
	.from(table("streams")));
Function Description
select Expression defining the selection of columns or other manipulations of its values.
distinct Filter the query so it only returns distinct values.
from Defines what tables or combination of them the data must be fetched from. By default the listed sources will be joined together with an inner join.
all Defines a reference to all column the underlying query might return.
k Creates a Constant from obj.
lit Creates a Literal from obj.
column Defines a reference to a given column.
with Provides a way to write auxiliary statements for use in a larger query.

This is useful but not very interesting. We should proceed by filtering the results with some condition of our choice.

import static com.feedzai.commons.sql.abstraction.dml.dialect.SqlBuilder.*;

(...)

results = engine.query(
	select(all())
	.from(table("streams"))
	.where(eq(column("data_type_id"), k(4)))
	.andWhere(like(column("description"), k("match t%xt"))));
Function Description
where Defines a series of testes a entry must oblige in order to be part of the result set.
andWhere If there is already ab where clause it defines an and expression with the old clause.
eq Applies the equality condition to the expressions. It is also used in insertion queries to represent attribution.
neq Negation of the equality condition.
like Likelihood comparison between expression. Those expression must resolve to String constants or columns of the same type.
lt Predicate over numerical or alphanumerical values.
lteq Predicate over numerical or alphanumerical values.
gt Predicate over numerical or alphanumerical values.
gteq Predicate over numerical or alphanumerical values.

A more complex filter would be one that select Streams from a given range of data Types and a set of Providers. And we manage just that with the following query.

import static com.feedzai.commons.sql.abstraction.dml.dialect.SqlBuilder.*;

(...)

results = engine.query(
	select(all())
	.from(table("streams"))
	.where(
		and(between(column("data_type_id"), k(2), k(5)),
			notIn(column("provider_id"), L(k(1), k(7), k(42))))));
Function Description
and Computes the boolean result of the underlying expressions.
or Computes the boolean result of the underlying expressions.
between Defines a test condition that asserts if exp1 is part of the range of values from exp2 to exp3.
notBetween Defines a test condition that asserts if exp1 is part of the range of values from exp2 to exp3.
in Defines a test condition that asserts if exp1 is part of exp2. Expression exp2 might be a List of constants or the result of a sub query.
notIn Defines a test condition that asserts if exp1 is part of exp2. Expression exp2 might be a List of constants or the result of a sub query.
L Defines a list of elements represent by the passing expressions.
caseWhen Defines a test using a list of conditions by going through them and returning a value when the first condition is met. If none are met, it will return the otherwise clause or NULL if not defined.
cast Specifies how to perform a conversion between two data types.

It is widely known that greater the id greater the Stream of data. For this purpose you just design a query that selects the maximum Stream id of data Type 4 from Provider 1. You might just get a raise for this.

import static com.feedzai.commons.sql.abstraction.dml.dialect.SqlBuilder.*;

(...)

results = engine.query(
	select(max(column("id")).alias("the_best"))
	.from(table("streams"))
	.where(
		and(eq(column("data_type_id"), k(4)),
			eq(column("provider_id"), k(1)))));
Function Description
alias Assigns an alias to the expression.
count Aggregation operator for numeric values. They are applicable to expression involving columns.
max Aggregation operator for numeric values. They are applicable to expression involving columns.
min Aggregation operator for numeric values. They are applicable to expression involving columns.
sum Aggregation operator for numeric values. They are applicable to expression involving columns.
avg Aggregation operator for numeric values. They are applicable to expression involving columns.
stddev Aggregation operator for numeric values. They are applicable to expression involving columns.
stringAgg Aggregation operator that aggregates data of a column into a string.
[concat](https://feedzai.github.io/pdb/com/feedzai/commons/sql/abstraction/dml/dialect/SqlBuilder.html#concat(com.feedzai.commons.sql.abstraction.dml.Expression, com.feedzai.commons.sql.abstraction.dml.Expression...)) Concatenates the expressions with a delimiter.
floor Operator that returns the largest integer value that is smaller than or equal to a number. They are applicable to expression involving columns.
ceil Operator that returns the smallest integer value that is larger than or equal to a number. They are applicable to expression involving columns.
udf If you have defined your own sql function you may access it with udf.

Sometimes it is required to merge the content of more than one table. For that purpose you can use joins. They allow you to merge content of two or more table regrading some condition. In this example we provide a little bit more flavor to the result by adding the data Type information to the Stream information.

import static com.feedzai.commons.sql.abstraction.dml.dialect.SqlBuilder.*;

(...)

results = engine.query(
	select(all())
	from(table("stream")
		.innerJoin((table("data_type"),
					join(
					    column("stream", "data_type_id"),
					    column("data_type", "id")))));
Function Description
innerJoin Merges the table results of two expression regarding a condition.
leftOuterJoin Merges the table results of two expression regarding a condition.
rightOuterJoin Merges the table results of two expression regarding a condition.
fullOuterJoin Merges the table results of two expression regarding a condition.
join Applies the equality condition to the expressions.
union Unions the results of multiple expressions.

The market is collapsing! The reason, some say, is that some provider messed up. In your contract it is stated that Provider with id 4 provides a given number of streams for each data_type. With the following query you will find out if the actual data in the database matches the contract. By filtering the results to only account for Provider 4 and grouping on the data Type you are able to count the number of streams by Type. Your Boss will be pleased.

import static com.feedzai.commons.sql.abstraction.dml.dialect.SqlBuilder.*;

(...)

results = engine.query(
	select(column("data_type_id"), count(column("id")).alias("count"))
	.from(table("streams"))
	.where(eq(column("provider_id"), k(4)))
	.groupby(column("data_type_id"))
	.orderby(column("data_type_id").asc());
Function Description
groupby Groups the result on some of the table columns.
orderby Orders the result according to some expression of the table columns.
asc Sets the ordering as ascendant.
desc Sets the ordering as descendant.

Some documents leaked online last week suggest that there are some hidden message in our data. To visualize this hidden message we need to do some arithmetic's with the ids of the provider and data_type on table Streams. Even more strange is the need to filter the description column, where in case of a null value an alternative is presented.

import static com.feedzai.commons.sql.abstraction.dml.dialect.SqlBuilder.*;

(...)

results = engine.query(
	select(
		plus(
			column("data_type_id"),
			column("provider_id"),
			k(1)
		).alias("mess_ids"),
		coalesce(
			column("description"),
			k("Romeo must die")))
	.from(table("streams")));
Function Description
minus Applies the subtraction operator to the list of value with left precedence.
mult Applies the multiplication operator to the list of value with left precedence.
plus Applies the addiction operator to the list of value with left precedence.
div Applies the division operator to the list of value with left precedence.
mod Applies the module operator to the list of value with left precedence.
coalesce Coalesce tests a given expression and returns its value if it is not null. If the primary expression is null, it will return the first alternative that is not.

For this next example, imagine you want to select all Streams for which the sum of data_type_id and provider_id is greater than 5. It might not be a very useful query, but when you had that you just want 10 rows of the result with and offset of 2, people might wonder what you are up to.

import static com.feedzai.commons.sql.abstraction.dml.dialect.SqlBuilder.*;

(...)

results = engine.query(
	select(all())
	.from(table("streams"))
	.having(
		gt(plus(
				column("data_type_id"),
				column("provider_id")),
			k(5)))
	.limit(10)
	.offset(2));
Function Description
having Query will select only the result rows where aggregate values meet the specified conditions.
limit Defines the number of rows that the query returns.
offset Defines the offset for the start position of the resulting rows.

Prepared Statements

PDB also allows the creation of prepared statements. Here you have two of the previous example queries done using prepared statements

import static com.feedzai.commons.sql.abstraction.dml.dialect.SqlBuilder.*;

(...)

Query query = select(all())
		.from(table("streams"))
		.where(eq(column("data_type_id"), lit("?")))
		.andWhere(like(column("description"), k("match t%xt")));

engine.createPreparedStatement("MyPS", query);

// It is always a good policy to clear the parameters
engine.clearParameters("MyPS");
engine.setParameter("MyPS", 1, 10);

engine.executePS("MyPS");
List<Map<String, ResultColumn>> result = engine.getPSResultSet("MyPS");

In PDB prepared statements are stored internally and they are maintained if the connection is lost, but the parameters are always lost.

import static com.feedzai.commons.sql.abstraction.dml.dialect.SqlBuilder.*;

(...)

Update update = update(table("stream"))
			.set(
				eq(column("cd"), lit("?")),
				eq(column("description", lit("?")))
			.where(eq(column(id), k(1))));

engine.createPreparedStatement("MyPS", query);

engine.clearParameters("MyPS");
engine.setParameter("MyPS", 1, "INT");
engine.setParameter("MyPS", 2, "Your regular integer implementation.");

int affectedEntries = engine.executePSUpdate("MyPS");
Function Description
createPreparedStatement Creates a prepared statement and assigns it to a given identifier.
clearParameters Clears the parameters of a given prepared statement.
setParameter Assigns a object to a given parameter of a prepared statement.
executePS Executes a given prepared statement.
executePSUpdate Executes a given update prepared statement and returns the number of affected rows.
getPSResultSet Returns the result set of the last executed query.
getPSIterator Returns an iterator to the result set of the last executed query.

Create View

Sometimes, for security reasons or just for simplicity, it is useful to have a view of the database.

import static com.feedzai.commons.sql.abstraction.dml.dialect.SqlBuilder.*;

(...)

Expression view = createView("simple_stream")
					.as(select(column("id"), column("data_type_id"))
						.from(table("stream")))
					.replace();

engine.executeUpdate(view);
Function Description
createView Creates a view with the given name.
as Defines the query that provides the data for this view.
replace Whether or not the view creation is authorized to overwrite over existing views.

Drop View

After creating a view, you may also want to delete it.

engine.dropView("view_name");
Function Description
dropView Drops a view with the given name.

Further Documentation

For more insight on the available functionality please see projects javadoc.

Contact

For more information please contact [email protected], we will happily answer your questions.

Special Thanks

  • Miguel Miranda ([email protected]) for the documentation and first steps on making this library opensource

License

Copyright 2014 Feedzai

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

pdb's People

Contributors

bernardino avatar ctosin avatar dependabot[bot] avatar diogoeag avatar dspereira004 avatar francisco-polaco avatar fz-andre-prata avatar helenapoleri avatar henriquevcosta avatar hugomiguelabreu avatar ip-fernandes avatar jcsf avatar jmf-tls avatar joaonuno avatar jrrbarros avatar leitaop avatar lfac-pt avatar lilianafribeiro avatar marpereira avatar nmldiegues avatar nuno-v-santos avatar pedrock avatar pedrorijo91 avatar rfer avatar rpvilao avatar smmf avatar tiagomssantos avatar upsidedownsmile avatar victorcmg avatar victorcmg-fdz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pdb's Issues

NaN values will cause insertions to fail in Oracle 11/12

The OracleTranslator is currently converting DbColumnType.DOUBLE to oracle's DOUBLE PRECISION. According to oracle's documentation we should be using BINARY_DOUBLE instead since that is what conforms to the IEEE standard - also what is supported by Java and many other databases.

As a consequence of not doing so we fall into the limitations of DOUBLE PRECISION

  • No support for NaN, -Inf or Inf.
  • Different floating point handling which caused #18

Changing the column format and a little bit more code in OracleEngine.entityToPreparedStatement fixes this.

This however has the downside of being not backwards compatible and potentially requiring changes in multiple client's DBs.

Let me know how you want to handle that.

PDB does not log exception thrown by connection.close()

Transcript from the original issue reported in the internal issue tracker.

PDB does not log exception thrown by connection.close(), merely reporting that an error has occurred.
JDBC API states the following on close()
It is strongly recommended that an application explicitly commits or rolls back an active transaction prior to calling the close method. If the close method is called and there is an active transaction, the results are implementation-defined.
โ€”
The DB2 JDBC implementation throws an exception if a transaction is still active on close(). It does not close the transaction, so if the transaction is still active and holding on to locks, they will be "alive" for a long time (whatever the timeout is) causing deadlocks. While not closing the transaction is a a project issue (which we fixed), not reporting the exception makes troubleshooting much harder.

Truncating an MD5 hash might fail

When truncating an MD5 hash to a specific number of chars, it is not validated if the number of chars is larger than the hash itself. Since we are using substring to truncate the hash, this will cause an IndexOutOfBoundsException. See substring documentation.

Reproducing this issue is fairly easy: just configure PDB property maxIdentifierSize to a value larger than 32.

If I have a little spare time I will submit a PR for this.

Performance: Two database queries are executed for each invocation of query()

When invoking the AbstractDatabaseEngine#query(Expression expr) method two database queries are executed.

The first call is invoked with the following call stack:

AbstractDatabaseEngine#checkConnection()
AbstractDatabaseEngine#getConnection()
AbstractDatabaseEngine#iterator(String query, int fetchSize)

This query is only used to check the aliveness of the connection. Because of this call each query() incurs in twice the round-trip time. It is also somewhat useless since it doesn't guarantee that the intended operation will succeed because the connection might be dropped meanwhile.

"schema" property isn't respected

Several problems were found regarding the usage of "schema" PDB property:

  • it is almost completely ignored (it seems only Oracle engine really sets current schema, other engines only used schema to get metadata and/or translate functions)
  • the methods to get metadata (getMetadata, getEntities) return results that don't match the current schema - for example, if schema is not set in PDB properties, updateEntity will do the following
    • check if the entity exists (since "schema" was not set in properties, it will try to find the entity in all the schemas)
    • if it doesn't exist, add it; at this point, if the entity exists on another schema, it won't be added to the current schema - inserting a new entry will either fail (if the engine doesn't look in other schemas if the entity is missing in the current one), or it will use the other schema (which translates into writing to a table that's not the one in the current default schema)
    • considering a migration scenario, where some data from a table is to be copied to other table with the same name, in the same database, but different schema: with a schema policy drop-create, updateEntity might even try to drop the entity on the wrong schema

Upgrade to Apache commons-lang3 instead of commons-lang

Apache commons-lang3 was designed to be able to live side-by-side with commons-lang, but it is extra bloat and mostly redundant to have both, and, having commons-lang alone I assume is more limited. I think most teams moving forward would be using commons-lang3 and it would be very nice if PDB was able to upgrade their dependency.

updateEntity call fails when the table has system-generated columns

When using Oracle (tested with version 12c) AbstractDatabaseEngine#updateEntity fails when it tries to drop a system-generated column (name starts with "SYS_") from a table.

I'm not sure what causes these system columns to be added though.

The offending SQL query:

OracleEngine.dropColumn:519 ALTER TABLE "FDZ_APP_WORKFLOW_ELEMENT" DROP ( "SYS_STS348AI90XHRJWNMOT2K_PHJ#" )

And the exception stack trace:

com.feedzai.commons.sql.abstraction.engine.DatabaseEngineException: Error dropping column
at com.feedzai.commons.sql.abstraction.engine.impl.OracleEngine.dropColumn(OracleEngine.java:526)
at com.feedzai.commons.sql.abstraction.engine.AbstractDatabaseEngine.updateEntity(AbstractDatabaseEngine.java:517)
[...]
Caused by: java.sql.SQLException: ORA-12996: cannot drop system-generated virtual column
[...]

Not sure what happens when the database connection goes down, or if it will ever stop

When a database connection goes down, it is not clear what is PDB going to do.

The messages printed on the console show that connection retries are being performed, but it seems that they will never end, although the displayed counter seems to imply so.

Although I'm not sure if this is needed (and whether you can reproduce it with H2), you can use docker to reproduce the issue on Oracle more easily:

$ docker pull filemon/oracle_11g
$ docker run -d --name oracle -p 1521:1521 filemon/oracle_11g

An Oracle database will be running on your local machine.

Then apply the following gist: https://gist.github.com/anonymous/794bac8bd2d8fdf15a2b
And then run the relevant test:

$ mvn  clean test -Dtest=com.feedzai.commons.sql.abstraction.engine.impl.oracle.OracleEngineSchemaTest

You should see the following output:

(...)
11:15:34.995 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection successful.
Test waiting for 10 seconds...
Test waiting for 10 seconds...
Test waiting for 10 seconds...

Now kill the Oracle database to make the connection go down:

$ docker rm -f oracle
oracle
$

The following will be seen on the console. It is not clear if this will ever end, and if so, what should be the correct action to be taken by the API user:

Test waiting for 10 seconds...
11:16:05.352 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection is down.
java.sql.SQLRecoverableException: No more data to read from socket
    at oracle.jdbc.driver.T4CMAREngine.unmarshalUB1(T4CMAREngine.java:1157) ~[ojdbc6-11.2.0.2.0.jar:11.2.0.4.0]
    at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:350) ~[ojdbc6-11.2.0.2.0.jar:11.2.0.4.0]
    at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:227) ~[ojdbc6-11.2.0.2.0.jar:11.2.0.4.0]
    at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:531) ~[ojdbc6-11.2.0.2.0.jar:11.2.0.4.0]
    at oracle.jdbc.driver.T4CStatement.doOall8(T4CStatement.java:195) ~[ojdbc6-11.2.0.2.0.jar:11.2.0.4.0]
    at oracle.jdbc.driver.T4CStatement.executeForDescribe(T4CStatement.java:876) ~[ojdbc6-11.2.0.2.0.jar:11.2.0.4.0]
    at oracle.jdbc.driver.OracleStatement.executeMaybeDescribe(OracleStatement.java:1175) ~[ojdbc6-11.2.0.2.0.jar:11.2.0.4.0]
    at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1296) ~[ojdbc6-11.2.0.2.0.jar:11.2.0.4.0]
    at oracle.jdbc.driver.OracleStatement.executeQuery(OracleStatement.java:1498) ~[ojdbc6-11.2.0.2.0.jar:11.2.0.4.0]
    at oracle.jdbc.driver.OracleStatementWrapper.executeQuery(OracleStatementWrapper.java:406) ~[ojdbc6-11.2.0.2.0.jar:11.2.0.4.0]
    at com.feedzai.commons.sql.abstraction.engine.impl.OracleEngine.checkConnection(OracleEngine.java:782) ~[classes/:na]
    at com.feedzai.commons.sql.abstraction.engine.AbstractDatabaseEngine.getConnection(AbstractDatabaseEngine.java:272) [classes/:na]
    at com.feedzai.commons.sql.abstraction.engine.AbstractDatabaseEngine.beginTransaction(AbstractDatabaseEngine.java:408) [classes/:na]
    at com.feedzai.commons.sql.abstraction.engine.impl.oracle.OracleEngineSchemaTest.waitForever(OracleEngineSchemaTest.java:71) [test-classes/:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_11]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_11]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_11]
    at java.lang.reflect.Method.invoke(Method.java:483) ~[na:1.8.0_11]
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47) [junit-4.11.jar:na]
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) [junit-4.11.jar:na]
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44) [junit-4.11.jar:na]
    at mockit.integration.junit4.internal.JUnit4TestRunnerDecorator.executeTestMethod(JUnit4TestRunnerDecorator.java:141) [jmockit-1.9.jar:na]
    at mockit.integration.junit4.internal.JUnit4TestRunnerDecorator.invokeExplosively(JUnit4TestRunnerDecorator.java:71) [jmockit-1.9.jar:na]
    at mockit.integration.junit4.internal.MockFrameworkMethod.invokeExplosively(MockFrameworkMethod.java:44) [jmockit-1.9.jar:na]
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java) [junit-4.11.jar:na]
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) [junit-4.11.jar:na]
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26) [junit-4.11.jar:na]
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271) [junit-4.11.jar:na]
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70) [junit-4.11.jar:na]
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50) [junit-4.11.jar:na]
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238) [junit-4.11.jar:na]
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63) [junit-4.11.jar:na]
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236) [junit-4.11.jar:na]
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53) [junit-4.11.jar:na]
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229) [junit-4.11.jar:na]
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309) [junit-4.11.jar:na]
    at org.junit.runners.Suite.runChild(Suite.java:127) [junit-4.11.jar:na]
    at org.junit.runners.Suite.runChild(Suite.java:26) [junit-4.11.jar:na]
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238) [junit-4.11.jar:na]
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63) [junit-4.11.jar:na]
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236) [junit-4.11.jar:na]
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53) [junit-4.11.jar:na]
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229) [junit-4.11.jar:na]
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309) [junit-4.11.jar:na]
    at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:264) [surefire-junit4-2.14.jar:2.14]
    at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:153) [surefire-junit4-2.14.jar:2.14]
    at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:124) [surefire-junit4-2.14.jar:2.14]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_11]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_11]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_11]
    at java.lang.reflect.Method.invoke(Method.java:483) ~[na:1.8.0_11]
    at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray2(ReflectionUtils.java:208) [surefire-api-2.14.jar:2.14]
    at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:158) [surefire-booter-2.14.jar:2.14]
    at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:86) [surefire-booter-2.14.jar:2.14]
    at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:153) [surefire-booter-2.14.jar:2.14]
    at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:95) [surefire-booter-2.14.jar:2.14]
11:16:05.355 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection is down.
11:16:05.355 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 1 in 10 seconds...
11:16:15.362 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:16:15.363 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 2 in 10 seconds...
11:16:25.366 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:16:25.367 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 3 in 10 seconds...
11:16:35.372 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:16:35.374 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 4 in 10 seconds...
11:16:45.379 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:16:45.380 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 5 in 10 seconds...
11:16:55.384 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:16:55.384 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 6 in 10 seconds...
11:17:05.387 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:17:05.387 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 7 in 10 seconds...
11:17:15.391 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:17:15.391 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 8 in 10 seconds...
11:17:25.394 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:17:25.394 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 9 in 10 seconds...
11:17:35.397 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:17:35.398 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 10 in 10 seconds...
11:17:35.398 [main] ERROR admin-notifications - The connection to the database was lost. Retry number 10 in 10
11:17:45.405 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:17:45.406 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 11 in 10 seconds...
11:17:55.409 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:17:55.411 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 12 in 10 seconds...
11:18:05.413 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:18:05.413 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 13 in 10 seconds...
11:18:15.416 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:18:15.417 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 14 in 10 seconds...
11:18:25.420 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:18:25.421 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 15 in 10 seconds...
11:18:35.424 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:18:35.425 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 16 in 10 seconds...
11:18:45.429 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:18:45.429 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 17 in 10 seconds...
11:18:55.431 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:18:55.431 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 18 in 10 seconds...
11:19:05.433 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:19:05.433 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 19 in 10 seconds...
11:19:15.438 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:19:15.441 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 20 in 10 seconds...
11:19:15.442 [main] ERROR admin-notifications - The connection to the database was lost. Retry number 20 in 10
11:19:25.446 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:19:25.446 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 21 in 10 seconds...
11:19:35.450 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:19:35.451 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 22 in 10 seconds...
11:19:45.457 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:19:45.457 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 23 in 10 seconds...
11:19:55.465 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:19:55.466 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 24 in 10 seconds...
11:20:05.469 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:20:05.471 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 25 in 10 seconds...
11:20:15.474 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:20:15.474 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 26 in 10 seconds...
11:20:25.476 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:20:25.476 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 27 in 10 seconds...
11:20:35.478 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:20:35.479 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 28 in 10 seconds...
11:20:45.485 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:20:45.485 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 29 in 10 seconds...
11:20:55.488 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:20:55.488 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 30 in 10 seconds...
11:20:55.488 [main] ERROR admin-notifications - The connection to the database was lost. Retry number 30 in 10
11:21:05.490 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:21:05.491 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 31 in 10 seconds...
11:21:15.492 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:21:15.493 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 32 in 10 seconds...
11:21:25.494 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:21:25.495 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 33 in 10 seconds...
11:21:35.497 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:21:35.498 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 34 in 10 seconds...
11:21:45.500 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:21:45.501 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 35 in 10 seconds...
11:21:55.504 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:21:55.504 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 36 in 10 seconds...
11:22:05.507 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:22:05.509 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 37 in 10 seconds...
11:22:15.517 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:22:15.517 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 38 in 10 seconds...
11:22:25.520 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:22:25.520 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 39 in 10 seconds...
11:22:35.529 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:22:35.529 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 40 in 10 seconds...
11:22:35.529 [main] ERROR admin-notifications - The connection to the database was lost. Retry number 40 in 10
11:22:45.532 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:22:45.532 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 41 in 10 seconds...
11:22:55.535 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:22:55.536 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 42 in 10 seconds...
11:23:05.542 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:23:05.543 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 43 in 10 seconds...
11:23:15.547 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:23:15.548 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 44 in 10 seconds...
11:23:25.551 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:23:25.552 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 45 in 10 seconds...
11:23:35.555 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:23:35.556 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 46 in 10 seconds...
11:23:45.558 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:23:45.559 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 47 in 10 seconds...
11:23:55.562 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:23:55.563 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 48 in 10 seconds...
11:24:05.565 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:24:05.565 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 49 in 10 seconds...
11:24:15.568 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:24:15.568 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 50 in 10 seconds...
11:24:15.568 [main] ERROR admin-notifications - The connection to the database was lost. Retry number 50 in 10
11:24:25.574 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:24:25.574 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 51 in 10 seconds...
11:24:35.576 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:24:35.576 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 52 in 10 seconds...
11:24:45.579 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:24:45.579 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 53 in 10 seconds...
11:24:55.582 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:24:55.583 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 54 in 10 seconds...
11:25:05.585 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:25:05.586 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 55 in 10 seconds...
11:25:15.589 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:25:15.591 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 56 in 10 seconds...
11:25:25.594 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:25:25.594 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 57 in 10 seconds...
11:25:35.598 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:25:35.599 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 58 in 10 seconds...
11:25:45.602 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:25:45.605 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 59 in 10 seconds...
11:25:55.608 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:25:55.612 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 60 in 10 seconds...
11:25:55.612 [main] ERROR admin-notifications - The connection to the database was lost. Retry number 60 in 10
11:26:05.616 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:26:05.617 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 61 in 10 seconds...
11:26:15.621 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:26:15.621 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 62 in 10 seconds...
11:26:25.624 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:26:25.624 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 63 in 10 seconds...
11:26:35.626 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:26:35.626 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 64 in 10 seconds...
11:26:45.632 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:26:45.632 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 65 in 10 seconds...
11:26:55.635 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:26:55.635 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 66 in 10 seconds...
11:27:05.640 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:27:05.642 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 67 in 10 seconds...
11:27:15.644 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:27:15.645 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 68 in 10 seconds...
11:27:25.658 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:27:25.658 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 69 in 10 seconds...
11:27:35.660 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:27:35.661 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 70 in 10 seconds...
11:27:35.661 [main] ERROR admin-notifications - The connection to the database was lost. Retry number 70 in 10
11:27:45.663 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:27:45.663 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 71 in 10 seconds...
11:27:55.665 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:27:55.665 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 72 in 10 seconds...
11:28:05.666 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Connection failed.
11:28:05.666 [main] DEBUG c.f.c.s.a.engine.impl.OracleEngine - Retry number 73 in 10 seconds...
(...)

Typo at `ORDER BY` Clause documentation.

There is a typo regarding how PDB's ORDER BY Clause should be used together with the ASC argument. Currently, the documentation exemplifies the usage of both with the following expression: orderby(column("data_type_id")).asc()). However, such expression is not correct and thus it should be fixed to: orderby(column("data_type_id").asc()).

This issues is solved within the Pull Request: #42

Batches don't retry the connections after a failure

When a batch flush occurs and the db connection fails (and the retry policy is exausted) the onBatchFailure is invoked. Then all the subsequent batch flushes will fail instead of retrying the connection.

In use cases where the onBatchFailure is used to dump the data in disk to further recovery process, it would be helpful to retry the connection on the next flush.

Loss of precision when using ResultColumn.toLong()

The implementation of ResultColumn#toLong converts the string value read from the database to a Double which then is cast to a Long.

First of all I would imagine that the Double conversion is to allow you to call toLong in double columns and get their values. This should be confirmed otherwise this is just an inefficient way of getting the value.

The main issue stems from the fact that a Double does not have precision to store all the values that can be represented by a Long. Both data types use 64 bits. In fact if you try to call this function with "-9223372036854775805" it will be converted to -9223372036854775808.

PDBs insert prepared statements prevent column defaults

We can create entities and specify default values for columns. However, when persisting entities, the default values are not used, because the prepared statement lists all the fields, and forces that are not specified to a NULL value.

In this case, the "default value" isn't used by the database. This applies at least to PostgreSQL and DB2.

Getting metadata/entities may not return the expected results in Oracle/MySql

MySql: just like getMetadata, the getEntities method also has a "schema" parameter which is ignored since for this DB engine the "catalog" is considered the schema.

Oracle: the method getMetadata(String name) was overriding the same method in AbstractDatabaseEngine, when the method getMetadata(String schemaPattern, String tableNamePattern) should have been overridden instead (otherwise it would only work for the current schema).

The tests should be changed to verify that these methods are working as expected.

Support DatabaseEngine pools.

I always find myself adding custom code to add support for connection pooling.
Native PDB support for pooling would be a time saver.

H2Engine, IndexOutOfBoundsException when translating an empty list

When using the in(column(...), L(values)) operator, translation crashes with the H2Engine if the values list is empty.

java.lang.IndexOutOfBoundsException: index (0) must be less than size (0)
    at com.google.common.base.Preconditions.checkElementIndex(Preconditions.java:310) ~[guava-18.0.jar:na]
    at com.google.common.base.Preconditions.checkElementIndex(Preconditions.java:292) ~[guava-18.0.jar:na]
    at com.google.common.collect.RegularImmutableList.get(RegularImmutableList.java:65) ~[guava-18.0.jar:na]
    at com.feedzai.commons.sql.abstraction.engine.impl.H2Translator.translate(H2Translator.java:125) ~[pdb-2.1.4.jar:na]
    at com.feedzai.commons.sql.abstraction.dml.RepeatDelimiter.translate(RepeatDelimiter.java:148) ~[pdb-2.1.4.jar:na]
    at com.feedzai.commons.sql.abstraction.engine.impl.H2Translator.translate(H2Translator.java:138) ~[pdb-2.1.4.jar:na]
    at com.feedzai.commons.sql.abstraction.dml.RepeatDelimiter.translate(RepeatDelimiter.java:148) ~[pdb-2.1.4.jar:na]
    at com.feedzai.commons.sql.abstraction.engine.AbstractTranslator.translate(AbstractTranslator.java:314) ~[pdb-2.1.4.jar:na]
    at com.feedzai.commons.sql.abstraction.dml.Update.translate(Update.java:69) ~[pdb-2.1.4.jar:na]
    at com.feedzai.commons.sql.abstraction.engine.AbstractDatabaseEngine.translate(AbstractDatabaseEngine.java:767) ~[pdb-2.1.4.jar:na]
    at com.feedzai.commons.sql.abstraction.engine.AbstractDatabaseEngine.executeUpdate(AbstractDatabaseEngine.java:753) ~[pdb-2.1.4.jar:na]

DatabaseEngine.updateEntity always drops/adds foreign keys even if the schema didn't change

The expected behavior of DatabaseEngine.updateEntity, with schema policy create, is that if the entity does not need to be updated no DDL is executed; however, the current implementation (in AbstractDatabaseEngine) is always dropping all foreign keys at the beginning and adding them back at the end.
Calling DatabaseEngine.updateEntity should be a no-op if the entity did not change.

PDB throws cryptic exceptions when trying to use an inexistent/unreachable database (or any other wrong configurations)

Hi,

When PDB is configured to use a given database, it is hard to tell if there is a problem on the configuration, a transient network error, or if something is wrong with PDB itself.

You can see the problem by applying the following gist and running the tests included in PDB itself:
https://gist.github.com/anonymous/f96162ecb64f70c82799

You can run only a single test to see the problem:

mvn test -e -Dtest=com.feedzai.commons.sql.abstraction.engine.impl.oracle.OracleEngineSchemaTest
(...)

Stacktrace was: com.feedzai.commons.sql.abstraction.engine.DatabaseFactoryException: java.lang.reflect.InvocationTargetException
    at com.feedzai.commons.sql.abstraction.engine.DatabaseFactory.getConnection(DatabaseFactory.java:89)
    at com.feedzai.commons.sql.abstraction.engine.impl.abs.EngineCreateTest.stopsWhenTableAlreadyExistsTest(EngineCreateTest.java:165)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:264)
    at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:153)
    at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:124)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray2(ReflectionUtils.java:208)
    at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:158)
    at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:86)
    at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:153)
    at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:95)
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:408)
    at com.feedzai.commons.sql.abstraction.engine.DatabaseFactory.getConnection(DatabaseFactory.java:64)
    ... 13 more
Caused by: com.feedzai.commons.sql.abstraction.engine.DatabaseEngineException: Unable to connect to database
    at com.feedzai.commons.sql.abstraction.engine.AbstractDatabaseEngine.<init>(AbstractDatabaseEngine.java:158)
    at com.feedzai.commons.sql.abstraction.engine.impl.OracleEngine.<init>(OracleEngine.java:83)
    ... 16 more
Caused by: java.sql.SQLRecoverableException: IO Error: The Network Adapter could not establish the connection
    at oracle.jdbc.driver.T4CConnection.logon(T4CConnection.java:489)
    at oracle.jdbc.driver.PhysicalConnection.<init>(PhysicalConnection.java:553)
    at oracle.jdbc.driver.T4CConnection.<init>(T4CConnection.java:254)
    at oracle.jdbc.driver.T4CDriverExtension.getConnection(T4CDriverExtension.java:32)
    at oracle.jdbc.driver.OracleDriver.connect(OracleDriver.java:528)
    at java.sql.DriverManager.getConnection(DriverManager.java:664)
    at java.sql.DriverManager.getConnection(DriverManager.java:247)
    at com.feedzai.commons.sql.abstraction.engine.AbstractDatabaseEngine.connect(AbstractDatabaseEngine.java:217)
    at com.feedzai.commons.sql.abstraction.engine.AbstractDatabaseEngine.<init>(AbstractDatabaseEngine.java:154)
    ... 17 more
Caused by: oracle.net.ns.NetException: The Network Adapter could not establish the connection
    at oracle.net.nt.ConnStrategy.execute(ConnStrategy.java:439)
    at oracle.net.resolver.AddrResolution.resolveAndExecute(AddrResolution.java:454)
    at oracle.net.ns.NSProtocol.establishConnection(NSProtocol.java:693)
    at oracle.net.ns.NSProtocol.connect(NSProtocol.java:251)
    at oracle.jdbc.driver.T4CConnection.connect(T4CConnection.java:1140)
    at oracle.jdbc.driver.T4CConnection.logon(T4CConnection.java:340)
    ... 25 more
Caused by: java.net.UnknownHostException: inexistent_host
    at java.net.InetAddress.getAllByName0(InetAddress.java:1259)
    at java.net.InetAddress.getAllByName(InetAddress.java:1171)
    at java.net.InetAddress.getAllByName(InetAddress.java:1105)
    at oracle.net.nt.TcpNTAdapter.connect(TcpNTAdapter.java:117)
    at oracle.net.nt.ConnOption.connect(ConnOption.java:133)
    at oracle.net.nt.ConnStrategy.execute(ConnStrategy.java:405)
    ... 30 more

(...)

The problem I have with this is that the DatabaseFactory.getConnection(Properties) method is throwing an InvocationTargetException when a simple mistake like a wrong hostname or IP address was given on the database configuration.
InvocationTargetException is an unchecked exception, and is not even declared on getConnection's signature. The cause for this Exception is only detectable by looking at the exception and iteratively navigating through the cause()s of this exception.
This makes it very hard to debug, and it is a situation that should be easy to handle by the API user, so that a cleaner resolution for configuration problems like these can be provided to the end-user.
Other, equally cryptic exceptions, can be thrown in the case of other configuration errors.

I propose a more reasonable behaviour should be provided instead. Configuration problems should be detected and explicit, obvious exceptions should be thrown for those cases.

For example, declare that a checked UnableToConnectException or even an IncorrectConfigurationException can be thrown by getConnection() (or a new version of it to avoid breaking the current API). Alternatively, a validateConfigurations method can be provided to be called before trying to establish the connection.

Note: I have only tested this with Oracle, and with the given error. Other configuration errors may throw other kinds of hard-to-debug (or easier?) exceptions that should also be treated more elegantly, but I didn't check that thoroughly.

Thanks.

BLOB operations in user defined prepared statements are not consistent with operations on entities

When setting a prepared statement column the following is generally used:
if (o instanceof byte[])
{ ps.ps.setBytes(i, (byte[]) o); }
else
{ ps.ps.setObject(i, o); }
whereas when using entities, values of BLOB columns are always serialized (custom encoded starting in PDB 2.0.5). Therefore, if a BLOB value is updated using an user-defined prepared statement, the written value will not be the returned on subsequent reads. For example, if a byte[] containing a serialized value is written, the read value will not be the provided byte[], it will be the de-serialized object.

[MySQL] Pdb incorrectly escapes queries with backslash

When performing a query to the database (using mysql backend):

Query q = select(all())
                .from(table("table"))
                .where(eq("id", k("my\id"));

engine.query(q);

pdb incorrectly escapes the query to

SELECT * FROM `table` WHERE `id` = 'my\id' ;

where it should be (notice the double \ )

SELECT * FROM `table` WHERE `id` = 'my\\id' ;

this is independent of the data in the table itself.

This behaviour does not appear in prepared statements because the escaping is delegated to the driver

BatchUpdateTest tests don't destroy() the batches when they finish

This is annoying when debugging, because after all tests in BatchUpdateTest have finished (and regardless of whether they succeeded or failed) they will leave threads running.
These batch threads will only disappear when the VM exits (when the Maven test goal finishes), and until they do, they will keep producing log outputs.

It is not evident if creating a prepared statement should be retried when an error occurs

I have a simple use-case where I want to make sure that a prepared statement exists on the database. If it exists, it should be updated.

I have the following code snippet:

        try {
            engine.createPreparedStatement(name, query);

        } catch (NameAlreadyExistsException e) {

            // Update the statement if it already exists
            engine.removePreparedStatement(name);
            try {
                engine.createPreparedStatement(name, query);

            } catch (NameAlreadyExistsException e1) {
                //This should never happen
            }
        }

So, after this point, I see three possibilities:

  1. Everything goes well on the first call and the statement is created;
    .
  2. Creating the statement fails and a DatabaseEngineException is thrown by the first createPreparedStatement.
    This appears to be possible both when the connection to the database is down, or when providing an invalid name or query.
    In such situations, its not clear if the API user should call the method again (transient connection error) or stop trying to execute the statement.
    .
  3. Creating the statement fails on the second createPreparedStatement with a DatabaseEngineException. The same uncertainty holds as for the second point.

There should be some way of determining that a name and query are syntactically valid. Either by having an exception type that makes that evident, or by providing additional validation methods. This way, when an exception is thrown by executePreparedStatement the user will be able to determine if it was due to a problem on his side.

There is a 4th possibility, that I'm not assuming, that is the "This should never happen" block getting reached. When issue #7 is taken into account, it may actually happen, but fixing both this and #7 should resolve that.

Documentation for the DatabaseEngine.commit() method is inconsistent

The DatabaseEngine.commit() method states that it throws DatabaseEngineException if something goes wrong while persisting data.
However, the method's declaration states that it can throw a DatabaseEngine*Runtime*Exception.

This is especially dangerous, as users of the API are intuitively taken to use a block such as:

boolean success = false;
while (!success) {
 try {
   //do something
   (...)

   engine.commit();
   success = true;
 } catch (DatabaseEngineException) {
   //deal with the error
 }
}

However, if a DatabaseEngineRuntimeException is thrown, it will never be caught by the catch block, and it will inevitably bubble up from the caller.

The javadoc should declare that a DatabaseEngineRuntimeException is thrown, or, better yet, the method should throw DatabaseEngineException to force the caller to deal with the exception (although that would now break the API).

H2 remote is not working

H2 remote, for some reason, doesn't support the JDBC methods getSchema and setSchema

This was known in the last fix, and getSchema is replaced with a query when H2 is remote.
The same was done for setSchema, but the query is using a PreparedStatement to replace the schema identifier, which is not allowed (PreparedStatements replace values only).

This should be fixed, and H2 remote should be added to the DBs that are tested, to asses if everything else works ok or there are any more issues.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.