How can I be DRY in columns names in this MySQL procedure? - mysql

I'm referencig name, description and user_id columns of meta table. Twice, and maybe more (who knows?) in future. Those columns are used to compute the ETag of my meta resource.
Adding one column that contributes to compute ETag in the future will force me to change the code N times, and this is bad.
Is there any way to make it DRY and store these column names elsewhere? Because I'd like to use these column names also when INSERT on meta is performed.
IF only = true THEN
-- Calculate ETag on meta fields only
UPDATE meta
SET etag = etag(CONCAT(name, description, user_id))
WHERE id = meta_id;
ELSE
-- Calculate Etag on meta fields and meta customers
BEGIN
DECLARE c_etags VARCHAR(32);
-- Compute c_etags
UPDATE meta
SET etag = etag(CONCAT(etag(name, description, user_id), c_etags))
WHERE id = meta_id;
END;
END IF;
Disclaimer: this code is untested, I'm pretty new to MySQL stuff, apart for simple statements.
EDIT: etag is MD5 MySQL function. Maybe this is one option:
CREATE PROCEDURE set_meta_etag(IN meta_id INT, IN related TEXT)
NOT DETERMINISTIC
BEGIN
UPDATE meta
SET etag = etag(CONCAT(name, description, user_id,
IF(related IS NOT NULL, related, '')))
WHERE id = meta_id;
END //
-- First call
CALL set_meta_etag(meta_id, NULL);
-- Second call
CALL set_meta_etag(meta_id, c_etags);
But it won't work for INSERT statement.

The obvious thing (foreach column, if it's the one I want, use it to help make the etag) doesn't work in SQL with any ease, because SQL doesn't, historically, contemplate column names stored in variables.
You could write a program in your favorite non-SQL programming language (Java, PHP, etc) to create and then define your procedure.
You could also use so-called "dynamic sql" to do this, if you were willing to do the work and take the slight performance hit. See
How To have Dynamic SQL in MySQL Stored Procedure
for information on how to PREPARE and EXECUTE statements in a stored procedure.
By the way, I have had good success building systems that have various kind of metadata stored in the column contents. For example, you could write code looking for the string '[etag]' in your column contents. The comments for columns are stored in
information_schema.COLUMNS.COLUMN_COMMENT
and are very easy to process when your program is starting up.

If you know this is confined to one table, you could add a trigger. Using an AFTER trigger should allow your stored proc to work for both INSERT and UPDATE. See MySQL Fire Trigger for both Insert and Update.

Related

How to convert mssql user-defined table type into mysql UDT

This is my mssql UDT
create type ConditionUDT as Table
(
Name varchar(150),
PackageId int
);
This is my mssql Stored Procedure
create Procedure [dbo].[Condition_insert]
#terms_conditions ConditionUDT readonly
as
begin
insert into dbo.condition (name, p_id)
select [Name],[PackageId]
from #terms_conditions;
end
There is a workaround solution if you do not have any other choice but definitely migrate from sql server to mysql.
The closest structural predefined object that takes on many rows in mysql is an actual table. So you need 1 table per UDDT of sql server. Make sure you use a specific schema or naming conversion so you know those tables are UDDT emulations.
The idea is fill in the info, use them into the sp and then delete them. You need however to gurantee who reads what and that info are deleted after usage, consumed. So:
For any of those tables you need 2 columns, i suggest put them always first. That will be the key and the variable name. The key can be char(38) and use UUID() to get a unique identifier. It can also be int and use the connectionid() instead. Unique identifier is better however as ensures that nobody will ever use information not indented for him no matter what. The variable name will be the one used into the sql server parameter, just a string. This way:
You know what UDDT you use out of the table name.
You know the identity of your process through the key.
You know the 'variable' out of the name.
So, in your application code you:
Begin transaction.
Insert the data into the proper (UDDT emulator) tables using a key and the variable name(s)
Supply to the stored procedure the key and the variable name(s). You can use the same key for many table type parameters within the same sp call.
The stored procedure can now use that information as before from the UDDT variable using key and variable name as filters to query the proper UDDT emulated table.
Delete the data you insert
Commit
On catch, rollback.
For simplicity your sp can read the data into temp table and you do not need to change a line of code from the original sql server sp for this aspect.
Transaction into your app code will help you make sure your temporary variable data will either be deleted or never committed no matter what goes wrong.
As Larnu thought might be the case, MySQL doesn't support user defined types at all, let alone user defined table types.
You will have to make them all separate scalar parameters.

How can we prevent SQL-Injection from MySQL?

We all know that we can prevent SQL-Injection in PHP by Prepared Statement of query or mysqli_real_escape_string() function. But if i want to prevent it from MySQL side ? Do you have any idea? How can i achieve it ?
You can use stored procedure to query the database. The stored procedure checks the data type and parameters supplied, if there is a mismatch a query is not executed.
Here is a sample of stored procedure you can use to insert a record in mysql -
DELIMITER $$
CREATE PROCEDURE book_Insert (
in title varchar(30),
in isbn varchar(30),
out bookID tinyint(3) unsigned
)
BEGIN
insert into books (title, isbn)
values(title, isbn);
set bookID =last_insert_id();
END $$
As the comment from #Ferrybig says, on the MySQL side there's no way to be sure that an SQL query is legitimate or the result of SQL injection.
Suppose the database server receives this query:
SELECT col1, col2, col3 FROM MyTable WHERE account_id = 1
UNION SELECT user, password, NULL FROM mysql.user
This looks pretty suspicious. Why would the app want to read all passwords, and append it to the query we expect to see? There's a strong chance this is an attempt at hacking.
Another example:
SELECT col1, col2, col3 FROM MyTable WHERE account_id = 1
OR account_id = 473
Is this legitimate? Or is it illicit? Is the query executed for a user who should have privileges to read data for account 473? How can you know? On the MySQL side, the query looks exactly the same whether it was the result of SQL injection or not.
It might have been code like the following PHP, which is vulnerable to SQL injection (this is not a failing of PHP, because similar vulnerable code can be written in any programming language):
$sql = "SELECT col1, col2, col3 FROM MyTable WHERE account_id = " . $_GET['id'];
If the attacker caused the input parameter to be: "1 OR account_id = 473"
The point is, once the query is formatted in the app and submitted to MySQL, MySQL can't tell how it was formatted. MySQL can only trust that the developer of the code did format the query in a safe way.
One method of blocking illicit queries is by using a type of Web Application Firewall (WAF) that you design to recognize legitimate inputs and block requests that are not legitimate. In other words, you need to program the WAF with a whitelist or set of patterns to recognize safe requests. This list will be unique for each app, so you need to be very familiar with the app. Any modification to the app may require you to update the WAF's whitelist.
The WAF is typically a proxy at the http layer, to prevent illicit request from reaching the web app. There are also proxy solutions to protect the request between the app and the database. You program the proxy to recognize which SQL queries are expected, and it blocks any queries that have unexpected terms. It would
An example of a database firewall is https://www.oracle.com/database/technologies/security/audit-vault-firewall.html
They aren't perfect. They may block queries you want to allow. And they don't work for dynamic queries run in stored procedures.
Ultimately, you should just establish safe programming standards in your application code. Use query parameters whenever you combine unsafe data with your SQL queries.
You can use
assuming that you input a parameter $bookID
DELIMITER $$
CREATE PROCEDURE `spGetBook`(
in bookID int)
BEGIN
SELECT *
FROM categories
where bookID=bookID;
END $$

MySQL - Fast query but slow Stored Procedures

I know this question has been discussed quite a lot here. But I have a particular case when I need to pass a list of parameters (comma - separated) which prevents me to have a local variable declared and used for input parameter.
As pointed out in the above discussion, it is suggested to declare a local variable and assign the parameters to this variable. However, what should I do in case my parameter is of type Text and can be comma - separated list?
For example -
CREATE DEFINER=`Admin`#`%` PROCEDURE `MyReport`(
p_myparameter_HK Text
)
BEGIN
SELECT
*
FROM MyTable
WHERE
(find_in_set(MyTable.column_HK, p_myparameter_HK) <> 0 OR MyTable.column_HK IS NULL)
;
END
Performance:
Query
If I just run the query - 300 ms
Stored Procedure
CALL MyReport('0000_abcd_fake_000')
This procedure keeps running endlessly.
My question is, how can I disable parameter sniffling and use local variable instead of find_in_set to match the query performance.
The times that I have needed to pass an arbitrary list of things to a Stored Procedure, I did it this way:
CREATE (or already have) a TABLE for passing the info in. Both the caller and the Procedure know the name of the procedure. (Or it could be passed in, but adds some messy "prepare-executes".)
Do a bulk INSERT into that table. (INSERT INTO tbl (a,b) VALUES (...), (..), ...;)
Perform JOINs or whatever to use the table efficiently.
In my case, the extra effort was worth it.

Parameter sniffing on table valued parameters

I'm fairly certain that adding parameter sniffing to table valued parameters is of little or no value however I was wondering if someone could confirm this?
(INT_LIST is a user defined table type which is a single column of type INT)
CREATE PROCEDURE [dbo].[TVPSniffTest](
#param1 varchar(50),
#idList INT_LIST readonly
)
AS
BEGIN
DECLARE #param1_sniff VARCHAR(50) = #param1 --this is worth doing
DECLARE #idList_sniff INT_LIST
INSERT INTO #idList_sniff SELECT value FROM #idList --will this help?
--query code here
END
As Jeroen already mentioned, there is no parameter sniffing issue with TVPs. And also that one option to mitigate the lack of statistics is to copy the TVP to a local temp table (which does maintain statistics).
But, another option that is sometimes more efficient is to do a statement-level recompile on any queries using the table variable (i.e. the TVP). The statistics won't be maintained across queries so it needs to be done on any query that involves the table variable that is not something like a simple SELECT.
The following illustrates this behavior:
DECLARE #TableVariable TABLE (Col1 INT NOT NULL);
INSERT INTO #TableVariable (Col1)
SELECT so.[object_id]
FROM [master].[sys].[objects] so;
-- Control-M to turn on "Include Actual Execution Plan".
-- For each of the 3 following queries, hover over the "Table Scan"
-- operator to see the "Estimated Number of Rows".
SELECT * FROM #TableVariable; -- Estimated Number of Rows = 1 (incorrect)
SELECT * FROM #TableVariable
OPTION (RECOMPILE); -- Estimated Number of Rows = 91 (correct)
SELECT * FROM #TableVariable; -- Estimated Number of Rows = 1 (back to incorrect)
This has no effect whatsoever -- in fact, it's detrimental to performance because you're copying the whole table first.
The optimizer maintains no statistics for either table-valued parameters or table variables. This can easily lead to bad query plans with cardinality mismatches; the solution for that is usually an intermediate temp table. In any case, parameter sniffing won't be an issue -- the table contents are never used to optimize the query plan.
Incidentally, while you can assign the parameter to a local variable to circumvent sniffing, a more flexible option is to use the OPTIMIZE FOR or RECOMPILE hints in queries that are particularly affected (or WITH RECOMPILE on the whole stored procedure, but that's a little more drastic). This prevents cluttering the procedure with copies of everything.

save stored procedure output into a table

I have execute only access to a stored procedure.
This SP seems to select some data from multiple tables, and returns one row. I need to store two columns of the output of this SP into a table.
Is there any way to do this within MySQL?
If it returns a row, this is a stored function and not a stored procedure. You can use something like the following to insert into your table:
INSERT INTO tablename SELECT (SELECT col1, col2 FROM (SELECT somefunction()))
Otherwise, it will be a stored procedure and you should do something like this, assuming that #var1 and #var2 are output parameters:
CALL someprocedure(#var1, #var2, #var3)
INSERT INTO tablename SELECT(#var1, #var2)
See the documentation about Create Procedure and Create Function for more information about functions versus procedures.
MySQL has an extension to stored procedures that allows the procedure to return one or more result sets to the client, as if the client had issued a SELECT query... but those results are ephemeral. They don't persist and they can't be stored in variables or otherwise accessed after the procedure finishes -- they can only be "fetched" the one time.
There is a way to make them accessible without breaking the way the procedure already works, as I discussed here, but you can't do it without a change to the procedure:
How to use Table output from stored MYSQL Procedure
The idea is for the procedure to write its output in a temporary table, and then return it to the caller by calling SELECT against the temporary table -- but to leave the temporary table behind so that the caller can access it directly if desired.
That's not exactly the same as what you're asking though, which is why I didn't mark this question as a duplicate, since you, unlike the other poster, do not appear to have administrative control of the procedure... but unless you can make the case for a change like this, there's not another way within MySQL to access those returned values, since they only exist in the result-set that's returned.
Of course, procedures do have optional OUT parameters, where you can hand variables to the procedure as part of arguments you use to call it, and it can set those variables, so that they'll have the values you need when the procedure is done, but that only works when the return values are scalars and would require a change to the procedure's interface, since procs in MySQL do not have "optional" arguments... if the procedure were changed to permit this, it would require an increased number of arguments to be provided every time it was called, and if other components are calling it, that could easily break other things.