I'm trying to use Dapper to call a PostreSQL function declared as the following:
CREATE OR REPLACE FUNCTION public.test(param1 integer, param2 character varying, param3 character varying)
I'm using Dapper-Async to call this function like this:
cnx.QueryAsync($#"SELECT * FROM public.test(#Param1, #Param2, #Param3);",
new { Param1 = 12, Param2 = mylongstring1, Param3 = mylongstring2});
This call fails with the following error:
Npgsql.PostgresException (0x80004005): 42883: function public.test(integer, text, text) does not exist
As you can see, the DbString matches text parameter types.
How can I match character varying instead ?
The issue is probably the fact that your function is defined with parameter type character varying, but as the error shows you're sending text. While this may be somewhat confusing, these are two different backend types.
By default, Npgsql maps .NET string to text, not to character varying. You can either change your function to accept text (or add an overload), or use NpgsqlDbType to specify character varying (not sure how to do this with Dapper though).
Related
I have a fullname column for authors and would like to extract the surname into another column. I do that with the following raw SQL:
SELECT name,
SUBSTRING_INDEX(`name`, ' ', -1) AS `surname`
FROM qr.authors;
Output:
Under "Using SQL Functions" the Cookbook says:
In addition to the above functions, the func() method can be used to create any generic SQL function such as year, date_format, convert, etc.
But how can I create this SUBSTRING_INDEX function through the func() method so that I can use it with the CakePHP query builder?
The functions builder comes with predefined methods/functions
The FunctionsBuilder class ships with a bunch of ready-made methods/functions for you to use, like sum(), count(), concat(), dateDiff(), now(), etc. You can find a complete list of the supported functions and examples on how to use them in the Cookbook and the API docs.
Arbitrary functions can be built by just calling them
The FunctionsBuilder class uses the magic method __call handler to build arbitrary SQL function expressions, so in case there is no ready made method for your function, you can just "call" your SQL function:
$query = $this->SomeTable->find();
$func = $query->func()->substring_index([
'name' => 'identifier',
' ',
-1 => 'literal'
]);
$query->select([/* ... */, 'surname' => $func]);
This should be mostly rather self explanatory, the magic method name is the SQL function name, and the passed array holds the arguments that should be passed to the function, where in this case the first and last argument are defined to be treated as identifier respectively as a literal, and thus both being inserted into the query directly, ie not as bound parameter that would be escaped!
The identifier one will additionally be subject to possible automatic identifier quoting, ie name would be transformed to for example `name`, "name", or [name] depending on the database driver in use. The second argument could be made a literal too (by passing for example '" "'), I've just not set it as one for example purposes. Not doing so will cause the value to be bound/casted as a string.
The resulting compiled SQL will look something like this:
substring_index(name, :c0, -1)
and will finally be executed as
substring_index(name, ' ', -1)
Handling non-hard-coded data, for example user input
When working with data that isn't hard-coded, ie dynamic, or otherwise subject to possible change, make sure that you define the proper types for casting/escaping in the second argument if necessary, like integer, datetime, etc. In order to get this working properly, you'll have to use an identifier expression for the column name value, as otherwise the second argument would be ignored when using the 'xyz' => 'identifier' syntax:
$func = $query->func()->substring_index(
[
new \Cake\Database\Expression\IdentifierExpression('title'),
' ',
$userInput,
],
[
null, // no typecasting for the first argument
'string', // second argument will be bound/casted as string
'integer' // third argument will be bound/casted as integer
]
);
The types will be matched via the numeric indices, and the first one is going to be ignored, since it is an expression, hence passing just null.
You could even use raw expressions
In your case, where you are passing safe, hard-coded values that don't need to be inserted into the query as bound parameters, and SUBSTRING_INDEX isn't a function that is covered by any of the dialects that ship with CakePHP, you could even use raw queries instead - you'll however loose the ability to transform the expression in custom dialects, and automatic identifier quoting will also not apply any more, so only do this if you know what you are doing!
$query->newExpr('SUBSTRING_INDEX(`name`, "", -1)')
See also
Cookbook > Database Access & ORM > Query Builder > Using SQL functions
API > \Cake\Database\Query::newExpr()
I'm having trouble passing a NULL value to a JDBC stored function (using a callable statement) that expects an Array type. This is only a problem if I'm trying to set the IN parameter to NULL (eg., I can create and pass an empty array, but I shouldn't have to do that).
For example, I can do this as a work-around:
callableStatement.setObject(index, callableStatement.getConnection.createArrayOf("numeric", Array().asInstanceOf[Array[AnyRef]]))
But this bothers me. First of all, there is supposed to be an API for passing NULL arrays. Second of all, I'm creating an empty array for no good reason (and I'll have to create the correct array type so this is not a one-liner, I'll need to support several different types). It gets messy.
I should be able to do this, I think (or at least something pretty similar):
callableStatement.setNull(index, Types.ARRAY)
But it results in an exception:
com.edb.util.PSQLException: ERROR: function app_fetch_user_list_pkg.n_execute(character varying, character varying[], character varying, boolean, integer) does not exist
Any ideas? (We are working with Postgresql/EDB as well as Oracle... so far, I've been experimenting with the Postgresql instance).
Well, I'm using PreparedStatement.setNull(position, Types.ARRAY) with the 9.4-1202-jdbc41 JDBC driver and that is working as expected.
Try this:
callableStatement.setString(index, "{}");
I have the requirement to insert records into a SQL table that contains varbinary and image columns.
How do I get 0x00120A011EFD89F94DDA363BA64F57441DE9 (This is the same for all records)
into this
BLOB_TYPE (MY_UDDT(varbinary(18))?
This is what I have so far, everything being very straightforward except for the SqlDbType.Udt parameter definition. With this code I'm getting the error, "Specified type is not registered on the target server.System.Byte[]", I can say 100% the 'User-Defined Data Type' exists on the server. I queried select * from sys.types and it returned the type.
Definition: CREATE TYPE [dbo].[MY_UDDT] FROM varbinary NOT NULL
sql = "INSERT INTO dbo.BLOBS (BLOB_TYPE) VALUES (#BLOB_TYPE);"
cmd = New SqlCommand(sql, conn)
Dim parameter As New SqlParameter("#BLOB_TYPE", SqlDbType.Udt)
With parameter
.SqlDbType = SqlDbType.Udt
.UdtTypeName = "dbo.MY_UDDT"
.Value = blobType
End With
cmd.Parameters.Add(parameter)
cmd.ExecuteNonQuery()
Public ReadOnly Property blobType As Byte()
Get
Dim Str As String = "0x00620A011EFD89F94DDA863BA64F57441DE9"
Dim bytes As Byte() = New Byte(Str.Length * 2 - 1) {}
System.Buffer.BlockCopy(Str.ToCharArray(), 0, bytes, 0, bytes.Length)
Return bytes
End Get
End Property
There are several things being misunderstood here:
There is a big difference between User-Defined Data Types (UDDTs) and User-Defined Types (UDTs), though it is understandable that people get confused given the similarity of the names.
What you created via CREATE TYPE is a UDDT, not a UDT. This is a T-SQL only construct.
Note for the future: when specifying a variable-length datatype, be sure to specify the length:
VARBINARY(18) instead of VARBINARY
A UDT is a SQLCLR construct. Here is an example on MSDN of using a UDT in this manner:
http://msdn.microsoft.com/library/ms131080.aspx
You don't need anything fancy here. You just need to set:
SqlDbType = SqlDbType.VarBinary
Size = 18
Your blobType function is not sending the hex representation of the string that you are expecting. It is sending the hex values for each character of the string "0x00620A011EFD89F94DDA863BA64F57441DE9". Meaning, the hex value for "0" (i.e. 48 or 0x30), then the hex value for "x" (i.e. 120 or 0x78), and so on for all 38 characters.
You need to take each set of 2 characters after the "0x" (i.e. "00" then "62" then "0A" and so on), convert those to actual bytes, and send the array.
OR
If you have the values in VB as strings and are using SQL Server 2008 or newer, you can pass the string in and have SQL Server convert it for you. You would just modify your INSERT statement to be:
INSERT INTO dbo.BLOBS (BLOB_TYPE) VALUES (CONVERT(VARBINARY(18), #BLOB_TYPE, 1));
The CONVERT function, using a style of "1" (meaning: a string representation of a binary, starting with "0x") will do the trick. For example:
SELECT CONVERT(VARBINARY(18), '0x00620A011EFD89F94DDA863BA64F57441DE9', 1)
Of course, then you need to change the SqlParameter properties to be:
SqlDbType = SqlDbType.VarChar
Size = 38
The problem is that the parameter value is a byte array, not an instance of the UDT you are claiming is going to be passed in, and System.Byte[] is, unsurprisingly, not a registered UDT.
It's confusing, but the "UDT" on the .NET side is used only for UDTs that are created in assemblies (CREATE TYPE ... EXTERNAL NAME) and not for UDTs based on SQL Server types (called "alias data types" in Books Online). To pass those, do not set the SqlDbType to Udt but simply pass in a value of the underlying base type. Basically, these types are only useful on the database end to ensure consistency.
Furthermore, you construct the blob incorrectly -- you need to convert the hexstring to bytes, not the characters that make up the hexstring. If it's really a constant, you can declare it as such. So:
cmd.Parameters.AddWithValue("#BLOB_TYPE", New Byte() { &H00, &H62, &H0A, &H01, &H1E, &HFD, &H89, &HF9, &H4D, &HDA, &H86, &H3B, &HA6, &H4F, &H57, &H44, &H1D, &HE9 })
Should do the trick.
SqlDbType.Udt is for SQLCLR types. In this case, you have a varbinary column so specify SqlDbType.VarBinary along with the max size (18). For the value, pass the byte array containing the raw binary value.
You mention an 18-byte varbinary data type but the data looks to be much longer. If your intent is to convert a string in hex notation to an 18-byte binary value, that's a different question than the one asked here. I suggest you post another question if you need help with that.
I have a report with integer parameters and while passing the parameters from WPF client to ReportParameter object, it gives an error:
"The best overloaded method match for 'Microsoft.Reporting.WinForms.ReportParameter.ReportParameter(string, string[])' has some invalid arguments".
How can I pass an integer type parameter to my SSRS report from my WPF client?
you should not use toString() to pass values to a integer parameter.
The ideal for parameters being passed from outside the report is set them as "Text". You dont even have to cast them (to integer in your case) on the dataset. For example, if you have a ID parameter like this:
where ID=#ID
you can create it as text and pass it as a parameter from the URL like if it were a text
As expected, I get an error when entering some characters not included in my database collation:
(1267, "Illegal mix of collations (latin1_swedish_ci,IMPLICIT) and (utf8_general_ci,COERCIBLE) for operation '='")
Is there any function I could use to make sure a string only contains characters existing in my database collation?
thanks
You can use a regular expression to only allow certain characters. The following allows only letters, numbers and _(underscore), but you can change to include whatever you want:
import re
exp = '^[A-Za-z0-9_]+$'
re.match(exp, my_string)
If an object is returned a match is found, if no return value, invalid string.
I'd look at Python's unicode.translate() and codec.encode() functions. Both of these would allow more elegant handling of non-legal input characters, and IIRC, translate() has been shown to be faster than a regexp for similar use-cases (should be easy to google the findings).
From Python's docs:
"For Unicode objects, the translate() method does not accept the optional deletechars argument. Instead, it returns a copy of the s where all characters have been mapped through the given translation table which must be a mapping of Unicode ordinals to Unicode ordinals, Unicode strings or None. Unmapped characters are left untouched. Characters mapped to None are deleted. Note, a more flexible approach is to create a custom character mapping codec using the codecs module (see encodings.cp1251 for an example)."
http://docs.python.org/library/stdtypes.html
http://docs.python.org/library/codecs.html