I'm having an odd issue where creating a table adds the back ticks to the table name in the database.
public function create_table($name)
{
$sql = "
CREATE TABLE IF NOT EXISTS `?` (
id int(11) NOT NULL AUTO_INCREMENT,
url varchar(255) NOT NULL,
resolved tinyint(1) NOT NULL,
PRIMARY KEY (id)
)";
$query = $this->_pdo->prepare($sql);
$query->bindValue(1, $name);
if($query->execute())
{
print("Created");
}
else
{
var_dump($query->errorInfo());
}
}
The reason I am doing it like that and binding the $name is that it will be done dynamically by a web crawler I'm making to look for common file and directory names and due to the large amounts of paths gatherable ive decided on a table for each site and its name generated from its hostname. (Example of a possible dodgy hostname: https://whe.re/)
But this has led to this.
So I tried without them and it throws an error
"You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ''random_site'
What am I not seeing or thinking, it's been a while since I've used PHP and I'm at a loss as all my searches lead me to why table names should be surrounded with back ticks when making a query which isn't my issue.
Thanks
Info: MariaDB
PHP: 7.4.6
That's because your are binding a string value, so it is injected with surrounding single quotes. But bottom line, you just can't pass a table name as a parameter in the query. The binding mechanism is meant to pass literal values, which obviously a table name is not.
In this specific situation, you don't have another choice than string concatenation:
$sql = "
CREATE TABLE IF NOT EXISTS `$name` (
id int(11) NOT NULL AUTO_INCREMENT,
url varchar(255) NOT NULL,
resolved tinyint(1) NOT NULL,
PRIMARY KEY (id)
)";
$query = $this->_pdo->prepare($sql);
if ($query->execute()) {
...
} else {
...
}
This implies that you need to throughly validate the variable on application side before passing it to the query, which is not an easy task.
This ultimately raises the question of why you would have a separate table for each site. I would not recommend this design, which violates the basic normalization rules, and can quickly turn to a maintenance nightmare. Instead, you should have a reference table that lists the sites, and a single table for all paths, with a foreign key column that references the sites table. With proper schema and indexing, you are not likely to experience performance issues, unless you have zillions of rows (in which case other options are available, such as logical partitioning).
Related
I have a variable that takes the value from previous page, and I want to use that value as my table name. The value passes is an integer so therefore I want my table name as schedule_12 if the value passed is 12.
I have used the code below, it does not give any kinds of error but the table was not created in my database. Is there any way to solve this ? Thanks in adance
<?php
$trNum=$_REQUEST["tr_num"];
include ("dbConnect.php");
#mysql_query("create table 'schedule_".$trNum."'(sid int primary key
auto_increment,st_name varchar(20), arr_time varchar(5), dep_time
varchar(5), halt varchar(5), dist int, day int);");
echo "Schedule created successfully";
?>
Something like this
<?php
$trNum=$_REQUEST["tr_num"];
include ("dbConnect.php");
mysql_query("
CREATE TABLE schedule_".(int)$trNum."
(
sid int primary key auto_increment,
st_name varchar(20), arr_time varchar(5),
dep_time varchar(5),
halt varchar(5),
dist int,
day int
)"
);
echo "Schedule created successfully";
Note as I said in the comments, table names should not be quoted like strings. Don't use the # sign because it will suppress errors, which we need to make sure things work. mysql_* family of functions are deprecated consider using mysqli_* or PDO.
Lastly and very important, because you are concatenating a variable into SQL it leaves you open to SQL injection attacks. In this case there probably isn't much you can do ( like prepared query etc.. ). That variable is not safe because its supplied by the Client, though $_REQUEST. So you need to check it before using it.
I showed you casting but you can check it several ways including Regx
if( preg_match('/^[0-9]+$/', $trNum ) ){
...create table code
}
This regx checks that it begins and ends with Numbers and only contains Numbers, which would limit what could be injected into the SQL. There may be better ways to sanitize this, but this should be sufficient in this case.
Casting it is ok, but it may cast a string to 0 and try to create a table named schedule_0 if someone tried to hack it, which is not Ideal. However it's better they get the error message for creating a table that exists then the alternative.
For a quick example of how this could be exploited, a user could supply a value of 0( id int ); UPDATE users SET password = 'password'; -- then your query becomes this.
mysql_query("
CREATE TABLE schedule_0( id ind ); UPDATE users SET password = 'password'; --
(
... orignal fields ..
)"
);
So the -- is the start of a comment in SQL, everything past it will not be ran by the DB. Then the 0( id int ); completes your original query creating a minimal table. Lastly we sneak in an Update to change the passwords of all your application users. And then we can login as anyone we want and do all sorts of nasty things.
This is a simplified and imagined case, but it's not to far from what is possible when leaving yourself wide open...
The comments pretty much gave you the answer (as well as some good suggestions). Here is how you'd correct it (using PDO). Notice I removed the quotes in the query, don't forget to sanitise $trNum if it is passed by a user, otherwise SQL injections are possible.
<?php
$trNum=$_REQUEST["tr_num"];
$db = new PDO("dbtype:host=yourhost;dbname=yourdbname;charset=utf8","username","password");
$db->query("create table schedule_" . $trNum . " (sid int primary key
auto_increment, st_name varchar(20), arr_time varchar(5), dep_time
varchar(5), halt varchar(5), dist int, day int)");
echo "Schedule created successfully";
?>
I am using SQL statements embedded in two PHP files to produce a database that appears in HTML as well as in phpMyAdmin. I have created a table like with the following command:
$sql = "CREATE TABLE WeatherCenter (OutgoingLongwaveRadiation VARCHAR(30))";
In the second PHP file, I have:
$sql = "ALTER TABLE WeatherCenter
ADD COLUMN (
`Barometric Pressure` SMALLINT NOT NULL,
`CloudType` VARCHAR(70) NOT NULL,
`WhenLikelyToRain` VARCHAR(30) NOT NULL);";
When I execute the files, I keep getting "Undefined column Barometric Pressure does not exist in filed list". I realized that this is because somewhere else in my PHP file I listed the variable as BarometricPressure and not Barometric Pressure (ie. one has a gap, and the other doesn't). When I corrected the mistake in the ALTER TABLE by deleting the gap, the same error message STILL appeared in the phpMyAdmin database. It seems to me that changing the name of the field in the PHP file does NOT change it in the phpMyAdmin database. As a result, no data is being transmitted to the database.
Could someone tell me if I changed the syntax of the above to
$sql = "ALTER TABLE WeatherCenter
ADD COLUMN `Barometric Pressure` SMALLINT NOT NULL AFTER `OutgoingLongwaveRaadiation`,
ADD COLUMN `CloudType` VARCHAR(70) NOT NULL AFTER `BarometricPressure`,
ADD COLUMN `WhenLikelyToRain` VARCHAR(30) NOT NULL AFTER `CloudType` ;";
will the problem go away? I mean, do you HAVE to use an "ADD COLUMN" before EACH field name, and do you HAVE to specify the "AFTER previous field name"? Do you have to take these additional measures to ensure that any mistake in the field name that appears in the phpMyAdmin can be erased by rewriting your code in the PHP file? Could someone simply tell me what the correct syntax is please, because there appears to be inconsistencies. Some of the ALTER TABLE statements do not have opening and close brackets and no backticks, but other ALTER TABLE statements make it clear that you must have opening and closing brackets and backticks such that the last few characters in the ALTER TABLE statement MUST be );";
I am throughly confused.
Problem:
I have the following table in MySQL.
For this example lets say that there is (and always will be) only one person in the world called "Tom" "Bell". So (name, surname) is the PRIMARY KEY in my table. Every person has his salary, an unsigned integer.
CREATE TABLE IF NOT EXISTS `user` (
`name` varchar(64) NOT NULL DEFAULT 'Default_name',
`surname` varchar(64) NOT NULL DEFAULT 'Default_surname',
`salary` int(10) unsigned NOT NULL DEFAULT '0',
UNIQUE KEY `id` (`name`,`surname`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
Whenever I insert a row using a PHP script I want my function to return the primary key of the inserted row (an array key=>value).
From PHP context I do not know what the primary key of table 'user' consists of and I do not always need to set all primary key values (example 2, very stupid, but possible).
I can add another argument to my insert function (for example I could pass the table name, in this case "user").
If this matters, I am using PDO (php data objects) to connect with my MySQL database.
Example 1:
$db->insert('INSERT INTO `user` (`name`,`surname`,`salary`) VALUES ('Tom','Bell','40');');
should return an array:
$arr = ['name' => 'Tom', 'surname' => 'Bell'];
Example 2:
$db->insert('INSERT INTO `user` (`name`,`salary`) VALUES ('Nelly','40');');
should return an array:
$arr = ['name' => 'Nelly', 'surname' => 'Default_surname'];
Disclaimer & other information:
I know this is not a well-designed table, I could use an auto_increment id column to make it much easier and probably more efficient as well. This is just an example to show the problem without having to explain my project structure.
Without loss of generality: Using functions like "getLastInsertId()" or "##identity" will return 0, I guess the reason is because the table does not have an auto_increment column.
What have I tried? Nothing (other than things stated in point 2 (which I was certain it wouldn't work) and searching for a solution).
There aren't "nice" ways around this problem. One of the reasons for having an auto_increment is to avoid having problems like you described.
Now, to avoid my answer to be one of those that take into account only half the picture - I do realize that sometimes you inherit a project or you simply screw up during initial stages and you have to fix things quickly.
To reflect on your example - your PK is a natural PK, not a surrogate one like auto_increment is. Due to that fact it's implied that you always know the PK.
In your example #1 - you inserted Tom Bell - that means you knew the PK was Tom Bell since you instructed MySQL to insert it. Therefore, since you knew what the PK was even before insert, you know how to return it.
In your example #2 you specified only a part of the PK. However, your table definition says thtat default values for both name and surname are Default_surname. That means, if you omit either part of the PK, you know it'll assume the default value. That also means you already know before insertion what the PK is.
Since you have to use a natural PK instead of a surrogate, the responsibility of "knowing" it shifts to you instead of RDBMS. There is no other way of performing this action. The problem becomes even more complex if you allow for a default value to become null. That would let you insert more than 1 Tom with null as surname, and the index constraint wouldn't apply (null is not equal to null, therefore (tom, null) is not equal to (tom, null) and insert can proceed).
Long story short is that you need a surrogate PK or the auto_increment. It does everything you require based on the description. If you can't use it then you have a huge problem at your hands that might not be solvable.
So here is my problem. I know there is a fair bit of literature on this site about this type of issue but I am confused about how several of these issues intertwine for my problem. First I have an array of row data that needs to be updated or inserted based on a remote id value within that array, in this case value_c. This array corresponds to a row instance from table foo. Basically, if a record with a matching value_c exists in the database then update that record otherwise insert the new record payload. The data structure of the array corresponds to the row schema of table foo in our db. This is the schema (obfuscated for safety):
CREATE TABLE IF NOT EXISTS `foos` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`value_a` varchar(13) DEFAULT NULL,
`value_b` int(11) DEFAULT NULL,
`value_c` int(11) DEFAULT NULL,
.
.
.
.
`value_x` enum('enum_a','enum_b','enum_c','enum_d') DEFAULT NULL,
`value_y` text,
`value_z` enum('daily','monthly','weekly') DEFAULT NULL,
`value_aa` tinyint(4) NOT NULL,
`value_bb` varchar(1000) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=829762 ;
There is a lot of data, and the plan in as followed. String-ify this data send it to a stored procedure that would then update or insert as needed. Something like the following (note that this will be happening in a model within codeigniter):
public function update_or_insert($event_array_payload)
{
// string-ify the data
$mod_payload = implode('<delimiter>', $event_array_payload)
//deal with NULLs in array
$mod_payload = $this->deal_with_nulls($mod_payload);
$this->stored_procedure_lib->update_or_insert_payload($mod_payload);
}
// then elsewhere in the stored procedure library
public function update_or_insert_payload($foo)
{
$this->_CI->db->query('CALL update_or_insert_foo(\'$foo\')');
}
My issue is as followed. A single string value is passed into the stored procedure. Then it needs to be parsed apart and places into either a single update or a single insert statement. I could create a variable for each column of the foo table and a loop to populate each variable and update/insert that way, but the foo table is very likely to be extended, and I do not want to create bugs further down the line. Is there a way to dynamically place the parsed apart contents of a string representation of an array into a single update or insert statement. I'm not sure if that is even possible, but I feel like a work around I do not know about might exist. Thank you for the help.
It is not a definitive answer but it would be an option to try.
If you want to avoid sending many parameters the procedure, you can create a table called foos_tmp with the same structure foos but with one field aditional id_foos_tmp (pk and autoincrement) and enter the array in table foos_tmp. Then the procedure you send only the id_foos_tmp of the table foos_tmp generated and internally the procedure to do a SELECT foos_tmp table and get the data that you had before in the array.
I hope it helps somewhat.
Greetings.
Can anyone recommend the best practice for storing general site preferences? For example, the default page title if the script doesn't set one, or the number of featured items to display in a content box, or a list of thumbnail sizes that the system should make when a picture is uploaded. Centralizing these values has the obvious benefit of allowing one to easily alter preferences that might be used on many pages.
My default approach was to place these preferences as attribute/value pairs in a *gulp* EAV table.
This table is unlikely ever to become of a significant size, so I'm not too worried about performance. The rest of my schema is relational. It does make for some damn ugly queries though:
$sql = "SELECT name, value FROM preferences"
. " WHERE name = 'picture_sizes'"
. " OR name = 'num_picture_fields'"
. " OR name = 'server_path_to_http'"
. " OR name = 'picture_directory'";
$query = mysql_query($sql);
if(!$query) {
echo "Oops! ".mysql_error();
}
while($results = mysql_fetch_assoc($query)) {
$pref[$results['name']] = $results['value'];
}
Can anyone suggest a better approach?
In my application, I use this structure:
CREATE TABLE `general_settings` (
`setting_key` varchar(255) NOT NULL,
`setting_group` varchar(255) NOT NULL DEFAULT 'general',
`setting_label` varchar(255) DEFAULT NULL,
`setting_type` enum('text','integer','float','textarea','select','radio','checkbox') NOT NULL DEFAULT 'text',
`setting_value` text NOT NULL,
`setting_options` varchar(255) DEFAULT NULL,
`setting_weight` int(11) DEFAULT '0',
PRIMARY KEY (`setting_key`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
Example data:
mysql> select * from general_settings;
+-----------------------------+---------------+------------------------------+--------------+-------------------------------+---------------------------------------+----------------+
| setting_key | setting_group | setting_label | setting_type | setting_value | setting_options | setting_weight |
+-----------------------------+---------------+------------------------------+--------------+-------------------------------+---------------------------------------+----------------+
| website_name | website | Website Name | text | s:6:"DeenTV"; | NULL | 1 |
I store a serialized value in setting_value column. I got this trick from wordpress way to save settings in database.
setting_options column is used for a select, radio, or checkbox setting_type. It will contain a serialized array value. In admin, this value will be displayed as a options, so admin can choose one of it.
Since I use CodeIgniter, I have a model to get a single value from the particular setting_key, so it's quite easy to use.
That looks fine the way you're doing it.
If you're worried that your queries are looking ugly, you could try cleaning up your SQL a bit.
Here's a cleaner version of the query you gave in your question:
SELECT name, value FROM preferences
WHERE name IN ('picture_sizes','num_picture_fields','server_path_to_http','picture_directory')";
Or perhaps create a stored function to return a preference value; for example, using a stored function like this:
DELIMITER $$
CREATE FUNCTION `getPreference` (p_name VARCHAR(50)) RETURNS VARCHAR(200)
BEGIN
RETURN (SELECT `value` FROM preferences WHERE `name` = p_name);
END $$
DELIMITER ;
You could get your preferences using a query like this:
SELECT getPreference('server_path_to_http')
You sacrifice a bit of speed by not having your preferences hard-coded (obviously). But if you plan to enable a "site administrator" to change the default preferences - you should keep them in the database.
I think that's a perfectly acceptable structure, especially for small amounts of configuration like you have.
You could also store these settings in an .ini file and call parse_ini_file. If you need a bit more flexibility than INI allows (eg: nested arrays, etc), then you could just put them all into a .php file and include that.
If you still want to go with the configuration in the database, then (given that there's only a handful of rows) perhaps just read all the records in one go and cache it.
$config = array();
$result = mysql_query("SELECT * FROM config");
while ($row = mysql_fetch_assoc($result)) {
$config[$row['name']] = $row['value'];
}
I would think that going with an included file will save you some hassle further on - especially if you ever want to include an array as one of your variables. If you plan on changing configuration variables on the fly then perhaps its better to db it, but if its going to remain relatively static I would recommend a 'config.php' file
A lot of applications, including e.g. Wordpress, make use of serialization and unserialization. It allows you to create a very simple table structure maybe with even just one record (e.g. with a site_id for your project(s)).
All your (many, many) variables in an array are serialized to a string and stored. Then fetched and unserialized back to your array structure.
Pro:
You don't have to plan perfect config structures beforehand, doing lots of ALTER TABLE stuff.
Con:
You can't search through your serialized array structure by means of SQL.
Commands:
string serialize ( mixed $value )
mixed unserialize ( string $str )
Works also with your objects. Unserializing an object can make use of the __wakeup() method.
Just create a configure class and store each value you want in variable of the class.
include this class in all files which is calling.
You can access this class in all files now and by declaring global in all function you can access the configure class.
Hope this help.
My approach to this problem is to create a table which a separate column for each config variable, just as you would with any other dataset, and to set the primary key in such a way that the table is incapable of containing more than a single entry. I do this by setting up the primary key as an enum with only one allowed value, like so:
CREATE TABLE IF NOT EXISTS `global_config` (
`row_limiter` enum('onlyOneRowAllowed') NOT NULL DEFAULT 'onlyOneRowAllowed',#only one possible value
`someconfigvar` int(10) UNSIGNED NOT NULL DEFAULT 0,
`someotherconfigvar` varchar(32) DEFAULT 'whatever',
PRIMARY KEY(`row_limiter`)#primary key on a field which only allows one possible value
) ENGINE = InnoDB;
INSERT IGNORE INTO `global_config` () VALUES ();#to ensure our one row exists
Once you have done this setup, any of the values can then be modified with a simple UPDATE statement, looked up with a simple SELECT statement, joined onto other tables to be used in more complex queries, etc.
Another benefit of this approach is that it allows for proper data types, foreign keys, and all the other things the come along with proper database design to ensure database integrity. (Just be sure to make your foreign keys ON DELETE SET NULL or ON DELETE RESTRICT rather than ON DELETE CASCADE). For example, let's say that one of your config variables is the user ID of the site's primary administrator, you could expand the example with the following:
CREATE TABLE IF NOT EXISTS `global_config` (
`row_limiter` enum('onlyOneRowAllowed') NOT NULL DEFAULT 'onlyOneRowAllowed',
`someconfigvar` int(10) UNSIGNED NOT NULL DEFAULT 0,
`someotherconfigvar` varchar(32) DEFAULT 'whatever',
`primary_admin_id` bigint(20) UNSIGNED NOT NULL,
PRIMARY KEY(`row_limiter`),
FOREIGN KEY(`primary_admin_id`) REFERENCES `users`(`user_id`) ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE = InnoDB;
INSERT IGNORE INTO `global_config` (`primary_admin_id`) VALUES (1);#assuming your DB is set up that the initial user created is also the admin
This assures that you always have a valid configuration in place, even when a configuration variable needs to reference some other entity in the database.