I am writing a bit of Java (1.7) code to test a given database table against a given sql file. What I would like is a way to turn my sql file into a java object, then test the db field names and field types are the same as the file backed object.
An example sql file looks like this:
create table foo (
id int not null auto_increment,
term_id varchar(128) not null,
term_name varchar(255) not null,
parent_id varchar(128) not null,
parent_name varchar(255),
top_term_flag varchar(5),
primary key (id)
);
create index foo_pn on foo ( parent_name );
create index foo_ttf on foo ( top_term_flag );
And the part of my Java program to do this check looks like this:
// Step 1, confirm the table exists
// Database and table tests
DatabaseMetaData dbm = connection.getMetaData();
// check if "this.dbtable" exists.
// The ToUpperCase covers Oracle
ResultSet tables = dbm.getTables(null, null, this.dbtable.toUpperCase(), null);
if (tables.next()) {
// Table exists
log.info("Table: {} exists!", this.dbtable);
// Step 2, get each field and test against the file
ResultSet columns = dbm.getColumns(null, null, this.dbtable, null);
while ( columns.next()) {
String name = columns.getString(4); // this gets the column name
-> Now what? <-
}
}
I've looked at Spring JDBCTestUnit and Flyway, but they don't seem to provide the functionality I need.
Thank you.
Update:
I understand I can also use Hibernate to generate my Java classes that represent my sql file and then test the DB table against those. Does any one have a sample for how to get this done?
Using JSqlParser 0.8.8 from https://github.com/JSQLParser/JSqlParser.
Here is a parsing example to get column names, table name, types. As a result you get a hierarchy of java objects from your sqls.
public class CheckSQLs {
public static void main(String[] args) throws JSQLParserException {
String sqls = "create table foo (\n"
+ " id int not null auto_increment,\n"
+ " term_id varchar(128) not null,\n"
+ " term_name varchar(255) not null,\n"
+ " parent_id varchar(128) not null,\n"
+ " parent_name varchar(255),\n"
+ " top_term_flag varchar(5),\n"
+ " primary key (id)\n"
+ ");\n"
+ "create index foo_pn on foo( parent_name );\n"
+ "create index foo_ttf on foo ( top_term_flag );";
for (String sql : sqls.split(";")) {
Statement parse = CCJSqlParserUtil.parse(sql);
System.out.println(parse);
if (parse instanceof CreateTable) {
CreateTable ct = (CreateTable)parse;
System.out.println("table=" + ct.getTable().getFullyQualifiedName());
for (ColumnDefinition colDef : ct.getColumnDefinitions()) {
System.out.println("column=" + colDef.getColumnName() + " " + colDef.getColDataType() + " " + colDef.getColumnSpecStrings());
}
}
}
}
}
This runs with the output:
CREATE TABLE foo (id int not null auto_increment, term_id varchar (128) not null, term_name varchar (255) not null, parent_id varchar (128) not null, parent_name varchar (255), top_term_flag varchar (5), primary key (id))
table=foo
column=id int [not, null, auto_increment]
column=term_id varchar (128) [not, null]
column=term_name varchar (255) [not, null]
column=parent_id varchar (128) [not, null]
column=parent_name varchar (255) null
column=top_term_flag varchar (5) null
Now you could use this object to validate against your database.
If the SQL file syntax doesn't vary much from your example, you could write a simple parser to read the file and generate your java object: table plus list of fields/types and indexes
"tablename" always comes after "create table"
the field names and types always come after that
indexes after that
Or there are parsers available:
jsqlparser
http://jsqlparser.sourceforge.net/
Other questions on this site cover some of the same ground
SQL parser library for Java
Related
I have tried to create three tables(CUSTOMERS, VEHICLES and RENTALS), the third table (RENTALS) has foreign keys referring to the two primary keys of the first two tables (CUSTOMERS and RENTALS). When creating this third table I get an error Missing columns in relationship(Rel=CUSTOMERS[[]] -> RENTALS[[]])
Here's my codes
private void createTables() throws SQLException {
Statement statement = conn.createStatement();
statement.executeUpdate("CREATE TABLE CUSTOMERS(custNumber AUTOINCREMENT PRIMARY KEY, " +
"firstName VARCHAR(155) NOT NULL, surname VARCHAR(155) NOT NULL, idNum INTEGER NOT NULL, phoneNum INTEGER NOT NULL, canRent BIT NOT NULL)");
statement.executeUpdate("CREATE TABLE VEHICLES(vehNumber AUTOINCREMENT PRIMARY KEY, make VARCHAR(155) NOT NULL, " +
"category VARCHAR(155) NOT NULL, rentalPrice FLOAT NOT NULL, availableForRent BIT NOT NULL)");
statement.executeUpdate("CREATE TABLE RENTALS(rentalNumber AUTOINCREMENT PRIMARY KEY, dateRental VARCHAR(155) NOT NULL, dateReturned VARCHAR(155) NOT NULL, " +
"pricePerDay FLOAT NOT NULL, totalRental FLOAT NOT NULL, custNumber INTEGER FOREIGN KEY REFERENCES CUSTOMERS(custNumber), " +
"vehNumber INTEGER FOREIGN KEY REFERENCES VEHICLES(vehNumber))");
System.out.println("Database populated");
}
and here's the error
Your help will be very much appreciated, I have looked around but found nothing that helps.
In Access, an AutoNumber field (DDL: AUTOINCREMENT or COUNTER) is a "Long Integer".
In UCanAccess DDL, INTEGER creates an "Integer" (16-bit) field and LONG creates a "Long Integer" (32-bit) field.
You need to declare your foreign key columns as LONG, not INTEGER.
I've got a database set up to store notes. I want to auto increment the first column. I've tried this, but when I read from the database every result in that column is 'null'.This is the code for creating the DB.
private static final String NOTES_TABLE_CREATE =
"CREATE TABLE " + NOTES_TABLE_NAME + " (" +
COLUMN_NAMES[0] + " INTEGER AUTO_INCREMENT, " +
COLUMN_NAMES[1] + " TEXT, " +
COLUMN_NAMES[2] + " TEXT, " +
COLUMN_NAMES[3] + " TEXT, " +
COLUMN_NAMES[4] + " TEXT, " +
COLUMN_NAMES[5] + " TEXT, " +
COLUMN_NAMES[6] + " TEXT);";
This is the code for getting the DB result.
SQLiteDatabase db = this.getReadableDatabase();
Cursor result = db.query(NOTES_TABLE_NAME, COLUMN_NAMES, null, null, null, null, null, null);
result.moveToFirst();
result.moveToNext();
System.out.println(result.getInt(0));
System.out.println(result.getString(1));
This is the output from logcat
04-09 17:56:17.981 22147-22147/com.example.a8460p.locationotes I/System.out: 0
04-09 17:56:17.981 22147-22147/com.example.a8460p.locationotes I/System.out: notetitle1234567890
AUTO_INCREMENT (as opposed to INTEGER PRIMARY KEY AUTOINCREMENT) is not supported in sqlite.
This is a little non-obvious, because sqlite silently ignores column constraints it does not recognize:
sqlite> CREATE TABLE test (
a INTEGER FABBELBABBEL NOT NULL
);
sqlite> .schema test
CREATE TABLE test (a INTEGER FABBELBABBEL NOT NULL);
sqlite> INSERT INTO test (a) VALUES (1);
sqlite> INSERT INTO test (a) VALUES (NULL);
Error: NOT NULL constraint failed: test.a
AUTOINCREMENT on the other hand, is supported for integer primary keys and only there, so the obvious workaround attempt is not supported, either:
sqlite> CREATE TABLE test (a INTEGER AUTOINCREMENT NOT NULL, b INTEGER);
Error: near "AUTOINCREMENT": syntax error
In short: Auto increment is only available for integer primary keys.
I am trying to test a REST service which queries restaurant data from a MySQL database with DBUnit and MockMvc. All tests work except the ones where full text search is executed. These always return no results. Test example:
#Test
public void searchRestaurantsForSimpleName() throws Exception {
mockMvc.perform(get("/restaurants?query=first").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.restaurantList[0].name").value("first"));
}
DAO code which is executed and not returning any results is:
#Query(value = "select *, match(search1) against(?1 in boolean mode) as score1," + ...
MySQL engine type is InnoDB, which supports fulltext search only when index is built. Could the problem be that DBUnit doesn't rebuild the index, therefore it gets no search results?
Do you have any other ideas where the problem could be or how to make a test for fulltext search?
Edit (more code):
RestaurantDao, whole query:
public interface RestaurantDao extends PagingAndSortingRepository<Restaurant, Long> {
//native query does not support pagination, which has to be done manually
#Query(value = "select *," +
" match(search1) against(?1 in boolean mode) as score1," +
" match(search2) against(?1 in boolean mode) as score2" +
" from restaurant r where" +
" (?2 is null or rating_point >= ?2)" +
" and (?3 is null or rating_point <= ?3)" +
" and (?4 is null or rating_bottle = ?4)" +
" and (?5 is null or ?5 = FALSE or accommodation = ?5)" +
" and (?6 is null or ?6 = FALSE or garden = ?6)" +
" and (?7 is null or rating_star = ?7)" +
" and (?8 is null or oecar = ?8)" +
" and (?9 = TRUE or hidden = ?9)" +
" having (length(?1) = 0 or score1 > 0 or score2 > 0)" +
" order by score1 desc, score2 desc, rating_point desc, rating_star desc, oecar asc, name asc" +
" limit ?10 offset ?11", nativeQuery = true)
List<Restaurant> fullTextSearch(
String query,
Integer minRating,
Integer maxRating,
Integer bottleRating,
Boolean accommodation,
Boolean garden,
Integer stars,
Integer oecar,
boolean includeHidden,
int limit,
int offset
);
MySQL table, which was automatically created by hibernate (i excluded some field for readability):
restaurant | CREATE TABLE `restaurant` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`accommodation` bit(1) DEFAULT NULL,
`date_created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`garden` bit(1) DEFAULT NULL,
`hidden` bit(1) DEFAULT NULL,
`kitchen_chef` varchar(255) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
`oecar` int(11) DEFAULT NULL,
`rating_point` int(11) DEFAULT NULL,
`search1` varchar(255) DEFAULT NULL,
`search2` varchar(255) DEFAULT NULL,
`rating_star` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `search1_hidx` (`search1`),
KEY `search2_hidx` (`search2`),
KEY `accommodation_hidx` (`accommodation`),
KEY `garden_hidx` (`garden`),
KEY `seats_hidx` (`seats`),
KEY `rating_star_hidx` (`rating_star`),
KEY `oecar_hidx` (`oecar`),
FULLTEXT KEY `ft_search1` (`search1`),
FULLTEXT KEY `ft_search2` (`search2`)
) ENGINE=InnoDB AUTO_INCREMENT=909 DEFAULT CHARSET=utf8 |
DBUnit datase definition
<dataset>
<restaurant id="1" name="first" search1="first" search2="wien test" garden="true" accommodation="true" seats="50" rating_point="90" rating_bottle="2" rating_star="5" oecar="3" hidden="false" />
...
</dataset>
I have a table containing four columns:
CREATE TABLE `participants` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(128) NOT NULL,
`function` VARCHAR(255) NOT NULL,
`contact` VARCHAR(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `name_function_contact` (`name`, `function`, `contact`)
)
From the application I get participants-objects, which might have values for name, functionand contactwhich are already in that exact matter in the database. In this case I want Hibernate to get me the idof that object, otherwise I want to save the object.
Using saveOrUpdate()I just get an:
org.hibernate.exception.ConstraintViolationException: Duplicate entry 'NAME-FUNCTION-CONTACT: NAME' for key 'name_function_contact'
How can I accomplish this? Thanks a lot!
Since the answers suggested that Hibernate cannot do it on its own (bummer!) I solved it the "native sql" way:
Participants tempParti = ((Participants) session.createQuery("FROM Participants WHERE name = '" + p.getName() + "' AND function = '" + p.getFunction() + "' AND contact = '" + p.getContact() + "'").uniqueResult());
if (tempParti != null) {
p = tempParti;
} else {
session.save(p);
}
Works like a charm! Thanks to all of you!
I am no expert in Hibernate. But from Mysql perspective, you do the following.
use INSERT IGNORE INTO... to add the value in the table. If the number of rows inserted is 0, then you can manually get the ID of the row by a SELECT statement.
EDIT: LAST_INSERT_ID() was wrong here. I have edited the answer.
When I enter data with my java program (simple dictionary ) it throws an error:
MySQLIntegrityConstraintViolationException: Cannot add or update a
child row: a foreign key constraint fails (singlehaw.card,
CONSTRAINT card_ibfk_1 FOREIGN KEY (wordId) REFERENCES word
(wordId))
But when I enter data through query in command prompt I don't face any problem.
here I post my method:
public boolean insert(Card card) {
Connection connection = MySqlUtils.getInstance().getConnection();
PreparedStatement statement = null;
ResultSet resultSet = null;
int cardId = -1;
try {
String INSERT_INTO_TABLE_CARD_QUERY = "INSERT INTO "
+ TBL_CARD + " ("
+ STATUS + ", "
+ RATING + ", "
+ INSERT_TIME + ", "
+ DIC_ID + ", "
+ WORD_ID
+ ") VALUES (?,?,NOW(),?,?)";
statement = connection.prepareStatement(INSERT_INTO_TABLE_WORDS_QUERY, Statement.RETURN_GENERATED_KEYS);
statement.setString(1, card.getStatus().name());
statement.setInt(2, card.getRating());
statement.setInt(3, card.getDictionaryId());
statement.setInt(4, card.getWordId());
statement.execute();
// get last inserted id
resultSet = statement.getGeneratedKeys();
if (resultSet.next())
cardId = resultSet.getInt(1);
} catch (SQLException e) {
e.printStackTrace();
return false;
} finally {
try {
if (connection != null)
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
card.setCardId(cardId);
return true;
}
and also scripts of creating tables:
CREATE TABLE dictionary (
dictionaryId SERIAL,
dictionary VARCHAR(128) NOT NULL,
PRIMARY KEY (dictionaryId)
) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE word (
wordId SERIAL,
word VARCHAR(255) NOT NULL UNIQUE,
transcription VARCHAR(255),
PRIMARY KEY (wordId)
) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE card (
cardId SERIAL,
status ENUM ('EDIT', 'POSTPONED', 'TO_LEARN', 'LEARNT') NOT NULL DEFAULT 'TO_LEARN',
rating TINYINT DEFAULT '0' NOT NULL,
insert_time TIMESTAMP DEFAULT NOW(),
update_time TIMESTAMP,
dictionaryId BIGINT UNSIGNED NOT NULL,
wordId BIGINT UNSIGNED NOT NULL,
PRIMARY KEY (cardId),
FOREIGN KEY (wordId) REFERENCES word (wordId),
FOREIGN KEY (dictionaryId) REFERENCES dictionary (dictionaryId) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Perhaps, the fact your wordID fields on the tables have different data types is affecting your program. SERIAL is an alias for Bigint. Idea discarded.
Print somwehere in the logs the actual statement being executed. Maybe there's something that's not being included.
Thnx guys a lot. Understood many things from this topic. Right now the problem has gone. The problem was due to populating tables via JUnit tests and because of maven my tests gone in a wrong order so it was difficult to recognize the real problem.
You can switch off your constraints ,execute your query, and switch constraints on.
SET FOREIGN_KEY_CHECKS=0;
... here is your sql ...
SET FOREIGN_KEY_CHECKS=1;