How to make ELSE associate with farthest IF in yacc? - yacc

I now know how to make ELSE associate with the nearest IF:
%nonassoc IFX
%nonassoc ELSE
| IF stmt %prec IFX
| IF stmt ELSE stmt
But how can I make ELSE associate with farthest IF?
I tried to switch the order of the two %nonassoc above,but after that if seems to be failing to recognizing any valid statement in th format of IF stmt ELSE stmt.
Why ?
How to do it correctly?

(Untested) You need to force bison to generate a parser which reduces if statements if there is an ambiguity between multiple ifs. According to http://www.delorie.com/gnu/docs/bison/bison_85.html this can be achieved if the IF stmt rule has a higher precedence then the ELSE token. So
%nonassoc ELSE
%nonassoc IFX
| IF stmt %prec IFX
| IF stmt ELSE stmt
should do the trick.

Related

Binding data to a variable in a CASE WHEN statement

I am writing an oracle PL/SQL compound trigger. In the code I'm querying a single value and putting it into a variable.
From there I check whether the variable is null or not. If it is, I need to assign 0 to another variable, if it isn't I assign 1.
I think this is possible with a CASE WHEN statement.
An example is as follows
//line 23 ↓
SELECT contract_end INTO contractEnd FROM contract WHERE contract_id = :new.expense_job;
//line 25 ↓
SELECT CASE WHEN contractEnd IS NOT NULL THEN
contractIDCollection(iterator).ended := 1
ELSE
contractIDCollection(iterator).ended := 0
END
FROM dual;
However, when I do this the compiler throws an error and says that I have not finished the statement.
Is this the correct way to go about doing this?
contractIDCollection is a record with parameters, the definition code is working properly
LINE/COL ERROR
-------- -----------------------------------------------------------------
23/5 PL/SQL: SQL Statement ignored
24/44 PL/SQL: ORA-00905: missing keyword
Do not try to switch from the PL/SQL scope to the SQL scope (with a SELECT statement) to try to assign the variable; just do it all in the PL/SQL scope:
IF contractEnd IS NOT NULL THEN
contractIDCollection(iterator).ended := 1;
ELSE
contractIDCollection(iterator).ended := 0;
END IF;
If you did want to incur the overheads of context-switching (don't, as you do not need to and it is likely to be slower) then you can use:
SELECT CASE WHEN contractEnd IS NOT NULL THEN 1 ELSE 0 END
INTO contractIDCollection(iterator).ended
FROM dual;
or do it all on the line before:
SELECT contract_end,
CASE WHEN contract_end IS NOT NULL THEN 1 ELSE 0 END
INTO contractEnd,
contractIDCollection(iterator).ended
FROM contract
WHERE contract_id = :new.expense_job;

Postgresql, How to escape single quote in dynamic json when it was concatenated into string?

Lets say I have function that triggers when table is being updated. Then it tries to send this data as json into remote database via dblink:
statement := 'INSERT INTO mytable(my_data) VALUES (''' || my_json || ''')';
PERFORM dblink('my connection data', statement);
my_json is formed by json_build_object method with some dynamic data. When some of this json fields values contains single quote, this function starts throw syntax errors.
I know that I need to use double single quotes, but I can't because data is dynamic.
For example if my json is like this:
{ "a": "It's a test" }
It throws:
Syntax error at s
You can use quote_literal() to add the 2nd apostrophe:
with src(a) as (select '{ "a": "It''s a test" }')
select a, quote_literal(a) from src;
a | quote_literal
-------------------------+----------------------------
{ "a": "It's a test" } | '{ "a": "It''s a test" }'
Use:
statement := format('INSERT INTO mytable(my_data) VALUES (%L)', myjson);
Based on your comments, I would point that:
there must not be single quotes around %L.
the fact that the contents represent a json value does not change anything to how it should be quoted. Any literal to inject into that statement would be treated the same.
Another way using quote_literal() and not using format() would be:
statement := 'INSERT INTO mytable(my_data) VALUES (' || quote_literal(myjson) || ')';
Again there are no single quotes to add around quote_literal(myjson). It's the responsibility of quote_literal to add these quotes.
I'm going to answer to my own question.
The way I was able to solve this problem I have added some fixes before forming my statement:
my_json := json_build_object(...);
my_json := REPLACE(payload, '''', '''''');
Only after that I have formed my statement for my dblink:
statement := 'INSERT INTO mytable(my_data) VALUES (''' || my_json || ''')';
PERFORM dblink('my connection data', statement);

ANTLR is taking the wrong branch

I have this very simple grammar:
grammar LispExp;
expression : LITERAL #LiteralExp
| '(' '-' expression ')' #UnaryMinusExp
| '(' OP expression expression ')' #OpExp
| '(' 'if' expression expression expression ')' #IfExp;
OP : '+' | '-' | '*' | '/' | '==' | '<';
LITERAL : '0'|('1'..'9')('0'..'9')*;
WS : ('\t' | '\n' | '\r' | ' ') -> skip;
It should be able to parse a "lisp-like" expression, but when I try to parse this:
(+ (+ 5 (* 7 (/ 5 (- 2 (- 9) ) ) ) ) 8)
ANTLR fails to recognize the last unary minus, and generates the following (with antlr v4) :
(expression ( + (expression ( + (expression 5) (expression ( * (expression 7) (expression ( / (expression 5) (expression ( - (expression 2))) ( -) 9 )) expression ))
So, how can I make ANTLR understand the priority of unary minus over binary expression?
You are using a combined grammar LispExp, as opposed to separate lexer grammar LispExpLexer and parser grammar LispExpParser. When working with combined grammars, if you use a string literal in a parser rule the code generator will create anonymous tokens according to those string literals, and silently override the lexer.
In this case, your expression rule includes the string literal '-'. All instances of - in your input will be assigned this token type, which means they will not ever have the token type OP. Your input contains a subexpression (- 2 (- 9) ) which can only be parsed if the first - is an OP token, so according to the parser you have a syntax error in your input.
If you update your code to use separate lexer and parser grammars, any attempt to use a string literal in the parser grammar which is not defined in the lexer grammar will produce an error when you attempt to generate your lexer and parser.

Dangling "else" Resolution in T-SQL

So I've been tasked with converting some T-SQL code to C code. Whoever wrote the code I'm converting indulged in little to no code etiquette. I know this because of the complete lack of commenting, lack of indentation, and lack of begin/end blocks except where absolutely syntactically necessary (and a few thrown in arbitrarily for good measure).
This raises a few problems. The code I'm converting is based on the Metaphone algorithm. I say "based on" because it has quite a few... "undocumented improvements" that make it differ from the official implementation. Because of this, I can't just go grab some Metaphone implementation, because then it wouldn't actually be a "correct" translation.
So here's the root of the issue(s):
if #str1='d'
if substring(#str,#cnt,3) in ('dge','dgy','dgi')
set #Result=#Result + 'j'
else
set #Result=#Result + 't'
Because of how Metaphone works, I'm pretty sure they meant:
if #str1='d'
if substring(#str,#cnt,3) in ('dge','dgy','dgi')
set #Result=#Result + 'j'
else
set #Result=#Result + 't'
But I'm not sure if it's actually being interpreted as:
if #str1='d'
if substring(#str,#cnt,3) in ('dge','dgy','dgi')
set #Result=#Result + 'j'
else
set #Result=#Result + 't'
This little snippet isn't too big of a deal, but just after it, there's a section with five "if" statements and only one "else" statement and with no begin/end blocks to explicitly arrange them. None of this would be a big deal if I could actually run the code to to test and see, but sadly, I don't have an environment to test it and nor do I have any output from previous usage of the code.
tl;dr: Do any of you T-SQL gurus out there know which of the above two statements it'll be interpreted as and what's the rule of them with shift/reduce conflicts in T-SQL? (Attach to first "if" statement, attach to last "if" statement, pick one at random?)
EDIT: Here's another fun one a few lines down.
if #str1='t'
if substring(#str,#cnt,3) in ('tia','tio')
set #Result=#Result + 'x'
else
if #str2='th'
set #Result=#Result + '0'
else
if substring(#str,#cnt,3) <> 'tch'
set #Result=#Result + 't'
EDIT2: Ok, if I'm reading these answers correctly, that means the above is actually
if #str1='t'
if substring(#str,#cnt,3) in ('tia','tio')
set #Result=#Result + 'x'
else
if #str2='th'
set #Result=#Result + '0'
else
if substring(#str,#cnt,3) <> 'tch'
set #Result=#Result + 't'
Here is something that should help you out
DECLARE #testvar INT;
DECLARE #testvar2 INT;
SET #testvar = 1;
SET #testvar2 = 1;
IF #testvar = 1
IF #testvar2 = 1
SELECT 'Got to 1';
ELSE
SELECT 'Got to 2';
If testvar and testvar2 are both 1, it outputs "Got to 1".
If testvar=1 and testvar2=2, it outputs "Got to 2".
If testvar=2, there is no output. So the else is getting paired off against the nearest if
Your assumption is correct.
IF () IF () X ELSE Y is equivilent to IF () BEGIN IF () X ELSE Y END
The ELSE keyword looks back to the most recent IF statement in the same scope. So to get what you think was intended, you need to add BEGIN and END statements...
IF (#str1='d')
BEGIN
IF (substring(#str,#cnt,3) in ('dge','dgy','dgi'))
SET #Result=#Result + 'j'
END
ELSE
SET #Result=#Result + 't'

How can I determine if a string is numeric in SQL?

In a SQL query on Oracle 10g, I need to determine whether a string is numeric or not. How can I do this?
You can use REGEXP_LIKE:
SELECT 1 FROM DUAL
WHERE REGEXP_LIKE('23.9', '^\d+(\.\d+)?$', '')
You ca try this:
SELECT LENGTH(TRIM(TRANSLATE(string1, ' +-.0123456789', ' '))) FROM DUAL
where string1 is what you're evaluating. It will return null if numeric. Look here for further clarification
I don't have access to a 10G instance for testing, but this works in 9i:
CREATE OR REPLACE FUNCTION is_numeric (p_val VARCHAR2)
RETURN NUMBER
IS
v_val NUMBER;
BEGIN
BEGIN
IF p_val IS NULL OR TRIM (p_val) = ''
THEN
RETURN 0;
END IF;
SELECT TO_NUMBER (p_val)
INTO v_val
FROM DUAL;
RETURN 1;
EXCEPTION
WHEN OTHERS
THEN
RETURN 0;
END;
END;
SELECT is_numeric ('333.5') is_numeric
FROM DUAL;
I have assumed you want nulls/empties treated as FALSE.
As pointed out by Tom Kyte in http://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:7466996200346537833, if you're using the built-in TO_NUMBER in a user defined function, you may need a bit of extra trickery to make it work.
FUNCTION is_number(x IN VARCHAR2)
RETURN NUMBER
IS
PROCEDURE check_number (y IN NUMBER)
IS
BEGIN
NULL;
END;
BEGIN
PRAGMA INLINE(check_number, 'No');
check_number(TO_NUMBER(x);
RETURN 1;
EXCEPTION
WHEN INVALID_NUMBER
THEN RETURN 0;
END is_number;
The problem is that the optimizing compiler may recognize that the result of the TO_NUMBER is not used anywhere and optimize it away.
Says Tom (his example was about dates rather then numbers):
the disabling of function inlining will make it do the call to
check_date HAS to be made as a function call - making it so that the
DATE has to be pushed onto the call stack. There is no chance for the
optimizing compiler to remove the call to to_date in this case. If the
call to to_date needed for the call to check_date fails for any
reason, we know that the string input was not convertible by that date
format.
Here is a method to determine numeric that can be part of a simple query, without creating a function. Accounts for embedded spaces, +- not the first character, or a second decimal point.
var v_test varchar2(20);
EXEC :v_test := ' -24.9 ';
select
(case when trim(:v_test) is null then 'N' ELSE -- only banks, or null
(case when instr(trim(:v_test),'+',2,1) > 0 then 'N' ELSE -- + sign not first char
(case when instr(trim(:v_test),'-',2,1) > 0 then 'N' ELSE -- - sign not first char
(case when instr(trim(:v_test),' ',1,1) > 0 then 'N' ELSE -- internal spaces
(case when instr(trim(:v_test),'.',1,2) > 0 then 'N' ELSE -- second decimal point
(case when LENGTH(TRIM(TRANSLATE(:v_test, ' +-.0123456789',' '))) is not null then 'N' ELSE -- only valid numeric charcters.
'Y'
END)END)END)END)END)END) as is_numeric
from dual;
I found that the solution
LENGTH(TRIM(TRANSLATE(string1, ' +-.0123456789', ' '))) is null
allows embedded blanks ... it accepts "123 45 6789" which for my purpose is not a number.
Another level of trim/translate corrects this. The following will detect a string field containing consecutive digits with leading or trailing blanks such that to_number(trim(string1)) will not fail
LENGTH(TRIM(TRANSLATE(translate(trim(string1),' ','X'), '0123456789', ' '))) is null
For integers you can use the below. The first translate changes spaces to be a character and the second changes numbers to be spaces. The Trim will then return null if only numbers exist.
TRIM(TRANSLATE(TRANSLATE(TRIM('1 2 3d 4'), ' ','#'),'0123456789',' ')) is null