Java prepairedStatement setInt is adding quotes in the SQL - java

I have a simple SQL statement that I want to execute via
String tokenInsQ = "INSERT INTO pitweb.tokens (token_user_id,token_token,token_valid,token_refresh) (?,?,?,?)";
try {
PreparedStatement p = SQL.dbConnection.prepareStatement(tokenInsQ);
p.setInt(1, r.getInt("user_id").intValue());
p.setString(2, token.getString("token"));
p.setTimestamp(3, (Timestamp)token.get("valid"));
p.setTimestamp(4, (Timestamp)token.get("renew"));
System.out.println(p);
p.executeUpdate();
p.close();
}
I expect this to work just fine. Token is a long string and r.getInt("user_id").intValue() Returns 11. The error I keep getting is
org.postgresql.util.PSQLException: ERROR: syntax error at or near "$1"
Position: 82
So I added the print statement to see the SQL that it is trying to execute. The program Prints.
INSERT INTO pitweb.tokens (token_user_id,token_token,token_valid,token_refresh) ('11','really long string ','2020-03-17 13:15:22.847000 -05:00','2020-03-14 13:15:22.847000 -05:00')
I assume that my problem is that the token_user_id ('11') is in quotes. Is this because of the way that I created the Prepared Statement? or do I have a different problem. the output seems vague to me
Here is what the token table looks like
CREATE TABLE pittweb.token
(
token_id integer NOT NULL DEFAULT nextval('pittweb.token_token_id_seq'::regclass),
toekn_user_id integer,
token_valid timestamp without time zone,
toekn_refresh timestamp without time zone,
token_token text COLLATE pg_catalog."default",
CONSTRAINT token_pkey PRIMARY KEY (token_id),
CONSTRAINT token_toekn_user_id_fkey FOREIGN KEY (toekn_user_id)
REFERENCES pittweb."user" (user_id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
)
WITH (
OIDS = FALSE
)

There is a typo in toekn_refresh as well along with toekn_user_id.

There is a typo in the column name toekn_user_id. In the insert statement, you have used token_user_id and in the DDL it is toekn_user_id

Related

If table exists drop table then create it - syntax in line Create

I just want to create a table, but if it exists it needs to be dropped and re-created.
I use MySQL 8.0.29
Error:
You have an error in your SQL syntax; check the manual that
corresponds to your MySQL server version for the right syntax to use
near 'CREATE TABLE users (Id long, name varchar(100), lastName
varchar(100), age tin' at line 1
Code:
public static void main(String[] args) throws SQLException {
try (Connection connection = Util.getConnection()) {
String sqlCommand = "DROP TABLE IF EXISTS `users`;" +
"CREATE TABLE `users` (Id long, name varchar(100), lastName varchar(100), age tinyint)";
Statement statement = connection.createStatement();
statement.executeUpdate(sqlCommand);
System.out.println("table created");
int rows = statement.executeUpdate("INSERT users(Id,name,lastName,age) VALUES (101,'Mike','Manson',31)");
}
}
The following may be the reason:
long is not a valid MySQL data type.
variable sqlCommand contains multiple statements. Each SQL statement should be terminated with a semicolon symbol. Adding a semicolon at the end of CREATE statement can solve your issue.
By default the JDBC driver does not support multiple SQL statements in one execute call. There's a connect string option to enable it:
https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-connp-props-security.html#cj-conn-prop_allowMultiQueries
allowMultiQueries
Allow the use of ';' to delimit multiple queries during one statement.
But there's no good reason to use multi-queries. They make your code more complex, not simpler. Allowing multi-queries creates the opportunity for a type of SQL injection vulnerabilities that are not possible otherwise. See https://xkcd.com/327/
Just run one statement per call to executeUpdate().

Bogus data in ResultSet

I'm having some issues with my ResultSet using JDBC.
Here's my relation:
create table person (
person_id number(5) generated always as identity
minvalue 1
maxvalue 99999
increment by 1 start with 1
cycle
cache 10,
firstname varchar(10) not null,
lastname varchar(10) not null,
);
I'm trying to insert a (firstname, lastname) into the tuple and then get the person_id that comes out of it. Here's my JDBC code:
//connection is taken care of beforehand and is named con
prep = con.prepareStatement("insert into person (firstname, lastname) values (?, ?)", Statement.RETURN_GENERATED_KEYS);
prep.setString(1, firstname);
prep.setString(2, lastname);
prep.execute();
ResultSet generated = prep.getGeneratedKeys();
if (generated.next()) {
String key = generated.getString("0");
System.out.println(key);
}
This works all fine. But my problem is that the key should be an integer, not a String. Every time I run this, I get a ResultSet that contains a string of "AAA3vaAAGAAAFwbAAG", or something along those lines. I want to get the person_id so I can use it later in my Java program.
Is there something I'm doing wrong in regards to searching through the ResultSet or the execution of the statement itself?
tl;dr
int id = generated.getInt( 1 ) ;
Details
Your Question seems confused.
There are two forms of each get… method on ResultSet.
Pass a column number (an int)
Pass a column name (a String)
You seem to have combined the two into this:
String key = generated.getString( "0" ) ;
I doubt that you have a column named with a single digit zero. Besides being a poor choice of name, standard SQL forbids starting an identifier with a digit.
So that line makes no sense. Perhaps you meant the first column by using a zero 0 and mistakenly wrapped it in quotes, thereby transforming your intended int into an actual String.
Even that intention would be wrong. The ResultSet::getString documentation incorrectly describes the int as an “columnIndex”. Usually “index” means a zero-based counting offset. But actually ResultSet::getString( int ) requires you pass an ordinal number with counting starting at one. So getString( 0 ) is never valid.
So if you want to retrieve the value of your result set’s first column as text, do this:
String key = generated.getString( 1 ) ; // Retrieve first column of result set as text.
Yet again, this would be wrong in the context of your code. You are apparently attempting to retrieve the primary key values being generated during the INSERT. Your primary key column person_id is defined as number(5) which is not a textual type. So retrieving as a String is not appropriate.
NUMBER(5) is not standard SQL. If you happen to be using Oracle database, the doc says that would be an integer type with a precision of five, meaning numbers with up to five digits. So retrieve that as a integer type in Java by calling ResultSet::getInt.
int id = generated.getInt( 1 ) ; // Retrieve the new row’s ID from the first column of the result set of generated key values returned by the `INSERT` prepared statement.
My comments above are for databases in general. But for Oracle specifically, see the Answer by Mark Rotteveel explaining that Oracle database does not return the generated sequence number when calling getGeneratedKeys. Instead it returns ROWID pseudo-column.
Your problem is that Oracle by default returns the ROWID of the inserted record, and not the generated identifier. From Oracle JDBC Developer's Guide: Retrieval of Auto-Generated Keys:
If key columns are not explicitly indicated, then Oracle JDBC drivers
cannot identify which columns need to be retrieved. When a column name
or column index array is used, Oracle JDBC drivers can identify which
columns contain auto-generated keys that you want to retrieve.
However, when the Statement.RETURN_GENERATED_KEYS integer flag is
used, Oracle JDBC drivers cannot identify these columns. When the
integer flag is used to indicate that auto-generated keys are to be
returned, the ROWID pseudo column is returned as key. The ROWID
can be then fetched from the ResultSet object and can be used to
retrieve other columns.
So, if you use Statement.RETURN_GENERATED_KEYS, you'll get the ROWID, and you can then use that ROWID to select the inserted row to obtain the other values (including the generated identifier).
If you want to specifically retrieve the generated id, for Oracle you'll need to explicitly ask for that column as follows:
String[] columns = { "PERSON_ID" }
prep = con.prepareStatement(
"insert into person (firstname, lastname) values (?, ?)", columns);
prep.setString(1, firstname);
prep.setString(2, lastname);
prep.executeUpdate();
ResultSet generated = prep.getGeneratedKeys();
if (generated.next()) {
int key = generated.getInt("PERSON_ID");
System.out.println(key);
}

ResultSet.updateRow() produces "Illegal mix of collations (latin1_bin,IMPLICIT) and (utf8_general_ci,COERCIBLE) for operation '<=>'"

I have the following table with name being LATIN1 and the rest being UTF8.
CREATE TABLE `test_names` (
`name` varchar(500) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`other_stuff_1` int DEFAULT NULL,
`other_stuff_2` varchar(45) DEFAULT NULL,
PRIMARY KEY (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
I encounter the following problem in Java:
I SELECT ... FOR UPDATE. Then I call updateInt(2, 1) and updateRow() on its ResultSet and get Illegal mix of collations (latin1_bin,IMPLICIT) and (utf8_general_ci,COERCIBLE) for operation '<=>'.
How can I make this work without changing the table’s / connection’s charset?
Thanks a lot.
--- UPDATE ---
I use SELECT name, other_stuff_1 FROM test_names LIMIT 1 FOR UPDATE; and the connection string is DriverManager.getConnection("jdbc:mysql://" + host + ":" + port + "/" + db + "?allowMultiQueries=true", user, password);.
The exact stack trace is:
java.sql.SQLException: Illegal mix of collations (latin1_bin,IMPLICIT) and (utf8_general_ci,COERCIBLE) for operation '<=>'
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1086)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4237)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4169)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2617)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2778)
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2834)
at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:2156)
at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2441)
at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2366)
at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2350)
at com.mysql.jdbc.UpdatableResultSet.updateRow(UpdatableResultSet.java:2405)
From my side, I can give you some suggestions
First update your Connector/J latest version.
Run this query
SET NAMES='utf8'
Add &characterEncoding=UTF-8 onto your JDBC connect string. Hope you have done it already.
Use convert() for insert or update and cast() for select query. For more details http://dev.mysql.com/doc/refman/5.7/en/charset-convert.html
For "Illegal mix of collations" related issue, you can follow up Troubleshooting "Illegal mix of collations" error in mysql
The problem is that you are trying to select from a table which has different charsets on different columns.
You have to convert the columns that have different charsets in the query to the proper charset using CONVERT().
http://dev.mysql.com/doc/refman/5.7/en/charset-convert.html
In your case, you should modify your query to:
SELECT CONVERT(name USING latin1), other_stuff_1 FROM test_names LIMIT 1 FOR UPDATE;
IMHO you can't use updatable resultset in this case when the encoding in the table is not uniform (for example the primary key is in latin1 but the other columns are in utf8).
As Andras pointed out, you can convert the encoding on the sql side but then you won't be able to update the resultset.
Why don't you simply update the table with executeUpdate(...)? You can narrow down the target line(s) with a simple select then iterate over the resulting primary key list and call executeUpdate.
For me that is working for mixed encoded columns. Just an example:
conn.setAutoCommit(false);
ResultSet rs = st.executeQuery("SELECT name other_stuff_1 FROM test_names");
List<String> keys = new ArrayList<String>();
while(rs.next()) {
keys.add(rs.getString(1));
}
rs.close();
for(String key : keys) {
st.executeUpdate("update test_names set other_stuff_1='"+key.length()+"', other_stuff_2='" + key.toUpperCase() + "' where name='" + key + "'");
}
conn.commit();

SQL works from PMA but not Java

I have a prepared statement like so:
CREATE TABLE IF NOT EXISTS ? (uuid VARCHAR(128), item VARCHAR(48), value FLOAT, UNIQUE (uuid))
If I execute this directly in PMA, but replacing the ? with any text text, it works perfectly and it creates the table correctly. However, if I run it from Java it doesn't work.
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''Murder_PlayerData' (uuid VARCHAR(128), item VARCHAR(48), value FLOAT, UNIQUE (u' at line 1
Here's the Java code
String table = "Murder_PlayerData";
String execute = "CREATE TABLE IF NOT EXISTS ? (uuid VARCHAR(128), item VARCHAR(48), value FLOAT(11), UNIQUE (uuid))";
PreparedStatement statement = sql.getConnection().prepareStatement(execute);
statement.setString(1, table);
statement.execute();
Why does it work in PMA but not when I do it from Java?
Prepared statements can't be used to define table names, but to define values related to columns (insert values, values for where or having conditions, etcetera). One way to understand it is: Prepared Statements are for DML operations, not for DDL operations.
If you want to build a table on runtime, you need to build the SQL statement "by hand":
String table = "Murder_PlayerData"
String strSQL = "create table if not exists " + table + "("
// Add your column definitions
+ ")"
Statement stmt = conn.createStatement();
stmt.execute(strSQL);
Notice that, if the variable table can be filled with data provided by the user, your code will be vulnerable to SQL Injection Attacks. I recommend you don't create tables if their names must be provided by users, but rather create the tables without any user interaction and then insert the values, using some kind of key to identify which records belong to each user.

newline charter \n gives "java.sql.SQLException: ORA-00911: invalid character\n" Oracle 11g

I have Oracle DB 11g Enterprise Edition and I want to create a table by reading the sql script from a file.Through java code I am reading following sql script from a file and storing it in a String sqlBlock:
CREATE SEQUENCE VerHist_SeqNum
START WITH 1
INCREMENT BY 1;
CREATE TABLE VerHist
(
SequenceNumber NUMBER(10,0) NOT NULL,
SQLFileName VARCHAR2(100) NOT NULL,
STATUS VARCHAR2(10) NOT NULL,
AppliedDate DATE NOT NULL,
DateCreated DATE
DEFAULT (SYSDATE),
DateUpdated DATE
DEFAULT (SYSDATE),
CONSTRAINT PK_VerHist PRIMARY KEY( SequenceNumber ),
CONSTRAINT UC_VerHist_SQLFileNa UNIQUE( SQLFileName )
);
CREATE OR REPLACE TRIGGER VerHist_SeqNum_TRG
BEFORE INSERT
ON VerHist
FOR EACH ROW
BEGIN
SELECT VerHist_SeqNum.NEXTVAL INTO :NEW.SequenceNumber
FROM DUAL;
END;
When I execute this query it gives
java.sql.SQLException: ORA-00911: invalid character\n
at
oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:112)
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:331) at
oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:288) at
oracle.jdbc.driver.T4C8Oall.receive(T4C8Oall.java:743) at
oracle.jdbc.driver.T4CStatement.doOall8(T4CStatement.java:207) at
oracle.jdbc.driver.T4CStatement.executeForRows(T4CStatement.java:946)
at
oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1168)
at
oracle.jdbc.driver.OracleStatement.executeInternal(OracleStatement.java:1687)
at
oracle.jdbc.driver.OracleStatement.execute(OracleStatement.java:1653)
Following is my code to execute the sql block:
Statement stmt = conn.createStatement();
String sqlBlock = //"this contains the content of the file (it contains \n charters)";
stmt.execute(sqlBlock);
Is the newline charter invalid here, if yes, how to get this working otherwise?
Please note that when I copy paste the contents of this file and run the script through Oracle SQL Developer it runs fine.
I think the \n reference is a red-herring, and an artefact of how the error is being logged. You're trying to run two SQL statements, separated by a semi-colon, in one execute. That is not allowed. The semi-colon is a statement separator in SQL*Plus, not in SQL, and will generate an ORA-00911 even with a single statement. And execute has to be a single statement.
If you were doing DML you could wrap the statements in a PL/SQL block and execute that, but since this is DDL you can't do that unless you resort to dynamic SQL, which is going to be overly complicated and messy for what you're trying to do.
You need to put each statement in a separate file (without the trailing semi-colon on the create sequence; you still need it on the create trigger because there it is ending the trigger's PL/SQL block, not acting as a statement separator - confusing, I know), and read and execute them separately. Then each execute has a single statement, and will be much happier.
As an aside, you don't need to select your sequence value into your variable in 11g; you can now do it like this:
CREATE OR REPLACE TRIGGER VerHist_SeqNum_TRG
BEFORE INSERT
ON VerHist
FOR EACH ROW
BEGIN
:NEW.SequenceNumber := VerHist_SeqNum.NEXTVAL;
END;
When you copy paste the contains of the file to browser. Browser will treat \n as new line.
Whereas for the code /n is character only.
Try to replace \n with single space and then run it will work
sqlBlock = sqlBlock.replaceAll("\n"," ");
Remove the \n from the query. If you want the query to be formatted in sql developer, you can select the query and press Ctrl+F7

Categories

Resources