I have connected Java to SSMS and can call data from the server no problems using something like this:
String cell = "SELECT [Close] FROM ExcelData WHERE id_num = 3";
Statement st4 = con.createStatement();
ResultSet rs4 = st4.executeQuery(cell);
while (rs4.next())
{
Float close = rs4.getFloat("close");
System.out.format("%s\n", close);
}
When I replace the "SELECT [Close] FROM ExcelData WHERE id_num = 3" to "SELECT #SMA" I get the much questioned "Must declare the scalar variable #SMA.
I do not know how to do that.
Answering my own question to help others who may need to know this...
It cannot be done. The work around is to create a table in SQL and insert the variable into the table.
DROP TABLE IF EXISTS SMA
CREATE TABLE SMA (
SMA_Price DECIMAL (6,5),
)
INSERT INTO SMA VALUES (#SMA);
SELECT * FROM SMA;
You can then call "SELECT * FROM SMA" from Java.
Related
I have the below SP which I am trying to convert into simple Java inline query :
CREATE OR REPLACE PROCEDURE public.spdummytable(
par_zone_no integer,
par_fpsallocid integer,
INOUT p_refcur refcursor)
LANGUAGE 'plpgsql'
AS $BODY$
BEGIN
OPEN p_refcur FOR
SELECT
z.zone_no,
m AS monthnumber,
COALESCE(fpsallocid, par_FPSallocid) AS fpsallocid,
to_char((CAST ('2000-01-01' AS TIMESTAMP WITHOUT TIME ZONE))::TIMESTAMP + (COALESCE(aw.month, m) - 1::NUMERIC || ' MONTH')::INTERVAL, 'Month') AS monthname,
week1,
week2,
week3,
week4
FROM (SELECT par_Zone_No AS zone_no) AS z
CROSS JOIN (SELECT 1 AS m
UNION SELECT 2
UNION SELECT 3
UNION SELECT 4
UNION SELECT 5
UNION SELECT 6
UNION SELECT 7
UNION SELECT 8
UNION SELECT 9
UNION SELECT 10
UNION SELECT 11
UNION SELECT 12) AS moty
LEFT OUTER JOIN anotherTable AS aw
ON z.zone_no = aw.zone_no AND
aw.month = moty.m AND
COALESCE(fpsallocid, par_FPSallocid) = par_FPSallocid;
END;
$BODY$;
ALTER PROCEDURE public.spdummytable(integer, integer, refcursor)
OWNER TO postgres;
This will fetch some weekly values for every month from Jan to Dec.
What I am trying is below :
public List<MyResponse> result = null;
Connection conn = DatabaseConnection.connect();
PreparedStatement stmt = conn.prepareStatement("call public.spdummytable(?,?,?,,....,cast('p_refcur' as refcursor)); FETCH ALL IN \"p_refcur\";");
stmt.setString(1, "8006");
stmt.setString(2, "8049");
----
----
boolean isResultSet = stmt.execute();
if(isResultSet) {
ResultSet rs = stmt.getResultSet();
while(rs.next()) {
MyResponse myResponse = new MyResponse();
myResponse.setSomeVariable(rs.getString("columnname"));
-----
-----
result.add(myResponse)
}
rs.close();
conn.close();
}
But I am confused on the query formation part from the above SP. This seems to be a complex conversion. Can someone please help form the inline query. Appreciate your help on this.
EDIT/UPDATE
If I am unable to explain myself, I just want to say that I need to form the postgresql SELECT query from the above SP. I know the PreparedStatement is wrong above, I am trying to form a basic sql query from the above SP . Changing/Modifying the SP is not an option for me. I am planning to cut the dependency from the database and control it over Java. Please help.
I don't think getResultSet works with a stored procedure like that but I'm unsure. You're operating on a cursor with your INOUT parameter. As suggested in the comments, this would be much easier with a set returning function.
Note: stored procedures didn't exist in Postgres before Postgres 11.
If you cannot convert this to a set returning function, you'll need to handle the cursor object in a different manner. Something like this:
CallableStatement stmt = conn.prepareCall("{? = call public.spdummytable(?,?) }");
stmt.registerOutParameter(1, Types.OTHER);
stmt.setString(2, "8006");
stmt.setString(3, "8049");
stmt.execute();
ResultSet results = (ResultSet) stmt.getObject(1);
while (results.next()) {
// do something with the results.
}
Set returning function:
CREATE OR REPLACE FUNCTION public.spdummytable
( par_zone_no INTEGER
, par_fpsallocid INTEGER
)
RETURNS TABLE ( zone_no INTEGER -- I don't know the data types of these fields
, monthnumber INTEGER
, fpsallocid INTEGER
, monthname TEXT
, week1 TEXT
, week2 TEXT
, week3 TEXT
, week4 TEXT
)
AS $$
BEGIN
RETURN QUERY
SELECT z.zone_no AS zn
, moty AS mo_num
, COALESCE(fpsallocid, par_FPSallocid) AS fpsid
, to_char((CAST ('2000-01-01' AS TIMESTAMP WITHOUT TIME ZONE))::TIMESTAMP + (COALESCE(aw.month, m) - 1::NUMERIC || ' MONTH')::INTERVAL, 'Month') AS mo_name
, week1 w1
, week2 w2
, week3 w3
, week4 w4
FROM (SELECT par_Zone_No AS zone_no) AS z
CROSS JOIN generate_series(1, 12) AS moty
LEFT OUTER JOIN anotherTable AS aw ON z.zone_no = aw.zone_no
AND aw.month = moty
AND COALESCE(fpsallocid, par_FPSallocid) = par_FPSallocid
;
END;
$$ LANGUAGE PLPGSQL;
You could also define your own return type and use RETURNS SETOF your_type. Use the same function body as above.
CREATE TYPE type_dummytype AS
( zone_no INTEGER -- I don't know the data types of these fields
, monthnumber INTEGER
, fpsallocid INTEGER
, monthname TEXT
, week1 TEXT
, week2 TEXT
, week3 TEXT
, week4 TEXT
);
CREATE OR REPLACE FUNCTION public.spdummytable
( par_zone_no INTEGER
, par_fpsallocid INTEGER
)
RETURNS SETOF type_dummytype
AS $$ ... $$
Then your prepared statement becomes something like this:
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM public.spdummytable(?, ?);");
stmt.setString(1, "8006");
stmt.setString(2, "8049");
All of the other java should be good to go.
I am having code something like this.
final PreparedStatement stmt = connection
.prepareStatement("delete from " + fullTableName
+ " where name= ?");
stmt.setString(1, addressName);
Calculation of fullTableName is something like:
public String getFullTableName(final String table) {
if (this.schemaDB != null) {
return this.schemaDB + "." + table;
}
return table;
}
Here schemaDB is the name of the environment(which can be changed over time) and table is the table name(which will be fixed).
Value for schemaDB is coming from an XML file which makes the query vulnerable to SQL injection.
Query: I am not sure how the table name can be used as a prepared statement(like the name used in this example), which is the 100% security measure against SQL injection.
Could anyone please suggest me, what could be the possible approach to deal with this?
Note: We can be migrated to DB2 in future so the solution should compatible with both Oracle and DB2(and if possible database independent).
JDBC, sort of unfortunately, does not allow you to make the table name a bound variable inside statements. (It has its reasons for this).
So you can not write, or achieve this kind of functionnality :
connection.prepareStatement("SELECT * FROM ? where id=?", "TUSERS", 123);
And have TUSER be bound to the table name of the statement.
Therefore, your only safe way forward is to validate the user input. The safest way, though, is not to validate it and allow user-input go through the DB, because from a security point of view, you can always count on a user being smarter than your validation.
Never trust a dynamic, user generated String, concatenated inside your statement.
So what is a safe validation pattern ?
Pattern 1 : prebuild safe queries
1) Create all your valid statements once and for all, in code.
Map<String, String> statementByTableName = new HashMap<>();
statementByTableName.put("table_1", "DELETE FROM table_1 where name= ?");
statementByTableName.put("table_2", "DELETE FROM table_2 where name= ?");
If need be, this creation itself can be made dynamic, with a select * from ALL_TABLES; statement. ALL_TABLES will return all the tables your SQL user has access to, and you can also get the table name, and schema name from this.
2) Select the statement inside the map
String unsafeUserContent = ...
String safeStatement = statementByTableName.get(usafeUserContent);
conn.prepareStatement(safeStatement, name);
See how the unsafeUserContent variable never reaches the DB.
3) Make some kind of policy, or unit test, that checks that all you statementByTableName are valid against your schemas for future evolutions of it, and that no table is missing.
Pattern 2 : double check
You can 1) validate that the user input is indeed a table name, using an injection free query (I'm typing pseudo sql code here, you'd have to adapt it to make it work cause I have no Oracle instance to actually check it works) :
select * FROM
(select schema_name || '.' || table_name as fullName FROM all_tables)
WHERE fullName = ?
And bind your fullName as a prepared statement variable here. If you have a result, then it is a valid table name. Then you can use this result to build a safe query.
Pattern 3
It's sort of a mix between 1 and 2.
You create a table that is named, e.g., "TABLES_ALLOWED_FOR_DELETION", and you statically populate it with all tables that are fit for deletion.
Then you make your validation step be
conn.prepareStatement(SELECT safe_table_name FROM TABLES_ALLOWED_FOR_DELETION WHERE table_name = ?", unsafeDynamicString);
If this has a result, then you execute the safe_table_name. For extra safety, this table should not be writable by the standard application user.
I somehow feel the first pattern is better.
You can avoid attack by checking your table name using regular expression:
if (fullTableName.matches("[_a-zA-Z0-9\\.]+")) {
final PreparedStatement stmt = connection
.prepareStatement("delete from " + fullTableName
+ " where name= ?");
stmt.setString(1, addressName);
}
It's impossible to inject SQL using such a restricted set of characters.
Also, we can escape any quotes from table name, and safely add it to our query:
fullTableName = StringEscapeUtils.escapeSql(fullTableName);
final PreparedStatement stmt = connection
.prepareStatement("delete from " + fullTableName
+ " where name= ?");
stmt.setString(1, addressName);
StringEscapeUtils comes with Apache's commons-lang library.
I think that the best approach is to create a set of possible table names and check for existance in this set before creating query.
Set<String> validTables=.... // prepare this set yourself
if(validTables.contains(fullTableName))
{
final PreparedStatement stmt = connection
.prepareStatement("delete from " + fullTableName
+ " where name= ?");
//and so on
}else{
// ooooh you nasty haker!
}
create table MYTAB(n number);
insert into MYTAB values(10);
commit;
select * from mytab;
N
10
create table TABS2DEL(tname varchar2(32));
insert into TABS2DEL values('MYTAB');
commit;
select * from TABS2DEL;
TNAME
MYTAB
create or replace procedure deltab(v in varchar2)
is
LvSQL varchar2(32767);
LvChk number;
begin
LvChk := 0;
begin
select count(1)
into LvChk
from TABS2DEL
where tname = v;
if LvChk = 0 then
raise_application_error(-20001, 'Input table name '||v||' is not a valid table name');
end if;
exception when others
then raise;
end;
LvSQL := 'delete from '||v||' where n = 10';
execute immediate LvSQL;
commit;
end deltab;
begin
deltab('MYTAB');
end;
select * from mytab;
no rows found
begin
deltab('InvalidTableName');
end;
ORA-20001: Input table name InvalidTableName is not a valid table name ORA-06512: at "SQL_PHOYNSAMOMWLFRCCFWUMTBQWC.DELTAB", line 21
ORA-06512: at "SQL_PHOYNSAMOMWLFRCCFWUMTBQWC.DELTAB", line 16
ORA-06512: at line 2
ORA-06512: at "SYS.DBMS_SQL", line 1721
I have some data in an SQLite Database. The following queries in SQLiteBrowser give me exactly the ResultSet I want:
With TempTable0 AS (SELECT Source, Date, Value AS Close FROM FinData WHERE Type = 'Close'),
TempTable1 AS (SELECT Source, Date, Value AS Colume FROM FinData WHERE Type = ' Colume')
SELECT DISTINCT TempTable0.Date, Close, Colume FROM TempTable0
JOIN TempTable1 ON TempTable1.Date = TempTable0.Date
Now, I tried to get this working as a single query via a simple executeQuery-Method from Java and read the data in a Matrixlike-DataStructure:
String sql = "With TempTable0 AS (SELECT Source, Date, Value AS Close FROM FinData WHERE Type = 'Close'), TempTable1 AS (SELECT Source, Date, Value AS Colume FROM FinData WHERE Type = ' Colume') SELECT DISTINCT TempTable0.Date, Close, Colume FROM TempTable0 JOIN TempTable1 ON TempTable1.Date = TempTable0.Date";
Connection Conn = DriverManager.getConnection(SQLCommunication.urlDB);
Statement stmt = Conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
while (rs.next() ) {
String res = rs.getString("Close");
DataMatrix.get("Close").add(res);
res = rs.getString("Colume");
DataMatrix.get("Colume").add(res);
}
However, the Resultset returns null.
I suspect this is because, it cannot work with two dependent SQLite-Queries, however, I have no idea, how to solve this.
I am no expert on SQLite and Java interaction and I am really running out of ideas, right now. Do you have any sugestions? (Even maybe a tip on nesting these two statements in one so it can be executed in one shot?)
Thanks so much!
Wiwi
Following is my code(Re-constructed) which select & update STATUS field depending upon the conditions. (Using Servlets, Oracle as Backend and JDBC driver)
ResultSet rs=null;
String query = "select A.NAME, A.ADDRESS, A.STATUS, B.CLASS from TABLE_ONE A, TABLE_TWO B where A.STATUS='N'";
pstmt = con.prepareStatement(query,ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_UPDATABLE);
rs = pstmt.executeQuery();
while(rs.next())
{
String name = rs.getString("NAME");
String address = rs.getString("ADDRESS");
String class = rs.getString("CLASS");
String msg = //Other statements to check what status to be set
if(msg.equals("OK"))
rs.updateString("STATUS", "S");
else
rs.updateString("STATUS", "E");
rs.updateRow();
}
I am getting the error while updating:
java.sql.SQLException: Invalid operation for read only resultset: updateString
Any suggestions will be appreciated.
Update 1:
The same code was working when select statement was selecting data from single table, so is there any issue when selecting data from two tables in single query?
[Note: As #javaBeginner has mentioned in comments it will work only for one table.]
The following limitations are placed on queries for enhanced result sets. Failure to follow these guidelines will result in the JDBC driver choosing an alternative result set type or concurrency type.
To produce an updatable result set (from specification):
A query can select from only a single table and cannot contain any join operations.
In addition, for inserts to be feasible, the query must select all non-nullable columns and all columns that do not have a default value.
* A query cannot use "SELECT * ". (But see the workaround below.)
* A query must select table columns only. It cannot select derived columns or aggregates such as the SUM or MAX of a set of columns.
To produce a scroll-sensitive result set:
A query cannot use "SELECT * ". (But see the workaround below.)
A query can select from only a single table.
Try This :
Statement stmt = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_UPDATABLE);
//Execute a query
String sql = "select A.NAME, A.ADDRESS, A.STATUS, B.CLASS from TABLE_ONE A, TABLE_TWO B where A.STATUS='N'";
ResultSet rs = stmt.executeQuery(sql);
//Extract data from result set
rs.beforeFirst();
while(rs.next())
{
String name = rs.getString("NAME");
String address = rs.getString("ADDRESS");
String class = rs.getString("CLASS");
String msg = //Other statements to check what status to be set
if(msg.equals("OK"))
rs.updateString("STATUS", "S");
else
rs.updateString("STATUS", "E");
rs.updateRow();
}
Just changed Prepared statement to create statement
SELECT * makes the resultSet readonly. SELECT COLUMN_NAME makes it updatable.
So instead of SELECT * FROM TABLE use SELECT COLUMN1, COLUMN2, ... FROM TABLE.
In PostgreSQL user is a reserved keyword that is used in an internal table, however I also have a separate user table in my own database that I need to use. Whenever I try to execute INSERT or UPDATE statements on the table, it generates the following error: The column name 'id' was not found in this ResultSet.
This is the Java code I am currently using:
PreparedStatement stat1 = conn.prepareStatement("SELECT id FROM user;");
PreparedStatement stat2 = conn.prepareStatement("UPDATE user SET date_created = ? , last_updated = ? , uuid = ? WHERE id = ?;");
ResultSet rs = stat1.executeQuery();
while(rs.next()){
UUID uuid = UUID.randomUUID();
String tempId = uuid.toString();
stat2.setTimestamp(1, curDate);
stat2.setTimestamp(2, curDate);
stat2.setString(3, tempId);
stat2.setLong(4,rs.getLong("id"));
stat2.executeUpdate();
}
So my question is, how could I insert or update the values in my personal user table without interfering with the keyword restriction?
Use this:
prepareStatement("UPDATE \"user\" set date_created = ?")
Or, better yet, rename your user table to something else, like users:
ALTER TABLE "user" RENAME TO users;
Escape the table name like this
select * from "user";