PostgreSQL/JPA - Import functions in import.sql file? - postgresql

I'm trying to define some PostgreSQL functions and triggers in my JPA import.sql file. I'm using Hibernate 5.x as my underlying JPA provider. Since my import.sql file has commands that are multiple lines, I've got this property set in my persistence.xml file:
<property name="hibernate.hbm2ddl.import_files_sql_extractor" value="org.hibernate.tool.hbm2ddl.MultipleLinesSqlCommandExtractor" />
From there, I'm trying to define my functions in import.sql. They look something like this:
DROP FUNCTION IF EXISTS update_total_feedback_count() CASCADE;
CREATE FUNCTION update_total_feedback_count() RETURNS TRIGGER AS
$$
DECLARE
application_version_id BIGINT := 0;
app_version_metadata_id BIGINT:= 0;
application_id BIGINT := 0;
app_metadata_id BIGINT := 0;
BEGIN
IF (TG_OP = 'INSERT') THEN
SELECT INTO application_version_id tbl_application_version.id
FROM tbl_application_version
INNER JOIN tbl_feedback ON tbl_feedback.application_version_id = tbl_application_version.id
WHERE tbl_feedback.id = NEW.id;
SELECT INTO app_version_metadata_id tbl_application_version.application_version_metadata_id
FROM tbl_application_version
WHERE id = application_version_id;
SELECT INTO app_metadata_id registered_application_metadata_id
FROM tbl_registered_application
INNER JOIN tbl_application_version ON tbl_application_version.registered_application_id = tbl_registered_application.id
WHERE tbl_application_version.id = application_version_id;
UPDATE tbl_registered_application_metadata SET feedbackcount = (feedbackcount + 1), lastfeedbackdate = NEW.createddate WHERE id = app_metadata_id;
UPDATE tbl_application_version_metadata SET feedbackcount = (feedbackcount + 1), lastfeedbackdate = NEW.createddate WHERE id = app_version_metadata_id;
RETURN NEW;
ELSIF (TG_OP = 'DELETE') THEN
-- IMPLEMENT THIS TRIGGER
RETURN NULL;
END IF;
END;
$$
LANGUAGE 'plpgsql';
ALTER FUNCTION update_total_feedback_count() OWNER TO feedback_tracker;
However, when I deploy my WAR file, I get an error saying something like this:
Unterminated dollar quote started at position 65 in SQL CREATE
FUNCTION my_function() RETURNS TRIGGER AS $$
So, clearly it's dying on the $$ in my function declaration. Is there a way around this? Should I be declaring my function/trigger differently? Is there a property I can set in my persistence.xml file that will get around this?

the problem with hibernate's default SqlStatementParser implementation, which is used in multiline sql command extractor.
if you look at grammar definition hibernate-core/src/main/antlr/sql-stmt.g there is definition of Statement End:
STMT_END
: ';' ( '\t' | ' ' | '\r' | '\n' )*
;
NOT_STMT_END
: ~( ';' )
;
This tells that statement end is semicolon symbol followed by "Space" "tab" "carret return" or "new line" symbols.
THUS: DEFAULT IMPLEMENTATION IN HIBERNATE DOESN'T SUPPORT DOLLAR QUOTING.
If you don't want to implement custom hibernate's parser you can rewrite all functions without dollar quoting, using simple ' quoting. But you will need to carefully escape ' chars.
UPDATE: you can create your custom ImportSqlCommandExtractor. For example: separate your commands with --****** (6 star symbols in comment, just to make your file proper SQL file, but with custom command separation in comments, or choose any insane combination, which you like) and then split those in simple implementation
public class ImportSqlCE implements ImportSqlCommandExtractor {
private static final Logger log = LoggerFactory.getLogger(ImportSqlCE.class);
#Override
public String[] extractCommands(Reader reader) {
try {
String allCommands = IOUtils.toString(reader);
return allCommands.split("--******");
} catch (IOException e) {
log.error("error reading import commands", e);
log.info("sengind default empty command set");
return new String[0];
}
}
}
and then configure hibernate to use it <property name="hibernate.hbm2ddl.import_files_sql_extractor" value="example.ImportSqlCE" />
with this your import.sql will support dollar quoting (i.e. it will simply ignore any sql awareness of what is happening.)

Related

How to check for custom Postgres exceptions raised via RAISE EXCEPTION in Golang?

I'm using Postgres with Golang via pgx
I've a trigger function something like the following:
CREATE OR REPLACE FUNCTION foo()
RETURNS TRIGGER AS
$$
BEGIN
IF (bar = 'baz') THEN
-- something
ELSE
RAISE EXCEPTION 'oops error';
END IF;
RETURN NEW;
END;
$$
LANGUAGE plpgsql;
How do I check for oops error in Go code?
The way I'm doing it now is:
errOops := errors.New("ERROR: oops error (SQLSTATE P0001)")
err := myDBFunc()
if errors.Is(err, errOops) {
}
But I wonder if there's a better way other than relying on the hardcoded message.
Should have read the Wiki: Error Handling in pgx
So I can do:
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) && pgErr.Code == "P0001" {
}
or something similar.
You can find that information in appendix A of the documentation: it will be a raise_exception, and the SQLSTATE is P0001.

Can SQL Server return an informational message to .NET System.Data.SqlClient program in contexts that do not involve any error

Is it possible with a .NET client program, communicating with back-end SQL Server using the System.Data.SqlClient library, for the server to send an informational message to the client program in contexts that do not involve any error and for the client program to obtain the message that was sent?
create proc NewFoo
#value text,
#userid text
as
begin
insert foo
(name, createdby) values (#value, #userid);
declare #recordcount int
select #recordcount = count(*) from foo where createdby= #userid;
if #recordcount = 100
begin
-- let user know they've created their 100th record. Woo-hoo!!
end
end
Here is an additional pseudo-code example, closer to the actual use case; note the differing severity:
if #value > #maxallowed
begin
if #userlevel = 'manager'
raiserror('Exceeded max. Intentional?',10,1)
else
raiserror('Exceeds max. Your edit was not saved',11,1)
end
P.S. Without using an OUT parameter or RETURN value parameter in the client-side command object.
P.P.S. I am using the SqlDataAdapter.Update method and some of the code uses a DataReader on the SqlCommand object.
You can use PRINT in your stored procedure, and to capture it in the code by subscribing to the InfoMessage event in the SqlConnection object.
In your sql just put:
PRINT 'Exceeded max. Intentional'
and in your code grab it like this:
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
string msg = null;
connection.InfoMessage += (sender, e) => { msg = e.Message; };
// execute the procedure
// check msg
}

function sign(json, unknown) does not exist

I'm following the tutorial on https://postgrest.org/en/v5.0/auth.html#jwt-from-sql
I created this function:
create or replace function
login(email text, pass text) returns basic_auth.jwt_token
language plpgsql
as $$
declare
_role name;
result basic_auth.jwt_token;
begin
-- check email and password
select basic_auth.user_role(email, pass) into _role;
if _role is null then
raise invalid_password using message = 'invalid user or password';
end if;
select sign(
row_to_json(r), 'reallyreallyreallyreallyverysafe'
) as token
from (
select _role as role, login.email as email,
extract(epoch from now())::integer + 60*60 as exp
) r
into result;
return result;
end;
$$;
When I try to do the login request, I get the following error message:
{
"hint": "No function matches the given name and argument types. You might need to add explicit type casts.",
"details": null,
"code": "42883",
"message": "function public.sign(json, unknown) does not exist" }
I don't see the sign function anywhere in the tutorial, does anyone know what is going on here?
The sign function is created when you install pgjwt.
Also, make sure that you install the extension on a schema that is included in your search_path(a list of imported schemas). This can be specified by doing CREATE EXTENSION pgjwt WITH SCHEMA public.
PostgREST adds the public schema by default on the search_path, so the extension should work with no additional config.

Ormlite exception with single quote in text using LIKE query

I've got what seems to be a bug. I can add an entry to the database which has a single quote in the text.
However, when I search using QueryBuilder, for any text LIKE xyz, if xyz has a single quote in it, I get MySQL complaining about malformed SQL.
Other than parsing all strings myself, is there some method in Ormlite I can call to "santize" my strings?
Sample code is below:
public boolean isDuplicate () {
QueryBuilder<Company, Long> qb = getDao().queryBuilder() ;
Where<Company, Long> where = qb.where() ;
try {
if (Strings.isValid(name))
where.like("name", name) ;
if (Strings.isValid(regNo)) {
if (Strings.isValid(name))
where.or() ;
where.eq("regNo", regNo) ;
List<Company> res = where.query() ;
if (res != null && res.size() > 0)
return true ;
else
return false ;
}
} catch (SQLException e) {
GlobalConfig.log(e, true);
}
return false ;
}
This creates a SQL error if the company name has a single quote in it:
Creating default entries for Well Don't Delete Me 2 Please Pte Ltd.
[12-07-2013 13:45:42] You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 't Delete Me 2 Please Pte Ltd.' OR regNo = 'delete' )' at line 1
Any suggestions welcome.
Ok, I figured this out- I need to use the SelectArg method of querying the database.
So it now looks like this:
...
if (Strings.isValid(name))
{
SelectArg arg = new SelectArg () ; // Added
arg.setValue (name) ; / Added
where.like("name", arg) ; // Changed
}
if (Strings.isValid(regNo))
{
if (Strings.isValid(name))
where.or() ;
SelectArg arg = new SelectArg () ; // Added
arg.setValue (regNo) ; // Added
where.eq("regNo", arg) ; // Changed
List<Company> res = where.query() ;
if (res != null && res.size() > 0)
return true ;
else
return false ;
...
What I've learned is this: you must use one SelectArg PER item.
Now my question to Gray is why not make this a default behaviour? When I insert or update it seems to happen automatically, and to get the problem I found solved I have to add more
lines of code that could easily be part of the internal query handling.
I understand his concerns in this post but I agree with Dale. Maybe a halfway house is to have a flag to say which way Ormlite should treat parameters to the query methods.
I admire the flexibility and simplicity of the "programmable" SQL in Ormlite and in almost every case, Ormlite related code is concise, easy to follow and logical. This is one rare case where I feel it is more verbose than necessary, for no net benefit. Just my opinion.

Is it possible to accept custom command line parameters with Inno Setup

I am preparing an installer with Inno Setup. But I'd like to add an additional custom (none of the available parameters) command line parameters and would like to get the value of the parameter, like:
setup.exe /do something
Check if /do is given, then get the value of something. Is it possible? How can I do this?
With InnoSetup 5.5.5 (and perhaps other versions), just pass whatever you want as a parameter, prefixed by a /
c:\> myAppInstaller.exe /foo=wiggle
and in your myApp.iss:
[Setup]
AppName = {param:foo|waggle}
The |waggle provides a default value if no parameter matches. Inno setup is not case sensitive. This is a particularly nice way to handle command line options: They just spring into existence. I wish there was as slick a way to let users know what command line parameters the installer cares about.
BTW, this makes both #knguyen's and #steve-dunn's answers somewhat redundant. The utility functions do exactly what the built-in {param: } syntax does.
Further to #DanLocks' answer, the {param:*ParamName|DefaultValue*} constant is documented near the bottom of the Constants page:
http://www.jrsoftware.org/ishelp/index.php?topic=consts
I found it quite handy for optionally suppressing the license page. Here is all I needed to add (using Inno Setup 5.5.6(a)):
[code]
{ If there is a command-line parameter "skiplicense=true", don't display license page }
function ShouldSkipPage(PageID: Integer): Boolean;
begin
Result := False
if PageId = wpLicense then
if ExpandConstant('{param:skiplicense|false}') = 'true' then
Result := True;
end;
Inno Setup directly supports switches with syntax /Name=Value using {param} constant.
You can use the constant directly in sections, though this use is quite limited.
An example:
[Registry]
Root: HKCU; Subkey: "Software\My Company\My Program\Settings"; ValueType: string; \
ValueName: "Mode"; ValueData: "{param:Mode|DefaultMode}"
You will more likely want to use switches in Pascal Script.
If your switch has the syntax /Name=Value, the easiest way to read its value is using ExpandConstant function.
For example:
if ExpandConstant('{param:Mode|DefaultMode}') = 'DefaultMode' then
begin
Log('Installing for default mode');
end
else
begin
Log('Installing for different mode');
end;
If you want to use a switch value to toggle entries in sections, you can use Check parameter and a auxiliary function, like:
[Files]
Source: "Client.exe"; DestDir: "{app}"; Check: SwitchHasValue('Mode', 'Client')
Source: "Server.exe"; DestDir: "{app}"; Check: SwitchHasValue('Mode', 'Server')
[Code]
function SwitchHasValue(Name: string; Value: string): Boolean;
begin
Result := CompareText(ExpandConstant('{param:' + Name + '}'), Value) = 0;
end;
Ironically it is more difficult to check for a mere presence of switch (without a value).
Use can use a function CmdLineParamExists from #TLama's answer to Passing conditional parameter in Inno Setup.
function CmdLineParamExists(const Value: string): Boolean;
var
I: Integer;
begin
Result := False;
for I := 1 to ParamCount do
if CompareText(ParamStr(I), Value) = 0 then
begin
Result := True;
Exit;
end;
end;
You can obviously use the function in Pascal Script:
if CmdLineParamExists('/DefaultMode') then
begin
Log('Installing for default mode');
end
else
begin
Log('Installing for different mode');
end;
But you can even use it in sections, most typically using Check parameter:
[Files]
Source: "MyProg.hlp"; DestDir: "{app}"; Check: CmdLineParamExists('/InstallHelp')
A related problem:
Add user defined command line parameters to /? window
If you want to parse command line arguments from code in inno, then use a method similar to this. Just call the inno script from the command line as follows:
c:\MyInstallDirectory>MyInnoSetup.exe -myParam parameterValue
Then you can call the GetCommandLineParam like this wherever you need it:
myVariable := GetCommandLineParam('-myParam');
{ ================================================================== }
{ Allows for standard command line parsing assuming a key/value organization }
function GetCommandlineParam (inParam: String):String;
var
LoopVar : Integer;
BreakLoop : Boolean;
begin
{ Init the variable to known values }
LoopVar :=0;
Result := '';
BreakLoop := False;
{ Loop through the passed in arry to find the parameter }
while ( (LoopVar < ParamCount) and
(not BreakLoop) ) do
begin
{ Determine if the looked for parameter is the next value }
if ( (ParamStr(LoopVar) = inParam) and
( (LoopVar+1) <= ParamCount )) then
begin
{ Set the return result equal to the next command line parameter }
Result := ParamStr(LoopVar+1);
{ Break the loop }
BreakLoop := True;
end;
{ Increment the loop variable }
LoopVar := LoopVar + 1;
end;
end;
This is the function I wrote, which is an improvement of Steven Dunn's answer. You can use it as:
c:\MyInstallDirectory>MyInnoSetup.exe /myParam="parameterValue"
myVariable := GetCommandLineParam('/myParam');
{ util method, equivalent to C# string.StartsWith }
function StartsWith(SubStr, S: String): Boolean;
begin
Result:= Pos(SubStr, S) = 1;
end;
{ util method, equivalent to C# string.Replace }
function StringReplace(S, oldSubString, newSubString: String): String;
var
stringCopy: String;
begin
stringCopy := S; { Prevent modification to the original string }
StringChange(stringCopy, oldSubString, newSubString);
Result := stringCopy;
end;
{ ================================================================== }
function GetCommandlineParam(inParamName: String): String;
var
paramNameAndValue: String;
i: Integer;
begin
Result := '';
for i := 0 to ParamCount do
begin
paramNameAndValue := ParamStr(i);
if (StartsWith(inParamName, paramNameAndValue)) then
begin
Result := StringReplace(paramNameAndValue, inParamName + '=', '');
break;
end;
end;
end;
Yes it is possible, you can use the ParamStr function in PascalScript to access all the commandline parameters. The ParamCount function will give you the number of commandline parameters.
Another possibility is to use GetCmdTail
In response to:
"With InnoSetup 5.5.5 (and perhaps other versions), just pass whatever you want as a parameter, prefixed by a /"
"#NickG, yes, every constant you can expand by the ExpandConstant function"
This is not the case. Trying to use a command line parameter in ExpandConstant in InnoSetup 5.5.6 results in a runtime error.
PS: I would have added a comment directly but apparently I dont have enough "reputation"
I've modified a little bit knguyen's answer. Now it's case insensitive (you can write en console /myParam or /MYPARAM) and it can accept default value. Also I fixed the case when you receive larger parameter then expected (for ex: /myParamOther="parameterValue" in place of /myParam="parameterValue". Now myParamOther doesn't match).
function GetCommandlineParam(inParamName: String; defaultParam: String): String;
var
paramNameAndValue: String;
i: Integer;
begin
Result := defaultParam;
for i := 0 to ParamCount do
begin
paramNameAndValue := ParamStr(i);
if (Pos(Lowercase(inParamName)+'=', AnsiLowercase(paramNameAndValue)) = 1) then
begin
Result := Copy(paramNameAndValue, Length(inParamName)+2, Length(paramNameAndValue)-Length(inParamName));
break;
end;
end;
end;
I found the answer: GetCmdTail.
You can pass parameters to your installer scripts. Install the Inno Setup Preprocessor and read the documentation on passing custom command-line parameters.