Quoting arguments in dynamic WHERE in OpenSQL - abap

I found this example how to create a dynamic WHERE:
REPORT ZII_RKP_TEST1.
DATA: cond(72) TYPE c,
itab LIKE TABLE OF cond.
PARAMETERS: source(10) TYPE c, dest(10) TYPE c.
DATA wa TYPE spfli-cityfrom.
CONCATENATE 'CITYFROM = ''' source '''' INTO cond.
APPEND cond TO itab.
CONCATENATE 'OR CITYFROM = ''' dest '''' INTO cond.
APPEND cond TO itab.
CONCATENATE 'OR CITYFROM = ''' 'BAYERN' '''' INTO cond.
APPEND cond TO itab.
LOOP AT itab INTO cond.
WRITE cond.
ENDLOOP.
SKIP.
SELECT cityfrom
INTO wa
FROM spfli
WHERE (itab).
WRITE / wa.
ENDSELECT.
Source: https://wiki.scn.sap.com/wiki/display/ABAP/Dynamic%2Bwhere%2Bclause
Above example uses static values like "BAYERN", but if I use arbitrary values, then I guess things could break for some special values like '''.
Is it necessary to do some quoting to make the dynamic WHERE unbreakable? And if yes, how to do it?

You can escape apostrophe in perform before adding them like below
PERFORM escape CHANGING source.
PERFORM escape CHANGING dest.
CONCATENATE 'CITYFROM = ''' source '''' INTO cond.
APPEND cond TO itab.
...
FORM escape CHANGING value TYPE c.
REPLACE ALL OCCURRENCES OF '''' IN value WITH ''''''.
ENDFORM.
Latest ABAP versions included escape function details are here. But it is not included quote escaping. We can use static escape_quotes method on class cl_abap_dyn_prg like below.
CALL METHOD cl_abap_dyn_prg=>escape_quotes
EXPORTING
val = source
receiving
out = output.
Method making something look like above perform.

I wonder why that example is written that way, maybe it's an old piece of code.
1st: if you have access in your system to the "new" string syntax, you can just use something like
WHERE = |CITYFROM = '| && source && |'|.
2nd: the nice thing about string WHERE clauses is that you can just use variables as part of the string, I mean, if you just write something like
WHERE = 'CITYFROM = source'.
ABAP will transform that into proper SQL, as if you were writing your SQL properly.
(I wish I could explain myself properly, do not hesitate to ask if you have any doubt)

The method cl_abap_dyn_prg=>quote( name ) should get used.
Example:
DATA(cond) = `country = 'DE' AND name = ` &&
cl_abap_dyn_prg=>quote( name ).
Source: SQL Injections Using Dynamic Tokens
Thanks to Sandra Rossi for pointing me in the right direction.

Related

Cannot create a Computed column using RIGHT function because CHARINDEX returns null

So I have a Document table where I want to add a computed column named FileType using the column FileName. Below is the code I used to add a Computed Column:
ALTER TABLE dbo.Document
Add FileType AS UPPER(RIGHT(FileName,CHARINDEX('.',REVERSE(FileName))-1))
It turns out some of the data in FileName column does not have a '.' in their name which is causing the issue. I want to somehow implement a check to return CharIndex of only those data consisting of a '.' in their FileName. But I cannot write a Select statement in the Alter statement.
NOTE: I tried to find solution from following links which helped me find the issue but I could not derive any solution from them.
Error: invalid length parameter passed to the right function in name parsing script
Invalid length parameter passed to the RIGHT function
Invalid length parameter passed to the RIGHT function in update statement
Use a case expression to make it conditional e.g.
UPPER(RIGHT(FileName, case when CHARINDEX('.',REVERSE(FileName)) > 0 then CHARINDEX('.',REVERSE(FileName))-1 else len(Filename) end))
The simplest method is to add the delimiter:
UPPER(RIGHT(FileName, CHARINDEX('.', REVERSE(FileName) + '.') - 1))
No conditional logic is needed.

Using PostgreSQL parameters with booleans in EXECUTE / USING

i want to use parameters for my dynamic queries. I have a statements like so:
RETURN QUERY EXECUTE 'SELECT * FROM boards AS b WHERE b.slug = $1 AND $2'
USING filter_slug, parent_id_query;
I get a ERROR: argument of AND must be type boolean, not type text
if i do it like this:
RETURN QUERY EXECUTE 'SELECT * FROM boards AS b WHERE b.slug = ''' || filter_slug || ''' AND ' || parent_id_query;
it works though.
I feel like i am missing something / not understanding something. Please help.
What you are missing is how parameters are used. Parameters are not macros that replace arbitrary text inside a SQL statement. Instead, they are literal values assigned to "variables" inside the code. These values are typically numbers, strings, or dates.
In particular, parameters cannot be used for:
identifiers (columns names and table names)
function names
operators
SQL keywords
general expressions
So, unfortunately, you have to construct that part of the query without a generic parameter (although you can have $2 = $3)

How do I split multiple times in abap?

I want to split my code into a workarea and append this to a an internal table for a later perform.
But sometimes the text contains more than 3 numbers for example 3;5;3;6;2;5 but its always 3,6,9,12... number. How can I solve the problem that I want to loop 3 times, then the next 3 numbers and so on?
DATA: text(100) type c,
it_1 TYPE STANDART TABLE LIKE text,
it_2 TYPE STANDART TABLE LIKE text,
it_3 TYPE STANDART TABLE LIKE text,
string(100) TYPE c.
text = '123;2;2'.
SPLIT text AT ';' INTO wa_1-c1 wa_1-c2 wa_1-c3.
APPEND wa_1-c1 to it_1.
APPEND wa_1-c2 to it_2.
APPEND wa_1-c3 to it_3.
LOOP at it_1 INTO string.
PERFORM task using string.
ENDLOOP.
You should use the INTO TABLE addition to the split keyword rather than hard coding the fields.
DATA: text_s TYPE string.
text_s = '123;2;2'.
DATA: text_tab TYPE TABLE OF string.
SPLIT text_s AT ';' INTO TABLE text_tab.
LOOP AT text_tab ASSIGNING FIELD-SYMBOL(<line>).
"do whatever on each token here
ENDLOOP.
This will split the string in 3-er blocks, while overwriting it with the rest:
WHILE text IS NOT INITIAL.
SPLIT AT ';'
INTO wa_1-c1
wa_1-c2
wa_1-c3
text.
APPEND: wa_1-c1 to it_1,
wa_1-c2 to it_2,
wa_1-c3 to it_3.
ENDWHILE.
Please note, the string variable text will be initial at the end, if its original value is still needed, than you can define another string, copy the value and use that one for the split.
You can try using Sy-tabix if you want to control the iterations three times and since you are saving the text values in 3 different internal tables.
DATA: text(100) type c,
it_1 TYPE STANDARD TABLE OF text,
it_2 TYPE STANDARD TABLE OF text,
it_3 TYPE STANDARD TABLE OF text,
string(100) TYPE c.
text = '123;2;2'.
SPLIT text AT ';' INTO TABLE it_1.
LOOP at it_1 INTO string WHERE sy-tabix = 3.
WRITE : string.
ENDLOOP.
if sy-tabix = 3.
LOOP AT it_2 INTO string WHERE sy-tabix = sy-tabix+3.
"do the next loop
ENDLOOP.
ENDIF.

error: bind message supplies 1 parameters, but prepared statement "" requires 0

I have a table 'article' with column 'content' .I want to query Postgresql in order to search for a string contained in variable 'temp'.This query works fine-
pool.query("select * from article where upper(content) like upper('%some_value%')");
But when I use placeholder $1 and [temp] in place of some_value , I get the above error -
pool.query("select * from article where upper(content) LIKE upper('%$1%')",[temp] );
Note - Here $1 is a placeholder and should be replaced by the value in [temp] , but it treats '%$1%' as a string , I guess. Without the quotes ' ' , the LIKE operator doesn't work. I have also tried the query -
pool.query("select * from article where upper(content) LIKE upper(concat('%',$1,'%'))",[temp] );
to ensure $1 is not treated as a string literal but it gives the error -
error: could not determine data type of parameter $1
pool.query(
"select * from article where upper(content) LIKE upper('%' || $1 || '%')",
[temp]
).then( res => {console.log(res)}, err => {console.error(err)})
This works for me. I just looked at this Postgres doc page to try and understand what concat was doing to the parameter notation. Can't say that I understand the difference between using || operators and using concat string function at this time.
The easiest way I found to do this is like the following:
// You can remove [0] from character[0] if you want the complete value of character.
database.query(`
SELECT * FROM users
WHERE LOWER(users.name) LIKE LOWER($1)
ORDER BY users.id ASC`,
["%" + character[0] + "%"]
);
// [%${character}%] string literal alternative to the last line in the function call.
There are several things going on here, so let me break each line it down.
SELECT * FROM users
This is selecting all the columns associated with table users
WHERE LOWER(users.name) LIKE $1
This is filtering out all the results from the first line so that where the name(lowercased) column of the users table is like the parameter $1.
ORDER BY users.id ASC
This is optional, but I like to include it because I want the data returned to me to be in ascending order (that is from 0 to infinity, or starting low and going high) based on the users.id or the id column of the users table. A popular alternative for client-side data presentation is users.created_at DESC which shows the latest user (or more than likely an article/post/comment) by its creation date in reverse order so you get the newest content at the top of the array to loop through and display on the client-side.
["%" + character + "%"]
This part is the second argument in the .query method call from the database object (or client if you kept with that name, you can name it what you want, and database to me makes for more a sensical read than "client", but that is just my personal opinion, and it's highly possible that "client" may be the more technically correct term to use).
The second argument needs to be an array of values. It takes the place of the parameters inserted in the query string, for example, $1 or ? are examples of parameter placeholders which are filled in with a value in the 2nd argument's array of values. In this case, I used JavaScript's built-in string concatenation to provide a "includes" like pattern, or in plain-broken English, "find me columns that contain a 'this' value" where name(lowercased) is the column and character is the parameter variable value. I am pulling in the parameter value for the character variable from req.params (the URL, so http://localhost:3000/users/startsWith/t), so combining that with % on both ends of the parameter, it returns me all the values that contain the letter t since is the first (and only) character here in the URL.
I know this is a VERY late response, but I wanted to respond with a more thorough answer in case anyone else needed it broken down further.
In my case :
My variable was $1, instead of ?1 ...
I was customizing my query with #Query

How to replace where clause dynamically in query (BIRT)?

In my report query I have a where clause that needs to be replaced dynamically based on the data chosen in the front end.
The query is something like :
where ?=?
I already have a code to replace the value - I created report parameter and linked to the value ? in the query.
Example:
where name=?
Any value of name that comes from front end replaces the ? in the where clause - this works fine.
But now I need to replace the entire clause (where ?=?). Should I create two parameters and link them to both the '?' ?
No, unfortunately most database engines do not allow to use a query parameter for handling a dynamic column name. This is for security considerations.
So you need to keep an arbitrary column name in the query:
where name=?
And then in "beforeOpen" script of the dataset replace 'name' with a report parameter value:
this.queryText=this.queryText.replace("name",params["myparameter"].value);
To prevent SQLIA i recommend to test the value of the parameter in this script. There are many ways to do this but a white list is the strongest test, for example:
var column=params["myparameter"].value;
if (column=="name" || column=="id" || column=="account" || column=="mycolumnname"){
this.queryText=this.queryText.replace("name",column);
}
In addition to Dominique's answer and your comment, then you'll just need a slightly more advanced logic.
For example, you could name your dynamic column-name-value pairs (column1, value1), (column2, value2) and so on. In the static text of the query, make sure to have bind variables for value1, value2 and so on (for example, with Oracle SQL, using the syntax
with params as (
select :value1 as value1,
:value2 as value2 ...
from dual
)
select ...
from params, my_table
where 1=1
and ... static conditions....
Then, in the beforeOpen script, append conditions to the query text in a loop as needed (the loop left as an exercise to the reader, and don't forget checking the column names for security reasons!):
this.queryText += " and " + column_name[i] + "= params.value" + i;
This way you can still use bind variables for the comparison values.