I am wondering if anyone could tell me any special meaning of the dot (.) in Informix regarding expressions and etc.
For example in stored procedures I see it used with integers, decimals and chars and one thing that is bugging me quite a lot is this:
if value = '.' then
//do something
end if
The above expression validates to true when value is of type numeric (5,1) and it is equal to 0.0
I have tried looking around and I can't find information on how a dot is treated but it seems " 0.0 = '.' " validates to true.
Can you show the data types and a working stored procedure that illustrates the problem?
There isn't supposed to be any special meaning to dot in that context. It is a string, and no numeric value should be equal to it; if the number is converted to a string, there will be either nothing (for NULL) or at least one digit, neither of which is the same as the string '.', and if the string '.' is converted to a number, that conversion should fail (arguably when the procedure is created, certainly at runtime).
One thing that puzzles me is that the syntax you are showing is not SPL syntax. SPL does not use 'end if', though I4GL does. Indeed, SPL (stored procedure language) only uses END in conjunction with a matching BEGIN.
It appears that my memory is failing and that I should not try reading manuals just before midnight.
It also appears that this code does what I would not expect...
+ set debug file to "/tmp/x1";
SET DEBUG FILE TO: Rows processed = 0
+ drop procedure so2139024();
DROP PROCEDURE: Rows processed = 0
+ create procedure so2139024() returning int as rv;
define value numeric(5,1);
define rv integer;
trace on;
let rv = 0;
let value = 0.0;
if value = '.' then
let rv = 1;
end if;
return rv;
end procedure;
CREATE PROCEDURE: Rows processed = 0
+ execute procedure so2139024();
1
EXECUTE PROCEDURE: Rows processed = 1
So, for some reason, the comparison is working; the value zero compares equal to dot. This was tested on MacOS X 10.6.2 with IBM Informix Dynamic Server 11.50.FC6 (and SQLCMD 86.04, built with CSDK 3.50.FC4, but running with 3.50.FC6).
The debug file contains:
trace on
expression:(= value, ".")
evaluates to t
let rv = 1
expression:rv
evaluates to 1
procedure so2139024 returns 1
iteration of cursory procedure so2139024
A priori, this should be reported via IBM/Informix Tech Support. I think it is most likely a bug of some sort, but I don't know how it is coming up with the answer. I will check through back-door channels too.
Back-door channels show that the likely problem is that the function deccvasc() in the ESQL/C library (also used internally by the server) is mishandling the string '.'.
The ESQL/C test code here shows that:
#include <stdio.h>
#include "dumpesql.h"
int main(void)
{
dec_t d;
int rc = deccvasc(".", 1, &d);
printf("rc = %d\n", rc);
dump_decimal(stdout, "Decimal", &d);
return(0);
}
The dump_decimal() function is non-standard, but prints information from the decimal structure. The output is:
rc = 0
DECIMAL: Decimal -- address 0x7FFF5FBFF090
E: -64, S = 1 (+), N = 0, M = value is ZERO
Consequently, the server is (mistakenly) accepting '.' as a valid representation of zero, rather than getting an error reported. For the time being, you will have to edit the stored procedure to make more sense - it is not clear what the test was supposed to achieve, but it clearly isn't written correctly. (That is not denying that there is also a bug in the server.) Please report this to IBM/Informix Technical Support.
Related
I'm confused because when I define a type from 1 to maxint in Pascal and I make the choice "0" which should return back to the repeat loop. Here is my code:
program Tanken;
type
tZahl = 1..maxint;
var
tag1 : tZahl;
wahl : tZahl;
liter,
p : real;
BEGIN
repeat
write ('Bitte Tag eingeben 1=Mon 2=Die 3=Mit 4=Don 5=Fre 6=Sam 7=Son: ');
readln (tag1);
writeln(tag1);
until tag1 <= 7;
....
end
This is how my constant, type and variable looks. Si I define tag1 as tZahl which should be from 1 to maxint but how ever when I run this at the first repeat loop when I type "0" it is accepted. I found this a bit confusing any ideas?
To force type range checking at runtime you need to explicitly tell the compiler to with most used compilers by adding {$R+} to the top of the program.
However this will only throw a runtime error, which is not the input validation that you want. You will really need to program input validation yourself. E.g. by reading a string, and then converting it to a number using the VAL() procedure and checking the code argument for errors.
This works:
SELECT TASKT_ID FROM DATA . TASKT WHERE TASK_WEB_IDENTIFIER = CAST ( HEXTORAW ( '0213725501A421D384233E5001' ) AS CHAR ( 26 ) ) ;
Since that work I put it into the procedure:
BEGIN
DECLARE GET_TASKT_ID_BY_TASK_WEB_IDENTIFIER_C1 CURSOR WITH RETURN FOR
SELECT TASKT_ID FROM DATA . TASKT WHERE TASK_WEB_IDENTIFIER = CAST ( HEXTORAW ( P_WEB_IDENTIFIER ) AS CHAR ( 26 ) ) ;
OPEN GET_TASKT_ID_BY_TASK_WEB_IDENTIFIER_C1 ;
END
The procedure has the one parameter P_WEB_IDENTIFIER which is a CHAR(26) for bit data with the CCSID 65535
However, when I now call it with the string like so:
call PROGRAM . GET_TASKT_ID_BY_TASK_WEB_IDENTIFIER ('0213725501A421D384233E5001');
I get back that the argument for VARBINARY_FUNCTION is invalid by length or data type.
Also, this works:
call PROGRAM . GET_TASKT_ID_BY_TASK_WEB_IDENTIFIER (CAST('0213725501A421D384233E5001' as char(26)));
What Can I do to make sure that string converts with only the string being passed?
What are you using to call the stored procedure?
What version and release of Db2 for i?
In the Run SQL Scripts component of IBM ACS or the older Access for Windows, string literals in your statements are treated as varchar.
Thus the CAST('0213725501A421D384233E5001' as char(26)) makes sense. What doesn't is the error message. Normally, you'd get a procedure not found error as the Db is looking for a procedure named PROGRAM.GET_TASKT_ID_BY_TASK_WEB_IDENTIFIER that takes a varchar parameter and the only thing that exists is a procedure that takes a char(26).
IBM's tools have gotten better at implicitly converting when needed. But I usually go with an explicit conversion when testing manually (as you've done here). Or I just make the parms varchar to start. And convert to character within the procedure if needed.
The char/varchar difference doesn't usually matter to the client code, as it can be specific in it's type definitions. It's only a factor for interactive tools like ACS that are executing dynamic statements.
I am trying to check the input length, to see if it's less than 7. It should show an error message, but the code below doesn't work. What's wrong with it?
CREATE OR REPLACE PROCEDURE prc_staffContact(IN_staffID IN CHAR, IN_staffContact IN VARCHAR) IS
v_staffName VARCHAR(50);
v_staffID CHAR(6);
v_staffContact VARCHAR(11);
BEGIN
SELECT s.staffName, s.staffID, s.staffContact
INTO v_staffName, v_staffID, v_staffContact
FROM staff s
WHERE staffID = IN_staffID;
IF (LENGTH(IN_staffContact) < 7 )
THEN
DBMS_OUTPUT.PUT_LINE('Error. Contact number at least 7 digits.');
ELSE
UPDATE staff
SET staffContact = IN_staffContact
WHERE staffID = IN_staffID;
DBMS_OUTPUT.PUT_LINE('=============================================================================');
DBMS_OUTPUT.PUT_LINE('The contact number of [ ' ||v_staffName || ' ] has been updated successfully.');
DBMS_OUTPUT.PUT_LINE('New contact number: [ ' ||v_staffContact || ' ].');
DBMS_OUTPUT.PUT_LINE('=============================================================================');
END IF;
END;
/
You said something that appears to be contradictory:
it shows [PL/SQL procedure successfully completed.] but doesn't checking even my input is less than 7 characters. and the data doesn't update also.
You described that you're doing as:
i save it under procedure1.sql and i start it in sql plus. that is my 1st call. after that i call the exec prc_staffContact('100001', '0000')
Together those suggest that when you say the data isn't updated, what you really mean is you don't get the contact number/new contact number message from the else branch, and I think you're assuming that means the update doesn't happen either, so it didn't execute either branch. But you must have gone into either the if or the else, by definition.
So if you didn't get either message, then you haven't done:
set serveroutput on
in SQL*Plus before calling exec. That setting is off by default, unless you have it turned on in your login.sql or glogin.sql, so you have to turn it on explicitly if you want to see dbms_output messages.
In this case, for the validation, (a) you probably want the select inside the elsef` too, partly because (b) if the passed values doesn't exist you'll get a no_data_found exception, and (c) you might want to consider throwing an exception if the length is less than 7 rather than (only) displaying a message. Someone else calling this might not have serverout on either, or might be using a different client that doesn't have that option.
You've also got v_staffID defined as char(6). Apart from wondering why that isn't a varchar2, the length you've given it means that if IN_staffID is 7 chars or more, the select into will get a 'character string buffer too small' error. I'd declare that as:
v_staffID staff.staffID%TYPE;
... to avoid issues like that, and the same for the other fields that relate to table columns.
And your 'success' message is showing the old contact number, not the new one. Not sure you need v_staffContact at all.
Look at your code carefully. May be your variable types not compatible or stored procedure parameters not compatible with other variables (or table columns, column's types). I tested your code in my database, all success. but may be error not data found in your select statement, or may be buffer too small error. Hope this help you. thanks
I am trying to replicate the IF function from MySQL into PostgreSQL.
The syntax of IF function is IF(condition, return_if_true, return_if_false)
I created following formula:
CREATE OR REPLACE FUNCTION if(boolean, anyelement, anyelement)
RETURNS anyelement AS $$
BEGIN
CASE WHEN ($1) THEN
RETURN ($2);
ELSE
RETURN ($3);
END CASE;
EXCEPTION WHEN division_by_zero THEN
RETURN ($3);
END;
$$ LANGUAGE plpgsql;
It works well with most of the things like if(2>1, 2, 1) but it raises an error for:
if( 5/0 > 0, 5, 0)
fatal error division by zero.
In my program I can't check the denominator as the condition is provided by user.
Is there any way around? Maybe if we can replace first parameter from boolean to something else, as in that case the function will work as it will raise and return the exception.
PostgreSQL is following the standard
This behaviour appears to be specified by the SQL standard. This is the first time I've seen a case where it's a real problem, though; you usually just use a CASE expression or a PL/PgSQL BEGIN ... EXCEPTION block to handle it.
MySQL's default behaviour is dangerous and wrong. It only works that way to support older code that relies on this behaviour. It has been fixed in newer versions when strict mode is active (which it absolutely always should be) but unfortunately has not yet been made the default. When using MySQL, always enable STRICT_TRANS_TABLES or STRICT_ALL_TABLES.
ANSI-standard zero division is a pain sometimes, but it'll also protect against mistakes causing data loss.
SQL injection warning, consider re-design
If you're executing expressions from the user then you quite likely have SQL injection problems. Depending on your security requirements you might be able to live with that, but it's pretty bad if you don't totally trust all your users. Remember, your users could be tricked into entering the malicious code from elsewhere.
Consider re-designing to expose an expression builder to the user and use a query builder to create the SQL from the user expressions. This would be much more complicated, but secure.
If you can't do that, see if you can parse the expressions the user enters into an abstract syntax, validate it before execution, and then produce new SQL expressions based on the parsed expression. That way you can at least limit what they can write, so they don't slip any nasties into the expression. You can also rewrite the expression to add things like checks for zero division. Finding (or writing) parsers for algebraic expressions isn't likely to be hard, but it'll depend on what kinds of expressions you need to let users write.
At minimum, the app needs to be using a role ("user") that has only SELECT privileges on the tables, is not a superuser, and does not own the tables. That'll minimise the harm any SQL injection will cause.
CASE won't solve this problem as written
In any case, because you currently don't validate and can't inspect the expression from the user, you can't use the SQL-standard CASE statement to solve this. For if( a/b > 0, a, b) you'd usually write something like:
CASE
WHEN b = 0 THEN b
ELSE CASE
WHEN a/b=0 THEN a
ELSE b
END
END
This explicitly handles the zero denominator case, but is only possible when you can break the expression up.
Ugly workaround #1
An alternative solution would be to get Pg to return a placeholder instead of raising an exception for division by zero by defining a replacement division operator or function. This will only solve the divide-by-zero case, not others.
I wanted to return 'NaN' as that's the logical result. Unfortunately, 'NaN' is greater than numbers not less then, and you want a less-than or false-like result.
regress=# SELECT NUMERIC 'NaN' > 0;
?column?
----------
t
(1 row)
This means we have to use the icky hack of returning NULL instead:
CREATE OR REPLACE FUNCTION div_null_on_zero(numeric,numeric) returns numeric AS $$
VALUES (CASE WHEN $2 = 0 THEN NULL ELSE $1/$2 END)
$$ LANGUAGE 'SQL' IMMUTABLE;
CREATE OPERATOR #/# (
PROCEDURE = div_null_on_zero(numeric,numeric),
LEFTARG = numeric,
RIGHTARG = numeric
);
with usage:
regress=# SELECT 5 #/# 0, 5 #/# 0>0, CASE WHEN 5 #/# 0 > 0 THEN 5 ELSE 0 END;
?column? | ?column? | case
----------+----------+------
| | 0
(1 row)
Your app can rewrite '/' in incoming expressions into #/# or whatever operator name you choose pretty easily.
There's one pretty critical problem with this approach, and that's that #/# will have different precedence to / so expressions without explicit parentheses may not be evaluated as you expect. You might be able to get around this by creating a new schema, defining an operator named / in that schema that does your null-on-error trick, and then adding that schema to your search_path before executing user expressions. It's probably a bad idea, though.
Ugly workaround #2
Since you can't inspect the denominator, all I can think of is to wrap the whole thing in a DO block (Pg 9.0+) or PL/PgSQL function and catch any exceptions from the evaluation of the expression.
Erwin's answer provides a better example of this than I did, so I've removed this. In any case, this is an awful and dangerous thing to do, do not do it. Your app needs to be fixed.
With a boolean argument, a division by zero will always throw an exception (and that's a good thing), before your function is even called. There is nothing you can do about it. It's already happened.
CREATE OR REPLACE FUNCTION if(boolean, anyelement, anyelement)
RETURNS anyelement LANGUAGE SQL AS
$func$
SELECT CASE WHEN $1 THEN $2 ELSE $3 END
$func$;
I would strongly advise against a function named if to begin with. IF is a keyword in PL/pgSQL. If you use user defined functions written in PL/pgSQL this will be very confusing.
Just use the standard SQL expression CASE directly.
The alternative would be to take a text argument and evaluate it with dynamic SQL.
Proof of concept
What you ask for would work like this:
CREATE OR REPLACE FUNCTION f_if(_expr text
, _true anyelement
, _else anyelement
, OUT result anyelement)
RETURNS anyelement LANGUAGE plpgsql AS
$func$
BEGIN
EXECUTE '
SELECT CASE WHEN (' || _expr || ') THEN $1 ELSE $2 END' -- !! dangerous !!
USING _true, _else
INTO result;
EXCEPTION WHEN division_by_zero THEN
result := _else;
-- possibly catch more types of exceptions ...
END
$func$;
Test:
SELECT f_if('TRUE' , 1, 2) --> 1
,f_if('FALSE' , 1, 2) --> 2
,f_if('NULL' , 1, 2) --> 2
,f_if('1/0 > 0', 1, 2); --> 2
This is a big security hazard in the hands of untrusted users. Read #Craig's answer about making this more secure.
However, I fail to see how it can be made bulletproof and would never use it.
Hi i executed the following stored procedure in .net web application. Then run the application. I got this error
" Divide By zero error "
Stored procedure:
CREATE procedure Details(#Stringtext varchar(8000),#SearchStringtext varchar(100))
as begin
SELECT ({fn LENGTH(#Stringtext)}-
{fn LENGTH({fn REPLACE(#Stringtext, #SearchStringtext, '')})})/
{ fn LENGTH(#SearchStringtext)}AS String_Count
end
Evidently:
{ fn LENGTH(#SearchStringtext)}
... is evaluating to zero.
The length of the SearchStringText is Zero and hence there is divide by zero error.
Make sure that when the function is called, the string is of non-zero length.
Alternatively check for length before doing the select
{ fn LENGTH(#SearchStringtext)}
is evaluating to 0.
However, why is this a stored procedure? You are not using any db feature? Unless this is a simplified problem , all this (length, replace etc) could be done in your .net application
If SearchStringtext is empty, the length of it becomes 0. Thus the stored procedure tries to divide by zero (which is an undefined thing to do).
In other words the following part becomes zero:
{ fn LENGTH(#SearchStringtext)}
You might want to add some logic (if statement perhaps) to prevent the division to happen if the SearchStringtext is empty.
The only division operation in this procedure, has fn LENGTH(#SearchStringtext) as the divisor.
Hence it seems that length of **SearchStringtext** is evaluating to zero. It might be possible that you are trying to search an Empty string.
Please check and then elaborate the question details, along with the DB platform.
It seems that the length of SearchStringtext is 0 -- so the procedure tries to divide by zero.