I have the following in a file called function.sql:
CREATE FUNCTION increment(i integer) RETURNS integer AS $$
BEGIN
RETURN i + 1;
END;
$$ LANGUAGE plpgsql;
And I'm trying to execute it against a local Postgres (9.3.4) database with Ant:
<target name = "create" >
<sql driver="org.postgresql.Driver" url="${db.url}" userid="${db.username}" password="${db.password}">
<transaction src="create/functions.sql"/>
<classpath>
<pathelement location="lib/postgresql-9.3-1101.jdbc41.jar"/>
</classpath>
</sql>
</target>
(I've created an example project on github here.)
When I execute ant, I get the error:
BUILD FAILED
/home/paul/jobhop-workspace/AntSqlPostgresFunctions/build.xml:17: org.postgresql.util.PSQLException: ERROR: syntax error at or near "$"
Position: 58
However if I execute the file directly:
psql -d <mydb> functions.sql
it creates the function just fine. I've tried the solutions suggested here and here, which sound like the same problem, but they didn't work for me.
The error message is because Ant's property expansion behaviour sees the $$ and replaces it with a single $ before it gets to PostgreSQL (see http://ant.apache.org/manual/properties.html, section $$ Expansion)
We can get around that in a few ways. Doubling up the dollar characters as recommended in the Ant manual works, but at the expense of making the file no longer work when passed to the psql client. I've had more success using $BODY$ or ($whatever-you-fancy$) instead of $$ to delimit the function body, or by setting the SQL task's expandproperties argument to false in the build.xml.
Once that's done we hit a second problem: the Ant task parses your file
to find statements which it feeds to JDBC. However, it's not very smart: it just
splits on ';' characters, even when they're in the middle of the body
of your function definition (see http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/SQLExec.java?view=markup#l724), so then we'll see an error about an unterminated string (i.e. the first half of the function body).
We can get round that by using a different delimiter and telling the SQL task about it via the delimiter and delimitertype arguments. The following patch to your project
gets it working for me:
diff --git a/build.xml b/build.xml
index 7d8a990..5dbbdc5 100644
--- a/build.xml
+++ b/build.xml
## -14,7 +14,7 ##
</target>
<target name = "create" >
- <sql driver="org.postgresql.Driver" url="${db.url}"
userid="${db.username}" password="${db.password}">
+ <sql driver="org.postgresql.Driver" url="${db.url}"
userid="${db.username}" password="${db.password}" delimiter="/* END_STATEMENT */" delimitertype="row" >
<transaction src="create/functions.sql"/>
diff --git a/create/functions.sql b/create/functions.sql
index 3238d3e..1a8518f 100644
--- a/create/functions.sql
+++ b/create/functions.sql
## -1,9 +1,9 ##
-CREATE FUNCTION increment(i integer) RETURNS integer AS $$
+CREATE FUNCTION increment(i integer) RETURNS integer AS $BODY$
BEGIN
RETURN i + 1;
END;
-$$ LANGUAGE plpgsql;
-
+$BODY$ LANGUAGE plpgsql;
+/* END_STATEMENT */
I initially put the statement delimiter in a comment of the form -- END_STATEMENT. That only gives the illusion of working, since, when parsing the SQL, the Ant task discards those comments before it goes looking for delimiters. Finding no delimiter it just passes the entire contents of the file to PostgreSQL as a single statement, which works in your simple case but won't be ideal as the list of functions gets longer.
Instead, I used C-style comments which are recognized by PostgreSQL, but not (currently) by the Ant task's parser. Of course, this could stop working if the Ant task's parser is changed.
If the functions.sql file doesn't need to work from psql then you can consider using anything you like as delimiter. It doesn't even have to be valid SQL syntax since the Ant task removes it before passing on the statements to PostgreSQL.
Related
I'm running Oracle 18.c on a Windows 10 platform.
I have a large DOS script which calls SQL Plus to run the first SQL script, passing a parameter to it.
The parameter is successfully passed to the first SQL script. In that SQL script I'm trying to append some text to the passed parameter so that it can call a second script using the "#" feature.
DOS Script test1.bat
#echo off
SET value1=2020_01_19_17_00_01
sqlplus my_username/my_password#my_TNS as sysdba #C:\Backups\test1.sql %value1%
SQL Script test1.sql
SET SERVEROUTPUT ON
variable param1 varchar2(512);
variable full_file_name varchar2(512);
BEGIN
:param1 := '&&1';
dbms_output.put_line('The value of the passed parameter is: ' || :param1);
:full_file_name := :param1 || '_f104.sql';
dbms_output.put_line('The new filename would be: ' || :full_file_name);
#:full_file_name;
END;
/
I'm having problems getting the value in :full_file_name to execute from the test1.sql script. If a variable were not involved I would simply use the line #2020_01_19_17_00_01_f104.sql
How do I go about getting the script file whose name is stored in :full_file_name to execute?
I found a way to do this. My sql script now looks like:
Define ffn104 = '&&1._f104.sql'
#&ffn104
exit
Note that in the Define statement I had to put a period after the passed variable &&1 to end its definition. Then I appended _f104.sql.
That solved the problem.
In my Informix 4GL program, I have an input field where the user can insert a URL and the feed is later being sent over to the web via a script.
How can I validate the URL at the time of input, to ensure that it's a live link? Can I make a call and see if I get back any errors?
I4GL checking the URL
There is no built-in function to do that (URLs didn't exist when I4GL was invented, amongst other things).
If you can devise a C method to do that, you can arrange to call that method through the C interface. You'll write the method in native C, and then write an I4GL-callable C interface function using the normal rules. When you build the program with I4GL c-code, you'll link the extra C functions too. If you build the program with I4GL-RDS (p-code), you'll need to build a custom runner with the extra function(s) exposed. All of this is standard technique for I4GL.
In general terms, the C interface code you'll need will look vaguely like this:
#include <fglsys.h>
// Standard interface for I4GL-callable C functions
extern int i4gl_validate_url(int nargs);
// Using obsolescent interface functions
int i4gl_validate_url(int nargs)
{
if (nargs != 1)
fgl_fatal(__FILE__, __LINE__, -1318);
char url[4096];
popstring(url, sizeof(url));
int r = validate_url(url); // Your C function
retint(r);
return 1;
}
You can and should check the manuals but that code, using the 'old style' function names, should compile correctly. The code can be called in I4GL like this:
DEFINE url CHAR(256)
DEFINE rc INTEGER
LET url = "http://www.google.com/"
LET rc = i4gl_validate_url(url)
IF rc != 0 THEN
ERROR "Invalid URL"
ELSE
MESSAGE "URL is OK"
END IF
Or along those general lines. Exactly what values you return depends on your decisions about how to return a status from validate_url(). If need so be, you can return multiple values from the interface function (e.g. error number and text of error message). Etc. This is about the simplest possible design for calling some C code to validate a URL from within an I4GL program.
Modern C interface functions
The function names in the interface library were all changed in the mid-00's, though the old names still exist as macros. The old names were:
popstring(char *buffer, int buflen)
retint(int retval)
fgl_fatal(const char *file, int line, int errnum)
You can find the revised documentation at IBM Informix 4GL v7.50.xC3: Publication library in PDF in the 4GL Reference Manual, and you need Appendix C "Using C with IBM Informix 4GL".
The new names start ibm_lib4gl_:
ibm_libi4gl_popMInt()
ibm_libi4gl_popString()
As to the error reporting function, there is one — it exists — but I don't have access to documentation for it any more. It'll be in the fglsys.h header. It takes an error number as one argument; there's the file name and a line number as the other arguments. And it will, presumably, be ibm_lib4gl_… and there'll be probably be Fatal or perhaps fatal (or maybe Err or err) in the rest of the name.
I4GL running a script that checks the URL
Wouldn't it be easier to write a shell script to get the status code? That might work if I can return the status code or any existing results back to the program into a variable? Can I do that?
Quite possibly. If you want the contents of the URL as a string, though, you'll might end up wanting to call C. It is certainly worth thinking about whether calling a shell script from within I4GL is doable. If so, it will be a lot simpler (RUN "script", IIRC, where the literal string would probably be replaced by a built-up string containing the command and the URL). I believe there are file I/O functions in I4GL now, too, so if you can get the script to write a file (trivial), you can read the data from the file without needing custom C. For a long time, you needed custom C to do that.
I just need to validate the URL before storing it into the database. I was thinking about:
#!/bin/bash
read -p "URL to check: " url
if curl --output /dev/null --silent --head --fail "$url"; then
printf '%s\n' "$url exist"
else
printf '%s\n' "$url does not exist"
fi
but I just need the output instead of /dev/null to be into a variable. I believe the only option is to dump the output into a temp file and read from there.
Instead of having I4GL run the code to validate the URL, have I4GL run a script to validate the URL. Use the exit status of the script and dump the output of curl into /dev/null.
FUNCTION check_url(url)
DEFINE url VARCHAR(255)
DEFINE command_line VARCHAR(255)
DEFINE exit_status INTEGER
LET command_line = "check_url ", url
RUN command_line RETURNING exit_status
RETURN exit_status
END FUNCTION {check_url}
Your calling code can analyze exit_status to see whether it worked. A value of 0 indicates success; non-zero indicates a problem of some sort, which can be deemed 'URL does not work'.
Make sure the check_url script (a) exits with status zero on success and non-zero on any sort of failure, and (b) doesn't write anything to standard output (or standard error) by default. The writing to standard error or output will screw up screen layouts, etc, and you do not want that. (You can obviously have options to the script that enable standard output, or you can invoke the script with options to suppress standard output and standard error, or redirect the outputs to /dev/null; however, when used by the I4GL program, it should be silent.)
Your 'script' (check_url) could be as simple as:
#!/bin/bash
exec curl --output /dev/null --silent --head --fail "${1:-http://www.example.com/"
This passes the first argument to curl, or the non-existent example.com URL if no argument is given, and replaces itself with curl, which generates a zero/non-zero exit status as required. You might add 2>/dev/null to the end of the command line to ensure that error messages are not seen. (Note that it will be hell debugging this if anything goes wrong; make sure you've got provision for debugging.)
The exec is a minor optimization; you could omit it with almost no difference in result. (I could devise a scheme that would probably spot the difference; it involves signalling the curl process, though — kill -9 9999 or similar, where the 9999 is the PID of the curl process — and isn't of practical significance.)
Given that the script is just one line of code that invokes another program, it would be possible to embed all that in the I4GL program. However, having an external shell script (or Perl script, or …) has merits of flexibility; you can edit it to log attempts, for example, without changing the I4GL code at all. One more file to distribute, but better flexibility — keep a separate script, even though it could all be embedded in the I4GL.
As Jonathan said "URLs didn't exist when I4GL was invented, amongst other things". What you will find is that the products that have grown to superceed Informix-4gl such as FourJs Genero will cater for new technologies and other things invented after I4GL.
Using FourJs Genero, the code below will do what you are after using the Informix 4gl syntax you are familiar with
IMPORT com
MAIN
-- Should succeed and display 1
DISPLAY validate_url("http://www.google.com")
DISPLAY validate_url("http://www.4js.com/online_documentation/fjs-fgl-manual-html/index.html#c_fgl_nf.html") -- link to some of the features added to I4GL by Genero
-- Should fail and display 0
DISPLAY validate_url("http://www.google.com/testing")
DISPLAY validate_url("http://www.google2.com")
END MAIN
FUNCTION validate_url(url)
DEFINE url STRING
DEFINE req com.HttpRequest
DEFINE resp com.HttpResponse
-- Returns TRUE if http request to a URL returns 200
TRY
LET req = com.HttpRequest.create(url)
CALL req.doRequest()
LET resp = req.getResponse()
IF resp.getStatusCode() = 200 THEN
RETURN TRUE
END IF
-- May want to handle other HTTP status codes
CATCH
-- May want to capture case if not connected to internet etc
END TRY
RETURN FALSE
END FUNCTION
I am running my own proxy objects which extend org.mozilla.javascript.ScriptableObject.
I also have my own functions which extend org.mozilla.javascript.Function.
My desire is to have any exceptions thrown here return the line no and if possible the column number where they occurred in the evaluated script. Is this possible? I only have access to the context and the scope.
Whenever an exception is thrown from a script, Rhino throws RhinoException which already has line and column number (and more). However when you execute the script you need to provide the line number that will be used by Rhino as the starting line number. The actual line number of where the exception/error occurred will be relative to this number. So something line this:
//-- Define a simple test script to test if things are working or not.
String testScript = "function simpleJavascriptFunction() {" +
" this line has syntax error." +
"}" +
"simpleJavascriptFunction();";
//-- Compile the test script.
Script compiledScript = Context.getCurrentContext().compileString(testScript, "My Test Script", 2, null);
//-- Execute the test script.
compiledScript.exec(Context.getCurrentContext(), anyJavascriptScope);
In the above code, the starting line number is set to 2 (third parameter of the call to compileString()). When this is executed, Rhino will throw a RhinoException that will have the lineNumber property set to the value '3' (the first line is treated as the second line b/c we passed 2).
Hope this helps.
My first question. Please bear with me!
I have an Ant task that need to get some input from the user before proceeding. I use the Input task to achieve this. The input message will contain Swedish characters (eg å, ä and ö) but I am unable to get Ant to output the message properly. I'm testing this using the command line on a machine running Windows 7 Pro English (but obviously using a Swedish keyboard). Example:
<input message="åäö"/>
will output:
[input] Õõ÷
The build.xml is saved in UTF-8 format. If I do 'chcp' on the command line I get "Active code page: 850".
The same result can be seen when doing an echo:
<echo message="åäö"/>
will output:
[echo] Õõ÷
But in the case of the echo task I'm able to do:
<echo encoding="850" message=åäö">
to get the expected output:
[echo] åäö
The input task does however not have an encoding attribute and I'd very much prefer to not have to define an encoding at all, especially not on a per-task level (since I can't tell for sure on what machine the Ant script will be run from).
PS I have additional problems with the received input if it contains åäö and I set the input as a property that is later used in a filter copy task, but I guess that's a whole other question
I can observe the issue on my Polish Windows.
<script language="javascript">
java.lang.System.out.println("default charset: "
+ java.nio.charset.Charset.defaultCharset());
</script>
reports default charset as "windows-1250" while the console operates at "iso-8859-2" (I guess so).
Looks like <input> task uses the default charset thinking it would match the console input. In case it does not, I managed to override the encoding this way:
set ANT_OPTS=-Dfile.encoding=iso-8859-2
ant
In your case I would try to force 850, as it looks like JRE defaults to something else.
This question helped me to find the property name.
It is also important where ant is run from. If I run it from my ide, jedit console plugin, I don't need to override encoding, because I configured it to operate in windows-1250. So it seems to be another workaround, using an IDE.
I want to use sqlplus within ruby. Dont want to use any gems[bec I cannot get it installed on our servers without much help from other teams ..etc] and want to keep it very minimal.
I am trying something as simple as this in my ruby script:
`rlwrap sqlplus user/pswd#host << EOF`
`set serveroutput on;`
`commit;` #ERROR1: sh: commit: not found
sql = "insert /*+ APPEND*/ INTO table(col1, col2) values (#{data[0]},#{data[1]});"
`#{sql}` #ERROR2: sh: Syntax error: "(" unexpected
Can anyone help me with ERROR1 and ERROR2 above
Basically for "commit: not found" I think its getting executed on shell rather than in sqlplus. However seems like "set serveroutput on" seems to execute fine !
For ERROR2, I am clueless. I also tried using escape slash for the "/" in the sql.
Thanks
The answer is, don't use SQL*Plus. Don't call a command-line utility from inside your script; between the ruby-oci8 gem and the ruby-plsql gem, you can do anything you could accomplish from within SQL*Plus.
The reason you get the errors is that you are sending each line to the shell individually. If your entire statement was wrapped in a single pair of backticks, it might work.
But if you really are unable to install the proper gems, put the commands in a temporary file and tell sqlplus to execute that, eg:
require 'tempfile'
file = Tempfile.open(['test', '.sql'])
file.puts "set serveroutput on;"
file.puts "commit;"
file.puts "insert /*+ APPEND*/ INTO table(col1, col2) values (#{data[0]},#{data[1]});"
file.puts "exit;" # needed or sqlplus will never return control to your script
file.close
output = `sqlplus user/pswd#host ##{file.path}`
file.unlink
You'll have to be very careful about:
Quoting values (if using oci8/dbi you could use bind variables)
Error handling. If using ruby libraries, errors would raise exceptions. Using sqlplus, you'll have to parse the output instead. Yuck!
So it can be done but I highly recommend you jump through whatever hoops are required to get oci8 (and maybe ruby-DBI) installed properly :)
ps are you sure you want to commit before the insert?